PluginProbe
Elementor Website Builder – more than just a page builder / 3.4.0-dev9
Elementor Website Builder – more than just a page builder v3.4.0-dev9
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.4.0-dev9, at includes/utils.php

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