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

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