balmet.com

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

class-redux-functions-ex.php (17880B)


      1 <?php
      2 /**
      3  * Redux Framework Private Extended Functions Container Class
      4  *
      5  * @class       Redux_Functions_Ex
      6  * @since       3.0.0
      7  * @package     Redux_Framework/Classes
      8  */
      9 
     10 // Exit if accessed directly.
     11 defined( 'ABSPATH' ) || exit;
     12 
     13 // Don't duplicate me!
     14 if ( ! class_exists( 'Redux_Functions_Ex', false ) ) {
     15 
     16 	/**
     17 	 * Redux Functions Class
     18 	 * A Class of useful functions that can/should be shared among all Redux files.
     19 	 *
     20 	 * @since       3.0.0
     21 	 */
     22 	class Redux_Functions_Ex {
     23 
     24 		/**
     25 		 * What is this for?
     26 		 *
     27 		 * @var array
     28 		 */
     29 		public static $args;
     30 
     31 		/**
     32 		 * Output alpha data tag for Iris alpha color picker, if enabled.
     33 		 *
     34 		 * @param array $data Data array.
     35 		 *
     36 		 * @return string
     37 		 */
     38 		public static function output_alpha_data( array $data ): string {
     39 			extract( $data ); // phpcs:ignore WordPress.PHP.DontExtract
     40 
     41 			$value = false;
     42 
     43 			if ( isset( $field['color_alpha'] ) && $field['color_alpha'] ) {
     44 				if ( is_array( $field['color_alpha'] ) ) {
     45 					$value = $field['color_alpha'][ $index ] ?? false;
     46 				} else {
     47 					$value = $field['color_alpha'];
     48 				}
     49 			}
     50 
     51 			return 'data-alpha-enabled="' . (bool) esc_attr( $value ) . '"';
     52 		}
     53 
     54 		/**
     55 		 * Parses the string into variables without the max_input_vars limitation.
     56 		 *
     57 		 * @param     string $string String of data.
     58 		 *
     59 		 * @return  array|false $result
     60 		 * @since   3.5.7.11
     61 		 * @author  harunbasic
     62 		 * @access  private
     63 		 */
     64 		public static function parse_str( string $string ) {
     65 			if ( '' === $string ) {
     66 				return false;
     67 			}
     68 
     69 			$result = array();
     70 			$pairs  = explode( '&', $string );
     71 
     72 			foreach ( $pairs as $key => $pair ) {
     73 				// use the original parse_str() on each element.
     74 				parse_str( $pair, $params );
     75 
     76 				$k = key( $params );
     77 
     78 				if ( ! isset( $result[ $k ] ) ) {
     79 					$result += $params;
     80 				} elseif ( is_array( $result[ $k ] ) && is_array( $params[ $k ] ) ) {
     81 					$result[ $k ] = self::array_merge_recursive_distinct( $result[ $k ], $params[ $k ] );
     82 				}
     83 			}
     84 
     85 			return $result;
     86 		}
     87 
     88 		/**
     89 		 * Merge arrays without converting values with duplicate keys to arrays as array_merge_recursive does.
     90 		 * As seen here http://php.net/manual/en/function.array-merge-recursive.php#92195
     91 		 *
     92 		 * @since   3.5.7.11
     93 		 *
     94 		 * @param     array $array1 array one.
     95 		 * @param     array $array2 array two.
     96 		 *
     97 		 * @return  array $merged
     98 		 * @author  harunbasic
     99 		 * @access  private
    100 		 */
    101 		public static function array_merge_recursive_distinct( array $array1, array $array2 ): array {
    102 			$merged = $array1;
    103 
    104 			foreach ( $array2 as $key => $value ) {
    105 
    106 				if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
    107 					$merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
    108 				} elseif ( is_numeric( $key ) && isset( $merged[ $key ] ) ) {
    109 					$merged[] = $value;
    110 				} else {
    111 					$merged[ $key ] = $value;
    112 				}
    113 			}
    114 
    115 			return $merged;
    116 		}
    117 
    118 		/**
    119 		 * Records calling function.
    120 		 *
    121 		 * @param string $opt_name Panel opt_name.
    122 		 */
    123 		public static function record_caller( string $opt_name = '' ) {
    124 			global $pagenow;
    125 
    126 			// phpcs:ignore WordPress.Security.NonceVerification
    127 			if ( ! ( 'options-general.php' === $pagenow && isset( $_GET['page'] ) && ( 'redux-framework' === $_GET['page'] || 'health-check' === $_GET['page'] ) ) ) {
    128 				return;
    129 			}
    130 
    131 			// phpcs:ignore WordPress.PHP.DevelopmentFunctions
    132 			$caller = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['file'];
    133 
    134 			if ( ! empty( $caller ) && ! empty( $opt_name ) && class_exists( 'Redux_Core' ) ) {
    135 				if ( ! isset( Redux_Core::$callers[ $opt_name ] ) ) {
    136 					Redux_Core::$callers[ $opt_name ] = array();
    137 				}
    138 
    139 				if ( strpos( $caller, 'class-redux-' ) !== false || strpos( $caller, 'redux-core/framework.php' ) ) {
    140 					return;
    141 				}
    142 
    143 				if ( ! in_array( $caller, Redux_Core::$callers[ $opt_name ], true ) ) {
    144 					Redux_Core::$callers[ $opt_name ][] = $caller;
    145 				}
    146 
    147 				if ( ! empty( self::$args[ $opt_name ]['callers'] ) && ! in_array( $caller, self::$args[ $opt_name ]['callers'], true ) ) {
    148 					self::$args[ $opt_name ]['callers'][] = $caller;
    149 				}
    150 			}
    151 		}
    152 
    153 		/**
    154 		 * Normalize path.
    155 		 *
    156 		 * @param string $path Path to normalize.
    157 		 *
    158 		 * @return string|string[]|null
    159 		 */
    160 		public static function wp_normalize_path( string $path = '' ) {
    161 			if ( function_exists( 'wp_normalize_path' ) ) {
    162 				$path = wp_normalize_path( $path );
    163 			} else {
    164 				// Shim for pre WP 3.9.
    165 				$path = str_replace( '\\', '/', $path );
    166 				$path = preg_replace( '|(?<=.)/+|', '/', $path );
    167 
    168 				if ( ':' === substr( $path, 1, 1 ) ) {
    169 					$path = ucfirst( $path );
    170 				}
    171 			}
    172 
    173 			return $path;
    174 		}
    175 
    176 		/**
    177 		 * Action to add generator tag to page HEAD.
    178 		 */
    179 		public static function generator() {
    180 			add_action( 'wp_head', array( 'Redux_Functions_Ex', 'meta_tag' ) );
    181 		}
    182 
    183 
    184 		/**
    185 		 * Callback for wp_head hook to add meta tag.
    186 		 */
    187 		public static function meta_tag() {
    188 			echo '<meta name="framework" content="Redux ' . esc_html( Redux_Core::$version ) . '" />';
    189 		}
    190 
    191 		/**
    192 		 * Run URL through a ssl check.
    193 		 *
    194 		 * @param string $url URL to check.
    195 		 *
    196 		 * @return string
    197 		 */
    198 		public static function verify_url_protocol( string $url ): string {
    199 			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
    200 			$protocol = ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] || ( ! empty( $_SERVER['SERVER_PORT'] ) && 443 === $_SERVER['SERVER_PORT'] ) ? 'https://' : 'http://';
    201 
    202 			if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
    203 				$new_protocol = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) . '://'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
    204 				if ( 'http://' === $protocol && $new_protocol !== $protocol && false === strpos( $url, $new_protocol ) ) {
    205 					$url = str_replace( $protocol, $new_protocol, $url );
    206 				}
    207 			}
    208 
    209 			return $url;
    210 		}
    211 
    212 		/**
    213 		 * Check s.
    214 		 *
    215 		 * @access public
    216 		 * @since 4.0.0
    217 		 * @return bool
    218 		 */
    219 		public static function s(): bool {
    220 			if ( ! get_option( 'redux_p' . 'ro_lic' . 'ense_key', false ) ) { // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
    221 				$s = get_option( 'redux_p' . 'ro_l' . 'icense_status', false ); // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
    222 
    223 				if ( false !== $s && in_array( $s, array( 'valid', 'site_inactive' ), true ) ) {
    224 					return true;
    225 				}
    226 			}
    227 
    228 			return false;
    229 		}
    230 
    231 		/**
    232 		 * Is file in theme.
    233 		 *
    234 		 * @param string $file File to check.
    235 		 *
    236 		 * @return bool
    237 		 */
    238 		public static function file_in_theme( string $file ): bool {
    239 			$file_path = self::wp_normalize_path( dirname( $file ) );
    240 
    241 			if ( strpos( $file_path, self::wp_normalize_path( get_template_directory() ) ) !== false ) {
    242 				return true;
    243 			} elseif ( strpos( $file_path, self::wp_normalize_path( get_stylesheet_directory() ) ) !== false ) {
    244 				return true;
    245 			}
    246 
    247 			return false;
    248 		}
    249 
    250 		/**
    251 		 * Is Redux embedded inside a plugin.
    252 		 *
    253 		 * @param string $file File to check.
    254 		 *
    255 		 * @return array|bool
    256 		 */
    257 		public static function is_inside_plugin( string $file ) {
    258 			$file            = self::wp_normalize_path( $file );
    259 			$plugin_basename = self::wp_normalize_path( plugin_basename( $file ) );
    260 
    261 			if ( self::file_in_theme( $file ) ) {
    262 				return false;
    263 			}
    264 
    265 			if ( $plugin_basename !== $file ) {
    266 				$slug = explode( '/', $plugin_basename );
    267 				$slug = $slug[0];
    268 
    269 				return array(
    270 					'slug'      => $slug,
    271 					'basename'  => $plugin_basename,
    272 					'path'      => $file,
    273 					'url'       => self::verify_url_protocol( plugins_url( $plugin_basename ) ),
    274 					'real_path' => self::wp_normalize_path( dirname( realpath( $file ) ) ),
    275 				);
    276 			}
    277 
    278 			return false;
    279 		}
    280 
    281 		/**
    282 		 * Is Redux embedded in a theme.
    283 		 *
    284 		 * @param string $file File to check.
    285 		 *
    286 		 * @return array|bool
    287 		 */
    288 		public static function is_inside_theme( string $file = '' ) {
    289 
    290 			if ( ! self::file_in_theme( $file ) ) {
    291 				return false;
    292 			}
    293 
    294 			$theme_paths = array(
    295 				self::wp_normalize_path( get_template_directory() )   => get_template_directory_uri(),
    296 				// parent.
    297 				self::wp_normalize_path( get_stylesheet_directory() ) => get_stylesheet_directory_uri(),
    298 				// child.
    299 			);
    300 
    301 			$theme_paths = array_unique( $theme_paths );
    302 			$file_path   = self::wp_normalize_path( $file );
    303 
    304 			$filename = explode( '\\', $file );
    305 
    306 			end( $filename );
    307 
    308 			$filename = prev( $filename );
    309 
    310 			foreach ( $theme_paths as $theme_path => $url ) {
    311 				$real_path = self::wp_normalize_path( realpath( $theme_path ) );
    312 
    313 				if ( empty( $real_path ) ) {
    314 					continue;
    315 				}
    316 
    317 				if ( strpos( $file_path, $real_path ) !== false ) {
    318 					$slug             = explode( '/', $theme_path );
    319 					$slug             = end( $slug );
    320 					$relative_path    = explode( $slug . '/', dirname( $file_path ) );
    321 					$relative_path    = $relative_path[1];
    322 					$data             = array(
    323 						'slug'      => $slug,
    324 						'path'      => trailingslashit( trailingslashit( $theme_path ) . $relative_path ) . $filename,
    325 						'real_path' => trailingslashit( trailingslashit( $real_path ) . $relative_path ) . $filename,
    326 						'url'       => self::verify_url_protocol( trailingslashit( trailingslashit( $url ) . $relative_path ) . $filename ),
    327 						'basename'  => trailingslashit( $slug ) . trailingslashit( $relative_path ) . $filename,
    328 					);
    329 					$data['realpath'] = $data['real_path'];  // Shim for old extensions.
    330 
    331 					if ( count( $theme_paths ) > 1 ) {
    332 						$key = array_search( $theme_path, $theme_paths, true );
    333 
    334 						if ( false !== $key ) {
    335 							unset( $theme_paths[ $key ] );
    336 						}
    337 
    338 						$theme_paths_end = end( $theme_paths );
    339 						$parent_slug_end = explode( '/', $theme_paths_end );
    340 						$parent_slug_end = end( $parent_slug_end );
    341 
    342 						$data['parent_slug'] = $theme_paths_end;
    343 						$data['parent_slug'] = $parent_slug_end;
    344 					}
    345 
    346 					return $data;
    347 				}
    348 			}
    349 
    350 			return false;
    351 		}
    352 
    353 		/**
    354 		 * Used to fix 3.x and 4 compatibility for extensions
    355 		 *
    356 		 * @param object $parent         The extension parent object.
    357 		 * @param string $path           - Path of the file.
    358 		 * @param string $ext_class      - Extension class name.
    359 		 * @param string $new_class_name - New dynamic class name.
    360 		 * @param string $name           extension name.
    361 		 *
    362 		 * @return object - Extended field class.
    363 		 */
    364 		public static function extension_compatibility( $parent, string $path, string $ext_class, string $new_class_name, string $name ) {
    365 			if ( empty( $new_class_name ) ) {
    366 				return;
    367 			}
    368 			$upload_dir = ReduxFramework::$_upload_dir . '/extension_compatibility/';
    369 			if ( ! file_exists( $upload_dir . $ext_class . '.php' ) ) {
    370 				if ( ! is_dir( $upload_dir ) ) {
    371 					$parent->filesystem->mkdir( $upload_dir );
    372 					$parent->filesystem->put_contents( $upload_dir . 'index.php', '<?php // Silence is golden.' );
    373 				}
    374 				if ( ! class_exists( $ext_class ) ) {
    375 					require_once $path;
    376 				}
    377 				if ( ! file_exists( $upload_dir . $new_class_name . '.php' ) ) {
    378 					$class_file = '<?php' . PHP_EOL . PHP_EOL .
    379 						'class {{ext_class}} extends Redux_Extension_Abstract {' . PHP_EOL .
    380 						'    private $c;' . PHP_EOL .
    381 						'    public function __construct( $parent, $path, $ext_class ) {' . PHP_EOL .
    382 						'        $this->c = $parent->extensions[\'' . $name . '\'];' . PHP_EOL .
    383 						'        // Add all the params of the Abstract to this instance.' . PHP_EOL .
    384 						'        foreach( get_object_vars( $this->c ) as $key => $value ) {' . PHP_EOL .
    385 						'            $this->$key = $value;' . PHP_EOL .
    386 						'        }' . PHP_EOL .
    387 						'        parent::__construct( $parent, $path );' . PHP_EOL .
    388 						'    }' . PHP_EOL .
    389 						'    // fake "extends Redux_Extension_Abstract\" using magic function' . PHP_EOL .
    390 						'    public function __call( $method, $args ) {' . PHP_EOL .
    391 						'        return call_user_func_array( array( $this->c, $method ), $args );' . PHP_EOL .
    392 						'    }' . PHP_EOL .
    393 						'}' . PHP_EOL;
    394 					$template   = str_replace( '{{ext_class}}', $new_class_name, $class_file );
    395 					$parent->filesystem->put_contents( $upload_dir . $new_class_name . '.php', $template );
    396 				}
    397 				if ( file_exists( $upload_dir . $new_class_name . '.php' ) ) {
    398 					if ( ! class_exists( $new_class_name ) ) {
    399 						require_once $upload_dir . $new_class_name . '.php';
    400 					}
    401 					if ( class_exists( $new_class_name ) ) {
    402 						return new $new_class_name( $parent, $path, $ext_class );
    403 					}
    404 				}
    405 			}
    406 		}
    407 
    408 		/**
    409 		 * Used to merge two deep arrays.
    410 		 *
    411 		 * @param array $a First array to deep merge.
    412 		 * @param array $b Second array to deep merge.
    413 		 *
    414 		 * @return    array - Deep merge of the two arrays.
    415 		 */
    416 		public static function nested_wp_parse_args( array &$a, array $b ): array {
    417 			$a      = $a;
    418 			$b      = $b;
    419 			$result = $b;
    420 
    421 			foreach ( $a as $k => &$v ) {
    422 				if ( is_array( $v ) && isset( $result[ $k ] ) ) {
    423 					$result[ $k ] = self::nested_wp_parse_args( $v, $result[ $k ] );
    424 				} else {
    425 					$result[ $k ] = $v;
    426 				}
    427 			}
    428 
    429 			return $result;
    430 		}
    431 
    432 		/**
    433 		 * AJAX callback key
    434 		 */
    435 		public static function hash_key(): string {
    436 			$key  = defined( 'AUTH_KEY' ) ? AUTH_KEY : get_site_url();
    437 			$key .= defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '';
    438 
    439 			return $key;
    440 		}
    441 
    442 		/**
    443 		 * Check if Redux is activated.
    444 		 *
    445 		 * @access public
    446 		 * @since 4.0.0
    447 		 */
    448 		public static function activated(): bool {
    449 			if ( Redux_Core::$insights->tracking_allowed() ) {
    450 				return true;
    451 			}
    452 
    453 			return false;
    454 		}
    455 
    456 		/**
    457 		 * Set Redux to activate.
    458 		 *
    459 		 * @access public
    460 		 * @since 4.0.0
    461 		 */
    462 		public static function set_activated() {
    463 			Redux_Core::$insights->optin();
    464 		}
    465 
    466 		/**
    467 		 * Set Redux to deactivate.
    468 		 *
    469 		 * @access public
    470 		 * @since 4.0.0
    471 		 */
    472 		public static function set_deactivated() {
    473 			Redux_Core::$insights->optout();
    474 		}
    475 
    476 		/**
    477 		 * Register a class path to be autoloaded.
    478 		 * Registers a namespace to be autoloaded from a given path, using the
    479 		 * WordPress/HM-style filenames (`class-{name}.php`).
    480 		 *
    481 		 * @link https://engineering.hmn.md/standards/style/php/#file-naming
    482 		 *
    483 		 * @param string $prefix Prefix to autoload from.
    484 		 * @param string $path   Path to validate.
    485 		 */
    486 		public static function register_class_path( string $prefix = '', string $path = '' ) {
    487 			if ( ! class_exists( 'Redux_Autoloader' ) ) {
    488 				require_once Redux_Path::get_path( '/inc/classes/class-redux-autoloader.php' );
    489 			}
    490 
    491 			$loader = new Redux_Autoloader( $prefix, $path );
    492 
    493 			spl_autoload_register( array( $loader, 'load' ) );
    494 		}
    495 
    496 		/**
    497 		 * Check if a string starts with a string.
    498 		 *
    499 		 * @param string $haystack Full string.
    500 		 * @param string $needle   String to check if it starts with.
    501 		 *
    502 		 * @return bool
    503 		 */
    504 		public static function string_starts_with( string $haystack, string $needle ): bool {
    505 			$length = strlen( $needle );
    506 			return substr( $haystack, 0, $length ) === $needle;
    507 		}
    508 
    509 		/**
    510 		 * Check if a string ends with a string.
    511 		 *
    512 		 * @param string $haystack Full string.
    513 		 * @param string $needle   String to check if it starts with.
    514 		 *
    515 		 * @return bool
    516 		 */
    517 		public static function string_ends_with( string $haystack, string $needle ): bool {
    518 			$length = strlen( $needle );
    519 
    520 			if ( ! $length ) {
    521 				return true;
    522 			}
    523 
    524 			return substr( $haystack, -$length ) === $needle;
    525 		}
    526 
    527 		/**
    528 		 * Get the url where the Admin Columns website is hosted
    529 		 *
    530 		 * @param string $path Path to add to url.
    531 		 *
    532 		 * @return string
    533 		 */
    534 		private static function get_site_url( string $path = '' ): string {
    535 			$url = 'https://redux.io';
    536 
    537 			if ( ! empty( $path ) ) {
    538 				$url .= '/' . trim( $path, '/' ) . '/';
    539 			}
    540 
    541 			return $url;
    542 		}
    543 
    544 		/**
    545 		 * Url with utm tags
    546 		 *
    547 		 * @param string      $path         Path on site.
    548 		 * @param string      $utm_medium   Medium var.
    549 		 * @param string|null $utm_content  Content var.
    550 		 * @param bool        $utm_campaign Campaign var.
    551 		 *
    552 		 * @return string
    553 		 */
    554 		public static function get_site_utm_url( string $path, string $utm_medium, string $utm_content = null, bool $utm_campaign = false ): string {
    555 			$url = self::get_site_url( $path );
    556 
    557 			if ( ! $utm_campaign ) {
    558 				$utm_campaign = 'plugin-installation';
    559 			}
    560 
    561 			$args = array(
    562 				// Referrer: plugin.
    563 				'utm_source'   => 'plugin-installation',
    564 
    565 				// Specific promotions or sales.
    566 				'utm_campaign' => $utm_campaign,
    567 
    568 				// Marketing medium: banner, documentation or email.
    569 				'utm_medium'   => $utm_medium,
    570 
    571 				// Used for differentiation of medium.
    572 				'utm_content'  => $utm_content,
    573 			);
    574 
    575 			$args = array_map( 'sanitize_key', array_filter( $args ) );
    576 
    577 			return add_query_arg( $args, $url );
    578 		}
    579 
    580 		/**
    581 		 * Conversion.
    582 		 */
    583 		public static function pro_to_ext() {
    584 
    585 			// If they are a pro user, convert their key to use with Extendify.
    586 			$redux_pro_key = get_option( 'redux_pro_license_key' );
    587 
    588 			if ( $redux_pro_key && ! get_user_option( 'extendifysdk_redux_key_moved' ) ) {
    589 				try {
    590 					$extendify_user_state = get_user_meta( get_current_user_id(), 'extendifysdk_user_data' );
    591 
    592 					if ( ! isset( $extendify_user_state[0] ) ) {
    593 						$extendify_user_state[0] = '{}';
    594 					}
    595 
    596 					$extendify_user_data                    = json_decode( $extendify_user_state[0], true );
    597 					$extendify_user_data['state']['apiKey'] = $redux_pro_key;
    598 
    599 					update_user_meta( get_current_user_id(), 'extendifysdk_user_data', wp_json_encode( $extendify_user_data ) );
    600 				} catch ( Exception $e ) {
    601 					// Just have it fail gracefully.
    602 				}
    603 				// Run this regardless. If the try/catch failed, better not to keep trying as something else is wrong.
    604 				// In that case we can expect them to come to support, and we can give them a fresh key.
    605 				update_user_option( get_current_user_id(), 'extendifysdk_redux_key_moved', true );
    606 			}
    607 		}
    608 	}
    609 }