This commit is contained in:
proelements
2026-05-04 15:07:06 +03:00
parent 872bc6fb57
commit 741540b767
148 changed files with 11063 additions and 1016 deletions
@@ -0,0 +1,75 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Action_Base {
/**
* Get the action type identifier.
*
* @return string Action type (e.g., 'email', 'webhook', 'collect-submissions')
*/
abstract public function get_type(): string;
/**
* Execute the action with the provided form data and widget settings.
*
* @param array $form_data Sanitized form data submitted by the user.
* Example: ['name' => 'John', 'email' => 'john@example.com']
* @param array $widget_settings Full widget settings - action extracts what it needs.
* Example: ['email_to' => 'admin@site.com', 'email_subject' => 'New form', ...]
* @param array $context Additional context (post_id, form_id, form_name).
* Example: ['post_id' => 123, 'form_id' => 'contact', 'form_name' => 'Contact Form']
* @return array Result array with 'status' and optional data.
* Success: ['status' => 'success', 'message' => '...', ...]
* Failure: ['status' => 'failed', 'error' => '...', ...]
*/
abstract public function execute( array $form_data, array $widget_settings, array $context ): array;
/**
* Validate widget settings for this action.
*
* @param array $widget_settings Widget settings to validate.
* @return bool|\WP_Error True if valid, WP_Error otherwise.
*/
protected function validate_settings( array $widget_settings ) {
return true;
}
/**
* Format a success result.
*
* @param string $message Success message.
* @param array $additional_data Additional data to include.
* @return array
*/
protected function success( string $message, array $additional_data = [] ): array {
return array_merge(
[
'status' => 'success',
'message' => $message,
],
$additional_data
);
}
/**
* Format a failure result.
*
* @param string $error Error message.
* @param array $additional_data Additional data to include.
* @return array
*/
protected function failure( string $error, array $additional_data = [] ): array {
return array_merge(
[
'status' => 'failed',
'error' => $error,
],
$additional_data
);
}
}
@@ -0,0 +1,136 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Action_Runner {
/**
* Registered actions.
*
* @var Action_Base[]
*/
private static $actions = [];
/**
* Register an action.
*
* @param Action_Base $action Action instance to register.
* @return void
*/
public static function register_action( Action_Base $action ): void {
self::$actions[ $action->get_type() ] = $action;
}
/**
* Create an action instance by type.
*
* @param string $type Action type.
* @return Action_Base|null Action instance or null if not found.
*/
public static function create_action( string $type ): ?Action_Base {
if ( ! isset( self::$actions[ $type ] ) ) {
return null;
}
return self::$actions[ $type ];
}
/**
* Get all registered actions.
*
* @return Action_Base[] Array of registered actions.
*/
public static function get_registered_actions(): array {
return self::$actions;
}
/**
* Check if an action type is registered.
*
* @param string $type Action type.
* @return bool
*/
public static function has_action( string $type ): bool {
return isset( self::$actions[ $type ] );
}
/**
* Execute multiple actions and gather results.
*
* @param string[] $actions Array of action type strings.
* @param array $form_data Sanitized form data.
* @param array $widget_settings Full widget settings for actions to extract what they need.
* @param array $context Form context (post_id, form_id, form_name).
* @return array Results containing actionResults, allActionsSucceeded, failedActions, and optional submissionId.
*/
public static function execute_actions( array $actions, array $form_data, array $widget_settings, array $context ): array {
$action_results = [];
$failed_actions = [];
foreach ( $actions as $action_type ) {
if ( ! Action_Type::is_valid( $action_type ) ) {
$action_results[] = [
'type' => $action_type,
'status' => 'failed',
'error' => sprintf( __( 'Invalid action type: %s', 'elementor-pro' ), $action_type ),
];
$failed_actions[] = $action_type;
continue;
}
try {
$action = self::create_action( $action_type );
if ( ! $action ) {
throw new \Exception( sprintf( __( 'Could not create action: %s', 'elementor-pro' ), $action_type ) );
}
$result = $action->execute( $form_data, $widget_settings, $context );
$action_results[] = array_merge(
[ 'type' => $action_type ],
$result
);
} catch ( \Exception $e ) {
$action_results[] = [
'type' => $action_type,
'status' => 'failed',
'error' => $e->getMessage(),
];
$failed_actions[] = $action_type;
}
}
$all_actions_succeeded = empty( $failed_actions );
$response = [
'actionResults' => $action_results,
'allActionsSucceeded' => $all_actions_succeeded,
'failedActions' => $failed_actions,
];
return $response;
}
/**
* Initialize default actions.
*
* @return void
*/
public static function init(): void {
self::register_action( new Email_Action() );
self::register_action( new Collect_Submissions_Action() );
self::register_action( new Webhook_Action() );
/**
* Allow registering custom actions.
*
* @param Action_Factory $factory The action factory instance.
*/
do_action( 'elementor_pro/atomic_forms/actions/register', __CLASS__ );
}
}
@@ -0,0 +1,35 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Action_Type {
const EMAIL = 'email';
const COLLECT_SUBMISSIONS = 'collect-submissions';
const WEBHOOK = 'webhook';
/**
* Get all registered action types.
*
* @return array
*/
public static function get_all_types(): array {
return [
self::EMAIL,
self::COLLECT_SUBMISSIONS,
self::WEBHOOK,
];
}
/**
* Check if an action type is valid.
*
* @param string $type Action type to validate.
* @return bool
*/
public static function is_valid( string $type ): bool {
return in_array( $type, self::get_all_types(), true );
}
}
@@ -0,0 +1,151 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Core\Utils;
use ElementorPro\Modules\Forms\Submissions\Database\Query;
use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository;
use Elementor\Utils as ElementorUtils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Collect_Submissions_Action extends Action_Base {
public function get_type(): string {
return Action_Type::COLLECT_SUBMISSIONS;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$metadata_keys = $this->normalize_metadata_keys( $widget_settings['submissions_metadata'] ?? [] );
$field_metadata = $context['field_metadata'] ?? [];
$fields = $this->prepare_fields( $form_data, $field_metadata );
$submission_id = Query::get_instance()->add_submission(
[
'main_meta_id' => 0,
'post_id' => $context['post_id'],
'referer' => $this->get_referer(),
'referer_title' => $this->get_referer_title(),
'element_id' => $context['form_id'],
'form_name' => $context['form_name'],
'campaign_id' => 0,
'user_id' => get_current_user_id(),
'user_ip' => in_array( 'remote_ip', $metadata_keys, true ) ? Utils::get_client_ip() : '',
'user_agent' => in_array( 'user_agent', $metadata_keys, true ) ? $this->get_user_agent() : '',
'actions_count' => 0,
'actions_succeeded_count' => 0,
'meta' => wp_json_encode( [] ),
],
$fields
);
if ( ! $submission_id ) {
return $this->failure( __( 'Failed to save submission to database', 'elementor-pro' ) );
}
$this->store_form_snapshot( $context, $fields );
return $this->success(
__( 'Submission saved successfully', 'elementor-pro' ),
[ 'submissionId' => $submission_id ]
);
}
private function normalize_metadata_keys( array $raw ): array {
$allowed = [ 'remote_ip', 'user_agent' ];
return array_values( array_intersect( $raw, $allowed ) );
}
private function prepare_fields( array $form_data, array $field_metadata = [] ): array {
$fields = [];
foreach ( $form_data as $key => $value ) {
$meta = $field_metadata[ $key ] ?? [];
$label = ! empty( $meta['label'] ) ? $meta['label'] : ucwords( str_replace( [ '_', '-' ], ' ', $key ) );
$type = ! empty( $meta['type'] ) ? $meta['type'] : $this->guess_field_type( $key, $value );
$fields[] = [
'id' => $key,
'type' => $type,
'label' => $label,
'value' => is_array( $value ) ? implode( ', ', $value ) : $value,
];
}
return $fields;
}
private function guess_field_type( string $key, $value ): string {
$key_lower = strtolower( $key );
if ( strpos( $key_lower, 'email' ) !== false ) {
return 'email';
}
if ( strpos( $key_lower, 'phone' ) !== false || strpos( $key_lower, 'tel' ) !== false ) {
return 'tel';
}
if ( is_array( $value ) ) {
return 'checkbox';
}
if ( strpos( $key_lower, 'message' ) !== false || ( is_string( $value ) && strlen( $value ) > 100 ) ) {
return 'textarea';
}
if ( strpos( $key_lower, 'url' ) !== false || strpos( $key_lower, 'website' ) !== false ) {
return 'url';
}
return 'text';
}
private function store_form_snapshot( array $context, array $fields ): void {
$snapshot_fields = array_map(
function ( $field ) {
return [
'id' => $field['id'],
'type' => $field['type'],
'label' => $field['label'],
];
},
$fields
);
Form_Snapshot_Repository::instance()->create_or_update(
$context['post_id'],
$context['form_id'],
[
'name' => $context['form_name'],
'fields' => $snapshot_fields,
]
);
}
private function get_referer(): string {
$referer = ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_REFERER' );
if ( $referer ) {
return esc_url_raw( wp_unslash( $referer ) );
}
return '';
}
private function get_referer_title(): string {
// For now, return empty as we don't have access to the frontend page title
return '';
}
private function get_user_agent(): string {
$user_agent = ElementorUtils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' );
if ( $user_agent ) {
return sanitize_textarea_field( wp_unslash( $user_agent ) );
}
return '';
}
}
@@ -0,0 +1,152 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Modules\AtomicForm\Actions\Email_Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Email_Action extends Action_Base {
public function get_type(): string {
return Action_Type::EMAIL;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$validation = $this->validate_settings( $widget_settings );
if ( is_wp_error( $validation ) ) {
return $this->failure( $validation->get_error_message() );
}
$email_settings = new Email_Settings( $widget_settings );
$to = $email_settings->to();
$from = $email_settings->from();
$from_name = $email_settings->from_name();
$message = $email_settings->message();
$subject = $email_settings->subject();
$reply_to = $email_settings->reply_to();
$cc = $email_settings->cc();
$bcc = $email_settings->bcc();
$content_type = $email_settings->content_type();
$field_metadata = $context['field_metadata'] ?? [];
$message = $this->replace_shortcodes( $message, $form_data, 'html' === $content_type, $field_metadata );
$headers = [];
$headers[] = sprintf( 'From: %s <%s>', $from_name, $from );
$headers[] = sprintf( 'Reply-To: %s', $reply_to );
if ( 'html' === $content_type ) {
$headers[] = 'Content-Type: text/html; charset=UTF-8';
}
if ( ! empty( $cc ) ) {
$headers[] = sprintf( 'Cc: %s', $cc );
}
if ( ! empty( $bcc ) ) {
$headers[] = sprintf( 'Bcc: %s', $bcc );
}
/**
* Filter email headers for atomic forms.
*
* @param array $headers Email headers.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
*/
$headers = apply_filters( 'elementor_pro/atomic_forms/email_headers', $headers, $form_data, $widget_settings );
/**
* Filter email message for atomic forms.
*
* @param string $message Email message.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
*/
$message = apply_filters( 'elementor_pro/atomic_forms/email_message', $message, $form_data, $widget_settings );
$email_sent = wp_mail( $to, $subject, $message, $headers );
if ( ! $email_sent ) {
return $this->failure( __( 'Failed to send email', 'elementor-pro' ) );
}
return $this->success( __( 'Email sent successfully', 'elementor-pro' ) );
}
protected function validate_settings( array $widget_settings ) {
$email_settings = new Email_Settings( $widget_settings );
$email_to = $email_settings->to();
if ( ! empty( $email_to ) && ! is_email( $email_to ) ) {
$emails = array_map( 'trim', explode( ',', $email_to ) );
foreach ( $emails as $email ) {
if ( ! is_email( $email ) ) {
return new \WP_Error(
'invalid_email',
sprintf(
/* translators: %s: Invalid email address. */
__( 'Invalid email address: %s', 'elementor-pro' ),
$email
)
);
}
}
}
return true;
}
private function replace_shortcodes( string $message, array $form_data, bool $is_html, array $field_metadata = [] ): string {
$line_break = $is_html ? '<br>' : "\n";
if ( strpos( $message, '[all-fields]' ) !== false ) {
$all_fields_text = '';
foreach ( $form_data as $key => $value ) {
$meta = $field_metadata[ $key ] ?? [];
$formatted_key = ! empty( $meta['label'] ) ? $meta['label'] : ucwords( str_replace( [ '_', '-' ], ' ', $key ) );
$formatted_value = is_array( $value ) ? implode( ', ', $value ) : $value;
if ( $is_html ) {
$formatted_key = esc_html( $formatted_key );
if ( is_string( $formatted_value ) ) {
$formatted_value = nl2br( esc_html( $formatted_value ) );
}
}
$all_fields_text .= sprintf(
'%s: %s%s',
$formatted_key,
$formatted_value,
$line_break
);
}
$message = str_replace( '[all-fields]', $all_fields_text, $message );
}
$message = preg_replace_callback(
'/\[field[^\]]*id=["\']([^"\']+)["\'][^\]]*\]/',
function ( $matches ) use ( $form_data ) {
$field_id = $matches[1];
if ( isset( $form_data[ $field_id ] ) ) {
$value = $form_data[ $field_id ];
return is_array( $value ) ? implode( ', ', $value ) : $value;
}
return '';
},
$message
);
return $message;
}
}
@@ -0,0 +1,56 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Email_Settings {
private $email_settings;
public function __construct( array $widget_settings ) {
$this->email_settings = $widget_settings['email'] ?? [];
}
public function to() {
return $this->email_settings['to'] ?? get_option( 'admin_email' );
}
public function from() {
return $this->email_settings['from'] ?? 'noreply@' . Utils::get_site_domain();
}
public function from_name() {
return $this->email_settings['from-name'] ?? get_bloginfo( 'name' );
}
public function subject() {
return $this->email_settings['subject'] ?? sprintf(
/* translators: %s: Site title. */
__( 'New message from "%s"', 'elementor-pro' ),
get_bloginfo( 'name' )
);
}
public function message() {
return $this->email_settings['message'] ?? '[all-fields]';
}
public function reply_to() {
return $this->email_settings['reply-to'] ?? $this->from();
}
public function cc() {
return $this->email_settings['cc'] ?? '';
}
public function bcc() {
return $this->email_settings['bcc'] ?? '';
}
public function content_type() {
return $this->email_settings['send-as'] ?? 'html';
}
}
@@ -0,0 +1,128 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Webhook_Action extends Action_Base {
public function get_type(): string {
return Action_Type::WEBHOOK;
}
public function execute( array $form_data, array $widget_settings, array $context ): array {
$validation = $this->validate_settings( $widget_settings );
if ( is_wp_error( $validation ) ) {
return $this->failure( $validation->get_error_message() );
}
$url = $widget_settings['webhook_url'];
$method = strtoupper( $widget_settings['webhook_method'] ?? 'POST' );
$timeout = isset( $widget_settings['webhook_timeout'] ) ? absint( $widget_settings['webhook_timeout'] ) : 30;
$payload = [
'formData' => $form_data,
'postId' => $context['post_id'],
'formId' => $context['form_id'],
'formName' => $context['form_name'],
'timestamp' => current_time( 'mysql' ),
'siteUrl' => get_site_url(),
];
/**
* Filter webhook payload for atomic forms.
*
* @param array $payload Webhook payload.
* @param array $form_data Form data.
* @param array $widget_settings Widget settings.
* @param array $context Form context.
*/
$payload = apply_filters(
'elementor_pro/atomic_forms/webhook_payload',
$payload,
$form_data,
$widget_settings,
$context
);
$args = [
'method' => $method,
'timeout' => $timeout,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'Elementor Pro Atomic Forms/' . ELEMENTOR_PRO_VERSION,
],
'body' => wp_json_encode( $payload ),
];
if ( ! empty( $widget_settings['webhook_headers'] ) && is_array( $widget_settings['webhook_headers'] ) ) {
$args['headers'] = array_merge( $args['headers'], $widget_settings['webhook_headers'] );
}
$response = wp_remote_request( $url, $args );
if ( is_wp_error( $response ) ) {
return $this->failure(
sprintf(
/* translators: %s: Error message. */
__( 'Webhook request failed: %s', 'elementor-pro' ),
$response->get_error_message()
)
);
}
$response_code = wp_remote_retrieve_response_code( $response );
$response_body = wp_remote_retrieve_body( $response );
if ( $response_code >= 200 && $response_code < 300 ) {
return $this->success(
__( 'Webhook delivered successfully', 'elementor-pro' ),
[
'responseCode' => $response_code,
'responseBody' => $response_body,
]
);
}
return $this->failure(
sprintf(
/* translators: %d: HTTP status code. */
__( 'Webhook returned error status code: %d', 'elementor-pro' ),
$response_code
),
[
'responseCode' => $response_code,
'responseBody' => $response_body,
]
);
}
protected function validate_settings( array $widget_settings ) {
if ( empty( $widget_settings['webhook_url'] ) ) {
return new \WP_Error(
'missing_url',
__( 'Webhook URL is required', 'elementor-pro' )
);
}
if ( ! filter_var( $widget_settings['webhook_url'], FILTER_VALIDATE_URL ) ) {
return new \WP_Error(
'invalid_url',
__( 'Invalid webhook URL', 'elementor-pro' )
);
}
if ( isset( $widget_settings['webhook_method'] ) ) {
$allowed_methods = [ 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ];
if ( ! in_array( strtoupper( $widget_settings['webhook_method'] ), $allowed_methods, true ) ) {
return new \WP_Error(
'invalid_method',
__( 'Invalid HTTP method', 'elementor-pro' )
);
}
}
return true;
}
}
@@ -0,0 +1,292 @@
<?php
namespace ElementorPro\Modules\AtomicForm;
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
use Elementor\Utils as ElementorUtils;
use ElementorPro\Modules\AtomicForm\Actions\Action_Runner;
use ElementorPro\Modules\AtomicWidgets\Settings_Resolver;
use ElementorPro\Modules\Forms\Classes\Ajax_Handler;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Atomic_Form_Controller {
const NONCE_ACTION = 'elementor_pro_atomic_forms_send_form';
public static function is_form_submitted(): bool {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is validated in ajax_send_form.
return wp_doing_ajax()
&& 'elementor_pro_atomic_forms_send_form' === ElementorUtils::get_super_global_value( $_POST, 'action' );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function ajax_send_form(): void {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is validated below.
$post_data = [
'_nonce' => ElementorUtils::get_super_global_value( $_POST, '_nonce' ),
'post_id' => ElementorUtils::get_super_global_value( $_POST, 'post_id' ),
'form_id' => ElementorUtils::get_super_global_value( $_POST, 'form_id' ),
'form_name' => ElementorUtils::get_super_global_value( $_POST, 'form_name' ),
'form_fields' => ElementorUtils::get_super_global_value( $_POST, 'form_fields' ) ?? [],
];
// phpcs:enable WordPress.Security.NonceVerification.Missing
if ( ! $this->is_nonce_valid( $post_data ) ) {
$this->send_invalid_form_response();
}
$post_id = absint( $post_data['post_id'] ?? 0 );
$form_id = sanitize_text_field( $post_data['form_id'] ?? '' );
$form_fields = $post_data['form_fields'] ?? [];
if ( ! $post_id || ! $form_id || empty( $form_fields ) ) {
$this->send_invalid_form_response();
}
$form_data = $this->convert_form_fields_to_data( $form_fields );
if ( empty( $form_data ) ) {
$this->send_invalid_form_response();
}
$field_metadata = $this->extract_field_metadata( $form_fields );
$widget_settings = $this->get_widget_settings( $post_id, $form_id );
if ( is_wp_error( $widget_settings ) ) {
$this->send_error_response( $widget_settings->get_error_message() );
}
$posted_form_name = sanitize_text_field( $post_data['form_name'] ?? '' );
$form_name = $this->resolve_form_name( $posted_form_name, $form_id );
$spam_check = apply_filters(
'elementor_pro/atomic_forms/spam_check',
false,
$form_fields,
$widget_settings,
$post_id
);
if ( $spam_check ) {
$this->send_error_response(
__( 'Your submission was flagged as spam. Please try again or contact the site administrator.', 'elementor-pro' )
);
}
$actions = $widget_settings['actions-after-submit'] ?? [];
if ( empty( $actions ) ) {
$this->send_error_response( __( 'No actions configured for this form', 'elementor-pro' ) );
}
$results = Action_Runner::execute_actions(
$actions,
$form_data,
$widget_settings,
[
'post_id' => $post_id,
'form_id' => $form_id,
'form_name' => $form_name,
'field_metadata' => $field_metadata,
]
);
$this->send_response(
$results['actionResults'],
$results['allActionsSucceeded'],
$results['failedActions']
);
}
private function is_nonce_valid( array $post_data ): bool {
$nonce = $post_data['_nonce'] ?? '';
if ( ! $nonce ) {
return false;
}
return wp_verify_nonce( $nonce, self::NONCE_ACTION );
}
private function convert_form_fields_to_data( array $form_fields ): array {
$form_data = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$id = sanitize_text_field( $field['id'] ?? '' );
$value = $field['value'] ?? '';
if ( ! $id ) {
continue;
}
if ( is_array( $value ) ) {
$form_data[ $id ] = array_map( 'sanitize_text_field', $value );
} else {
$type = sanitize_text_field( $field['type'] ?? 'text' );
if ( 'textarea' === $type ) {
$form_data[ $id ] = sanitize_textarea_field( $value );
} else {
$form_data[ $id ] = sanitize_text_field( $value );
}
}
}
return $form_data;
}
private function resolve_form_name( string $posted_form_name, string $form_id ): string {
return ! empty( $posted_form_name ) ? $posted_form_name : $form_id;
}
private function extract_field_metadata( array $form_fields ): array {
$metadata = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$id = sanitize_text_field( $field['id'] ?? '' );
if ( ! $id ) {
continue;
}
$metadata[ $id ] = [
'label' => sanitize_text_field( $field['label'] ?? '' ),
'type' => sanitize_text_field( $field['type'] ?? '' ),
];
}
return $metadata;
}
private function get_widget_settings( int $post_id, string $form_id ) {
$document = Plugin::elementor()->documents->get( $post_id );
if ( ! $document ) {
return new \WP_Error(
'document_not_found',
__( 'Document not found', 'elementor-pro' )
);
}
$element_data = $document->get_elements_data();
$form_element = ElementorUtils::find_element_recursive( $element_data, $form_id );
if ( empty( $form_element ) ) {
return new \WP_Error(
'form_not_found',
__( 'Form element not found', 'elementor-pro' )
);
}
$settings = $form_element['settings'] ?? [];
$settings = $this->resolve_dynamic_tags_in_settings( $settings, $post_id );
$resolved = Settings_Resolver::resolve( $settings );
if ( ! isset( $resolved['actions-after-submit'] ) && isset( $resolved['email'] ) ) {
$resolved['actions-after-submit'] = [ 'email' ];
}
return $resolved;
}
/**
* @param array|string $value
* @param int $post_id
* @return array|string|null
*/
private function resolve_dynamic_tags_in_settings( $value, int $post_id ) {
if ( ! is_array( $value ) ) {
return $value;
}
if ( Dynamic_Prop_Type::is_dynamic_prop_value( $value ) ) {
if ( ! empty( $value['disabled'] ) ) {
return null;
}
$tag_data = $value['value'] ?? [];
$tag_name = $tag_data['name'] ?? '';
$tag_settings = $tag_data['settings'] ?? [];
if ( empty( $tag_name ) ) {
return null;
}
Plugin::elementor()->db->switch_to_post( $post_id );
try {
return Plugin::elementor()->dynamic_tags->get_tag_data_content(
null,
$tag_name,
$tag_settings
);
} finally {
Plugin::elementor()->db->restore_current_post();
}
}
return array_map( function ( $item ) use ( $post_id ) {
return $this->resolve_dynamic_tags_in_settings( $item, $post_id );
}, $value );
}
private function send_invalid_form_response(): void {
wp_send_json_error( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::INVALID_FORM, [] ),
] );
}
private function send_error_response( string $message = '' ): void {
wp_send_json_error( [
'message' => $message ?? Ajax_Handler::get_default_message( Ajax_Handler::ERROR, [] ),
] );
}
private function send_response( array $action_results, bool $all_actions_succeeded, array $failed_actions ): void {
$response_data = [
'actionResults' => $action_results,
'allActionsSucceeded' => $all_actions_succeeded,
'failedActions' => $failed_actions,
];
if ( $all_actions_succeeded ) {
wp_send_json_success( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::SUCCESS, [] ),
'data' => $response_data,
] );
} else {
$has_success = ! empty( $action_results ) && count( $failed_actions ) < count( $action_results );
if ( $has_success ) {
wp_send_json_success( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::SUCCESS, [] ),
'data' => $response_data,
] );
} else {
wp_send_json_error( [
'message' => Ajax_Handler::get_default_message( Ajax_Handler::ERROR, [] ),
'data' => $response_data,
] );
}
}
}
public function __construct() {
add_action( 'wp_ajax_elementor_pro_atomic_forms_send_form', [ $this, 'ajax_send_form' ] );
add_action( 'wp_ajax_nopriv_elementor_pro_atomic_forms_send_form', [ $this, 'ajax_send_form' ] );
}
}
@@ -0,0 +1,22 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
{% set placeholder_attribute = settings.placeholder is not empty ? 'placeholder=' ~ settings.placeholder | e('html_attr') : '' %}
{% set required_attribute = settings.required ? 'required' : '' %}
{% set checked_attribute = settings.checked ? 'checked' : '' %}
{% set name = settings.name is not empty ? settings.name : settings._cssid is not empty ? 'checkbox_' ~ settings._cssid : 'checkbox_' ~ id %}
{% set name_attribute = 'name=' ~ name | e('html_attr') %}
{% set value_attribute = settings.value is not empty ? 'value=' ~ settings.value | e('html_attr') : '' %}
<input
{{ id_attribute }}
{{ name_attribute }}
{{ value_attribute }}
class="{{ classes }}"
type="checkbox"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ checked_attribute }}
/>
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Checkbox;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Checkbox extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a checkbox input with required, readonly, and attributes.';
public static function get_element_type(): string {
return 'e-form-checkbox';
}
public function get_title(): string {
return esc_html__( 'Checkbox', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-checkbox';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'checkbox' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'name' => String_Prop_Type::make()
->default( '' ),
'value' => String_Prop_Type::make()
->default( '' ),
'required' => Boolean_Prop_Type::make()
->default( false ),
'checked' => Boolean_Prop_Type::make()
->default( false ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor-pro' ) )
->set_items( [
Text_Control::bind_to( 'name' )
->set_label( __( 'Group name', 'elementor-pro' ) )
->set_placeholder( __( 'Enter checkbox group name', 'elementor-pro' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
Text_Control::bind_to( 'value' )
->set_label( __( 'Choice value', 'elementor-pro' ) )
->set_placeholder( __( 'Enter choice value', 'elementor-pro' ) )
->set_meta( [
'layout' => 'two-columns',
] ),
Switch_Control::bind_to( 'required' )
->set_label( __( 'Required', 'elementor-pro' ) ),
Switch_Control::bind_to( 'checked' )
->set_label( __( 'Checked', 'elementor-pro' ) ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'checkbox' => __DIR__ . '/checkbox.html.twig',
];
}
protected function define_base_styles(): array {
return [];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
protected function define_atomic_pseudo_states(): array {
return [
Style_States::get_pseudo_states_map()['checked'],
];
}
}
+192
View File
@@ -0,0 +1,192 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Classes;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use ElementorPro\Core\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Akismet {
public function __construct() {
add_filter( 'elementor/atomic-widgets/props-schema', [ $this, 'register_props' ] );
add_filter( 'elementor/atomic-widgets/controls', [ $this, 'register_controls' ], 10, 2 );
add_filter( 'elementor_pro/atomic_forms/spam_check', [ $this, 'is_spam' ], 10, 4 );
}
public function register_props( array $schema ): array {
if ( ! isset( $schema['actions-after-submit'] ) ) {
return $schema;
}
if ( ! $this->is_akismet_active() ) {
return $schema;
}
$schema['akismet-enabled'] = Boolean_Prop_Type::make()->default( false );
return $schema;
}
public function register_controls( array $controls, $element ): array {
if ( ! ( $element instanceof Atomic_Form ) ) {
return $controls;
}
if ( ! $this->is_akismet_active() ) {
return $controls;
}
$controls[] = Section::make()
->set_label( __( 'Akismet', 'elementor-pro' ) )
->set_items( [
Switch_Control::bind_to( 'akismet-enabled' )
->set_label( __( 'Akismet spam protection', 'elementor-pro' ) ),
] );
return $controls;
}
public function is_akismet_active(): bool {
if ( ! class_exists( '\Akismet' ) ) {
return false;
}
$akismet_key = \Akismet::get_api_key();
return ! empty( $akismet_key );
}
public function is_spam( bool $is_spam, array $form_fields, array $widget_settings, int $post_id = 0 ): bool {
if ( $is_spam ) {
return true;
}
if ( empty( $widget_settings['akismet-enabled'] ) ) {
return false;
}
if ( ! $this->is_akismet_active() ) {
return false;
}
$mapped = $this->map_fields( $form_fields );
$params = $this->build_params( $mapped, $post_id );
return $this->remote_check_is_spam( $params );
}
private function map_fields( array $form_fields ): array {
$mapped = [
'comment_author' => '',
'comment_author_email' => '',
'comment_author_url' => '',
'comment_content' => '',
];
$text_fields = [];
$textarea_fields = [];
foreach ( $form_fields as $field ) {
if ( ! is_array( $field ) ) {
continue;
}
$type = sanitize_text_field( $field['type'] ?? 'text' );
$value = sanitize_text_field( $field['value'] ?? '' );
$label = sanitize_text_field( $field['label'] ?? '' );
switch ( $type ) {
case 'email':
if ( empty( $mapped['comment_author_email'] ) ) {
$mapped['comment_author_email'] = $value;
}
break;
case 'url':
if ( empty( $mapped['comment_author_url'] ) ) {
$mapped['comment_author_url'] = $value;
}
break;
case 'textarea':
$textarea_fields[] = [
'label' => $label,
'value' => sanitize_textarea_field( $field['value'] ?? '' ),
];
break;
case 'text':
$text_fields[] = [
'label' => $label,
'value' => $value,
];
break;
}
}
$mapped['comment_author'] = $this->concatenate_fields( $text_fields );
$mapped['comment_content'] = $this->concatenate_fields( $textarea_fields );
return $mapped;
}
private function concatenate_fields( array $fields ): string {
if ( empty( $fields ) ) {
return '';
}
if ( 1 === count( $fields ) ) {
return $fields[0]['value'];
}
$parts = [];
foreach ( $fields as $field ) {
$label = ! empty( $field['label'] ) ? $field['label'] : 'Field';
$parts[] = $label . ': ' . $field['value'];
}
return implode( ' | ', $parts );
}
private function build_params( array $mapped, int $post_id = 0 ): array {
$params = $mapped;
$params['blog'] = get_option( 'home' );
$params['blog_lang'] = get_locale();
$params['blog_charset'] = get_option( 'blog_charset' );
if ( $post_id ) {
$params['permalink'] = get_permalink( $post_id );
}
$params['user_ip'] = Utils::get_client_ip();
$params['referrer'] = wp_get_referer();
if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
$params['user_agent'] = sanitize_textarea_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
$params['comment_type'] = 'contact-form';
$ignore = [ 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ];
foreach ( $_SERVER as $key => $value ) {
if ( ! in_array( $key, $ignore, true ) && is_string( $value ) ) {
$params[ $key ] = sanitize_text_field( wp_unslash( $value ) );
}
}
return $params;
}
private function remote_check_is_spam( array $params ): bool {
$response = \Akismet::http_post( _http_build_query( $params, '', '&' ), 'comment-check' );
return 'true' === $response[1];
}
}
@@ -11,10 +11,10 @@
{{ name_attribute }}
class="{{ classes }}"
type="{{ settings.type }}"
data-interaction-id="{{ id }}"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ readonly_attribute }}
/>
@@ -1,5 +1,5 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Widgets;
namespace ElementorPro\Modules\AtomicForm\Input;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
@@ -12,7 +12,11 @@ use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
@@ -40,7 +44,7 @@ class Input extends Atomic_Widget_Base {
}
public function get_keywords() {
return [ 'atomic', 'form', 'input', 'text', 'email' ];
return [ 'atomic', 'form', 'input', 'text', 'email', 'number', 'tel', 'password' ];
}
protected static function define_props_schema(): array {
@@ -51,12 +55,12 @@ class Input extends Atomic_Widget_Base {
->default( '' ),
'type' => String_Prop_Type::make()
->default( 'text' )
->enum( [ 'text', 'email' ] ),
->enum( [ 'text', 'email', 'number', 'tel', 'password' ] ),
'required' => Boolean_Prop_Type::make()
->default( false ),
'readonly' => Boolean_Prop_Type::make()
->default( false ),
'attributes' => Attributes_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
@@ -79,6 +83,18 @@ class Input extends Atomic_Widget_Base {
'label' => __( 'Email', 'elementor-pro' ),
'value' => 'email',
],
[
'label' => __( 'Number', 'elementor-pro' ),
'value' => 'number',
],
[
'label' => __( 'Tel', 'elementor-pro' ),
'value' => 'tel',
],
[
'label' => __( 'Password', 'elementor-pro' ),
'value' => 'password',
],
] ),
Switch_Control::bind_to( 'required' )
->set_label( __( 'Required', 'elementor-pro' ) ),
@@ -102,13 +118,52 @@ class Input extends Atomic_Widget_Base {
protected function get_templates(): array {
return [
'input' => __DIR__ . '/../templates/input.html.twig',
'input' => __DIR__ . '/input.html.twig',
];
}
protected function define_base_styles(): array {
$border_radius_value = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
$height_value = Size_Prop_Type::generate( [
'size' => 36,
'unit' => 'px',
] );
$border_color_value = Color_Prop_Type::generate( '#D6D5D5' );
return [
'base' => Style_Definition::make(),
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'border-radius' => $border_radius_value,
'height' => $height_value,
'border-color' => $border_color_value,
'font-size' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
] ),
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::FOCUS )
->add_props( [
'border-color' => Color_Prop_Type::generate( '#706F6F' ),
'outline-style' => String_Prop_Type::generate( 'none' ),
] ),
),
'base::placeholder' => Style_Definition::make() // this should be changed once we support placeholder/pseudo-elements styles in the styles system.
->add_variant(
Style_Variant::make()
->add_props( [
'color' => Color_Prop_Type::generate( '#9DA5AE' ),
] ),
),
];
}
@@ -118,4 +173,5 @@ class Input extends Atomic_Widget_Base {
'topDivider' => false,
];
}
}
@@ -7,7 +7,7 @@
{{ id_attribute }}
class="{{ classes }}"
{{ for_attribute }}
data-interaction-id="{{ id }}"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
>{{ settings.text | striptags(allowed_tags) | raw }}</label>
@@ -1,5 +1,5 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Widgets;
namespace ElementorPro\Modules\AtomicForm\Label;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
@@ -9,9 +9,12 @@ use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
@@ -44,13 +47,18 @@ class Label extends Atomic_Widget_Base {
protected static function define_props_schema(): array {
return [
'tag' => String_Prop_Type::make()
->default( 'label' ),
'classes' => Classes_Prop_Type::make()
->default( [] ),
'text' => Html_Prop_Type::make()
->default( 'Form label' ),
'text' => Html_V3_Prop_Type::make()
->default( [
'content' => String_Prop_Type::generate( 'Form label' ),
'children' => [],
] ),
'input-id' => String_Prop_Type::make()
->default( '' ),
'attributes' => Attributes_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
@@ -84,7 +92,7 @@ class Label extends Atomic_Widget_Base {
protected function get_templates(): array {
return [
'label' => __DIR__ . '/../templates/label.html.twig',
'label' => __DIR__ . '/label.html.twig',
];
}
@@ -94,4 +102,24 @@ class Label extends Atomic_Widget_Base {
'topDivider' => false,
];
}
protected function define_base_styles(): array {
$text_color_value = Color_Prop_Type::generate( '#0c0d0e' );
$font_size_value = Size_Prop_Type::generate( [
'size' => 14,
'unit' => 'px',
] );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'color' => $text_color_value,
'font-size' => $font_size_value,
] ),
),
];
}
}
@@ -0,0 +1,11 @@
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') | trim %}
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
{% set interactions_attribute = interactions is not empty ? 'data-interactions=' ~ interactions | json_encode | e('html_attr') : '' %}
<button
{{ id_attribute }}
class="{{ classes }}"
type="submit"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
>{{ settings.label | e }}</button>
@@ -0,0 +1,155 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Submit_Button;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Submit_Button extends Atomic_Widget_Base {
use Has_Template;
public static $widget_description = 'Display a submit button with customizable label text.';
private static $button_background_color_hover = '#323232';
public static function get_element_type(): string {
return 'e-form-submit-button';
}
public function get_title(): string {
return esc_html__( 'Submit button', 'elementor-pro' );
}
public function get_icon(): string {
return 'eicon-atomic-submit-button';
}
public function get_categories(): array {
return [ 'atomic-form' ];
}
public function get_keywords() {
return [ 'atomic', 'form', 'button', 'submit', 'send' ];
}
protected static function define_props_schema(): array {
return [
'classes' => Classes_Prop_Type::make()
->default( [] ),
'label' => String_Prop_Type::make()
->default( 'Submit' ),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
protected function define_atomic_controls(): array {
return [
Section::make()
->set_label( __( 'Content', 'elementor-pro' ) )
->set_items( [
Text_Control::bind_to( 'label' )
->set_label( __( 'Button Text', 'elementor-pro' ) )
->set_placeholder( 'Submit' ),
] ),
Section::make()
->set_label( __( 'Settings', 'elementor-pro' ) )
->set_id( 'settings' )
->set_items( $this->get_settings_controls() ),
];
}
protected function get_settings_controls(): array {
return [
Text_Control::bind_to( '_cssid' )
->set_label( __( 'ID', 'elementor-pro' ) )
->set_meta( $this->get_css_id_control_meta() ),
];
}
protected function get_templates(): array {
return [
'submit_button' => __DIR__ . '/submit-button.html.twig',
];
}
protected function define_base_styles(): array {
$background_color_value = Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( '#000' ),
] );
$text_color_value = Color_Prop_Type::generate( '#fff' );
$display_value = String_Prop_Type::generate( 'flex' );
$padding_value = Dimensions_Prop_Type::generate( [
'block-start' => Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
]),
'inline-end' => Size_Prop_Type::generate( [
'size' => 30,
'unit' => 'px',
]),
'block-end' => Size_Prop_Type::generate( [
'size' => 10,
'unit' => 'px',
]),
'inline-start' => Size_Prop_Type::generate( [
'size' => 28,
'unit' => 'px',
]),
]);
$justify_content_value = String_Prop_Type::generate( 'center' );
$align_items_value = String_Prop_Type::generate( 'center' );
$background_color_hover_value = Background_Prop_Type::generate( [
'color' => Color_Prop_Type::generate( self::$button_background_color_hover ),
] );
$border_base_size = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
return [
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_prop( 'background', $background_color_value )
->add_prop( 'color', $text_color_value )
->add_prop( 'display', $display_value )
->add_prop( 'padding', $padding_value )
->add_prop( 'justify-content', $justify_content_value )
->add_prop( 'align-items', $align_items_value )
->add_prop( 'border-radius', $border_base_size )
->add_prop( 'border-width', $border_base_size )
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::HOVER )
->add_prop( 'background', $background_color_hover_value )
),
];
}
protected function get_css_id_control_meta(): array {
return [
'layout' => 'two-columns',
'topDivider' => false,
];
}
public static function get_inline_styles(): string {
$base_class = self::get_element_type() . '-base';
return ".{$base_class} { cursor: pointer; box-sizing: border-box; }";
}
}
@@ -14,10 +14,10 @@
{{ id_attribute }}
{{ name_attribute }}
class="{{ classes }}"
data-interaction-id="{{ id }}"
data-interaction-id="{{ interaction_id | default(id) }}"
{{ settings.attributes | raw }}
{{ interactions_attribute }}
{{ placeholder_attribute }}
{{ placeholder_attribute | raw }}
{{ required_attribute }}
{{ readonly_attribute }}
{{ rows_attribute }}
@@ -1,5 +1,5 @@
<?php
namespace ElementorPro\Modules\AtomicForm\Widgets;
namespace ElementorPro\Modules\AtomicForm\Textarea;
use Elementor\Modules\AtomicWidgets\Controls\Section;
use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control;
@@ -13,6 +13,11 @@ use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
@@ -59,7 +64,7 @@ class Textarea extends Atomic_Widget_Base {
->default( true ),
'minlength' => Number_Prop_Type::make(),
'maxlength' => Number_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make(),
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
];
}
@@ -107,13 +112,46 @@ class Textarea extends Atomic_Widget_Base {
protected function get_templates(): array {
return [
'textarea' => __DIR__ . '/../templates/textarea.html.twig',
'textarea' => __DIR__ . '/textarea.html.twig',
];
}
protected function define_base_styles(): array {
$border_radius_value = Size_Prop_Type::generate( [
'size' => 0,
'unit' => 'px',
] );
$border_color_value = Color_Prop_Type::generate( '#D6D5D5' );
return [
'base' => Style_Definition::make(),
'base' => Style_Definition::make()
->add_variant(
Style_Variant::make()
->add_props( [
'border-radius' => $border_radius_value,
'border-color' => $border_color_value,
'font-size' => Size_Prop_Type::generate( [
'size' => 12,
'unit' => 'px',
] ),
] ),
)
->add_variant(
Style_Variant::make()
->set_state( Style_States::FOCUS )
->add_props( [
'border-color' => Color_Prop_Type::generate( '#706F6F' ),
'outline-style' => String_Prop_Type::generate( 'none' ),
] ),
),
'base::placeholder' => Style_Definition::make() // this should be changed once we support placeholder/pseudo-elements styles in the styles system.
->add_variant(
Style_Variant::make()
->add_props( [
'color' => Color_Prop_Type::generate( '#9DA5AE' ),
] ),
),
];
}
@@ -123,4 +161,12 @@ class Textarea extends Atomic_Widget_Base {
'topDivider' => false,
];
}
public static function get_inline_styles(): string {
$base_class = self::get_element_type() . '-base';
// Default html textarea is resizable, but we want control over it from settings.
$inline_css = ".{$base_class}:not([data-resizable]) { resize: none; }";
return $inline_css;
}
}