PluginProbe
Elementor Website Builder – more than just a page builder / 3.1.4
Elementor Website Builder – more than just a page builder v3.1.4
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 / includes / utils.php

utils.php in Elementor Website Builder – more than just a page builder 3.1.4, at includes/utils.php

660 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit; // Exit if accessed directly.
6 }
7
8 /**
9 * Elementor utils.
10 *
11 * Elementor utils handler class is responsible for different utility methods
12 * used by Elementor.
13 *
14 * @since 1.0.0
15 */
16 class Utils {
17
18 const DEPRECATION_RANGE = 0.4;
19
20 /**
21 * A list of safe tage for `validate_html_tag` method.
22 */
23 const ALLOWED_HTML_WRAPPER_TAGS = [
24 'article',
25 'aside',
26 'div',
27 'footer',
28 'h1',
29 'h2',
30 'h3',
31 'h4',
32 'h5',
33 'h6',
34 'header',
35 'main',
36 'nav',
37 'p',
38 'section',
39 'span',
40 ];
41
42 /**
43 * Is ajax.
44 *
45 * Whether the current request is a WordPress ajax request.
46 *
47 * @since 1.0.0
48 * @deprecated 2.6.0 Use `wp_doing_ajax()` instead.
49 * @access public
50 * @static
51 *
52 * @return bool True if it's a WordPress ajax request, false otherwise.
53 */
54 public static function is_ajax() {
55 _deprecated_function( __METHOD__, '2.6.0', 'wp_doing_ajax()' );
56
57 return wp_doing_ajax();
58 }
59
60 /**
61 * Is WP CLI.
62 *
63 * @return bool
64 */
65 public static function is_wp_cli() {
66 return defined( 'WP_CLI' ) && WP_CLI;
67 }
68
69 /**
70 * Is script debug.
71 *
72 * Whether script debug is enabled or not.
73 *
74 * @since 1.0.0
75 * @access public
76 * @static
77 *
78 * @return bool True if it's a script debug is active, false otherwise.
79 */
80 public static function is_script_debug() {
81 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
82 }
83
84 /**
85 * Get pro link.
86 *
87 * Retrieve the link to Elementor Pro.
88 *
89 * @since 1.7.0
90 * @access public
91 * @static
92 *
93 * @param string $link URL to Elementor pro.
94 *
95 * @return string Elementor pro link.
96 */
97 public static function get_pro_link( $link ) {
98 static $theme_name = false;
99
100 if ( ! $theme_name ) {
101 $theme_obj = wp_get_theme();
102 if ( $theme_obj->parent() ) {
103 $theme_name = $theme_obj->parent()->get( 'Name' );
104 } else {
105 $theme_name = $theme_obj->get( 'Name' );
106 }
107
108 $theme_name = sanitize_key( $theme_name );
109 }
110
111 $link = add_query_arg( 'utm_term', $theme_name, $link );
112
113 return $link;
114 }
115
116 /**
117 * Replace URLs.
118 *
119 * Replace old URLs to new URLs. This method also updates all the Elementor data.
120 *
121 * @since 2.1.0
122 * @static
123 * @access public
124 *
125 * @param $from
126 * @param $to
127 *
128 * @return string
129 * @throws \Exception
130 */
131 public static function replace_urls( $from, $to ) {
132 $from = trim( $from );
133 $to = trim( $to );
134
135 if ( $from === $to ) {
136 throw new \Exception( __( 'The `from` and `to` URL\'s must be different', 'elementor' ) );
137 }
138
139 $is_valid_urls = ( filter_var( $from, FILTER_VALIDATE_URL ) && filter_var( $to, FILTER_VALIDATE_URL ) );
140 if ( ! $is_valid_urls ) {
141 throw new \Exception( __( 'The `from` and `to` URL\'s must be valid URL\'s', 'elementor' ) );
142 }
143
144 global $wpdb;
145
146 // @codingStandardsIgnoreStart cannot use `$wpdb->prepare` because it remove's the backslashes
147 $rows_affected = $wpdb->query(
148 "UPDATE {$wpdb->postmeta} " .
149 "SET `meta_value` = REPLACE(`meta_value`, '" . str_replace( '/', '\\\/', $from ) . "', '" . str_replace( '/', '\\\/', $to ) . "') " .
150 "WHERE `meta_key` = '_elementor_data' AND `meta_value` LIKE '[%' ;" ); // meta_value LIKE '[%' are json formatted
151 // @codingStandardsIgnoreEnd
152
153 if ( false === $rows_affected ) {
154 throw new \Exception( __( 'An error occurred', 'elementor' ) );
155 }
156
157 Plugin::$instance->files_manager->clear_cache();
158
159 return sprintf(
160 /* translators: %d: Number of rows */
161 _n( '%d row affected.', '%d rows affected.', $rows_affected, 'elementor' ),
162 $rows_affected
163 );
164 }
165
166 /**
167 * Is post supports Elementor.
168 *
169 * Whether the post supports editing with Elementor.
170 *
171 * @since 1.0.0
172 * @access public
173 * @static
174 *
175 * @param int $post_id Optional. Post ID. Default is `0`.
176 *
177 * @return string True if post supports editing with Elementor, false otherwise.
178 */
179 public static function is_post_support( $post_id = 0 ) {
180 $post_type = get_post_type( $post_id );
181
182 $is_supported = self::is_post_type_support( $post_type );
183
184 /**
185 * Is post type support.
186 *
187 * Filters whether the post type supports editing with Elementor.
188 *
189 * @since 1.0.0
190 * @deprecated 2.2.0 Use `elementor/utils/is_post_support` Instead
191 *
192 * @param bool $is_supported Whether the post type supports editing with Elementor.
193 * @param int $post_id Post ID.
194 * @param string $post_type Post type.
195 */
196 $is_supported = apply_filters( 'elementor/utils/is_post_type_support', $is_supported, $post_id, $post_type );
197
198 /**
199 * Is post support.
200 *
201 * Filters whether the post supports editing with Elementor.
202 *
203 * @since 2.2.0
204 *
205 * @param bool $is_supported Whether the post type supports editing with Elementor.
206 * @param int $post_id Post ID.
207 * @param string $post_type Post type.
208 */
209 $is_supported = apply_filters( 'elementor/utils/is_post_support', $is_supported, $post_id, $post_type );
210
211 return $is_supported;
212 }
213
214
215 /**
216 * Is post type supports Elementor.
217 *
218 * Whether the post type supports editing with Elementor.
219 *
220 * @since 2.2.0
221 * @access public
222 * @static
223 *
224 * @param string $post_type Post Type.
225 *
226 * @return string True if post type supports editing with Elementor, false otherwise.
227 */
228 public static function is_post_type_support( $post_type ) {
229 if ( ! post_type_exists( $post_type ) ) {
230 return false;
231 }
232
233 if ( ! post_type_supports( $post_type, 'elementor' ) ) {
234 return false;
235 }
236
237 return true;
238 }
239
240 /**
241 * Get placeholder image source.
242 *
243 * Retrieve the source of the placeholder image.
244 *
245 * @since 1.0.0
246 * @access public
247 * @static
248 *
249 * @return string The source of the default placeholder image used by Elementor.
250 */
251 public static function get_placeholder_image_src() {
252 $placeholder_image = ELEMENTOR_ASSETS_URL . 'images/placeholder.png';
253
254 /**
255 * Get placeholder image source.
256 *
257 * Filters the source of the default placeholder image used by Elementor.
258 *
259 * @since 1.0.0
260 *
261 * @param string $placeholder_image The source of the default placeholder image.
262 */
263 $placeholder_image = apply_filters( 'elementor/utils/get_placeholder_image_src', $placeholder_image );
264
265 return $placeholder_image;
266 }
267
268 /**
269 * Generate random string.
270 *
271 * Returns a string containing a hexadecimal representation of random number.
272 *
273 * @since 1.0.0
274 * @access public
275 * @static
276 *
277 * @return string Random string.
278 */
279 public static function generate_random_string() {
280 return dechex( rand() );
281 }
282
283 /**
284 * Do not cache.
285 *
286 * Tell WordPress cache plugins not to cache this request.
287 *
288 * @since 1.0.0
289 * @access public
290 * @static
291 */
292 public static function do_not_cache() {
293 if ( ! defined( 'DONOTCACHEPAGE' ) ) {
294 define( 'DONOTCACHEPAGE', true );
295 }
296
297 if ( ! defined( 'DONOTCACHEDB' ) ) {
298 define( 'DONOTCACHEDB', true );
299 }
300
301 if ( ! defined( 'DONOTMINIFY' ) ) {
302 define( 'DONOTMINIFY', true );
303 }
304
305 if ( ! defined( 'DONOTCDN' ) ) {
306 define( 'DONOTCDN', true );
307 }
308
309 if ( ! defined( 'DONOTCACHCEOBJECT' ) ) {
310 define( 'DONOTCACHCEOBJECT', true );
311 }
312
313 // Set the headers to prevent caching for the different browsers.
314 nocache_headers();
315 }
316
317 /**
318 * Get timezone string.
319 *
320 * Retrieve timezone string from the WordPress database.
321 *
322 * @since 1.0.0
323 * @access public
324 * @static
325 *
326 * @return string Timezone string.
327 */
328 public static function get_timezone_string() {
329 $current_offset = (float) get_option( 'gmt_offset' );
330 $timezone_string = get_option( 'timezone_string' );
331
332 // Create a UTC+- zone if no timezone string exists.
333 if ( empty( $timezone_string ) ) {
334 if ( $current_offset < 0 ) {
335 $timezone_string = 'UTC' . $current_offset;
336 } else {
337 $timezone_string = 'UTC+' . $current_offset;
338 }
339 }
340
341 return $timezone_string;
342 }
343
344 /**
345 * Get create new post URL.
346 *
347 * Retrieve a custom URL for creating a new post/page using Elementor.
348 *
349 * @since 1.9.0
350 * @access public
351 * @static
352 *
353 * @param string $post_type Optional. Post type slug. Default is 'page'.
354 * @param string|null $template_type Optional. Query arg 'template_type'. Default is null.
355 *
356 * @return string A URL for creating new post using Elementor.
357 */
358 public static function get_create_new_post_url( $post_type = 'page', $template_type = null ) {
359 $query_args = [
360 'action' => 'elementor_new_post',
361 'post_type' => $post_type,
362 ];
363
364 if ( $template_type ) {
365 $query_args['template_type'] = $template_type;
366 }
367
368 $new_post_url = add_query_arg( $query_args, admin_url( 'edit.php' ) );
369
370 $new_post_url = add_query_arg( '_wpnonce', wp_create_nonce( 'elementor_action_new_post' ), $new_post_url );
371
372 return $new_post_url;
373 }
374
375 /**
376 * Get post autosave.
377 *
378 * Retrieve an autosave for any given post.
379 *
380 * @since 1.9.2
381 * @access public
382 * @static
383 *
384 * @param int $post_id Post ID.
385 * @param int $user_id Optional. User ID. Default is `0`.
386 *
387 * @return \WP_Post|false Post autosave or false.
388 */
389 public static function get_post_autosave( $post_id, $user_id = 0 ) {
390 global $wpdb;
391
392 $post = get_post( $post_id );
393
394 $where = $wpdb->prepare( 'post_parent = %d AND post_name LIKE %s AND post_modified_gmt > %s', [ $post_id, "{$post_id}-autosave%", $post->post_modified_gmt ] );
395
396 if ( $user_id ) {
397 $where .= $wpdb->prepare( ' AND post_author = %d', $user_id );
398 }
399
400 $revision = $wpdb->get_row( "SELECT * FROM $wpdb->posts WHERE $where AND post_type = 'revision'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
401
402 if ( $revision ) {
403 $revision = new \WP_Post( $revision );
404 } else {
405 $revision = false;
406 }
407
408 return $revision;
409 }
410
411 /**
412 * Is CPT supports custom templates.
413 *
414 * Whether the Custom Post Type supports templates.
415 *
416 * @since 2.0.0
417 * @access public
418 * @static
419 *
420 * @return bool True is templates are supported, False otherwise.
421 */
422 public static function is_cpt_custom_templates_supported() {
423 require_once ABSPATH . '/wp-admin/includes/theme.php';
424
425 return method_exists( wp_get_theme(), 'get_post_templates' );
426 }
427
428 /**
429 * @since 2.1.2
430 * @access public
431 * @static
432 */
433 public static function array_inject( $array, $key, $insert ) {
434 $length = array_search( $key, array_keys( $array ), true ) + 1;
435
436 return array_slice( $array, 0, $length, true ) +
437 $insert +
438 array_slice( $array, $length, null, true );
439 }
440
441 /**
442 * Render html attributes
443 *
444 * @access public
445 * @static
446 * @param array $attributes
447 *
448 * @return string
449 */
450 public static function render_html_attributes( array $attributes ) {
451 $rendered_attributes = [];
452
453 foreach ( $attributes as $attribute_key => $attribute_values ) {
454 if ( is_array( $attribute_values ) ) {
455 $attribute_values = implode( ' ', $attribute_values );
456 }
457
458 $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
459 }
460
461 return implode( ' ', $rendered_attributes );
462 }
463
464 public static function get_meta_viewport( $context = '' ) {
465 $meta_tag = '<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />';
466 /**
467 * Viewport meta tag.
468 *
469 * Filters the Elementor preview URL.
470 *
471 * @since 2.5.0
472 *
473 * @param string $meta_tag Viewport meta tag.
474 */
475 return apply_filters( 'elementor/template/viewport_tag', $meta_tag, $context );
476 }
477
478 /**
479 * Add Elementor Config js vars to the relevant script handle,
480 * WP will wrap it with <script> tag.
481 * To make sure this script runs thru the `script_loader_tag` hook, use a known handle value.
482 * @param string $handle
483 * @param string $js_var
484 * @param mixed $config
485 */
486 public static function print_js_config( $handle, $js_var, $config ) {
487 $config = wp_json_encode( $config );
488
489 if ( get_option( 'elementor_editor_break_lines' ) ) {
490 // Add new lines to avoid memory limits in some hosting servers that handles the buffer output according to new line characters
491 $config = str_replace( '}},"', '}},' . PHP_EOL . '"', $config );
492 }
493
494 $script_data = 'var ' . $js_var . ' = ' . $config . ';';
495
496 wp_add_inline_script( $handle, $script_data, 'before' );
497 }
498
499 public static function handle_deprecation( $item, $version, $replacement = null ) {
500 preg_match( '/^[0-9]+\.[0-9]+/', ELEMENTOR_VERSION, $current_version );
501
502 $current_version_as_float = (float) $current_version[0];
503
504 preg_match( '/^[0-9]+\.[0-9]+/', $version, $alias_version );
505
506 $alias_version_as_float = (float) $alias_version[0];
507
508 if ( round( $current_version_as_float - $alias_version_as_float, 1 ) >= self::DEPRECATION_RANGE ) {
509 _deprecated_file( $item, $version, $replacement );
510 }
511 }
512
513 /**
514 * Checks a control value for being empty, including a string of '0' not covered by PHP's empty().
515 *
516 * @param mixed $source
517 * @param bool|string $key
518 *
519 * @return bool
520 */
521 public static function is_empty( $source, $key = false ) {
522 if ( is_array( $source ) ) {
523 if ( ! isset( $source[ $key ] ) ) {
524 return true;
525 }
526
527 $source = $source[ $key ];
528 }
529
530 return '0' !== $source && empty( $source );
531 }
532
533 public static function has_pro() {
534 return defined( 'ELEMENTOR_PRO_VERSION' );
535 }
536
537 /**
538 * Convert HTMLEntities to UTF-8 characters
539 *
540 * @param $string
541 * @return string
542 */
543 public static function urlencode_html_entities( $string ) {
544 $entities_dictionary = [
545 '&#145;' => "'", // Opening single quote
546 '&#146;' => "'", // Closing single quote
547 '&#147;' => '"', // Closing double quote
548 '&#148;' => '"', // Opening double quote
549 '&#8216;' => "'", // Closing single quote
550 '&#8217;' => "'", // Opening single quote
551 '&#8218;' => "'", // Single low quote
552 '&#8220;' => '"', // Closing double quote
553 '&#8221;' => '"', // Opening double quote
554 '&#8222;' => '"', // Double low quote
555 ];
556
557 // Decode decimal entities
558 $string = str_replace( array_keys( $entities_dictionary ), array_values( $entities_dictionary ), $string );
559
560 return rawurlencode( html_entity_decode( $string, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
561 }
562
563 /**
564 * Parse attributes that come as a string of comma-delimited key|value pairs.
565 * Removes Javascript events and unescaped `href` attributes.
566 *
567 * @param string $attributes_string
568 *
569 * @param string $delimiter Default comma `,`.
570 *
571 * @return array
572 */
573 public static function parse_custom_attributes( $attributes_string, $delimiter = ',' ) {
574 $attributes = explode( $delimiter, $attributes_string );
575 $result = [];
576
577 foreach ( $attributes as $attribute ) {
578 $attr_key_value = explode( '|', $attribute );
579
580 $attr_key = mb_strtolower( $attr_key_value[0] );
581
582 // Remove any not allowed characters.
583 preg_match( '/[-_a-z0-9]+/', $attr_key, $attr_key_matches );
584
585 if ( empty( $attr_key_matches[0] ) ) {
586 continue;
587 }
588
589 $attr_key = $attr_key_matches[0];
590
591 // Avoid Javascript events and unescaped href.
592 if ( 'href' === $attr_key || 'on' === substr( $attr_key, 0, 2 ) ) {
593 continue;
594 }
595
596 if ( isset( $attr_key_value[1] ) ) {
597 $attr_value = trim( $attr_key_value[1] );
598 } else {
599 $attr_value = '';
600 }
601
602 $result[ $attr_key ] = $attr_value;
603 }
604
605 return $result;
606 }
607
608 public static function find_element_recursive( $elements, $id ) {
609 foreach ( $elements as $element ) {
610 if ( $id === $element['id'] ) {
611 return $element;
612 }
613
614 if ( ! empty( $element['elements'] ) ) {
615 $element = self::find_element_recursive( $element['elements'], $id );
616
617 if ( $element ) {
618 return $element;
619 }
620 }
621 }
622
623 return false;
624 }
625
626 /**
627 * Change Submenu First Item Label
628 *
629 * Overwrite the label of the first submenu item of an admin menu item.
630 *
631 * Fired by `admin_menu` action.
632 *
633 * @since 3.1.0
634 *
635 * @param $menu_slug
636 * @param $new_label
637 * @access public
638 */
639 public static function change_submenu_first_item_label( $menu_slug, $new_label ) {
640 global $submenu;
641
642 if ( isset( $submenu[ $menu_slug ] ) ) {
643 // @codingStandardsIgnoreStart
644 $submenu[ $menu_slug ][0][0] = $new_label;
645 // @codingStandardsIgnoreEnd
646 }
647 }
648
649 /**
650 * Validate an HTML tag against a safe allowed list.
651 *
652 * @param string $tag
653 *
654 * @return string
655 */
656 public static function validate_html_tag( $tag ) {
657 return in_array( strtolower( $tag ), self::ALLOWED_HTML_WRAPPER_TAGS ) ? $tag : 'div';
658 }
659 }
660