PluginProbe
Elementor Website Builder – more than just a page builder / 3.34.2
Elementor Website Builder – more than just a page builder v3.34.2
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 / has-atomic-base.php

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

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