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

401 lines 10.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\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 extract_prop_value( $data, $key, $default = '' ) {
138 if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) {
139 return $default;
140 }
141
142 $value = $data[ $key ];
143
144 if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) {
145 return $value['value'];
146 }
147
148 return null !== $value ? $value : $default;
149 }
150
151 public function get_atomic_controls() {
152 $controls = apply_filters(
153 'elementor/atomic-widgets/controls',
154 $this->define_atomic_controls(),
155 $this
156 );
157
158 $schema = static::get_props_schema();
159
160 // Validate the schema only in the Editor.
161 static::validate_schema( $schema );
162
163 return $this->get_valid_controls( $schema, $controls );
164 }
165
166 protected function get_css_id_control_meta(): array {
167 return [
168 'layout' => 'two-columns',
169 'topDivider' => true,
170 ];
171 }
172
173 final public function get_controls( $control_id = null ) {
174 if ( ! empty( $control_id ) ) {
175 return null;
176 }
177
178 return [];
179 }
180
181 final public function get_data_for_save() {
182 $data = parent::get_data_for_save();
183
184 $data['version'] = $this->version;
185 $data['settings'] = $this->parse_atomic_settings( $data['settings'] );
186 $data['styles'] = $this->parse_atomic_styles( $data );
187 $data['editor_settings'] = $this->parse_editor_settings( $data['editor_settings'] );
188
189 if ( isset( $data['interactions'] ) && ! empty( $data['interactions'] ) ) {
190 $data['interactions'] = $this->transform_interactions_for_save( $data['interactions'] );
191 } else {
192 $data['interactions'] = [];
193 }
194 return $data;
195 }
196
197 private function transform_interactions_for_save( $interactions ) {
198 $decoded = $this->decode_interactions_data( $interactions );
199
200 if ( empty( $decoded['items'] ) ) {
201 return [];
202 }
203 return $decoded;
204 }
205
206 private function decode_interactions_data( $interactions ) {
207 if ( is_array( $interactions ) ) {
208 return $interactions;
209 }
210
211 if ( is_string( $interactions ) ) {
212 $decoded = json_decode( $interactions, true );
213 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
214 return $decoded;
215 }
216 }
217
218 return [
219 'items' => [],
220 'version' => 1,
221 ];
222 }
223
224 final public function get_raw_data( $with_html_content = false ) {
225 $raw_data = parent::get_raw_data( $with_html_content );
226
227 $raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] );
228 $raw_data['interactions'] = $this->interactions ?? [];
229 $raw_data['editor_settings'] = $this->editor_settings;
230
231 return $raw_data;
232 }
233
234 final public function get_stack( $with_common_controls = true ) {
235 return [
236 'controls' => [],
237 'tabs' => [],
238 ];
239 }
240
241 public function get_atomic_settings(): array {
242 $schema = static::get_props_schema();
243 $props = $this->get_settings();
244
245 $merged_attribute_values = array_merge(
246 $this->get_initial_attributes()['value'] ?? [],
247 $props['attributes']['value'] ?? []
248 );
249 $props['attributes'] = Attributes_Prop_Type::generate( $merged_attribute_values );
250
251 $parsed = Render_Props_Resolver::for_settings()->resolve( $schema, $props );
252 $link_attributes = isset( $parsed['link'] ) ? $this->get_link_attributes_string( $parsed['link'] ) : '';
253
254 $parsed['link'] = ! empty( $link_attributes ) ? [
255 'tag' => $parsed['link']['tag'],
256 'attributes' => $link_attributes,
257 ] : null;
258
259 return $parsed;
260 }
261
262 protected function get_initial_attributes() {
263 return Attributes_Prop_Type::generate( [
264 Key_Value_Prop_Type::generate( [
265 'key' => String_Prop_Type::generate( 'data-e-type' ),
266 'value' => $this->get_type(),
267 ] ),
268 Key_Value_Prop_Type::generate( [
269 'key' => String_Prop_Type::generate( 'data-id' ),
270 'value' => $this->get_id(),
271 ] ),
272 ] );
273 }
274
275 public function get_atomic_setting( string $key ) {
276 $schema = static::get_props_schema();
277
278 if ( ! isset( $schema[ $key ] ) ) {
279 return null;
280 }
281
282 $props = $this->get_settings();
283 $prop_value = $props[ $key ] ?? null;
284
285 $single_schema = [ $key => $schema[ $key ] ];
286 $single_props = [ $key => $prop_value ];
287
288 $resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props );
289
290 return $resolved[ $key ] ?? null;
291 }
292
293 protected function parse_editor_settings( array $data ): array {
294 $editor_data = [];
295
296 if ( isset( $data['title'] ) && is_string( $data['title'] ) ) {
297 $editor_data['title'] = sanitize_text_field( $data['title'] );
298 }
299
300 return $editor_data;
301 }
302
303 public static function get_props_schema(): array {
304 $schema = static::define_props_schema();
305 $schema['_cssid'] = String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() );
306
307 return apply_filters(
308 'elementor/atomic-widgets/props-schema',
309 $schema
310 );
311 }
312
313 protected function set_render_context( array $context_pairs ): void {
314 foreach ( $context_pairs as $context_pair ) {
315 $context_key = $context_pair['context_key'] ?? static::class;
316 $context = $context_pair['context'];
317 Render_Context::push( $context_key, $context );
318 }
319 }
320
321 protected function clear_render_context( array $context_pairs ): void {
322 foreach ( $context_pairs as $context_pair ) {
323 $context_key = $context_pair['context_key'] ?? static::class;
324 Render_Context::pop( $context_key );
325 }
326 }
327
328 public function print_content() {
329 $defined_context = $this->define_render_context();
330
331 if ( empty( $defined_context ) ) {
332 return parent::print_content();
333 }
334
335 $this->set_render_context( $defined_context );
336
337 parent::print_content();
338
339 $this->clear_render_context( $defined_context );
340 }
341
342 /**
343 * Define the context for element's Render_Context.
344 *
345 * @return array Array of context pairs. Each pair is an associative array with:
346 * - 'context_key' (optional): The context key. Defaults to static::class if not provided.
347 * - 'context' (required): The context value (can be any type).
348 *
349 * @example
350 * [
351 * [
352 * 'context_key' => 'custom-key',
353 * 'context' => ['some' => 'data'],
354 * ],
355 * [
356 * 'context' => ['instance_id' => $this->get_id()],
357 * ],
358 * ]
359 */
360 protected function define_render_context(): array {
361 return [];
362 }
363
364 protected function get_link_attributes( $link_settings ) {
365 if ( empty( $link_settings['href'] ) ) {
366 return [];
367 }
368
369 $tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG;
370 $url = $link_settings['href'];
371 $target = $link_settings['target'] ?? '_self';
372 $is_action_link = 'button' === $tag;
373 $url_attr_key = $is_action_link ? 'data-action-link' : 'href';
374
375 return [
376 $url_attr_key => $url,
377 'target' => $target,
378 ];
379 }
380
381 private function get_link_attributes_string( $link_settings ) {
382 $link_attributes = $this->get_link_attributes( $link_settings );
383
384 if ( empty( $link_attributes ) ) {
385 return '';
386 }
387
388 $parts = [];
389
390 foreach ( $link_attributes as $key => $value ) {
391 if ( 'tag' === $key ) {
392 continue;
393 }
394
395 $parts[] = sprintf( '%s="%s"', $key, esc_attr( $value ) );
396 }
397
398 return implode( ' ', $parts );
399 }
400 }
401