You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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, [] );
}
}