PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.9
Elementor Website Builder – more than just a page builder v3.35.9
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 4.0.7 All 451 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 3.35.9, at modules/atomic-widgets/elements/base/has-atomic-base.php

484 lines 13.1 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\PropsResolver\Render_Props_Resolver;
9 use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
10 use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
11 use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
12 use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
13 use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
14 use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
15 use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
16 use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
17 use Elementor\Utils;
18 use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
19 use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit; // Exit if accessed directly.
23 }
24
25 /**
26 * @mixin Element_Base
27 */
28 trait Has_Atomic_Base {
29 use Has_Base_Styles;
30
31 public function has_widget_inner_wrapper(): bool {
32 return false;
33 }
34
35 abstract public static function get_element_type(): string;
36
37 final public function get_name() {
38 return static::get_element_type();
39 }
40
41 private function get_valid_controls( array $schema, array $controls ): array {
42 $valid_controls = [];
43
44 foreach ( $controls as $control ) {
45 if ( $control instanceof Section ) {
46 $cloned_section = clone $control;
47
48 $cloned_section->set_items(
49 $this->get_valid_controls( $schema, $control->get_items() )
50 );
51
52 $valid_controls[] = $cloned_section;
53 continue;
54 }
55
56 if ( ( $control instanceof Atomic_Control_Base ) ) {
57 $prop_name = $control->get_bind();
58
59 if ( ! $prop_name ) {
60 Utils::safe_throw( 'Control is missing a bound prop from the schema.' );
61 continue;
62 }
63
64 if ( ! array_key_exists( $prop_name, $schema ) ) {
65 Utils::safe_throw( "Prop `{$prop_name}` is not defined in the schema of `{$this->get_name()}`." );
66 continue;
67 }
68 }
69
70 $valid_controls[] = $control;
71 }
72
73 return $valid_controls;
74 }
75
76 private static function validate_schema( array $schema ) {
77 $widget_name = static::class;
78
79 foreach ( $schema as $key => $prop ) {
80 if ( ! ( $prop instanceof Prop_Type ) ) {
81 Utils::safe_throw( "Prop `$key` must be an instance of `Prop_Type` in `{$widget_name}`." );
82 }
83 }
84 }
85
86 private function parse_atomic_styles( array $data ): array {
87 $styles = $data['styles'] ?? [];
88 $style_parser = Style_Parser::make( Style_Schema::get() );
89
90 foreach ( $styles as $style_id => $style ) {
91 $result = $style_parser->parse( $style );
92
93 if ( ! $result->is_valid() ) {
94 $widget_id = $data['id'] ?? 'unknown';
95 throw new \Exception( esc_html( "Styles validation failed for style `$style_id`. Widget ID: `$widget_id`. " . $result->errors()->to_string() ) );
96 }
97
98 $styles[ $style_id ] = $result->unwrap();
99 }
100
101 return $styles;
102 }
103
104 private function parse_atomic_settings( array $settings ): array {
105 $schema = static::get_props_schema();
106 $props_parser = Props_Parser::make( $schema );
107
108 $result = $props_parser->parse( $settings );
109
110 if ( ! $result->is_valid() ) {
111 throw new \Exception( esc_html( 'Settings validation failed. ' . $result->errors()->to_string() ) );
112 }
113
114 return $result->unwrap();
115 }
116
117 private function parse_atomic_interactions( $interactions ) {
118
119 if ( empty( $interactions ) ) {
120 return [];
121 }
122
123 if ( is_string( $interactions ) ) {
124 $decoded = json_decode( $interactions, true );
125 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
126 return $decoded;
127 }
128 }
129
130 if ( is_array( $interactions ) ) {
131 return $interactions;
132 }
133
134 return [];
135 }
136
137 private function convert_prop_type_interactions_to_legacy( $interactions ) {
138 $legacy_items = [];
139
140 foreach ( $interactions['items'] as $item ) {
141 if ( isset( $item['$$type'] ) && 'interaction-item' === $item['$$type'] ) {
142 $legacy_item = $this->extract_legacy_interaction_from_prop_type( $item );
143 if ( $legacy_item ) {
144 $legacy_items[] = $legacy_item;
145 }
146 } else {
147 $legacy_items[] = $item;
148 }
149 }
150
151 return [
152 'version' => $interactions['version'] ?? 1,
153 'items' => $legacy_items,
154 ];
155 }
156
157 private function extract_legacy_interaction_from_prop_type( $item ) {
158 if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
159 return null;
160 }
161
162 $item_value = $item['value'];
163
164 $interaction_id = $this->extract_prop_value( $item_value, 'interaction_id' );
165 $trigger = $this->extract_prop_value( $item_value, 'trigger' );
166 $animation = $this->extract_prop_value( $item_value, 'animation' );
167
168 if ( ! is_array( $animation ) ) {
169 return null;
170 }
171
172 $effect = $this->extract_prop_value( $animation, 'effect' );
173 $type = $this->extract_prop_value( $animation, 'type' );
174 $direction = $this->extract_prop_value( $animation, 'direction' );
175 $timing_config = $this->extract_prop_value( $animation, 'timing_config' );
176
177 $duration = 300;
178 $delay = 0;
179
180 if ( is_array( $timing_config ) ) {
181 $duration = $this->extract_prop_value( $timing_config, 'duration', 300 );
182 $delay = $this->extract_prop_value( $timing_config, 'delay', 0 );
183 }
184
185 $animation_id = implode( '-', [ $trigger, $effect, $type, $direction, $duration, $delay ] );
186
187 return [
188 'interaction_id' => $interaction_id,
189 'animation' => [
190 'animation_id' => $animation_id,
191 'animation_type' => 'full-preset',
192 ],
193 ];
194 }
195
196 private function extract_prop_value( $data, $key, $default = '' ) {
197 if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) {
198 return $default;
199 }
200
201 $value = $data[ $key ];
202
203 if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) {
204 return $value['value'];
205 }
206
207 return null !== $value ? $value : $default;
208 }
209
210 public function get_atomic_controls() {
211 $controls = apply_filters(
212 'elementor/atomic-widgets/controls',
213 $this->define_atomic_controls(),
214 $this
215 );
216
217 $schema = static::get_props_schema();
218
219 // Validate the schema only in the Editor.
220 static::validate_schema( $schema );
221
222 return $this->get_valid_controls( $schema, $controls );
223 }
224
225 protected function get_css_id_control_meta(): array {
226 return [
227 'layout' => 'two-columns',
228 'topDivider' => true,
229 ];
230 }
231
232 final public function get_controls( $control_id = null ) {
233 if ( ! empty( $control_id ) ) {
234 return null;
235 }
236
237 return [];
238 }
239
240 final public function get_data_for_save() {
241 $data = parent::get_data_for_save();
242
243 $data['version'] = $this->version;
244 $data['settings'] = $this->parse_atomic_settings( $data['settings'] );
245 $data['styles'] = $this->parse_atomic_styles( $data );
246 $data['editor_settings'] = $this->parse_editor_settings( $data['editor_settings'] );
247
248 if ( isset( $data['interactions'] ) && ! empty( $data['interactions'] ) ) {
249 $data['interactions'] = $this->transform_interactions_for_save( $data['interactions'] );
250 } else {
251 $data['interactions'] = [];
252 }
253 return $data;
254 }
255
256 private function transform_interactions_for_save( $interactions ) {
257 $decoded = $this->decode_interactions_data( $interactions );
258
259 if ( empty( $decoded['items'] ) ) {
260 return [];
261 }
262 return $decoded;
263 }
264
265 private function decode_interactions_data( $interactions ) {
266 if ( is_array( $interactions ) ) {
267 return $interactions;
268 }
269
270 if ( is_string( $interactions ) ) {
271 $decoded = json_decode( $interactions, true );
272 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
273 return $decoded;
274 }
275 }
276
277 return [
278 'items' => [],
279 'version' => 1,
280 ];
281 }
282
283 final public function get_raw_data( $with_html_content = false ) {
284 $raw_data = parent::get_raw_data( $with_html_content );
285
286 $raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] );
287 $raw_data['interactions'] = $this->interactions ?? [];
288 $raw_data['editor_settings'] = $this->editor_settings;
289
290 return $raw_data;
291 }
292
293 final public function get_stack( $with_common_controls = true ) {
294 return [
295 'controls' => [],
296 'tabs' => [],
297 ];
298 }
299
300 public function get_atomic_settings(): array {
301 $schema = static::get_props_schema();
302 $props = $this->get_settings();
303 $initial_attributes = $this->get_initial_attributes();
304
305 $props['attributes'] = Attributes_Prop_Type::generate( array_merge(
306 $initial_attributes['value'] ?? [],
307 $props['attributes']['value'] ?? []
308 ) );
309
310 return Render_Props_Resolver::for_settings()->resolve( $schema, $props );
311 }
312
313 protected function get_initial_attributes() {
314 return Attributes_Prop_Type::generate( [
315 Key_Value_Prop_Type::generate( [
316 'key' => String_Prop_Type::generate( 'data-e-type' ),
317 'value' => $this->get_type(),
318 ] ),
319 Key_Value_Prop_Type::generate( [
320 'key' => String_Prop_Type::generate( 'data-id' ),
321 'value' => $this->get_id(),
322 ] ),
323 ] );
324 }
325
326 public function get_atomic_setting( string $key ) {
327 $schema = static::get_props_schema();
328
329 if ( ! isset( $schema[ $key ] ) ) {
330 return null;
331 }
332
333 $props = $this->get_settings();
334 $prop_value = $props[ $key ] ?? null;
335
336 $single_schema = [ $key => $schema[ $key ] ];
337 $single_props = [ $key => $prop_value ];
338
339 $resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props );
340
341 return $resolved[ $key ] ?? null;
342 }
343
344 protected function parse_editor_settings( array $data ): array {
345 $editor_data = [];
346
347 if ( isset( $data['title'] ) && is_string( $data['title'] ) ) {
348 $editor_data['title'] = sanitize_text_field( $data['title'] );
349 }
350
351 return $editor_data;
352 }
353
354 public static function get_props_schema(): array {
355 $schema = static::define_props_schema();
356 $schema['_cssid'] = String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() );
357
358 return apply_filters(
359 'elementor/atomic-widgets/props-schema',
360 $schema
361 );
362 }
363
364 public function get_interactions_ids() {
365 $animation_ids = [];
366
367 $list_of_interactions = ( is_array( $this->interactions ) && isset( $this->interactions['items'] ) )
368 ? $this->interactions['items']
369 : [];
370
371 foreach ( $list_of_interactions as $interaction ) {
372 if ( isset( $interaction['$$type'] ) && 'interaction-item' === $interaction['$$type'] ) {
373 $animation_id = $this->extract_animation_id_from_prop_type( $interaction );
374 if ( $animation_id ) {
375 $animation_ids[] = $animation_id;
376 }
377 } elseif ( isset( $interaction['animation']['animation_id'] ) ) {
378 $animation_ids[] = $interaction['animation']['animation_id'];
379 }
380 }
381
382 return $animation_ids;
383 }
384
385 private function extract_animation_id_from_prop_type( $item ) {
386 if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
387 return null;
388 }
389
390 $item_value = $item['value'];
391
392 $trigger = $this->extract_prop_value( $item_value, 'trigger' );
393 $animation = $this->extract_prop_value( $item_value, 'animation' );
394
395 if ( ! is_array( $animation ) ) {
396 return null;
397 }
398
399 $effect = $this->extract_prop_value( $animation, 'effect' );
400 $type = $this->extract_prop_value( $animation, 'type' );
401 $direction = $this->extract_prop_value( $animation, 'direction' );
402 $timing_config = $this->extract_prop_value( $animation, 'timing_config' );
403 $config = $this->extract_prop_value( $animation, 'config' );
404
405 $duration = 300;
406 $delay = 0;
407 $replay = 0;
408 $relative_to = 'viewport';
409 $offset_top = 15;
410 $offset_bottom = 85;
411
412 if ( is_array( $timing_config ) ) {
413 $duration = $this->extract_prop_value( $timing_config, 'duration', 300 );
414 $delay = $this->extract_prop_value( $timing_config, 'delay', 0 );
415 }
416
417 if ( is_array( $config ) ) {
418 $relative_to = $this->extract_prop_value( $config, 'relative_to', 'viewport' );
419 $offset_top = $this->extract_prop_value( $config, 'offset_top', 15 );
420 $offset_bottom = $this->extract_prop_value( $config, 'offset_bottom', 85 );
421
422 $replay = $this->extract_prop_value( $config, 'replay', 0 );
423 if ( empty( $replay ) && 0 !== $replay && '0' !== $replay ) {
424 $replay = 0;
425 }
426 } else {
427 $replay = 0;
428 }
429
430 return implode( '-', [ $trigger, $effect, $type, $direction, $duration, $delay, $replay, $relative_to, $offset_top, $offset_bottom ] );
431 }
432
433 public function print_content() {
434 $defined_context = $this->define_render_context();
435
436 $context_key = $defined_context['context_key'] ?? static::class;
437 $element_context = $defined_context['context'] ?? [];
438
439 $has_context = ! empty( $element_context );
440
441 if ( ! $has_context ) {
442 return parent::print_content();
443 }
444
445 Render_Context::push( $context_key, $element_context );
446
447 parent::print_content();
448
449 Render_Context::pop( $context_key );
450 }
451
452 /**
453 * Define the context for element's Render_Context.
454 *
455 * @return array{context_key: ?string, context: array}
456 */
457 protected function define_render_context(): array {
458 return [
459 'context_key' => null,
460 'context' => [],
461 ];
462 }
463
464 protected function get_link_attributes( $link_settings, $add_key_to_result = false ) {
465 $tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG;
466 $href = $link_settings['href'];
467 $target = $link_settings['target'] ?? '_self';
468
469 $is_button = 'button' === $tag;
470 $href_attribute_key = $is_button ? 'data-action-link' : 'href';
471
472 $result = [
473 $href_attribute_key => $href,
474 'target' => $target,
475 ];
476
477 if ( $add_key_to_result ) {
478 $result['key'] = $href_attribute_key;
479 }
480
481 return $result;
482 }
483 }
484