PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.3
Elementor Website Builder – more than just a page builder v3.35.3
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.3, at modules/atomic-widgets/elements/base/has-atomic-base.php

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