PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.0-beta1
Elementor Website Builder – more than just a page builder v4.2.0-beta1
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.2.0-beta1, at modules/atomic-widgets/elements/base/has-atomic-base.php

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