File: /var/www/html/wp-content/plugins/wptelegram/modules/p2tg/TemplateParser.php
<?php
/**
* Post to Telegram message template parser.
*
* @link https://t.me/WPTelegram
* @since 4.1.0
*
* @package WPTelegram\Core
* @subpackage WPTelegram\Core\modules\p2tg
*/
namespace WPTelegram\Core\modules\p2tg;
use WPSocio\WPUtils\Options;
use WPTelegram\Core\includes\Utils as MainUtils;
use WP_Post;
/**
* Post to Telegram message template parser.
*
* @package WPTelegram\Core
* @subpackage WPTelegram\Core\modules\p2tg
* @author WP Socio
*/
class TemplateParser {
/**
* The post to be handled
*
* @var WP_Post $post Post object.
*/
protected $post;
/**
* The post data
*
* @since 4.1.0
* @access protected
* @var PostData $post_data The post data.
*/
protected $post_data;
/**
* The options for parsing the template.
*
* @var Options $options The options object.
*/
public $options;
/**
* Initialize the class and set its properties.
*
* @since 4.1.0
*
* @param int|WP_Post $post Post object or ID.
* @param Options $options The options for parsing the template.
*/
public function __construct( $post, $options = null ) {
$this->set_post( $post );
$this->set_options( $options );
}
/**
* Set the post
*
* @since 4.1.0
* @param int|WP_Post $post Post object or ID.
*/
public function set_post( $post ) {
$this->post = get_post( $post );
$this->reset_data();
return $this;
}
/**
* Set the post
*
* @since 4.1.0
*
* @param Options $options The options for parsing the template.
*/
public function set_options( $options ) {
if ( ! $options ) {
// set to an empty object by default.
$options = new Options();
}
$this->options = $options;
return $this;
}
/**
* Reset the existing post data.
*/
public function reset_data() {
$this->post_data = new PostData( $this->post );
return $this;
}
/**
* Returns the valid parse mode.
*/
public function get_parse_mode() {
return MainUtils::valid_parse_mode( $this->options->get( 'parse_mode' ) );
}
/**
* Parses the given template and converts it to the text.
*
* @since 4.1.0
*
* @param string $template The template to parse.
*
* @return string The parsed value.
*/
public function parse( $template ) {
$template = $this->normalize_template( $template );
$macro_values = $this->parse_macros( $template );
// lets replace the conditional macros.
$template = $this->process_template_logic( $template, $macro_values );
$text = str_replace( array_keys( $macro_values ), array_values( $macro_values ), $template );
$text = $this->encode_values( $text );
return apply_filters( 'wptelegram_p2tg_parsed_template', $text, $template, $this->post, $this->options );
}
/**
* Parses the given template to encode the values if needed.
*
* @since 4.1.0
*
* @param string $template The template to parse.
*
* @return string The parsed value.
*/
public function encode_values( $template ) {
$pattern = '#\{encode:([^\}]+?)\}#iu';
$encoded = preg_replace_callback(
$pattern,
function ( $match ) {
return rawurlencode( $match[1] );
},
$template
);
return apply_filters( 'wptelegram_p2tg_template_encoded_values', $encoded, $template );
}
/**
* Parses the given template to set the correct values to be parsed.
*
* @since 4.1.0
*
* @param string $template The template to parse.
*
* @return string The normalized value.
*/
public function normalize_template( $template ) {
$raw_template = $template;
// replace {tags} and {categories} with taxonomy names.
$replace = [ '{terms:post_tag}', '{terms:category}' ];
// Use {tags} and {categories} for WooCommerce products.
if ( class_exists( 'woocommerce' ) && 'product' === $this->post->post_type ) {
$replace = [ '{terms:product_tag}', '{terms:product_cat}' ];
}
// Modify the template.
$template = str_replace( [ '{tags}', '{categories}' ], $replace, $template );
return apply_filters( 'wptelegram_p2tg_normalized_template', $template, $raw_template, $this->post, $this->options );
}
/**
* Gets the default macro keys.
*
* @param array $options The options for parsing the template.
* Accepts:
* - 'with_aliases' (bool) Whether to include aliases for the keys.
*
* @return array The default macro keys.
*/
public static function get_default_macro_keys( $options = [] ) {
$default_keys = [
'post_title',
'post_excerpt',
'post_author',
'post_content',
'post_date',
'post_date_gmt',
'post_id',
'post_modified_date',
'post_modified_date_gmt',
'post_modified_time',
'post_modified_time_gmt',
'post_slug',
'post_time',
'post_time_gmt',
'post_type',
'post_type_label',
'short_url',
'full_url',
];
if ( $options['with_aliases'] ?? true ) {
$default_keys = array_merge(
$default_keys,
[
'featured_image_url',
// For post_id.
'id',
'ID',
// For post_title.
'title',
// For post_slug.
'slug',
'post_name',
// For post_author.
'author',
// For post_excerpt.
'excerpt',
// For post_content.
'content',
]
);
}
return apply_filters( 'wptelegram_p2tg_default_macro_keys', $default_keys, $options );
}
/**
* Parses the given template for all possible macros and returns the macro data.
*
* @since 4.1.0
*
* @param string $template The template to parse.
*
* @return array The parsed macro values.
*/
public function parse_macros( $template ) {
// Remove wpautop() from the `the_content` filter
// to preserve newlines.
self::bypass_wpautop_for( 'the_content' );
$excerpt_source = $this->options->get( 'excerpt_source' );
$excerpt_length = (int) $this->options->get( 'excerpt_length' );
$excerpt_preserve_eol = $this->options->get( 'excerpt_preserve_eol' );
$cats_as_tags = $this->options->get( 'cats_as_tags' );
$parse_mode = MainUtils::valid_parse_mode( $this->options->get( 'parse_mode' ) );
$template = $this->normalize_template( $template );
$macro_keys = self::get_default_macro_keys();
// for post excerpt.
$params = compact(
'excerpt_source',
'excerpt_length',
'excerpt_preserve_eol',
'cats_as_tags',
'parse_mode'
);
$macro_values = [];
foreach ( $macro_keys as $macro_key ) {
$key = '{' . $macro_key . '}';
// get the value only if it's in the template.
if ( false !== strpos( $template, $key ) ) {
$macro_values[ $key ] = $this->post_data->get_field( $macro_key, $params );
}
}
// if it's something unusual :) .
if ( preg_match_all( '/(?<=\{)(terms|cf):([^\}]+?)(?=\})/iu', $template, $matches ) ) {
foreach ( $matches[0] as $field ) {
$key = '{' . $field . '}';
$macro_values[ $key ] = $this->post_data->get_field( $field, $params );
}
}
/**
* Use this filter to replace your own macros
* with the corresponding values
*/
$macro_values = (array) apply_filters( 'wptelegram_p2tg_template_macro_values', $macro_values, $template, $this->post, $this->options );
$macro_values = (array) apply_filters_deprecated( 'wptelegram_p2tg_macro_values', [ $macro_values, $this->post, $this->options ], '4.1.0', 'wptelegram_p2tg_template_macro_values' );
// Prepare macro values for further processing.
$macro_values = array_map( [ $this, 'prepare_macro_value' ], $macro_values );
return $macro_values;
}
/**
* Prepare macro value for further processing.
*
* @since 4.1.0
*
* @param string $macro_value The value for a macro.
*
* @return string
*/
public function prepare_macro_value( $macro_value ) {
// Remove unwanted slashes.
return stripslashes( $macro_value );
}
/**
* Resolve the conditional macros in the template.
*
* @since 4.1.0
*
* @param string $template The message template being processed.
* @param array $macro_values The values for all macros.
*
* @return string
*/
private function process_template_logic( $template, $macro_values ) {
$raw_template = $template;
$pattern = '/\[if\s*? # Conditional block starts
(\{[^\}]+?\}) # Conditional expression, a macro
\] # Conditional block ends
\[ # Consequence block starts
([^\]]+?) # Consequence expression
\] # Consequence block ends
(?: # non-capturing alternative block
\[ # Alternative block starts
([^\]]*?) # Alternative expression
\] # Alternative block ends
)? # Make alternative block optional
/ix';
preg_match_all( $pattern, $template, $matches );
// loop through the conditional expressions.
foreach ( $matches[1] as $key => $macro ) {
// if expression is false, take from alternative.
$index = empty( $macro_values[ $macro ] ) ? 3 : 2;
$replace = str_replace( array_keys( $macro_values ), array_values( $macro_values ), $matches[ $index ][ $key ] );
$template = str_replace( $matches[0][ $key ], $replace, $template );
}
// remove the ugly empty lines.
$template = preg_replace( '/(?:\A|[\n\r]).*?\{remove_line\}.*/u', '', $template );
return apply_filters( 'wptelegram_p2tg_process_template_logic', $template, $macro_values, $raw_template, $this->post, $this->options );
}
/**
* Bypass wpautop() from the given filter
* to preserve newlines.
*
* @since 4.1.0
*
* @param string $tag The name of the filter hook like "the_content".
*/
public static function bypass_wpautop_for( $tag ) {
$priority = has_filter( $tag, 'wpautop' );
if ( false !== $priority ) {
remove_filter( $tag, 'wpautop', $priority );
add_filter( $tag, [ __CLASS__, 'restore_wpautop_hook' ], $priority + 1 );
}
}
/**
* Re-add wp_autop() to the given filter.
*
* @access public
*
* @since 4.1.0
*
* @param string $content The post content running through this filter.
* @return string The unmodified content.
*/
public static function restore_wpautop_hook( $content ) {
$tag = current_filter();
$current_priority = has_filter( $tag, [ __CLASS__, 'restore_wpautop_hook' ] );
add_filter( $tag, 'wpautop', $current_priority - 1 );
remove_filter( $tag, [ __CLASS__, 'restore_wpautop_hook' ], $current_priority );
return $content;
}
}