PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / atomic-widgets / elements / base / has-atomic-base.php

has-atomic-base.php in Elementor Website Builder – more than just a page builder 4.3.0-beta3, at modules/atomic-widgets/elements/base/has-atomic-base.php

531 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\AtomicWidgets\Elements\Base;
4
5 use Elementor\Element_Base;
6 use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
7 use Elementor\Modules\AtomicWidgets\Controls\Section;
8 use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form;
9 use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
10 use Elementor\Modules\AtomicWidgets\Logger\Logger;
11 use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
12 use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
13 use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
14 use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
15 use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
16 use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
17 use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
18 use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
19 use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
20 use Elementor\Modules\AtomicWidgets\PropTypes\Prop_Duplication_Behavior;
21 use Elementor\Utils;
22 use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
23 use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
24
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit; // Exit if accessed directly.
27 }
28
29 /**
30 * @mixin Element_Base
31 */
32 trait Has_Atomic_Base {
33 use Has_Base_Styles;
34 use Has_Base_Settings;
35
36 public static function html_tag_follows_link(): bool {
37 return true;
38 }
39
40 public static function get_computed_html_tag( array $settings ): string {
41 return Html_Tag_Computer::compute( $settings, 'div', [
42 Html_Tag_Computer::FOLLOW_LINK_OPTION => static::html_tag_follows_link(),
43 ] );
44 }
45
46 public function has_widget_inner_wrapper(): bool {
47 return false;
48 }
49
50 abstract public static function get_element_type(): string;
51
52 final public function get_name() {
53 return static::get_element_type();
54 }
55
56 private function get_valid_controls( array $schema, array $controls ): array {
57 $valid_controls = [];
58
59 foreach ( $controls as $control ) {
60 if ( $control instanceof Section ) {
61 $cloned_section = clone $control;
62
63 $cloned_section->set_items(
64 $this->get_valid_controls( $schema, $control->get_items() )
65 );
66
67 $valid_controls[] = $cloned_section;
68 continue;
69 }
70
71 if ( ( $control instanceof Atomic_Control_Base ) ) {
72 $prop_name = $control->get_bind();
73
74 if ( ! $prop_name ) {
75 Utils::safe_throw( 'Control is missing a bound prop from the schema.' );
76 continue;
77 }
78
79 if ( ! array_key_exists( $prop_name, $schema ) ) {
80 Utils::safe_throw( "Prop `{$prop_name}` is not defined in the schema of `{$this->get_name()}`." );
81 continue;
82 }
83 }
84
85 $valid_controls[] = $control;
86 }
87
88 return $valid_controls;
89 }
90
91 private static function validate_schema( array $schema ) {
92 $widget_name = static::class;
93
94 foreach ( $schema as $key => $prop ) {
95 if ( ! ( $prop instanceof Prop_Type ) ) {
96 Utils::safe_throw( "Prop `$key` must be an instance of `Prop_Type` in `{$widget_name}`." );
97 }
98 }
99 }
100
101 private function parse_atomic_styles( array $data ): array {
102 $styles = $data['styles'] ?? [];
103 $style_parser = Style_Parser::make( Style_Schema::get() );
104 $validated_styles = [];
105
106 foreach ( $styles as $style_id => $style ) {
107 $result = $style_parser->parse( $style );
108
109 if ( ! $result->is_valid() ) {
110 Logger::warning(
111 $this->format_styles_validation_error_message(
112 $style_id,
113 $data,
114 $style,
115 $result->errors()->to_string()
116 )
117 );
118 continue;
119 }
120
121 $validated_styles[ $style_id ] = $result->unwrap();
122 }
123
124 return $validated_styles;
125 }
126
127 private function format_styles_validation_error_message(
128 string $style_id,
129 array $data,
130 array $style,
131 string $validation_errors
132 ): string {
133 $widget_id = $data['id'] ?? 'unknown';
134 $structure_label = $this->get_editor_structure_label( $data );
135 $style_label = isset( $style['label'] ) && is_string( $style['label'] ) ? $style['label'] : null;
136
137 $message_parts = [
138 "Styles validation failed for style `$style_id` (widget `$widget_id`)",
139 ];
140
141 if ( $structure_label ) {
142 $message_parts[] = "Structure label: `$structure_label`";
143 } else {
144 $element_name = $this->get_title();
145
146 if ( '' === $element_name ) {
147 $element_name = $this->get_name();
148 }
149
150 $message_parts[] = "Element: `$element_name`";
151 }
152
153 if ( $style_label ) {
154 $message_parts[] = "Style label: `$style_label`";
155 }
156
157 return implode( '. ', $message_parts ) . '. ' . $validation_errors;
158 }
159
160 private function get_editor_structure_label( array $data ): ?string {
161 $title = $data['editor_settings']['title'] ?? $this->editor_settings['title'] ?? null;
162
163 if ( ! is_string( $title ) || '' === $title ) {
164 return null;
165 }
166
167 return $title;
168 }
169
170 private function parse_atomic_settings( array $settings ): array {
171 $schema = static::get_props_schema();
172 $props_parser = Props_Parser::make( $schema );
173
174 $result = $props_parser->parse( $settings );
175
176 if ( ! $result->is_valid() ) {
177 throw new \Exception( esc_html( 'Settings validation failed. ' . $result->errors()->to_string() ) );
178 }
179
180 return $result->unwrap();
181 }
182
183 private function parse_atomic_interactions( $interactions ) {
184
185 if ( empty( $interactions ) ) {
186 return [];
187 }
188
189 if ( is_string( $interactions ) ) {
190 $decoded = json_decode( $interactions, true );
191 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
192 return $decoded;
193 }
194 }
195
196 if ( is_array( $interactions ) ) {
197 return $interactions;
198 }
199
200 return [];
201 }
202
203 private function extract_prop_value( $data, $key, $default = '' ) {
204 if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) {
205 return $default;
206 }
207
208 $value = $data[ $key ];
209
210 if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) {
211 return $value['value'];
212 }
213
214 return null !== $value ? $value : $default;
215 }
216
217 public function get_atomic_controls() {
218 $controls = apply_filters(
219 'elementor/atomic-widgets/controls',
220 $this->define_atomic_controls(),
221 $this
222 );
223
224 $schema = static::get_props_schema();
225
226 // Validate the schema only in the Editor.
227 static::validate_schema( $schema );
228
229 return $this->get_valid_controls( $schema, $controls );
230 }
231
232 protected function get_css_id_control_meta(): array {
233 return [
234 'layout' => 'two-columns',
235 'topDivider' => true,
236 ];
237 }
238
239 final public function get_controls( $control_id = null ) {
240 if ( ! empty( $control_id ) ) {
241 return null;
242 }
243
244 return [];
245 }
246
247 final public function get_data_for_save() {
248 $data = parent::get_data_for_save();
249
250 $data['version'] = $this->version;
251
252 $this->set_data_field_for_save(
253 $data,
254 'settings',
255 $this->parse_atomic_settings( $data['settings'] ?? [] )
256 );
257
258 $this->set_data_field_for_save(
259 $data,
260 'styles',
261 $this->parse_atomic_styles( $data )
262 );
263
264 $this->set_data_field_for_save(
265 $data,
266 'editor_settings',
267 $this->parse_editor_settings( $data['editor_settings'] ?? [] )
268 );
269
270 $this->set_data_field_for_save(
271 $data,
272 'interactions',
273 $this->transform_interactions_for_save( $data['interactions'] ?? [] )
274 );
275
276 return $data;
277 }
278
279 private function set_data_field_for_save( array &$data, string $key, $value ): void {
280 if ( ! empty( $value ) ) {
281 $data[ $key ] = $value;
282 return;
283 }
284
285 unset( $data[ $key ] );
286 }
287
288 private function transform_interactions_for_save( $interactions ) {
289 $decoded = $this->decode_interactions_data( $interactions );
290
291 if ( empty( $decoded['items'] ) ) {
292 return [];
293 }
294 return $decoded;
295 }
296
297 private function decode_interactions_data( $interactions ) {
298 if ( is_array( $interactions ) ) {
299 return $interactions;
300 }
301
302 if ( is_string( $interactions ) ) {
303 $decoded = json_decode( $interactions, true );
304 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
305 return $decoded;
306 }
307 }
308
309 return [
310 'items' => [],
311 'version' => 1,
312 ];
313 }
314
315 final public function get_raw_data( $with_html_content = false ) {
316 $raw_data = parent::get_raw_data( $with_html_content );
317
318 $raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] );
319 $raw_data['interactions'] = $this->interactions ?? [];
320 $raw_data['editor_settings'] = $this->editor_settings;
321
322 return $raw_data;
323 }
324
325 final public function get_stack( $with_common_controls = true ) {
326 return [
327 'controls' => [],
328 'tabs' => [],
329 ];
330 }
331
332 public function get_atomic_settings(): array {
333 $schema = static::get_props_schema();
334 $props = $this->get_settings();
335
336 $merged_attribute_values = array_merge(
337 $this->get_initial_attributes()['value'] ?? [],
338 $props['attributes']['value'] ?? []
339 );
340 $props['attributes'] = Attributes_Prop_Type::generate( $merged_attribute_values );
341
342 $parsed = Render_Props_Resolver::for_settings()->resolve( $schema, $props );
343
344 return $this->transform_link_for_render( $parsed );
345 }
346
347 protected function transform_link_for_render( array $parsed ): array {
348 $link_attributes = isset( $parsed['link'] ) ? $this->get_link_attributes_string( $parsed['link'] ) : '';
349
350 $parsed['link'] = ! empty( $link_attributes ) ? [
351 'tag' => $parsed['link']['tag'],
352 'attributes' => $link_attributes,
353 ] : null;
354
355 return $parsed;
356 }
357
358 protected function get_initial_attributes() {
359 return Attributes_Prop_Type::generate( [
360 Key_Value_Prop_Type::generate( [
361 'key' => String_Prop_Type::generate( 'data-e-type' ),
362 'value' => $this->get_type(),
363 ] ),
364 Key_Value_Prop_Type::generate( [
365 'key' => String_Prop_Type::generate( 'data-id' ),
366 'value' => $this->get_id(),
367 ] ),
368 ] );
369 }
370
371 public function get_atomic_setting( string $key ) {
372 $schema = static::get_props_schema();
373
374 if ( ! isset( $schema[ $key ] ) ) {
375 return null;
376 }
377
378 $props = $this->get_settings();
379 $prop_value = $props[ $key ] ?? null;
380
381 $single_schema = [ $key => $schema[ $key ] ];
382 $single_props = [ $key => $prop_value ];
383
384 $resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props );
385
386 return $resolved[ $key ] ?? null;
387 }
388
389 protected function parse_editor_settings( array $data ): array {
390 $editor_data = [];
391
392 if ( isset( $data['title'] ) && is_string( $data['title'] ) ) {
393 $editor_data['title'] = sanitize_text_field( $data['title'] );
394 }
395
396 if ( isset( $data['grid_outline'] ) && is_bool( $data['grid_outline'] ) ) {
397 $editor_data['grid_outline'] = $data['grid_outline'];
398 }
399
400 return $editor_data;
401 }
402
403 public static function get_props_schema(): array {
404 $schema = static::define_props_schema();
405
406 if ( ! isset( $schema['_cssid'] ) ) {
407 $schema['_cssid'] = String_Prop_Type::make()
408 ->meta( Overridable_Prop_Type::ignore() )
409 ->meta( Prop_Duplication_Behavior::clear() );
410 }
411
412 return apply_filters(
413 'elementor/atomic-widgets/props-schema',
414 $schema
415 );
416 }
417
418 protected function set_render_context( array $context_pairs ): void {
419 foreach ( $context_pairs as $context_pair ) {
420 $context_key = $context_pair['context_key'] ?? static::class;
421 $context = $context_pair['context'];
422 Render_Context::push( $context_key, $context );
423 }
424 }
425
426 protected function clear_render_context( array $context_pairs ): void {
427 foreach ( $context_pairs as $context_pair ) {
428 $context_key = $context_pair['context_key'] ?? static::class;
429 Render_Context::pop( $context_key );
430 }
431 }
432
433 public function print_content() {
434 $defined_context = $this->define_render_context();
435
436 if ( empty( $defined_context ) ) {
437 return parent::print_content();
438 }
439
440 $this->set_render_context( $defined_context );
441
442 parent::print_content();
443
444 $this->clear_render_context( $defined_context );
445 }
446
447 /**
448 * Define the context for element's Render_Context.
449 *
450 * @return array Array of context pairs. Each pair is an associative array with:
451 * - 'context_key' (optional): The context key. Defaults to static::class if not provided.
452 * - 'context' (required): The context value (can be any type).
453 *
454 * @example
455 * [
456 * [
457 * 'context_key' => 'custom-key',
458 * 'context' => ['some' => 'data'],
459 * ],
460 * [
461 * 'context' => ['instance_id' => $this->get_id()],
462 * ],
463 * ]
464 */
465 protected function define_render_context(): array {
466 return [];
467 }
468
469 protected function get_link_attributes( $link_settings ) {
470 if ( empty( $link_settings['href'] ) ) {
471 return [];
472 }
473
474 $tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG;
475 $url = $link_settings['href'];
476 $target = $link_settings['target'] ?? '_self';
477 $is_action_link = 'button' === $tag;
478 $url_attr_key = $is_action_link ? 'data-action-link' : 'href';
479
480 return [
481 $url_attr_key => $url,
482 'target' => $target,
483 ];
484 }
485
486 private function get_link_attributes_string( $link_settings ) {
487 $link_attributes = $this->get_link_attributes( $link_settings );
488
489 if ( empty( $link_attributes ) ) {
490 return '';
491 }
492
493 $parts = [];
494
495 foreach ( $link_attributes as $key => $value ) {
496 if ( 'tag' === $key ) {
497 continue;
498 }
499
500 $parts[] = sprintf( '%s="%s"', $key, esc_attr( $value ) );
501 }
502
503 return implode( ' ', $parts );
504 }
505
506 public function has_action_link() {
507 if ( ! $this->get_id() ) {
508 return true;
509 }
510
511 $link_settings = $this->get_atomic_setting( 'link' ) ?? null;
512 $attributes = $this->get_link_attributes( $link_settings );
513
514 return isset( $attributes['data-action-link'] );
515 }
516
517 public function get_script_depends() {
518 $depends = parent::get_script_depends();
519
520 if ( $this->has_action_link() ) {
521 $depends[] = Frontend_Assets_Loader::ACTION_LINK_HANDLERS_HANDLE;
522 }
523
524 if ( Atomic_Form::is_instance_form( $this ) ) {
525 $depends[] = Frontend_Assets_Loader::FORM_HANDLERS_HANDLE;
526 }
527
528 return $depends;
529 }
530 }
531