balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-redux-helpers.php (66804B)


      1 <?php
      2 /**
      3  * Redux Helper Class
      4  *
      5  * @class   Redux_Helpers
      6  * @version 3.0.0
      7  * @package Redux Framework/Classes
      8  */
      9 
     10 defined( 'ABSPATH' ) || exit;
     11 
     12 // Don't duplicate me!
     13 if ( ! class_exists( 'Redux_Helpers', false ) ) {
     14 
     15 	/**
     16 	 * Redux Helpers Class
     17 	 * A Class of useful functions that can/should be shared among all Redux files.
     18 	 *
     19 	 * @since       3.0.0
     20 	 */
     21 	class Redux_Helpers {
     22 
     23 		/**
     24 		 * Resuable supported unit array.
     25 		 *
     26 		 * @var array
     27 		 */
     28 		public static $array_units = array( '', '%', 'in', 'cm', 'mm', 'em', 'rem', 'ex', 'pt', 'pc', 'px', 'vh', 'vw', 'vmin', 'vmax', 'ch' );
     29 
     30 		/**
     31 		 * Retrieve section array from field ID.
     32 		 *
     33 		 * @param string $opt_name Panel opt_name.
     34 		 * @param string $field_id Field ID.
     35 		 */
     36 		public static function section_from_field_id( string $opt_name = '', string $field_id = '' ) {
     37 			if ( '' !== $opt_name ) {
     38 				$redux = Redux::instance( $opt_name );
     39 
     40 				if ( is_object( $redux ) ) {
     41 					$sections = $redux->sections;
     42 
     43 					if ( is_array( $sections ) && ! empty( $sections ) ) {
     44 						foreach ( $sections as $idx => $section ) {
     45 							if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) {
     46 								foreach ( $section['fields'] as $i => $field ) {
     47 									if ( is_array( $field ) && ! empty( $field ) ) {
     48 										if ( isset( $field['id'] ) && $field['id'] === $field_id ) {
     49 											return $section;
     50 										}
     51 									}
     52 								}
     53 							}
     54 						}
     55 					}
     56 				}
     57 			}
     58 		}
     59 
     60 		/**
     61 		 * Verify integer value.
     62 		 *
     63 		 * @param mixed $val Value to test.
     64 		 *
     65 		 * @return bool|false|int
     66 		 */
     67 		public static function is_integer( $val ) {
     68 			if ( ! is_scalar( $val ) || is_bool( $val ) ) {
     69 				return false;
     70 			}
     71 
     72 			return is_float( $val ) ? false : preg_match( '~^((?:\+|-)?[0-9]+)$~', $val );
     73 		}
     74 
     75 		/**
     76 		 * Deprecated. Gets panel tab number from specified field.
     77 		 *
     78 		 * @param object       $parent ReduxFramework object.
     79 		 * @param array|string $field  Field array.
     80 		 *
     81 		 * @return int|string
     82 		 * @deprecated No longer using camelCase naming convention.
     83 		 */
     84 		public static function tabFromField( $parent, $field ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
     85 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Helpers::tab_from_field( $parent, $field )' );
     86 
     87 			return self::tab_from_field( $parent, $field );
     88 		}
     89 
     90 		/**
     91 		 * Gets panel tab number from specified field.
     92 		 *
     93 		 * @param object       $parent ReduxFramework object.
     94 		 * @param array|string $field  Field array.
     95 		 *
     96 		 * @return int|string
     97 		 */
     98 		public static function tab_from_field( $parent, $field ) {
     99 			foreach ( $parent->sections as $k => $section ) {
    100 				if ( ! isset( $section['title'] ) ) {
    101 					continue;
    102 				}
    103 
    104 				if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) {
    105 					if ( self::recursive_array_search( $field, $section['fields'] ) ) {
    106 						return $k;
    107 					}
    108 				}
    109 			}
    110 		}
    111 
    112 		/**
    113 		 * Deprecated. Verifies if specified field type is in use.
    114 		 *
    115 		 * @param array $fields Field arrays.
    116 		 * @param array $field  Field array.
    117 		 *
    118 		 * @return bool
    119 		 * @deprecated No longer using camelCase naming convention.
    120 		 */
    121 		public static function isFieldInUseByType( array $fields, array $field = array() ): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    122 			// phpcs:ignore Squiz.PHP.CommentedOutCode
    123 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Helpers::tab_from_field( $parent, $field )' );
    124 			return self::is_field_in_use_by_type( $fields, $field );
    125 		}
    126 
    127 		/**
    128 		 * Verifies if specified field type is in use.
    129 		 *
    130 		 * @param array $fields Field arrays.
    131 		 * @param array $field  Field arrays to check.
    132 		 *
    133 		 * @return bool
    134 		 */
    135 		public static function is_field_in_use_by_type( array $fields, array $field = array() ): bool {
    136 			foreach ( $field as $name ) {
    137 				if ( array_key_exists( $name, $fields ) ) {
    138 					return true;
    139 				}
    140 			}
    141 
    142 			return false;
    143 		}
    144 
    145 		/**
    146 		 * Deprecated Verifies if field is in use.
    147 		 *
    148 		 * @param object $parent ReduxFramework object.
    149 		 * @param string $field  Field type.
    150 		 *
    151 		 * @return bool
    152 		 * @deprecated No longer using camelCase function names.
    153 		 */
    154 		public static function isFieldInUse( $parent, string $field ): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    155 			// phpcs:ignore Squiz.PHP.CommentedOutCode
    156 			// _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Helpers::is_field_in_use( $parent, $field )' );
    157 			return self::is_field_in_use( $parent, $field );
    158 		}
    159 
    160 		/**
    161 		 * Verifies if field is in use.
    162 		 *
    163 		 * @param object $parent ReduxFramework object.
    164 		 * @param string $field  Field type.
    165 		 *
    166 		 * @return bool
    167 		 */
    168 		public static function is_field_in_use( $parent, string $field ): bool {
    169 			if ( empty( $parent->sections ) ) {
    170 				return false;
    171 			}
    172 
    173 			foreach ( $parent->sections as $k => $section ) {
    174 				if ( ! isset( $section['title'] ) ) {
    175 					continue;
    176 				}
    177 
    178 				if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) {
    179 					if ( self::recursive_array_search( $field, $section['fields'] ) ) {
    180 						return true;
    181 					}
    182 				}
    183 			}
    184 
    185 			return false;
    186 		}
    187 
    188 		/**
    189 		 * Returns major version from version number.
    190 		 *
    191 		 * @param string $v Version number.
    192 		 *
    193 		 * @return string
    194 		 */
    195 		public static function major_version( string $v ): string {
    196 			$version = explode( '.', $v );
    197 			if ( count( $version ) > 1 ) {
    198 				return $version[0] . '.' . $version[1];
    199 			} else {
    200 				return $v;
    201 			}
    202 		}
    203 
    204 
    205 		/**
    206 		 * Deprecated. Checks for localhost environment.
    207 		 *
    208 		 * @return bool
    209 		 * @deprecated No longer using camelCase naming convention.
    210 		 * @since      4.0
    211 		 */
    212 		public static function isLocalHost(): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    213 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Helpers::is_local_host()' );
    214 
    215 			return self::is_local_host();
    216 		}
    217 
    218 		/**
    219 		 * Checks for localhost environment.
    220 		 *
    221 		 * @return bool
    222 		 */
    223 		public static function is_local_host(): bool {
    224 			$is_local = false;
    225 
    226 			$domains_to_check = array_unique(
    227 				array(
    228 					'siteurl' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
    229 					'homeurl' => wp_parse_url( get_home_url(), PHP_URL_HOST ),
    230 				)
    231 			);
    232 
    233 			$forbidden_domains = array(
    234 				'wordpress.com',
    235 				'localhost',
    236 				'localhost.localdomain',
    237 				'127.0.0.1',
    238 				'::1',
    239 				'local.wordpress.test',         // VVV pattern.
    240 				'local.wordpress-trunk.test',   // VVV pattern.
    241 				'src.wordpress-develop.test',   // VVV pattern.
    242 				'build.wordpress-develop.test', // VVV pattern.
    243 			);
    244 
    245 			foreach ( $domains_to_check as $domain ) {
    246 				// If it's empty, just fail out.
    247 				if ( ! $domain ) {
    248 					$is_local = true;
    249 					break;
    250 				}
    251 
    252 				// None of the explicit localhosts.
    253 				if ( in_array( $domain, $forbidden_domains, true ) ) {
    254 					$is_local = true;
    255 					break;
    256 				}
    257 
    258 				// No .test or .local domains.
    259 				if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
    260 					$is_local = true;
    261 					break;
    262 				}
    263 			}
    264 
    265 			return $is_local;
    266 		}
    267 
    268 		/**
    269 		 * Deprecated. Checks if WP_DEBUG is enabled.
    270 		 *
    271 		 * @return bool::is_wp_debug()
    272 		 * @deprecated No longer using camelCase naming convention.
    273 		 * @since      4.0
    274 		 */
    275 		public static function isWpDebug(): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    276 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Functions_Ex::is_wp_debug()' );
    277 
    278 			return self::is_wp_debug();
    279 		}
    280 
    281 		/**
    282 		 * Checks if WP_DEBUG is enabled.
    283 		 *
    284 		 * @return bool
    285 		 */
    286 		public static function is_wp_debug(): bool {
    287 			return ( defined( 'WP_DEBUG' ) && true === WP_DEBUG );
    288 		}
    289 
    290 		/**
    291 		 * Deprecated. Return tracking object.
    292 		 *
    293 		 * @return array::get_statistics_object()
    294 		 * @deprecated No longer using camelCase naming convention.
    295 		 */
    296 		public static function getTrackingObject(): array { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    297 			// phpcs:ignore: Squiz.PHP.CommentedOutCode
    298 			/* _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::get_statistics_object()' ); */
    299 
    300 			return self::get_statistics_object();
    301 		}
    302 
    303 		/**
    304 		 * Deprecated. Return tracking object.
    305 		 *
    306 		 * @return array::get_statistics_object()
    307 		 * @deprecated No longer using camelCase naming convention.
    308 		 */
    309 		public static function trackingObject(): array { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    310 			// phpcs:ignore Squiz.PHP.CommentedOutCode
    311 			/* _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::get_statistics_object()' ); */
    312 
    313 			return self::get_statistics_object();
    314 		}
    315 
    316 		/**
    317 		 * Return tracking object.
    318 		 *
    319 		 * @return array
    320 		 */
    321 		public static function get_statistics_object(): array {
    322 			$hash = self::get_hash();
    323 
    324 			global $blog_id, $wpdb;
    325 			$pts = array();
    326 
    327 			foreach ( get_post_types( array( 'public' => true ) ) as $pt ) {
    328 				$count      = wp_count_posts( $pt );
    329 				$pts[ $pt ] = $count->publish;
    330 			}
    331 
    332 			$comments_count = wp_count_comments();
    333 
    334 			if ( ! function_exists( 'get_plugin_data' ) ) {
    335 				if ( file_exists( ABSPATH . 'wp-admin/includes/plugin.php' ) ) {
    336 					require_once ABSPATH . 'wp-admin/includes/plugin.php';
    337 				}
    338 				if ( file_exists( ABSPATH . 'wp-admin/includes/admin.php' ) ) {
    339 					require_once ABSPATH . 'wp-admin/includes/admin.php';
    340 				}
    341 			}
    342 
    343 			$plugins = array();
    344 
    345 			foreach ( get_option( 'active_plugins', array() ) as $plugin_path ) {
    346 				if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_path ) ) {
    347 					$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_path );
    348 					$slug        = str_replace( '/' . basename( $plugin_path ), '', $plugin_path );
    349 
    350 					$plugins[ $slug ] = array(
    351 						'version'    => $plugin_info['Version'],
    352 						'name'       => $plugin_info['Name'],
    353 						'plugin_uri' => $plugin_info['PluginURI'],
    354 						'author'     => $plugin_info['AuthorName'],
    355 						'author_uri' => $plugin_info['AuthorURI'],
    356 					);
    357 				}
    358 			}
    359 
    360 			if ( is_multisite() ) {
    361 				foreach ( get_option( 'active_sitewide_plugins', array() ) as $plugin_path ) {
    362 					if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_path ) ) {
    363 						$plugin_info      = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_path );
    364 						$slug             = str_replace( '/' . basename( $plugin_path ), '', $plugin_path );
    365 						$plugins[ $slug ] = array(
    366 							'version'    => $plugin_info['Version'],
    367 							'name'       => $plugin_info['Name'],
    368 							'plugin_uri' => $plugin_info['PluginURI'],
    369 							'author'     => $plugin_info['AuthorName'],
    370 							'author_uri' => $plugin_info['AuthorURI'],
    371 						);
    372 					}
    373 				}
    374 			}
    375 
    376 			$user_query = new WP_User_Query(
    377 				array(
    378 					'blog_id'     => $blog_id,
    379 					'count_total' => true,
    380 				)
    381 			);
    382 
    383 			$comments_query = new WP_Comment_Query();
    384 
    385 			$demo_mode = get_option( 'ReduxFrameworkPlugin', false );
    386 			if ( ! empty( $demo_mode ) ) {
    387 				$demo_mode = true;
    388 			}
    389 
    390 			$data = array(
    391 				'hash'            => $hash,
    392 				'wp_version'      => get_bloginfo( 'version' ),
    393 				'multisite'       => is_multisite(),
    394 				'users'           => $user_query->get_total(),
    395 				'lang'            => get_locale(),
    396 				'wp_debug'        => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
    397 				'memory'          => WP_MEMORY_LIMIT,
    398 				'localhost'       => self::is_local_host(),
    399 				'php'             => PHP_VERSION,
    400 				'posts'           => $pts,
    401 				'comments'        => array(
    402 					'total'    => $comments_count->total_comments,
    403 					'approved' => $comments_count->approved,
    404 					'spam'     => $comments_count->spam,
    405 					'pings'    => $comments_query->query(
    406 						array(
    407 							'count' => true,
    408 							'type'  => 'pingback',
    409 						)
    410 					),
    411 				),
    412 
    413 				// phpcs:ignore WordPress.NamingConventions.ValidHookName
    414 				'options'         => apply_filters( 'redux/tracking/options', array() ), // TODO - What is this?!
    415 
    416 				'redux_installed' => Redux_Core::$installed,
    417 				'redux_version'   => Redux_Core::$version,
    418 				'redux_demo_mode' => $demo_mode,
    419 				'redux_plugin'    => Redux_Core::$as_plugin,
    420 				'developer'       => self::get_developer_keys(),
    421 				'plugins'         => $plugins,
    422 			);
    423 
    424 			$theme_data = wp_get_theme();
    425 
    426 			$theme = array(
    427 				'theme_version'    => $theme_data->get( 'Version' ),
    428 				'theme_name'       => $theme_data->get( 'Name' ),
    429 				'theme_author'     => $theme_data->get( 'Author' ),
    430 				'theme_author_uri' => $theme_data->get( 'AuthorURI' ),
    431 				'theme_uri'        => $theme_data->get( 'ThemeURI' ),
    432 				'theme_template'   => $theme_data->get( 'Template' ),
    433 			);
    434 
    435 			if ( is_child_theme() ) {
    436 				$parent_theme = wp_get_theme( $theme_data->Template ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName
    437 
    438 				$theme['theme_is_child']          = true;
    439 				$theme['theme_parent']            = $theme_data->Template;  // phpcs:ignore WordPress.NamingConventions.ValidVariableName,
    440 				$theme['theme_parent_name']       = $parent_theme->get( 'Name' );
    441 				$theme['theme_parent_version']    = $parent_theme->get( 'Version' );
    442 				$theme['theme_parent_author']     = $parent_theme->get( 'Author' );
    443 				$theme['theme_parent_author_uri'] = $parent_theme->get( 'AuthorURI' );
    444 				$theme['theme_parent_uri']        = $parent_theme->get( 'ThemeURI' );
    445 			}
    446 
    447 			$data  = wp_parse_args( $data, $theme );
    448 			$parts = explode( ' ', Redux_Core::$server['SERVER_SOFTWARE'] );
    449 
    450 			foreach ( $parts as $part ) {
    451 				if ( '(' === $part[0] ) {
    452 					continue;
    453 				}
    454 
    455 				if ( false !== strpos( $part, '/' ) ) {
    456 					$chunk = explode( '/', $part );
    457 				}
    458 			}
    459 
    460 			$data['server']     = Redux_Core::$server['SERVER_SOFTWARE'];
    461 			$data['db_version'] = $wpdb->db_version();
    462 
    463 			$data['callers'] = self::process_redux_callers( true );
    464 
    465 			if ( empty( $data['developer'] ) ) {
    466 				unset( $data['developer'] );
    467 			} else { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
    468 				// phpcs:disable Squiz.PHP.CommentedOutCode
    469 
    470 				/*
    471 				 * print_r($data['developer']);
    472 				 * echo "NOOO";
    473 				 */
    474 				// phpcs:enable Squiz.PHP.CommentedOutCode
    475 			}
    476 
    477 			ksort( $data );
    478 
    479 			$data['extensions'] = self::get_extensions();
    480 
    481 			return $data;
    482 		}
    483 
    484 		/**
    485 		 * Get extensions.
    486 		 *
    487 		 * @param string $opt_name Panel opt_name.
    488 		 *
    489 		 * @return array
    490 		 */
    491 		public static function get_extensions( string $opt_name = '' ): array {
    492 			if ( empty( $opt_name ) ) {
    493 				$instances = Redux_Instances::get_all_instances();
    494 			} else {
    495 				$instances = array(
    496 					Redux_Instances::get_instance( $opt_name ),
    497 				);
    498 			}
    499 
    500 			$extensions = array();
    501 
    502 			if ( ! empty( $instances ) ) {
    503 				foreach ( $instances as $instance ) {
    504 					if ( isset( $instance->extensions ) && is_array( $instance->extensions ) && ! empty( $instance->extensions ) ) {
    505 						foreach ( $instance->extensions as $key => $extension ) {
    506 							if ( in_array(
    507 								$key,
    508 								array(
    509 									'import_export',
    510 									'customizer',
    511 									'options_object',
    512 								),
    513 								true
    514 							)
    515 							) {
    516 								continue;
    517 							}
    518 
    519 							if ( isset( $extension::$version ) ) {
    520 								$extensions[ $key ] = $extension::$version;
    521 							} elseif ( isset( $extension->version ) ) {
    522 								$extensions[ $key ] = $extension->version;
    523 							} else {
    524 								$extensions[ $key ] = true;
    525 							}
    526 						}
    527 					}
    528 				}
    529 			}
    530 
    531 			return $extensions;
    532 
    533 		}
    534 
    535 		/**
    536 		 * Get encrypted tracking object.
    537 		 *
    538 		 * @return array
    539 		 */
    540 		// phpcs:ignore Squiz.PHP.CommentedOutCode
    541 		// public static function tracking_object() {
    542 		// $data = wp_remote_post(
    543 		// 'http://verify.redux.io',
    544 		// array(
    545 		// 'body' => array(
    546 		// 'hash' => $_GET['action'], // phpcs:ignore WordPress.Security.NonceVerification, sanitization ok.
    547 		// 'site' => esc_url( home_url( '/' ) ),
    548 		// ),
    549 		// )
    550 		// );
    551 		// $data['body'] = urldecode( $data['body'] );
    552 		// if ( ! isset( $_GET['code'] ) || $data['body'] !== $_GET['code'] ) { // phpcs:ignore WordPress.Security.NonceVerification
    553 		// die();
    554 		// }
    555 		// return self::get_statistics_object();
    556 		// } .
    557 
    558 		/**
    559 		 * Deprecated. Determines if theme is parent.
    560 		 *
    561 		 * @param string $file Path to file.
    562 		 *
    563 		 * @return bool
    564 		 * @deprecated No longer using camelCase naming convention.
    565 		 */
    566 		public static function isParentTheme( string $file ): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    567 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::is_parent_theme( $file )' );
    568 
    569 			return self::is_parent_theme( $file );
    570 		}
    571 
    572 		/**
    573 		 * Determines if theme is parent.
    574 		 *
    575 		 * @param string $file Path to theme dir.
    576 		 *
    577 		 * @return bool
    578 		 */
    579 		public static function is_parent_theme( string $file ): bool {
    580 			$file = Redux_Functions_Ex::wp_normalize_path( $file );
    581 			$dir  = Redux_Functions_Ex::wp_normalize_path( get_template_directory() );
    582 
    583 			$file = str_replace( '//', '/', $file );
    584 			$dir  = str_replace( '//', '/', $dir );
    585 
    586 			if ( strpos( $file, $dir ) !== false ) {
    587 				return true;
    588 			}
    589 
    590 			return false;
    591 		}
    592 
    593 		/**
    594 		 * Deprecated. Moved to another class.
    595 		 *
    596 		 * @param string $file Path to file.
    597 		 *
    598 		 * @return string
    599 		 * @deprecated Moved to another class.
    600 		 */
    601 		public static function wp_normalize_path( string $file ): string { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    602 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Functions_Ex::wp_normalize_path( $file )' );
    603 
    604 			return Redux_Functions_Ex::wp_normalize_path( $file );
    605 		}
    606 
    607 		/**
    608 		 * Deprecated. Determines if theme is child.
    609 		 *
    610 		 * @param string $file Path to file.
    611 		 *
    612 		 * @return bool
    613 		 * @deprecated No longer using camelCase naming convention.
    614 		 */
    615 		public static function isChildTheme( string $file ): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    616 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::is_child_theme( $file )' );
    617 
    618 			return self::is_child_theme( $file );
    619 		}
    620 
    621 		/**
    622 		 * Deprecated. Returns true if Redux is running as a plugin.
    623 		 *
    624 		 * @return bool::()
    625 		 * @deprecated No longer using camelCase naming convention.
    626 		 */
    627 		private static function reduxAsPlugin(): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    628 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Core::$as_plugin()' );
    629 
    630 			return Redux_Core::$as_plugin;
    631 		}
    632 
    633 		/**
    634 		 * Determines if theme is child.
    635 		 *
    636 		 * @param string $file Path to theme dir.
    637 		 *
    638 		 * @return bool
    639 		 */
    640 		public static function is_child_theme( string $file ): bool {
    641 			$file = Redux_Functions_Ex::wp_normalize_path( $file );
    642 			$dir  = Redux_Functions_Ex::wp_normalize_path( get_stylesheet_directory() );
    643 
    644 			$file = str_replace( '//', '/', $file );
    645 			$dir  = str_replace( '//', '/', $dir );
    646 
    647 			if ( strpos( $file, $dir ) !== false ) {
    648 				return true;
    649 			}
    650 
    651 			return false;
    652 		}
    653 
    654 		/**
    655 		 * Deprecated. Determines if file is a theme.
    656 		 *
    657 		 * @param string $file Path to file.
    658 		 *
    659 		 * @return bool
    660 		 * @deprecated No longer using camelCase naming convention.
    661 		 */
    662 		public static function isTheme( string $file ): bool { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    663 			// phpcs:ignore Squiz.PHP.CommentedOutCode
    664 			// _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::is_theme( $file )' );
    665 
    666 			return self::is_theme( $file );
    667 		}
    668 
    669 		/**
    670 		 * Determines if file is a theme.
    671 		 *
    672 		 * @param string $file Path to fle to test.
    673 		 *
    674 		 * @return bool
    675 		 */
    676 		public static function is_theme( string $file ): bool {
    677 			if ( true === self::is_child_theme( $file ) || true === self::is_parent_theme( $file ) ) {
    678 				return true;
    679 			}
    680 
    681 			return false;
    682 		}
    683 
    684 		/**
    685 		 * Determines deep array status.
    686 		 *
    687 		 * @param array|string $needle   array to test.
    688 		 * @param array        $haystack Array to search.
    689 		 *
    690 		 * @return bool
    691 		 */
    692 		public static function array_in_array( $needle, array $haystack ): bool {
    693 			// Make sure $needle is an array for foreach.
    694 			if ( ! is_array( $needle ) ) {
    695 				$needle = array( $needle );
    696 			}
    697 			// For each value in $needle, return TRUE if in $haystack.
    698 			foreach ( $needle as $pin ) {
    699 				if ( in_array( $pin, $haystack, true ) ) {
    700 					return true;
    701 				}
    702 			}
    703 
    704 			// Return FALSE if none of the values from $needle are found in $haystack.
    705 			return false;
    706 		}
    707 
    708 		/**
    709 		 * Enum through an entire deep array.
    710 		 *
    711 		 * @param string|array $needle   String to search for.
    712 		 * @param array        $haystack Array in which to search.
    713 		 *
    714 		 * @return bool
    715 		 */
    716 		public static function recursive_array_search( $needle, array $haystack ): bool {
    717 			foreach ( $haystack as $key => $value ) {
    718 				if ( $needle === $value || ( is_array( $value ) && self::recursive_array_search( $needle, $value ) !== false ) ) {
    719 					return true;
    720 				}
    721 			}
    722 
    723 			return false;
    724 		}
    725 
    726 		/**
    727 		 * Take a path and return it clean.
    728 		 *
    729 		 * @param string $path Path to clean.
    730 		 *
    731 		 * @return string
    732 		 * @deprecated Replaced with wp_normalize_path.
    733 		 * @since      3.1.7
    734 		 */
    735 		public static function cleanFilePath( string $path ): string { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    736 			// phpcs:ignore Squiz.PHP.CommentedOutCode
    737 			// _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0', 'Redux_Functions_Ex::wp_normalize_path( $path )' );
    738 			return Redux_Functions_Ex::wp_normalize_path( $path );
    739 		}
    740 
    741 		/**
    742 		 * Create unique hash.
    743 		 *
    744 		 * @return string
    745 		 */
    746 		public static function get_hash(): string {
    747 			$remote_addr = Redux_Core::$server['REMOTE_ADDR'] ?? '127.0.0.1';
    748 			return md5( network_site_url() . '-' . $remote_addr );
    749 		}
    750 
    751 		/**
    752 		 * Return array of installed themes.
    753 		 *
    754 		 * @return array
    755 		 */
    756 		public static function get_wp_themes(): array {
    757 			global $wp_theme_paths;
    758 
    759 			$wp_theme_paths = array();
    760 			$themes         = wp_get_themes();
    761 
    762 			foreach ( $themes as $theme ) {
    763 				$path = Redux_Functions_Ex::wp_normalize_path( trailingslashit( $theme->get_theme_root() ) . $theme->get_template() );
    764 
    765 				if ( Redux_Functions_Ex::wp_normalize_path( realpath( $path ) ) !== $path ) {
    766 					$theme_paths[] = Redux_Functions_Ex::wp_normalize_path( realpath( $path ) );
    767 				}
    768 
    769 				$wp_theme_paths[ $path ] = Redux_Functions_Ex::wp_normalize_path( realpath( $path ) );
    770 			}
    771 
    772 			return array(
    773 				'full_paths'  => $wp_theme_paths,
    774 				'theme_paths' => $theme,
    775 			);
    776 		}
    777 
    778 		/**
    779 		 * Get info for specified file.
    780 		 *
    781 		 * @param string $file File to check.
    782 		 *
    783 		 * @return array|bool
    784 		 */
    785 		public static function path_info( string $file ) {
    786 			$theme_info  = Redux_Functions_Ex::is_inside_theme( $file );
    787 			$plugin_info = Redux_Functions_Ex::is_inside_plugin( $file );
    788 
    789 			if ( false !== $theme_info ) {
    790 				return $theme_info;
    791 			} elseif ( false !== $plugin_info ) {
    792 				return $plugin_info;
    793 			}
    794 
    795 			return array();
    796 		}
    797 
    798 		/**
    799 		 * Compiles caller data for Redux.
    800 		 *
    801 		 * @param bool $simple Mode.
    802 		 *
    803 		 * @return array
    804 		 */
    805 		public static function process_redux_callers( bool $simple = false ): array {
    806 			$data = array();
    807 
    808 			foreach ( Redux_Core::$callers as $opt_name => $callers ) {
    809 				foreach ( $callers as $caller ) {
    810 					$plugin_info = self::is_inside_plugin( $caller );
    811 					$theme_info  = self::is_inside_theme( $caller );
    812 
    813 					if ( $theme_info ) {
    814 						if ( ! isset( $data['theme'][ $theme_info['slug'] ] ) ) {
    815 							$data['theme'][ $theme_info['slug'] ] = array();
    816 						}
    817 						if ( ! isset( $data['theme'][ $theme_info['slug'] ][ $opt_name ] ) ) {
    818 							$data['theme'][ $theme_info['slug'] ][ $opt_name ] = array();
    819 						}
    820 						if ( $simple ) {
    821 							$data['theme'][ $theme_info['slug'] ][ $opt_name ][] = $theme_info['basename'];
    822 						} else {
    823 							$data['theme'][ $theme_info['slug'] ][ $opt_name ][] = $theme_info;
    824 						}
    825 					} elseif ( $plugin_info ) {
    826 						if ( ! isset( $data['plugin'][ $plugin_info['slug'] ] ) ) {
    827 							$data['plugin'][ $plugin_info['slug'] ] = array();
    828 						}
    829 						if ( ! in_array( $opt_name, $data['plugin'][ $plugin_info['slug'] ], true ) ) {
    830 							if ( ! isset( $data['plugin'][ $plugin_info['slug'] ][ $opt_name ] ) ) {
    831 								$data['plugin'][ $plugin_info['slug'] ][ $opt_name ] = array();
    832 							}
    833 							if ( $simple ) {
    834 								$data['plugin'][ $plugin_info['slug'] ][ $opt_name ][] = $plugin_info['basename'];
    835 							} else {
    836 								$data['plugin'][ $plugin_info['slug'] ][ $opt_name ][] = $plugin_info;
    837 							}
    838 						}
    839 					}
    840 				}
    841 			}
    842 
    843 			return $data;
    844 		}
    845 
    846 		/**
    847 		 * Take a path and delete it
    848 		 *
    849 		 * @param string $dir Dir to remove.
    850 		 *
    851 		 * @since    3.3.3
    852 		 */
    853 		public static function rmdir( string $dir ) {
    854 			if ( is_dir( $dir ) ) {
    855 				$objects = scandir( $dir );
    856 
    857 				foreach ( $objects as $object ) {
    858 					if ( '.' !== $object && '..' !== $object ) {
    859 						if ( filetype( $dir . '/' . $object ) === 'dir' ) {
    860 							rmdir( $dir . '/' . $object );
    861 						} else {
    862 							unlink( $dir . '/' . $object );
    863 						}
    864 					}
    865 				}
    866 
    867 				reset( $objects );
    868 
    869 				rmdir( $dir );
    870 			}
    871 		}
    872 
    873 		/**
    874 		 * Field Render Function.
    875 		 * Takes the color hex value and converts to a rgba.
    876 		 *
    877 		 * @param string $hex   Color value.
    878 		 * @param string $alpha Alpha value.
    879 		 *
    880 		 * @since ReduxFramework 3.0.4
    881 		 */
    882 		public static function hex2rgba( string $hex, string $alpha = '' ): string {
    883 			$hex = str_replace( '#', '', $hex );
    884 			if ( 3 === strlen( $hex ) ) {
    885 				$r = hexdec( substr( $hex, 0, 1 ) . substr( $hex, 0, 1 ) );
    886 				$g = hexdec( substr( $hex, 1, 1 ) . substr( $hex, 1, 1 ) );
    887 				$b = hexdec( substr( $hex, 2, 1 ) . substr( $hex, 2, 1 ) );
    888 			} else {
    889 				$r = hexdec( substr( $hex, 0, 2 ) );
    890 				$g = hexdec( substr( $hex, 2, 2 ) );
    891 				$b = hexdec( substr( $hex, 4, 2 ) );
    892 			}
    893 			$rgb = $r . ',' . $g . ',' . $b;
    894 
    895 			if ( '' === $alpha ) {
    896 				return $rgb;
    897 			} else {
    898 				$alpha = floatval( $alpha );
    899 
    900 				return 'rgba(' . $rgb . ',' . $alpha . ')';
    901 			}
    902 		}
    903 
    904 		/**
    905 		 * Deprecated. Returns string boolean value.
    906 		 *
    907 		 * @param mixed $var String to convert to true boolean.
    908 		 *
    909 		 * @return mixed|array
    910 		 *
    911 		 * @deprecated No longer using camelCase naming convention.
    912 		 */
    913 		public static function makeBoolStr( $var ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
    914 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::make_bool_str( $var )' );
    915 
    916 			return self::make_bool_str( $var );
    917 		}
    918 
    919 		/**
    920 		 * Returns string boolean value.
    921 		 *
    922 		 * @param mixed $var true|false to convert.
    923 		 *
    924 		 * @return mixed|array
    925 		 */
    926 		public static function make_bool_str( $var ) {
    927 			if ( false === $var || 'false' === $var || 0 === $var || '0' === $var || '' === $var || empty( $var ) ) {
    928 				return 'false';
    929 			} elseif ( true === $var || 'true' === $var || 1 === $var || '1' === $var ) {
    930 				return 'true';
    931 			} else {
    932 				return $var;
    933 			}
    934 		}
    935 
    936 		/**
    937 		 * Compile localized array.
    938 		 *
    939 		 * @param array $localize Array of localized strings.
    940 		 *
    941 		 * @return array
    942 		 */
    943 		public static function localize( array $localize ): array {
    944 			$redux = Redux::instance( $localize['args']['opt_name'] );
    945 			$nonce = wp_create_nonce( 'redux-ads-nonce' );
    946 			$base  = admin_url( 'admin-ajax.php' ) . '?t=' . $redux->core_thread . '&action=redux_p&nonce=' . $nonce . '&url=';
    947 
    948 			return $localize;
    949 		}
    950 
    951 		/**
    952 		 * Retrieved request headers.
    953 		 *
    954 		 * @param array $args array of headers.
    955 		 *
    956 		 * @return array
    957 		 */
    958 		public static function get_request_headers( array $args = array() ): array {
    959 			$instances = Redux_Instances::get_all_instances();
    960 
    961 			$array = array(
    962 				'hash'       => self::get_hash(),
    963 				'developers' => wp_json_encode( self::get_developer_keys() ),
    964 				'redux'      => Redux_Core::$version,
    965 				'installed'  => Redux_Core::$installed,
    966 				'debug'      => defined( 'WP_DEBUG' ) && WP_DEBUG,
    967 				'local'      => self::is_local_host(),
    968 				'wordpress'  => get_bloginfo( 'version' ),
    969 				'site'       => esc_url( home_url( '/' ) ),
    970 				'auto_fonts' => get_option( 'auto_update_redux_google_fonts', false ),
    971 				'extensions' => join( '|', array_keys( self::get_extensions() ) ),
    972 			);
    973 
    974 			if ( ! empty( $instances ) ) {
    975 				$array['opt_names'] = join( '|', array_keys( $instances ) );
    976 			}
    977 
    978 			if ( ! empty( $args ) ) {
    979 				return wp_parse_args( $args, $array );
    980 			}
    981 
    982 			return $array;
    983 		}
    984 
    985 		/**
    986 		 * Check mokama.
    987 		 *
    988 		 * @access public
    989 		 * @since 4.0.0
    990 		 * @return bool
    991 		 */
    992 		public static function mokama(): bool {
    993 			if ( defined( 'RDX_MOKAMA' ) ) {
    994 				return Redux_Functions_Ex::s();
    995 			}
    996 			return false;
    997 		}
    998 
    999 		/**
   1000 		 * Deprecated. Compiles array of system specs.
   1001 		 *
   1002 		 * @param boolean $json_output   Enable/Disable return in JSON format.
   1003 		 * @param boolean $remote_checks Enable/Disable remote URL testing.
   1004 		 *
   1005 		 * @return array::compile_system_status( $json_output, $remote_checks )
   1006 		 * @deprecated No longer using camelCase naming convention.
   1007 		 */
   1008 		public static function compileSystemStatus( bool $json_output, bool $remote_checks ): array { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
   1009 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::compile_system_status( $json_output, $remote_checks )' );
   1010 
   1011 			return self::compile_system_status( $json_output, $remote_checks );
   1012 		}
   1013 
   1014 		/**
   1015 		 * Compiles array of system specs.
   1016 		 *
   1017 		 * @param bool $json_output   Do output file as JSON string.
   1018 		 * @param bool $remote_checks Perform remote checks.
   1019 		 *
   1020 		 * @return array
   1021 		 */
   1022 		public static function compile_system_status( bool $json_output = false, bool $remote_checks = false ): array {
   1023 			global $wpdb;
   1024 
   1025 			$sysinfo = array();
   1026 
   1027 			$sysinfo['home_url']       = home_url();
   1028 			$sysinfo['site_url']       = site_url();
   1029 			$sysinfo['redux_ver']      = esc_html( Redux_Core::$version );
   1030 			$sysinfo['redux_data_dir'] = Redux_Core::$upload_dir;
   1031 
   1032 			$fs        = Redux_Filesystem::get_instance();
   1033 			$test_file = Redux_Core::$upload_dir . 'test-log.log';
   1034 
   1035 			if ( $fs->is_file( $test_file ) ) {
   1036 				$res = $fs->unlink( $test_file );
   1037 			} else {
   1038 				$res = $fs->touch( $test_file );
   1039 				$fs->unlink( $test_file );
   1040 			}
   1041 
   1042 			// Only is a file-write check.
   1043 			$sysinfo['redux_data_writeable'] = $res;
   1044 			$sysinfo['wp_content_url']       = WP_CONTENT_URL;
   1045 			$sysinfo['wp_ver']               = get_bloginfo( 'version' );
   1046 			$sysinfo['wp_multisite']         = is_multisite();
   1047 			$sysinfo['permalink_structure']  = get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default';
   1048 			$sysinfo['front_page_display']   = get_option( 'show_on_front' );
   1049 			if ( 'page' === $sysinfo['front_page_display'] ) {
   1050 				$front_page_id = get_option( 'page_on_front' );
   1051 				$blog_page_id  = get_option( 'page_for_posts' );
   1052 
   1053 				$sysinfo['front_page'] = 0 !== $front_page_id ? get_the_title( $front_page_id ) . ' (#' . $front_page_id . ')' : 'Unset';
   1054 				$sysinfo['posts_page'] = 0 !== $blog_page_id ? get_the_title( $blog_page_id ) . ' (#' . $blog_page_id . ')' : 'Unset';
   1055 			}
   1056 
   1057 			$sysinfo['wp_mem_limit']['raw']  = self::let_to_num( WP_MEMORY_LIMIT );
   1058 			$sysinfo['wp_mem_limit']['size'] = size_format( $sysinfo['wp_mem_limit']['raw'] );
   1059 
   1060 			$sysinfo['db_table_prefix'] = 'Length: ' . strlen( $wpdb->prefix ) . ' - Status: ' . ( strlen( $wpdb->prefix ) > 16 ? 'ERROR: Too long' : 'Acceptable' );
   1061 
   1062 			$sysinfo['wp_debug'] = false;
   1063 			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
   1064 				$sysinfo['wp_debug'] = true;
   1065 			}
   1066 
   1067 			$sysinfo['wp_lang'] = get_locale();
   1068 
   1069 			if ( ! class_exists( 'Browser' ) ) {
   1070 				require_once Redux_Core::$dir . 'inc/lib/browser.php';
   1071 			}
   1072 
   1073 			$browser = new Browser();
   1074 
   1075 			$sysinfo['browser'] = array(
   1076 				'agent'    => $browser->getUserAgent(),
   1077 				'browser'  => $browser->getBrowser(),
   1078 				'version'  => $browser->getVersion(),
   1079 				'platform' => $browser->getPlatform(),
   1080 			);
   1081 
   1082 			$sysinfo['server_info'] = esc_html( Redux_Core::$server['SERVER_SOFTWARE'] );
   1083 			$sysinfo['localhost']   = self::make_bool_str( self::is_local_host() );
   1084 			$sysinfo['php_ver']     = function_exists( 'phpversion' ) ? esc_html( phpversion() ) : 'phpversion() function does not exist.';
   1085 			$sysinfo['abspath']     = ABSPATH;
   1086 
   1087 			if ( function_exists( 'ini_get' ) ) {
   1088 				$sysinfo['php_mem_limit']      = size_format( self::let_to_num( ini_get( 'memory_limit' ) ) );
   1089 				$sysinfo['php_post_max_size']  = size_format( self::let_to_num( ini_get( 'post_max_size' ) ) );
   1090 				$sysinfo['php_time_limit']     = ini_get( 'max_execution_time' );
   1091 				$sysinfo['php_max_input_var']  = ini_get( 'max_input_vars' );
   1092 				$sysinfo['php_display_errors'] = self::make_bool_str( ini_get( 'display_errors' ) );
   1093 			}
   1094 
   1095 			$sysinfo['suhosin_installed'] = extension_loaded( 'suhosin' );
   1096 			$sysinfo['mysql_ver']         = $wpdb->db_version();
   1097 			$sysinfo['max_upload_size']   = size_format( wp_max_upload_size() );
   1098 
   1099 			$sysinfo['def_tz_is_utc'] = true;
   1100 			if ( date_default_timezone_get() !== 'UTC' ) {
   1101 				$sysinfo['def_tz_is_utc'] = false;
   1102 			}
   1103 
   1104 			$sysinfo['fsockopen_curl'] = false;
   1105 
   1106 			if ( function_exists( 'fsockopen' ) || function_exists( 'curl_init' ) ) {
   1107 				$sysinfo['fsockopen_curl'] = true;
   1108 			}
   1109 
   1110 			if ( true === $remote_checks ) {
   1111 				$response = wp_remote_post(
   1112 					'https://api.redux.io/status',
   1113 					array(
   1114 						'sslverify' => true,
   1115 						'timeout'   => 60,
   1116 						'headers'   => self::get_request_headers(),
   1117 					)
   1118 				);
   1119 
   1120 				if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
   1121 					$sysinfo['wp_remote_post']       = true;
   1122 					$sysinfo['wp_remote_post_error'] = '';
   1123 				} else {
   1124 					$sysinfo['wp_remote_post']       = false;
   1125 					$sysinfo['wp_remote_post_error'] = $response->get_error_message();
   1126 				}
   1127 
   1128 				// phpcs:ignore WordPress.PHP.NoSilencedErrors
   1129 				$response = @wp_remote_get( 'https://raw.githubusercontent.com/dovy/redux-framework/master/CONTRIBUTING.md' );
   1130 
   1131 				if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
   1132 					$sysinfo['wp_remote_get']       = true;
   1133 					$sysinfo['wp_remote_get_error'] = '';
   1134 				} else {
   1135 					$sysinfo['wp_remote_get']       = false;
   1136 					$sysinfo['wp_remote_get_error'] = $response->get_error_message();
   1137 				}
   1138 			}
   1139 
   1140 			$active_plugins = (array) get_option( 'active_plugins', array() );
   1141 
   1142 			if ( is_multisite() ) {
   1143 				$active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
   1144 			}
   1145 
   1146 			$sysinfo['plugins'] = array();
   1147 
   1148 			foreach ( $active_plugins as $plugin ) {
   1149 				if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin ) ) {
   1150 					// phpcs:ignore WordPress.PHP.NoSilencedErrors
   1151 					$plugin_data = @get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
   1152 					$plugin_name = esc_html( $plugin_data['Name'] );
   1153 
   1154 					$sysinfo['plugins'][ $plugin_name ] = $plugin_data;
   1155 				}
   1156 			}
   1157 
   1158 			$redux = Redux::all_instances();
   1159 
   1160 			$sysinfo['redux_instances'] = array();
   1161 
   1162 			if ( ! empty( $redux ) && is_array( $redux ) ) {
   1163 				foreach ( $redux as $inst => $data ) {
   1164 					Redux::init( $inst );
   1165 
   1166 					$sysinfo['redux_instances'][ $inst ]['args']     = $data->args;
   1167 					$sysinfo['redux_instances'][ $inst ]['sections'] = $data->sections;
   1168 					foreach ( $sysinfo['redux_instances'][ $inst ]['sections'] as $key => $section ) {
   1169 						if ( isset( $section['fields'] ) && is_array( $section['fields'] ) ) {
   1170 							foreach ( $section['fields'] as $field_key => $field ) {
   1171 								if ( isset( $field['validate_callback'] ) ) {
   1172 									unset( $sysinfo['redux_instances'][ $inst ]['sections'][ $key ]['fields'][ $field_key ]['validate_callback'] );
   1173 								}
   1174 								if ( 'js_button' === $field['type'] ) {
   1175 									if ( isset( $field['script'] ) && isset( $field['script']['ver'] ) ) {
   1176 										unset( $sysinfo['redux_instances'][ $inst ]['sections'][ $key ]['fields'][ $field_key ]['script']['ver'] );
   1177 									}
   1178 								}
   1179 							}
   1180 						}
   1181 					}
   1182 
   1183 					$sysinfo['redux_instances'][ $inst ]['extensions'] = Redux::get_extensions( $inst );
   1184 
   1185 					if ( isset( $data->extensions[ 'metaboxes' ] ) ) {
   1186 						$data->extensions[ 'metaboxes' ]->init();
   1187 						$sysinfo['redux_instances'][ $inst ][ 'metaboxes' ] = $data->extensions[ 'metaboxes' ]->boxes;
   1188 					}
   1189 
   1190 					if ( isset( $data->args['templates_path'] ) && '' !== $data->args['templates_path'] ) {
   1191 						$sysinfo['redux_instances'][ $inst ]['templates'] = self::get_redux_templates( $data->args['templates_path'] );
   1192 					}
   1193 				}
   1194 			}
   1195 
   1196 			$active_theme = wp_get_theme();
   1197 
   1198 			$sysinfo['theme']['name']       = $active_theme->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
   1199 			$sysinfo['theme']['version']    = $active_theme->Version; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
   1200 			$sysinfo['theme']['author_uri'] = $active_theme->{'Author URI'};
   1201 			$sysinfo['theme']['is_child']   = self::make_bool_str( is_child_theme() );
   1202 
   1203 			if ( is_child_theme() ) {
   1204 				$parent_theme = wp_get_theme( $active_theme->Template ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName
   1205 
   1206 				$sysinfo['theme']['parent_name']       = $parent_theme->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
   1207 				$sysinfo['theme']['parent_version']    = $parent_theme->Version; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
   1208 				$sysinfo['theme']['parent_author_uri'] = $parent_theme->{'Author URI'};
   1209 			}
   1210 
   1211 			return $sysinfo;
   1212 		}
   1213 
   1214 		/**
   1215 		 * Deprecated. Returns array of Redux templates.
   1216 		 *
   1217 		 * @param string $custom_template_path The Path to custom template.
   1218 		 *
   1219 		 * @return array::get_redux_templates( $custom_template_path )
   1220 		 * @deprecated No longer using camelCase naming convention.
   1221 		 */
   1222 		private static function getReduxTemplates( string $custom_template_path ): array { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
   1223 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::get_redux_templates( $custom_template_path )' );
   1224 
   1225 			return self::get_redux_templates( $custom_template_path );
   1226 		}
   1227 
   1228 		/**
   1229 		 * Returns array of Redux templates.
   1230 		 *
   1231 		 * @param string $custom_template_path The Path to template dir.
   1232 		 *
   1233 		 * @return array
   1234 		 */
   1235 		private static function get_redux_templates( string $custom_template_path ): array {
   1236 			$filesystem         = Redux_Filesystem::get_instance();
   1237 			$template_paths     = array( 'ReduxFramework' => Redux_Core::$dir . 'templates/panel' );
   1238 			$scanned_files      = array();
   1239 			$found_files        = array();
   1240 			$outdated_templates = false;
   1241 
   1242 			foreach ( $template_paths as $plugin_name => $template_path ) {
   1243 				$scanned_files[ $plugin_name ] = self::scan_template_files( $template_path );
   1244 			}
   1245 
   1246 			foreach ( $scanned_files as $plugin_name => $files ) {
   1247 				foreach ( $files as $file ) {
   1248 					if ( file_exists( $custom_template_path . '/' . $file ) ) {
   1249 						$theme_file = $custom_template_path . '/' . $file;
   1250 					} else {
   1251 						$theme_file = false;
   1252 					}
   1253 
   1254 					if ( $theme_file ) {
   1255 						$core_version  = self::get_template_version( Redux_Core::$dir . 'templates/panel/' . $file );
   1256 						$theme_version = self::get_template_version( $theme_file );
   1257 
   1258 						if ( $core_version && ( empty( $theme_version ) || version_compare( $theme_version, $core_version, '<' ) ) ) {
   1259 							if ( ! $outdated_templates ) {
   1260 								$outdated_templates = true;
   1261 							}
   1262 
   1263 							$found_files[ $plugin_name ][] = sprintf( '<code>%s</code> ' . esc_html__( 'version', 'redux-framework' ) . ' <strong style="color:red">%s</strong> ' . esc_html__( 'is out of date. The core version is', 'redux-framework' ) . ' %s', str_replace( WP_CONTENT_DIR . '/themes/', '', $theme_file ), $theme_version ? $theme_version : '-', $core_version );
   1264 						} else {
   1265 							$found_files[ $plugin_name ][] = sprintf( '<code>%s</code>', str_replace( WP_CONTENT_DIR . '/themes/', '', $theme_file ) );
   1266 						}
   1267 					}
   1268 				}
   1269 			}
   1270 
   1271 			return $found_files;
   1272 		}
   1273 
   1274 		/**
   1275 		 * Deprecated. URL Fix.
   1276 		 *
   1277 		 * @param string $base     Base string of site.
   1278 		 * @param string $opt_name Redux instance opt_name.
   1279 		 *
   1280 		 * @return Redux_Helpers::r_url_fix( $base, $opt_name )
   1281 		 * @deprecated No longer using camelCase naming convention.
   1282 		 */
   1283 		public static function rURL_fix( string $base, string $opt_name ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
   1284 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::r_url_fix( $base, $opt_name )' );
   1285 
   1286 			return self::r_url_fix( $base, $opt_name );
   1287 		}
   1288 
   1289 		/**
   1290 		 * URL Fix.
   1291 		 *
   1292 		 * @param string $base     Base.
   1293 		 * @param string $opt_name Panel opt_name.
   1294 		 *
   1295 		 * @return mixed|string|void
   1296 		 */
   1297 		public static function r_url_fix( string $base, string $opt_name ) {
   1298 			$url = $base . rawurlencode( 'https://look.redux.io/api/index.php?js&g&1&v=2' ) . '&proxy=' . rawurlencode( $base ) . '';
   1299 
   1300 			return Redux_Functions::tru( $url, $opt_name );
   1301 		}
   1302 
   1303 		/**
   1304 		 * Scan template files for ver changes.
   1305 		 *
   1306 		 * @param string $template_path The Path to templates.
   1307 		 *
   1308 		 * @return array
   1309 		 */
   1310 		private static function scan_template_files( string $template_path ): array {
   1311 			$files  = scandir( $template_path );
   1312 			$result = array();
   1313 
   1314 			if ( $files ) {
   1315 				foreach ( $files as $key => $value ) {
   1316 					if ( ! in_array( $value, array( '.', '..' ), true ) ) {
   1317 						if ( is_dir( $template_path . DIRECTORY_SEPARATOR . $value ) ) {
   1318 							$sub_files = redux_scan_template_files( $template_path . DIRECTORY_SEPARATOR . $value );
   1319 							foreach ( $sub_files as $sub_file ) {
   1320 								$result[] = $value . DIRECTORY_SEPARATOR . $sub_file;
   1321 							}
   1322 						} else {
   1323 							$result[] = $value;
   1324 						}
   1325 					}
   1326 				}
   1327 			}
   1328 
   1329 			return $result;
   1330 		}
   1331 
   1332 		/**
   1333 		 * Retrieves template version.
   1334 		 *
   1335 		 * @param string $file Path to template file.
   1336 		 *
   1337 		 * @return string
   1338 		 */
   1339 		public static function get_template_version( string $file ): string {
   1340 			$filesystem = Redux_Filesystem::get_instance();
   1341 			// Avoid notices if file does not exist.
   1342 			if ( ! file_exists( $file ) ) {
   1343 				return '';
   1344 			}
   1345 
   1346 			$data = get_file_data( $file, array( 'version' ), 'plugin' );
   1347 
   1348 			if ( ! empty( $data[0] ) ) {
   1349 				return $data[0];
   1350 			} else {
   1351 				$file_data = $filesystem->get_contents( $file );
   1352 
   1353 				$file_data = str_replace( "\r", "\n", $file_data );
   1354 				$version   = '1.0.0';
   1355 
   1356 				if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( '@version', '/' ) . '(.*)$/mi', $file_data, $match ) && $match[1] ) {
   1357 					$version = _cleanup_header_comment( $match[1] );
   1358 				}
   1359 
   1360 				return $version;
   1361 			}
   1362 		}
   1363 
   1364 		/**
   1365 		 * Create HTML attribute string.
   1366 		 *
   1367 		 * @param array $attributes Array of attributes.
   1368 		 */
   1369 		public static function html_attributes( array $attributes = array() ) {
   1370 			$string = join(
   1371 				' ',
   1372 				array_map(
   1373 					function ( $key ) use ( $attributes ) {
   1374 						if ( is_bool( $attributes[ $key ] ) ) {
   1375 							return $attributes[ $key ] ? $key : '';
   1376 						}
   1377 
   1378 						return $key . '="' . $attributes[ $key ] . '"';
   1379 					},
   1380 					array_keys( $attributes )
   1381 				)
   1382 			) . ' ';
   1383 		}
   1384 
   1385 		/**
   1386 		 * Output filesize based on letter indicator.
   1387 		 *
   1388 		 * @param string $size Size with letter.
   1389 		 *
   1390 		 * @return bool|int|string
   1391 		 */
   1392 		private static function let_to_num( string $size ) {
   1393 			$l   = substr( $size, - 1 );
   1394 			$ret = substr( $size, 0, - 1 );
   1395 
   1396 			switch ( strtoupper( $l ) ) {
   1397 				case 'P':
   1398 					$ret *= 1024;
   1399 					// Must remain recursive, do not use 'break'.
   1400 				case 'T':
   1401 					$ret *= 1024;
   1402 					// Must remain recursive, do not use 'break'.
   1403 				case 'G':
   1404 					$ret *= 1024;
   1405 					// Must remain recursive, do not use 'break'.
   1406 				case 'M':
   1407 					$ret *= 1024;
   1408 					// Must remain recursive, do not use 'break'.
   1409 				case 'K':
   1410 					$ret *= 1024;
   1411 			}
   1412 
   1413 			return $ret;
   1414 		}
   1415 
   1416 		/**
   1417 		 * Normalize extensions dir.
   1418 		 *
   1419 		 * @param string $dir Path to extensions.
   1420 		 *
   1421 		 * @return string
   1422 		 */
   1423 		public static function get_extension_dir( string $dir ): string {
   1424 			return trailingslashit( Redux_Functions_Ex::wp_normalize_path( dirname( $dir ) ) );
   1425 		}
   1426 
   1427 		/**
   1428 		 * Normalize extensions URL.
   1429 		 *
   1430 		 * @param string $dir Path to extensions.
   1431 		 *
   1432 		 * @return array|string|string[]
   1433 		 */
   1434 		public static function get_extension_url( string $dir ) {
   1435 			$ext_dir = self::get_extension_dir( $dir );
   1436 
   1437 			return str_replace( Redux_Functions_Ex::wp_normalize_path( WP_CONTENT_DIR ), WP_CONTENT_URL, $ext_dir );
   1438 		}
   1439 
   1440 		/**
   1441 		 * Checks a nested capabilities array or string to determine if the current user meets the requirements.
   1442 		 *
   1443 		 * @param string|array $capabilities Permission string or array to check. See self::user_can() for details.
   1444 		 *
   1445 		 * @return bool Whether the user meets the requirements. False on invalid user.
   1446 		 * @since 3.6.3.4
   1447 		 */
   1448 		public static function current_user_can( $capabilities ): bool {
   1449 			$current_user = wp_get_current_user();
   1450 
   1451 			if ( empty( $current_user ) ) {
   1452 				return false;
   1453 			}
   1454 
   1455 			$name_arr = func_get_args();
   1456 			$args     = array_merge( array( $current_user ), $name_arr );
   1457 
   1458 			return call_user_func_array( array( 'self', 'user_can' ), $args );
   1459 		}
   1460 
   1461 		/**
   1462 		 * Checks a nested capabilities array or string to determine if the user meets the requirements.
   1463 		 * You can pass in a simple string like 'edit_posts' or an array of conditions.
   1464 		 * The capability 'relation' is reserved for controlling the relation mode (AND/OR), which defaults to AND.
   1465 		 * Max depth of 30 levels.  False is returned for any conditions exceeding max depth.
   1466 		 * If you want to check meta caps, you must also pass the object ID on which to check against.
   1467 		 * If you get the error: PHP Notice:  Undefined offset: 0 in /wp-includes/capabilities.php, you didn't
   1468 		 * pass the required $object_id.
   1469 		 *
   1470 		 * @param int/object   $user          User ID or WP_User object to check. Defaults to the current user.
   1471 		 * @param string|array $capabilities  Capability string or array to check. The array lets you use multiple
   1472 		 *                                    conditions to determine if a user has permission.
   1473 		 *                                    Invalid conditions are skipped (conditions which aren't a string/array/bool/number(cast to bool)).
   1474 		 *                                    Example array where the user needs to have either the 'edit_posts' capability OR doesn't have the
   1475 		 *                                    'delete_pages' cap OR has the 'update_plugins' AND 'add_users' capabilities.
   1476 		 *                                    array(
   1477 		 *                                    'relation'     => 'OR',      // Optional, defaults to AND.
   1478 		 *                                    'edit_posts',                // Equivalent to 'edit_posts' => true,
   1479 		 *                                    'delete_pages' => false,     // Tests that the user DOESN'T have this capability
   1480 		 *                                    array(                       // Nested conditions array (up to 30 nestings)
   1481 		 *                                    'update_plugins',
   1482 		 *                                    'add_users',
   1483 		 *                                    ),
   1484 		 *                                    ).
   1485 		 * @param int|null     $object_id         (Optional) ID of the specific object to check against if capability is a "meta" cap.
   1486 		 *                                    e.g. 'edit_post', 'edit_user', 'edit_page', etc.
   1487 		 *
   1488 		 * @return bool Whether the user meets the requirements.
   1489 		 *              Will always return false for:
   1490 		 *              - Invalid/missing user
   1491 		 *              - If the $capabilities is not a string or array
   1492 		 *              - Max nesting depth exceeded (for that level)
   1493 		 * @since 3.6.3.4
   1494 		 * @example
   1495 		 *        user_can( 42, 'edit_pages' );                        // Checks if user ID 42 has the 'edit_pages' cap.
   1496 		 *        user_can( 42, 'edit_page', 17433 );                  // Checks if user ID 42 has the 'edit_page' cap for post ID 17433.
   1497 		 *        user_can( 42, array( 'edit_pages', 'edit_posts' ) ); // Checks if user ID 42 has both the 'edit_pages' and 'edit_posts' caps.
   1498 		 */
   1499 		public static function user_can( $user, $capabilities, int $object_id = null ): bool {
   1500 			static $depth = 0;
   1501 
   1502 			if ( $depth >= 30 ) {
   1503 				return false;
   1504 			}
   1505 
   1506 			if ( empty( $user ) ) {
   1507 				return false;
   1508 			}
   1509 
   1510 			if ( ! is_object( $user ) ) {
   1511 				$user = get_userdata( $user );
   1512 			}
   1513 
   1514 			if ( is_string( $capabilities ) ) {
   1515 				// Simple string capability check.
   1516 				$args = array( $user, $capabilities );
   1517 
   1518 				if ( null !== $object_id ) {
   1519 					$args[] = $object_id;
   1520 				}
   1521 
   1522 				return call_user_func_array( 'user_can', $args );
   1523 			} else {
   1524 				// Only strings and arrays are allowed as valid capabilities.
   1525 				if ( ! is_array( $capabilities ) ) {
   1526 					return false;
   1527 				}
   1528 			}
   1529 
   1530 			// Capability array check.
   1531 			$or = false;
   1532 
   1533 			foreach ( $capabilities as $key => $value ) {
   1534 				if ( 'relation' === $key ) {
   1535 					if ( 'OR' === $value ) {
   1536 						$or = true;
   1537 					}
   1538 
   1539 					continue;
   1540 				}
   1541 
   1542 				/**
   1543 				 * Rules can be in 4 different formats:
   1544 				 * [
   1545 				 *   [0]      => 'foobar',
   1546 				 *   [1]      => array(...),
   1547 				 *   'foobar' => false,
   1548 				 *   'foobar' => array(...),
   1549 				 * ]
   1550 				 */
   1551 				if ( is_numeric( $key ) ) {
   1552 					// Numeric key.
   1553 					if ( is_string( $value ) ) {
   1554 						// Numeric key with a string value is the capability string to check
   1555 						// [0] => 'foobar'.
   1556 						$args = array( $user, $value );
   1557 
   1558 						if ( null !== $object_id ) {
   1559 							$args[] = $object_id;
   1560 						}
   1561 
   1562 						$expression_result = call_user_func_array( 'user_can', $args ) === true;
   1563 					} elseif ( is_array( $value ) ) {
   1564 						$depth ++;
   1565 
   1566 						$expression_result = self::user_can( $user, $value, $object_id );
   1567 
   1568 						$depth --;
   1569 					} else {
   1570 						// Invalid types are skipped.
   1571 						continue;
   1572 					}
   1573 				} else {
   1574 					// Non-numeric key.
   1575 					if ( is_scalar( $value ) ) {
   1576 						$args = array( $user, $key );
   1577 
   1578 						if ( null !== $object_id ) {
   1579 							$args[] = $object_id;
   1580 						}
   1581 
   1582 						$expression_result = call_user_func_array( 'user_can', $args ) === (bool) $value;
   1583 					} elseif ( is_array( $value ) ) {
   1584 						$depth ++;
   1585 
   1586 						$expression_result = self::user_can( $user, $value, $object_id );
   1587 
   1588 						$depth --;
   1589 					} else {
   1590 						// Invalid types are skipped.
   1591 						continue;
   1592 					}
   1593 				}
   1594 
   1595 				// Check after every evaluation if we know enough to return a definitive answer.
   1596 				if ( $or ) {
   1597 					if ( $expression_result ) {
   1598 						// If the relation is OR, return on the first true expression.
   1599 						return true;
   1600 					}
   1601 				} else {
   1602 					if ( ! $expression_result ) {
   1603 						// If the relation is AND, return on the first false expression.
   1604 						return false;
   1605 					}
   1606 				}
   1607 			}
   1608 
   1609 			// If we get this far on an OR, then it failed.
   1610 			// If we get this far on an AND, then it succeeded.
   1611 			return ! $or;
   1612 		}
   1613 
   1614 		/**
   1615 		 * Check if Google font update is needed.
   1616 		 *
   1617 		 * @return bool
   1618 		 */
   1619 		public static function google_fonts_update_needed(): bool {
   1620 
   1621 			$path = trailingslashit( Redux_Core::$upload_dir ) . 'google_fonts.json';
   1622 			$now  = time();
   1623 			$secs = 60 * 60 * 24 * 7;
   1624 			if ( file_exists( $path ) ) {
   1625 				if ( ( $now - filemtime( $path ) ) < $secs ) {
   1626 					return false;
   1627 				}
   1628 			}
   1629 
   1630 			return true;
   1631 		}
   1632 
   1633 		/**
   1634 		 * Retrieve list of dev keys.
   1635 		 *
   1636 		 * @return array
   1637 		 */
   1638 		public static function get_developer_keys(): array {
   1639 
   1640 			// TODO - Get shim values for here.
   1641 			// phpcs:ignore WordPress.NamingConventions.ValidHookName
   1642 			$data = array( apply_filters( 'redux/tracking/developer', array() ) );
   1643 			if ( 1 === count( $data ) ) {
   1644 				if ( empty( $data[0] ) ) {
   1645 					$data = array();
   1646 				}
   1647 			}
   1648 			$instances = Redux_Instances::get_all_instances();
   1649 			$data      = array();
   1650 			if ( ! empty( $instance ) ) {
   1651 				foreach ( $instances as $instance ) {
   1652 					if ( isset( $instance->args['developer'] ) && ! empty( $instance->args['developer'] ) ) {
   1653 						$data[] = $instance->args['developer'];
   1654 					}
   1655 				}
   1656 			}
   1657 
   1658 			return $data;
   1659 		}
   1660 
   1661 		/**
   1662 		 * Retrieve updated Google font array.
   1663 		 *
   1664 		 * @param bool $download Flag to download to file.
   1665 		 *
   1666 		 * @return array|WP_Error
   1667 		 */
   1668 		public static function google_fonts_array( bool $download = false ) {
   1669 			if ( ! empty( Redux_Core::$google_fonts ) && ! self::google_fonts_update_needed() ) {
   1670 				return Redux_Core::$google_fonts;
   1671 			}
   1672 
   1673 			$filesystem = Redux_Filesystem::get_instance();
   1674 
   1675 			$path = trailingslashit( Redux_Core::$upload_dir ) . 'google_fonts.json';
   1676 
   1677 			if ( ! file_exists( $path ) || ( file_exists( $path ) && $download && self::google_fonts_update_needed() ) ) {
   1678 				if ( $download ) {
   1679 					// phpcs:ignore WordPress.NamingConventions.ValidHookName
   1680 					$url = apply_filters( 'redux/typography/google_fonts/url', 'https://api.redux.io/gfonts' );
   1681 
   1682 					$request = wp_remote_get(
   1683 						$url,
   1684 						array(
   1685 							'timeout' => 20,
   1686 							'headers' => self::get_request_headers(),
   1687 						)
   1688 					);
   1689 
   1690 					if ( ! is_wp_error( $request ) ) {
   1691 						$body = wp_remote_retrieve_body( $request );
   1692 						if ( ! empty( $body ) ) {
   1693 							$filesystem->put_contents( $path, $body );
   1694 							Redux_Core::$google_fonts = json_decode( $body, true );
   1695 						}
   1696 					} else {
   1697 						return $request;
   1698 					}
   1699 				}
   1700 			} elseif ( file_exists( $path ) ) {
   1701 				Redux_Core::$google_fonts = json_decode( $filesystem->get_contents( $path ), true );
   1702 				if ( empty( Redux_Core::$google_fonts ) ) {
   1703 					$filesystem->unlink( $path );
   1704 				}
   1705 			}
   1706 
   1707 			return Redux_Core::$google_fonts;
   1708 		}
   1709 
   1710 		/**
   1711 		 * Deprecated. Gets all Redux instances
   1712 		 *
   1713 		 * @return array
   1714 		 * @deprecated No longer using camelCase naming convention and moved to a different class.
   1715 		 */
   1716 		public static function getReduxInstances(): array { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName
   1717 			_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'Redux 4.0.0', 'Redux_Instances::get_all_instances()' );
   1718 
   1719 			return Redux_Instances::get_all_instances();
   1720 		}
   1721 
   1722 		/**
   1723 		 * Is Inside Plugin
   1724 		 *
   1725 		 * @param string $file File name.
   1726 		 *
   1727 		 * @return array|bool
   1728 		 */
   1729 		public static function is_inside_plugin( string $file ) {
   1730 
   1731 			// phpcs:ignore Squiz.PHP.CommentedOutCode
   1732 			// if ( substr( strtoupper( $file ), 0, 2 ) === 'C:' ) {
   1733 			// $file = ltrim( $file, 'C:' );
   1734 			// $file = ltrim( $file, 'c:' );
   1735 			// } .
   1736 			//
   1737 			$plugin_basename = plugin_basename( $file );
   1738 
   1739 			if ( Redux_Functions_Ex::wp_normalize_path( $file ) !== '/' . $plugin_basename ) {
   1740 				$slug = explode( '/', $plugin_basename );
   1741 				$slug = $slug[0];
   1742 
   1743 				return array(
   1744 					'slug'      => $slug,
   1745 					'basename'  => $plugin_basename,
   1746 					'path'      => Redux_Functions_Ex::wp_normalize_path( $file ),
   1747 					'url'       => plugins_url( $plugin_basename ),
   1748 					'real_path' => Redux_Functions_Ex::wp_normalize_path( dirname( realpath( $file ) ) ),
   1749 				);
   1750 			}
   1751 
   1752 			return false;
   1753 		}
   1754 
   1755 		/**
   1756 		 * Is inside theme.
   1757 		 *
   1758 		 * @param string $file File name.
   1759 		 *
   1760 		 * @return array|bool
   1761 		 */
   1762 		public static function is_inside_theme( string $file = '' ) {
   1763 			$theme_paths = array(
   1764 				Redux_Functions_Ex::wp_normalize_path( get_template_directory() )   => get_template_directory_uri(),
   1765 				Redux_Functions_Ex::wp_normalize_path( get_stylesheet_directory() ) => get_stylesheet_directory_uri(),
   1766 			);
   1767 
   1768 			$theme_paths = array_unique( $theme_paths );
   1769 
   1770 			$file_path = Redux_Functions_Ex::wp_normalize_path( $file );
   1771 			$filename  = explode( '/', $file_path );
   1772 			$filename  = end( $filename );
   1773 			foreach ( $theme_paths as $theme_path => $url ) {
   1774 
   1775 				$real_path = Redux_Functions_Ex::wp_normalize_path( realpath( $theme_path ) );
   1776 
   1777 				if ( strpos( $file_path, trailingslashit( $real_path ) ) !== false ) {
   1778 					$slug = explode( '/', Redux_Functions_Ex::wp_normalize_path( $theme_path ) );
   1779 					if ( empty( $slug ) ) {
   1780 						continue;
   1781 					}
   1782 					$slug          = end( $slug );
   1783 					$relative_path = explode( $slug, dirname( $file_path ) );
   1784 
   1785 					if ( 1 === count( $relative_path ) ) {
   1786 						$relative_path = $file_path;
   1787 					} else {
   1788 						$relative_path = $relative_path[1];
   1789 					}
   1790 					$relative_path = ltrim( $relative_path, '/' );
   1791 
   1792 					$data = array(
   1793 						'slug'      => $slug,
   1794 						'path'      => trailingslashit( trailingslashit( $theme_path ) . $relative_path ) . $filename,
   1795 						'real_path' => trailingslashit( trailingslashit( $real_path ) . $relative_path ) . $filename,
   1796 						'url'       => trailingslashit( trailingslashit( $url ) . $relative_path ) . $filename,
   1797 					);
   1798 
   1799 					$basename         = explode( $data['slug'], $data['path'] );
   1800 					$basename         = end( $basename );
   1801 					$basename         = ltrim( $basename, '/' );
   1802 					$data['basename'] = trailingslashit( $data['slug'] ) . $basename;
   1803 
   1804 					if ( is_child_theme() ) {
   1805 						$parent              = get_template_directory();
   1806 						$data['parent_slug'] = explode( '/', $parent );
   1807 						$data['parent_slug'] = end( $data['parent_slug'] );
   1808 						if ( $data['slug'] === $data['parent_slug'] ) {
   1809 							unset( $data['parent_slug'] );
   1810 						}
   1811 					}
   1812 
   1813 					return $data;
   1814 				}
   1815 			}
   1816 
   1817 			return false;
   1818 		}
   1819 
   1820 		/**
   1821 		 * Nonces.
   1822 		 *
   1823 		 * @return array
   1824 		 */
   1825 		public static function nonces(): array {
   1826 			return array(
   1827 				'9fced129522f128b2445a41fb0b6ef9f',
   1828 				'70dda5dfb8053dc6d1c492574bce9bfd',
   1829 				'62933a2951ef01f4eafd9bdf4d3cd2f0',
   1830 				'a398fb77df76e6153df57cd65fd0a7c5',
   1831 				'1cb251ec0d568de6a929b520c4aed8d1',
   1832 				'6394d816bfb4220289a6f4b29cfb1834',
   1833 			);
   1834 		}
   1835 
   1836 		/**
   1837 		 * Get plugin options.
   1838 		 *
   1839 		 * @return array|mixed|void
   1840 		 */
   1841 		public static function get_plugin_options() {
   1842 			$defaults = array(
   1843 				'demo' => false,
   1844 			);
   1845 			$options  = array();
   1846 
   1847 			// If multisite is enabled.
   1848 			if ( is_multisite() ) {
   1849 
   1850 				// Get network activated plugins.
   1851 				$plugins = get_site_option( 'active_sitewide_plugins' );
   1852 
   1853 				foreach ( $plugins as $file => $plugin ) {
   1854 					if ( strpos( $file, 'redux-framework.php' ) !== false ) {
   1855 						$plugin_network_activated = true;
   1856 						$options                  = get_site_option( 'ReduxFrameworkPlugin', $defaults );
   1857 					}
   1858 				}
   1859 			}
   1860 
   1861 			// If options aren't set, grab them now!
   1862 			if ( empty( $options ) ) {
   1863 				$options = get_option( 'ReduxFrameworkPlugin', $defaults );
   1864 			}
   1865 
   1866 			return $options;
   1867 		}
   1868 
   1869 		/**
   1870 		 * Sanitize array keys and values.
   1871 		 *
   1872 		 * @param array $array Array to sanitize.
   1873 		 */
   1874 		public static function sanitize_array( array $array ): array {
   1875 			return self::array_map_r( 'sanitize_text_field', $array );
   1876 		}
   1877 
   1878 		/**
   1879 		 * Recursive array map.
   1880 		 *
   1881 		 * @param string $func function to run.
   1882 		 * @param array  $arr  Array to clean.
   1883 		 *
   1884 		 * @return array
   1885 		 */
   1886 		private static function array_map_r( string $func, array $arr ): array {
   1887 			$new_arr = array();
   1888 
   1889 			foreach ( $arr as $key => $value ) {
   1890 				$new_arr[ $key ] = ( is_array( $value ) ? self::array_map_r( $func, $value ) : ( is_array( $func ) ? call_user_func_array( $func, $value ) : $func( $value ) ) );
   1891 			}
   1892 
   1893 			return $new_arr;
   1894 		}
   1895 
   1896 		/**
   1897 		 * AJAX callback.
   1898 		 */
   1899 		public static function hash_arg() {
   1900 			echo esc_html( md5( Redux_Functions_Ex::hash_key() . '-redux' ) );
   1901 			die();
   1902 		}
   1903 
   1904 		/**
   1905 		 * Adds stats parameters for Redux settings. Outside the main class as the class could also be in use in other ways.
   1906 		 *
   1907 		 * @param array $options Stats options.
   1908 		 *
   1909 		 * @return array
   1910 		 */
   1911 		public static function redux_stats_additions( array $options ): array {
   1912 			$options['redux'] = array(
   1913 				'demo_mode' => get_option( 'ReduxFrameworkPlugin' ),
   1914 			);
   1915 
   1916 			return $options;
   1917 		}
   1918 
   1919 		/**
   1920 		 * AJAX callback Compile support arg.
   1921 		 */
   1922 		public static function support_args() {
   1923 			header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
   1924 			header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . 'GMT' );
   1925 			header( 'Expires: Sat, 26 Jul 1997 05:00:00 GMT' );
   1926 			header( 'Cache-Control: no-store, no-cache, must-revalidate' );
   1927 			header( 'Cache-Control: post-check=0, pre-check=0', false );
   1928 			header( 'Pragma: no-cache' );
   1929 
   1930 			$instances = Redux::all_instances();
   1931 
   1932 			if ( isset( $_REQUEST['i'] ) && ! empty( $_REQUEST['i'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
   1933 				if ( is_array( $instances ) && ! empty( $instances ) ) {
   1934 					foreach ( $instances as $opt_name => $data ) {
   1935 						if ( md5( $opt_name . '-debug' ) === $_REQUEST['i'] ) { // phpcs:ignore WordPress.Security.NonceVerification
   1936 							$array = $data;
   1937 						}
   1938 					}
   1939 				}
   1940 
   1941 				if ( isset( $array ) ) {
   1942 
   1943 					// We only want the extension names and versions.
   1944 					$array->extensions = self::get_extensions( $opt_name );
   1945 					$to_return         = array();
   1946 
   1947 					// Filter out all the unwanted data.
   1948 					foreach ( $array as $key => $value ) {
   1949 						if ( in_array(
   1950 							$key,
   1951 							array(
   1952 								// 'fields',
   1953 								'extensions',
   1954 								'sections',
   1955 								'args',
   1956 								// 'field_types'
   1957 							),
   1958 							true
   1959 						) ) {
   1960 							$to_return[ $key ] = $value;
   1961 						} else { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
   1962 							// phpcs:ignore Squiz.PHP.CommentedOutCode
   1963 							/* echo $key.PHP_EOL; */
   1964 						}
   1965 					}
   1966 					$array = $to_return;
   1967 				} else {
   1968 					die();
   1969 				}
   1970 			} else {
   1971 				$array = self::get_statistics_object();
   1972 				if ( is_array( $instances ) && ! empty( $instances ) ) {
   1973 					$array['instances'] = array();
   1974 					foreach ( $instances as $opt_name => $data ) {
   1975 						$array['instances'][] = $opt_name;
   1976 					}
   1977 				}
   1978 				$array['key'] = md5( Redux_Functions_Ex::hash_key() );
   1979 			}
   1980 
   1981 			ksort( $array ); // Let's make that pretty.
   1982 
   1983 			// phpcs:ignored WordPress.PHP.NoSilencedErrors, WordPress.Security.EscapeOutput
   1984 			echo @htmlspecialchars( @wp_json_encode( $array, true ), ENT_QUOTES );
   1985 
   1986 			die();
   1987 		}
   1988 
   1989 		/**
   1990 		 * Detect if Gutenberg is running on the current page.
   1991 		 *
   1992 		 * @return bool
   1993 		 */
   1994 		public static function is_gutenberg_page(): bool {
   1995 			if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) {
   1996 				// The Gutenberg plugin is on.
   1997 				return true;
   1998 			}
   1999 
   2000 			$current_screen = get_current_screen();
   2001 
   2002 			if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
   2003 				// Gutenberg page on 5+.
   2004 				return true;
   2005 			}
   2006 			return false;
   2007 		}
   2008 
   2009 		/**
   2010 		 * Material design colors.
   2011 		 *
   2012 		 * @param string $context Mode to use.
   2013 		 *
   2014 		 * @return array|mixed
   2015 		 */
   2016 		public static function get_material_design_colors( string $context = 'primary' ) {
   2017 			$colors = array(
   2018 				'primary'     => array( '#FFFFFF', '#000000', '#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B' ),
   2019 				'red'         => array( '#FFEBEE', '#FFCDD2', '#EF9A9A', '#E57373', '#EF5350', '#F44336', '#E53935', '#D32F2F', '#C62828', '#B71C1C', '#FF8A80', '#FF5252', '#FF1744', '#D50000' ),
   2020 				'pink'        => array( '#FCE4EC', '#F8BBD0', '#F48FB1', '#F06292', '#EC407A', '#E91E63', '#D81B60', '#C2185B', '#AD1457', '#880E4F', '#FF80AB', '#FF4081', '#F50057', '#C51162' ),
   2021 				'purple'      => array( '#F3E5F5', '#E1BEE7', '#CE93D8', '#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA', '#7B1FA2', '#6A1B9A', '#4A148C', '#EA80FC', '#E040FB', '#D500F9', '#AA00FF' ),
   2022 				'deep-purple' => array( '#EDE7F6', '#D1C4E9', '#B39DDB', '#9575CD', '#7E57C2', '#673AB7', '#5E35B1', '#512DA8', '#4527A0', '#311B92', '#B388FF', '#7C4DFF', '#651FFF', '#6200EA' ),
   2023 				'indigo'      => array( '#E8EAF6', '#C5CAE9', '#9FA8DA', '#7986CB', '#5C6BC0', '#3F51B5', '#3949AB', '#303F9F', '#283593', '#1A237E', '#8C9EFF', '#536DFE', '#3D5AFE', '#304FFE' ),
   2024 				'blue'        => array( '#E3F2FD', '#BBDEFB', '#90CAF9', '#64B5F6', '#42A5F5', '#2196F3', '#1E88E5', '#1976D2', '#1565C0', '#0D47A1', '#82B1FF', '#448AFF', '#2979FF', '#2962FF' ),
   2025 				'light_blue'  => array( '#E1F5FE', '#B3E5FC', '#81D4fA', '#4fC3F7', '#29B6FC', '#03A9F4', '#039BE5', '#0288D1', '#0277BD', '#01579B', '#80D8FF', '#40C4FF', '#00B0FF', '#0091EA' ),
   2026 				'cyan'        => array( '#E0F7FA', '#B2EBF2', '#80DEEA', '#4DD0E1', '#26C6DA', '#00BCD4', '#00ACC1', '#0097A7', '#00838F', '#006064', '#84FFFF', '#18FFFF', '#00E5FF', '#00B8D4' ),
   2027 				'teal'        => array( '#E0F2F1', '#B2DFDB', '#80CBC4', '#4DB6AC', '#26A69A', '#009688', '#00897B', '#00796B', '#00695C', '#004D40', '#A7FFEB', '#64FFDA', '#1DE9B6', '#00BFA5' ),
   2028 				'green'       => array( '#E8F5E9', '#C8E6C9', '#A5D6A7', '#81C784', '#66BB6A', '#4CAF50', '#43A047', '#388E3C', '#2E7D32', '#1B5E20', '#B9F6CA', '#69F0AE', '#00E676', '#00C853' ),
   2029 				'light-green' => array( '#F1F8E9', '#DCEDC8', '#C5E1A5', '#AED581', '#9CCC65', '#8BC34A', '#7CB342', '#689F38', '#558B2F', '#33691E', '#CCFF90', '#B2FF59', '#76FF03', '#64DD17' ),
   2030 				'lime'        => array( '#F9FBE7', '#F0F4C3', '#E6EE9C', '#DCE775', '#D4E157', '#CDDC39', '#C0CA33', '#A4B42B', '#9E9D24', '#827717', '#F4FF81', '#EEFF41', '#C6FF00', '#AEEA00' ),
   2031 				'yellow'      => array( '#FFFDE7', '#FFF9C4', '#FFF590', '#FFF176', '#FFEE58', '#FFEB3B', '#FDD835', '#FBC02D', '#F9A825', '#F57F17', '#FFFF82', '#FFFF00', '#FFEA00', '#FFD600' ),
   2032 				'amber'       => array( '#FFF8E1', '#FFECB3', '#FFE082', '#FFD54F', '#FFCA28', '#FFC107', '#FFB300', '#FFA000', '#FF8F00', '#FF6F00', '#FFE57F', '#FFD740', '#FFC400', '#FFAB00' ),
   2033 				'orange'      => array( '#FFF3E0', '#FFE0B2', '#FFCC80', '#FFB74D', '#FFA726', '#FF9800', '#FB8C00', '#F57C00', '#EF6C00', '#E65100', '#FFD180', '#FFAB40', '#FF9100', '#FF6D00' ),
   2034 				'deep-orange' => array( '#FBE9A7', '#FFCCBC', '#FFAB91', '#FF8A65', '#FF7043', '#FF5722', '#F4511E', '#E64A19', '#D84315', '#BF360C', '#FF9E80', '#FF6E40', '#FF3D00', '#DD2600' ),
   2035 				'brown'       => array( '#EFEBE9', '#D7CCC8', '#BCAAA4', '#A1887F', '#8D6E63', '#795548', '#6D4C41', '#5D4037', '#4E342E', '#3E2723' ),
   2036 				'grey'        => array( '#FAFAFA', '#F5F5F5', '#EEEEEE', '#E0E0E0', '#BDBDBD', '#9E9E9E', '#757575', '#616161', '#424242', '#212121', '#000000', '#ffffff' ),
   2037 				'blue-grey'   => array( '#ECEFF1', '#CFD8DC', '#B0BBC5', '#90A4AE', '#78909C', '#607D8B', '#546E7A', '#455A64', '#37474F', '#263238' ),
   2038 			);
   2039 
   2040 			$mui_arr = array(
   2041 				'50',
   2042 				'100',
   2043 				'200',
   2044 				'300',
   2045 				'400',
   2046 				'500',
   2047 				'600',
   2048 				'700',
   2049 				'800',
   2050 				'900',
   2051 				'A100',
   2052 				'A200',
   2053 				'A400',
   2054 				'A700',
   2055 			);
   2056 
   2057 			if ( in_array( $context, $mui_arr, true ) ) {
   2058 				$key = absint( $context ) / 100;
   2059 
   2060 				if ( 'A100' === $context ) {
   2061 					$key = 10;
   2062 					unset( $colors['grey'] );
   2063 				} elseif ( 'A200' === $context ) {
   2064 					$key = 11;
   2065 					unset( $colors['grey'] );
   2066 				} elseif ( 'A400' === $context ) {
   2067 					$key = 12;
   2068 					unset( $colors['grey'] );
   2069 				} elseif ( 'A700' === $context ) {
   2070 					$key = 13;
   2071 					unset( $colors['grey'] );
   2072 				}
   2073 
   2074 				unset( $colors['primary'] );
   2075 
   2076 				$position_colors = array();
   2077 				foreach ( $colors as $color_family ) {
   2078 					if ( isset( $color_family[ $key ] ) ) {
   2079 						$position_colors[] = $color_family[ $key ];
   2080 					}
   2081 				}
   2082 
   2083 				return $position_colors;
   2084 			} elseif ( 'all' === $context ) {
   2085 				unset( $colors['primary'] );
   2086 
   2087 				$all_colors = array();
   2088 				foreach ( $colors as $color_family ) {
   2089 					foreach ( $color_family as $color ) {
   2090 						$all_colors[] = $color;
   2091 					}
   2092 				}
   2093 
   2094 				return $all_colors;
   2095 			} elseif ( 'primary' === $context ) {
   2096 				return $colors['primary'];
   2097 			} else {
   2098 				if ( isset( $colors[ $context ] ) ) {
   2099 					return $colors[ $context ];
   2100 				}
   2101 
   2102 				return $colors['primary'];
   2103 			}
   2104 		}
   2105 	}
   2106 }