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

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