339 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			339 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Variable replacement functionality.
 | 
						|
 *
 | 
						|
 * @since      1.0.33
 | 
						|
 * @package    RankMath
 | 
						|
 * @subpackage RankMath\Replace_Variables
 | 
						|
 * @author     Rank Math <support@rankmath.com>
 | 
						|
 *
 | 
						|
 * @copyright Copyright (C) 2008-2019, Yoast BV
 | 
						|
 * The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
 | 
						|
 */
 | 
						|
 | 
						|
namespace RankMath\Replace_Variables;
 | 
						|
 | 
						|
use RankMath\Helper;
 | 
						|
use RankMath\Helpers\Str;
 | 
						|
use RankMath\Paper\Paper;
 | 
						|
 | 
						|
defined( 'ABSPATH' ) || exit;
 | 
						|
 | 
						|
/**
 | 
						|
 * Replacer class.
 | 
						|
 */
 | 
						|
class Replacer {
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Do not process the same string over and over again.
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 */
 | 
						|
	public static $replacements_cache = [];
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Non-cacheable replacements.
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 */
 | 
						|
	public static $non_cacheable_replacements;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Default post data.
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 */
 | 
						|
	public static $defaults = [
 | 
						|
		'ID'            => '',
 | 
						|
		'name'          => '',
 | 
						|
		'post_author'   => '',
 | 
						|
		'post_content'  => '',
 | 
						|
		'post_date'     => '',
 | 
						|
		'post_excerpt'  => '',
 | 
						|
		'post_modified' => '',
 | 
						|
		'post_title'    => '',
 | 
						|
		'taxonomy'      => '',
 | 
						|
		'term_id'       => '',
 | 
						|
		'term404'       => '',
 | 
						|
		'filename'      => '',
 | 
						|
	];
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Arguments.
 | 
						|
	 *
 | 
						|
	 * @var object
 | 
						|
	 */
 | 
						|
	public static $args;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Process post content once.
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 */
 | 
						|
	public static $content_processed = [];
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Exclude variables.
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 */
 | 
						|
	public $exclude = [];
 | 
						|
 | 
						|
	/**
 | 
						|
	 *  Replace `%variables%` with context-dependent value.
 | 
						|
	 *
 | 
						|
	 * @param string $string  The string containing the %variables%.
 | 
						|
	 * @param array  $args    Context object, can be post, taxonomy or term.
 | 
						|
	 * @param array  $exclude Excluded variables won't be replaced.
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public function replace( $string, $args = [], $exclude = [] ) {
 | 
						|
		$string = wp_strip_all_tags( $string );
 | 
						|
 | 
						|
		// Bail early.
 | 
						|
		if ( ! Str::contains( '%', $string ) ) {
 | 
						|
			return $string;
 | 
						|
		}
 | 
						|
 | 
						|
		if ( Str::ends_with( ' %sep%', $string ) ) {
 | 
						|
			$string = substr( $string, 0, -5 );
 | 
						|
		}
 | 
						|
 | 
						|
		$this->pre_replace( $args, $exclude );
 | 
						|
		$replacements = $this->set_up_replacements( $string );
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Filter: Allow customizing the replacements.
 | 
						|
		 *
 | 
						|
		 * @param array $replacements The replacements.
 | 
						|
		 * @param array $args The object some of the replacement values might come from,
 | 
						|
		 *                    could be a post, taxonomy or term.
 | 
						|
		 */
 | 
						|
		$replacements = apply_filters( 'rank_math/replacements', $replacements, self::$args );
 | 
						|
 | 
						|
		// Do the replacements.
 | 
						|
		if ( is_array( $replacements ) && [] !== $replacements ) {
 | 
						|
			$string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( isset( $replacements['%sep%'] ) && Str::is_non_empty( $replacements['%sep%'] ) ) {
 | 
						|
			$q_sep  = preg_quote( $replacements['%sep%'], '`' );
 | 
						|
			$string = preg_replace( '`' . $q_sep . '(?:\s*' . $q_sep . ')*`u', $replacements['%sep%'], $string );
 | 
						|
		}
 | 
						|
 | 
						|
		// Remove excess whitespace.
 | 
						|
		return preg_replace( '[\s\s+]', ' ', $string );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Run prior to replacement.
 | 
						|
	 *
 | 
						|
	 * @param array $args    Context object, can be post, taxonomy or term.
 | 
						|
	 * @param array $exclude Excluded variables won't be replaced.
 | 
						|
	 */
 | 
						|
	private function pre_replace( $args, $exclude ) {
 | 
						|
		if ( is_array( $exclude ) ) {
 | 
						|
			$this->exclude = $exclude;
 | 
						|
		}
 | 
						|
 | 
						|
		self::$args = (object) array_merge( self::$defaults, (array) $args );
 | 
						|
		$this->process_content();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Process content only once, because it's expensive.
 | 
						|
	 *
 | 
						|
	 * @return void
 | 
						|
	 */
 | 
						|
	private function process_content() {
 | 
						|
		if ( ! isset( self::$content_processed[ self::$args->ID ]['post_content'] ) ) {
 | 
						|
			self::$content_processed[ self::$args->ID ]['post_content'] = Paper::should_apply_shortcode() ? do_shortcode( self::$args->post_content ) : Helper::strip_shortcodes( self::$args->post_content );
 | 
						|
			self::$content_processed[ self::$args->ID ]['post_excerpt'] = Paper::should_apply_shortcode() ? do_shortcode( self::$args->post_excerpt ) : Helper::strip_shortcodes( self::$args->post_excerpt );
 | 
						|
		}
 | 
						|
 | 
						|
		self::$args->post_content = self::$content_processed[ self::$args->ID ]['post_content'];
 | 
						|
		self::$args->post_excerpt = self::$content_processed[ self::$args->ID ]['post_excerpt'];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the replacements for the variables.
 | 
						|
	 *
 | 
						|
	 * @param string $string String to parse for variables.
 | 
						|
	 *
 | 
						|
	 * @return array Retrieved replacements.
 | 
						|
	 */
 | 
						|
	private function set_up_replacements( $string ) {
 | 
						|
		if ( $this->has_cache( $string ) ) {
 | 
						|
			return $this->get_cache( $string );
 | 
						|
		}
 | 
						|
 | 
						|
		$replacements = [];
 | 
						|
		if ( ! preg_match_all( '/%(([a-z0-9_-]+)\(([^)]*)\)|[^\s]+)%/iu', $string, $matches ) ) {
 | 
						|
			$this->set_cache( $string, $replacements );
 | 
						|
			return $replacements;
 | 
						|
		}
 | 
						|
 | 
						|
		foreach ( $matches[1] as $index => $variable_id ) {
 | 
						|
			$value = $this->get_variable_value( $matches, $index, $variable_id );
 | 
						|
			if ( false !== $value ) {
 | 
						|
				$replacements[ $matches[0][ $index ] ] = $value;
 | 
						|
			}
 | 
						|
 | 
						|
			unset( $variable );
 | 
						|
		}
 | 
						|
 | 
						|
		$this->set_cache( $string, $replacements );
 | 
						|
		return $replacements;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get non-cacheable variables.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	private function get_non_cacheable_variables() {
 | 
						|
		if ( ! is_null( self::$non_cacheable_replacements ) ) {
 | 
						|
			return self::$non_cacheable_replacements;
 | 
						|
		}
 | 
						|
 | 
						|
		$non_cacheable = [];
 | 
						|
		foreach ( rank_math()->variables->get_replacements() as $variable ) {
 | 
						|
			if ( ! $variable->is_cacheable() ) {
 | 
						|
				$non_cacheable[] = $variable->get_id();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Filter: Allow changing the non-cacheable variables.
 | 
						|
		 *
 | 
						|
		 * @param array $non_cacheable The non-cacheable variable IDs.
 | 
						|
		 */
 | 
						|
		self::$non_cacheable_replacements = apply_filters( 'rank_math/replacements/non_cacheable', $non_cacheable );
 | 
						|
 | 
						|
		return self::$non_cacheable_replacements;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check if we have cache for a string.
 | 
						|
	 *
 | 
						|
	 * @param string $string String to check.
 | 
						|
	 *
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	private function has_cache( $string ) {
 | 
						|
		return isset( self::$replacements_cache[ md5( $string ) ] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get cache for a string. Handles non-cacheable variables.
 | 
						|
	 *
 | 
						|
	 * @param string $string String to get cache for.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	private function get_cache( $string ) {
 | 
						|
		$non_cacheable = $this->get_non_cacheable_variables();
 | 
						|
		$replacements  = self::$replacements_cache[ md5( $string ) ];
 | 
						|
		if ( empty( $non_cacheable ) ) {
 | 
						|
			return $replacements;
 | 
						|
		}
 | 
						|
 | 
						|
		foreach ( $replacements as $key => $value ) {
 | 
						|
			$id = explode( '(', trim( $key, '%' ) )[0];
 | 
						|
			if ( ! in_array( $id, $non_cacheable, true ) ) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			$var_args = '';
 | 
						|
			$parts    = explode( '(', trim( $key, '%)' ) );
 | 
						|
			if ( isset( $parts[1] ) ) {
 | 
						|
				$var_args = $this->normalize_args( $parts[1] );
 | 
						|
			}
 | 
						|
 | 
						|
			$replacements[ $key ] = $this->get_variable_by_id( $id, $var_args )->run_callback( $var_args, self::$args );
 | 
						|
		}
 | 
						|
 | 
						|
		return $replacements;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Set cache for a string.
 | 
						|
	 *
 | 
						|
	 * @param string $string String to set cache for.
 | 
						|
	 *
 | 
						|
	 * @param array  $cache  Cache to set.
 | 
						|
	 */
 | 
						|
	private function set_cache( $string, $cache ) {
 | 
						|
		self::$replacements_cache[ md5( $string ) ] = $cache;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get variable value.
 | 
						|
	 *
 | 
						|
	 * @param array  $matches Regex matches found in the string.
 | 
						|
	 * @param int    $index   Index of the matched.
 | 
						|
	 * @param string $id      Variable id.
 | 
						|
	 *
 | 
						|
	 * @return mixed
 | 
						|
	 */
 | 
						|
	private function get_variable_value( $matches, $index, $id ) {
 | 
						|
		// Don't set up excluded replacements.
 | 
						|
		if ( isset( $matches[0][ $index ] ) && in_array( $matches[0][ $index ], $this->exclude, true ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		$has_args = ! empty( $matches[2][ $index ] ) && ! empty( $matches[3][ $index ] );
 | 
						|
		$id       = $has_args ? $matches[2][ $index ] : $id;
 | 
						|
		$var_args = $has_args ? $this->normalize_args( $matches[3][ $index ] ) : [];
 | 
						|
		$variable = $this->get_variable_by_id( $id, $var_args );
 | 
						|
 | 
						|
		if ( is_null( $variable ) ) {
 | 
						|
			return rank_math()->variables->remove_non_replaced ? '' : false;
 | 
						|
		}
 | 
						|
 | 
						|
		return $variable->run_callback( $var_args, self::$args );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Find variable.
 | 
						|
	 *
 | 
						|
	 * @param string $id   Variable id.
 | 
						|
	 * @param array  $args Array of arguments.
 | 
						|
	 *
 | 
						|
	 * @return Variable|null
 | 
						|
	 */
 | 
						|
	private function get_variable_by_id( $id, $args ) {
 | 
						|
		if ( ! isset( rank_math()->variables ) ) {
 | 
						|
			return null;
 | 
						|
		}
 | 
						|
 | 
						|
		$replacements = rank_math()->variables->get_replacements();
 | 
						|
		if ( isset( $replacements[ $id ] ) ) {
 | 
						|
			return $replacements[ $id ];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( ! empty( $args ) && isset( $replacements[ $id . '_args' ] ) ) {
 | 
						|
			return $replacements[ $id . '_args' ];
 | 
						|
		}
 | 
						|
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Convert arguments string to arguments array.
 | 
						|
	 *
 | 
						|
	 * @param  string $string The string that needs to be converted.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	private function normalize_args( $string ) {
 | 
						|
		$string = wp_specialchars_decode( $string );
 | 
						|
		if ( ! Str::contains( '=', $string ) ) {
 | 
						|
			return $string;
 | 
						|
		}
 | 
						|
 | 
						|
		return wp_parse_args( $string, [] );
 | 
						|
	}
 | 
						|
}
 |