File: //proc/self/cwd/wp-content/plugins/wptelegram/includes/Utils.php
<?php
/**
* WPTelegram Utilities
*
* @link https://wpsocio.com
* @since 1.0.0
*
* @package WPTelegram
* @subpackage WPTelegram\Core\includes
*/
namespace WPTelegram\Core\includes;
use WPTelegram\Core\includes\restApi\RESTController;
use WPSocio\TelegramFormatText\HtmlConverter;
use WPSocio\TelegramFormatText\Converter\Utils as FormatTextUtils;
use WPSocio\TelegramFormatText\Exceptions\ConverterException;
use WP_REST_Request;
use WP_Error;
/**
* WPTelegram Utilities
*
* @link https://wpsocio.com
* @since 1.0.0
*
* @package WPTelegram
* @subpackage WPTelegram\Core\includes
*/
class Utils extends \WPSocio\WPUtils\Helpers {
/**
* HTML tags allowed in Telegram messages.
*
* @var string Tags.
* @since 4.0.0
*/
const SUPPORTED_HTML_TAGS = [
// Link.
'a' => [
'href' => true,
],
// bold.
'b' => [],
'strong' => [],
// blockquote.
'blockquote' => [
'cite' => true,
],
// italic.
'em' => [],
'i' => [],
// code.
'pre' => [],
'code' => [
'class' => true,
],
// underline.
'u' => [],
'ins' => [],
];
/**
* Pattern to match the smart excerpt tag.
*
* @var string Pattern.
*
* @since 4.0.4
*/
const EXCERPT_PATTERN = '/<excerpt>(.*?)<\/excerpt>/ius';
/**
* The maximum size of an image to be sent to Telegram by URL.
*/
const IMAGE_BY_URL_SIZE_LIMIT = 1024 * 1024 * 5; // 5MB.
/**
* The maximum size of an image to be sent to Telegram by file.
*/
const IMAGE_BY_FILE_SIZE_LIMIT = 1024 * 1024 * 10; // 10MB.
/**
* The maximum dimensions of an image to be sent to Telegram.
*/
const IMAGE_DIMENSIONS_LIMIT = 10000; // 10000px (width + height)
/**
* The maximum width to height ratio of an image to be sent to Telegram.
*/
const IMAGE_MAX_W2H_RATIO = 20; // 20:1
/**
* Filter WP REST API errors.
*
* @param mixed $response Result to send to the client. Usually a WP_REST_Response or WP_Error.
* @param array $handler Route handler used for the request.
* @param WP_REST_Request $request Request used to generate the response.
*
* @since 3.0.3
*/
public static function filter_rest_errors( $response, $handler, $request ) {
$matches_route = 0 === strpos( ltrim( $request->get_route(), '/' ), RESTController::REST_NAMESPACE );
$is_invalid_param = is_wp_error( $response ) && 'rest_invalid_param' === $response->get_error_code();
if ( ! $is_invalid_param || ! $matches_route ) {
return $response;
}
$data = $response->get_error_data();
$invalid_params = [];
if ( ! empty( $data['params'] ) ) {
foreach ( $data['params'] as $error ) {
preg_match( '/\A\S+/', $error, $match );
$invalid_params[ $match[0] ] = $error;
}
}
$data['params'] = $invalid_params;
return new WP_Error(
$response->get_error_code(),
$response->get_error_message(),
$data
);
}
/**
* Handles sanitization for message template
*
* @since 1.0.0
*
* @param mixed $value The unsanitized value from the form.
* @param boolean $addslashes Whether to addslashes for database.
* @param boolean $json_encode Whether to json_encode.
*
* @return mixed Sanitized value.
*/
public static function sanitize_message_template( $value, $addslashes = false, $json_encode = false ) {
if ( is_object( $value ) || is_array( $value ) ) {
return '';
}
$value = (string) $value;
$guard = new TemplateGuard();
$value = $guard->safeguard_macros( $value );
$filtered = wp_check_invalid_utf8( $value );
$filtered = trim( wp_kses( $filtered, self::SUPPORTED_HTML_TAGS ) );
// Restore the macros with the original values.
$filtered = $guard->restore_macros( $filtered );
if ( $json_encode ) {
// json_encode to avoid errors when saving multi-byte emojis into database with no multi-byte support.
$filtered = wp_json_encode( $filtered );
}
if ( $addslashes ) {
// add slashes to avoid stripping of backslashes.
$filtered = addslashes( $filtered );
}
return apply_filters( 'wptelegram_sanitize_message_template', $filtered, func_get_args() );
}
/**
* Get a valid parse mode
*
* @since 1.0.0
*
* @param string $parse_mode Parse mode.
*
* @return string
*/
public static function valid_parse_mode( $parse_mode ) {
return 'HTML' === $parse_mode ? 'HTML' : '';
}
/**
* Generate a unique nonce field.
*
* @since 3.0.0
*
* @param boolean $echo Whether to echo the HTML.
* @return string
*/
public static function nonce_field( $echo = true ) {
return wp_nonce_field( self::nonce(), self::nonce(), false, $echo );
}
/**
* Generate a nonce.
*
* @since 3.0.0
* @param string $name The name suffix.
*
* @return string unique nonce string.
*/
public static function nonce( $name = '_wptelegram' ) {
return 'nonce_' . $name;
}
/**
* The HTMLConverter instance
*
* @param string $options The options for HtmlConverter.
* @param string $id The identifier of the converter options.
*
* @return HtmlConverter The HTMLConverter instance.
*/
public static function get_html_converter( $options = [], $id = 'default' ) {
$defaults = [
'format_to' => 'text',
'table_row_sep' => "\n" . str_repeat( '-', 30 ) . "\n",
'throw_on_doc_error' => true,
];
$options = wp_parse_args( $options, $defaults );
$options = apply_filters( 'wptelegram_html_converter_options', $options, $id );
$options = apply_filters( "wptelegram_{$id}_html_converter_options", $options );
$converter = new HtmlConverter( $options );
// Use this filter to add your own HTML converters.
$converter = apply_filters( 'wptelegram_html_converter', $converter, $options, $id );
$converter = apply_filters( "wptelegram_{$id}_html_converter", $converter, $options );
return $converter;
}
/**
* Prepare the content for sending to Telegram.
*
* @param string $content The content to prepare.
* @param array $options The options {
* Optional. The options to prepare the content.
*
* @type string $elipsis The elipsis to use. Default '…'.
* @type string $format_to The format to convert to. Default 'text'.
* @type string $id The identifier of the converter options. Default 'default'.
* @type int $limit The limit of the content. Default 55.
* @type string $limit_by The limit type. Default 'words'.
* @type bool $preserve_eol Whether to preserve new lines. Default true.
* }
*
* @return string The prepared content.
*/
public static function prepare_content( $content, $options = [] ) {
$defaults = [
'elipsis' => '…',
'format_to' => 'text',
'id' => 'default',
'limit' => 55,
'limit_by' => 'words',
'text_hyperlinks' => 'strip',
'preserve_eol' => true,
];
$options = wp_parse_args( $options, $defaults );
$converter = self::get_html_converter( $options, $options['id'] );
$result = trim( strip_shortcodes( $content ) );
try {
$result = $converter->convert( $result );
} catch ( ConverterException $exception ) {
// Since there was an error, we supposedly cannot format to HTML.
$result = wp_strip_all_tags( HtmlConverter::prepareHtml( $result ) );
$result = FormatTextUtils::decodeHtmlEntities( HtmlConverter::cleanUp( $result ) );
// If we were supposed to format to HTML,
// we need to ensure that special characters are escaped.
if ( 'HTML' === $options['format_to'] ) {
$result = FormatTextUtils::htmlSpecialChars( $result );
}
do_action( 'wptelegram_prepare_content_error', $exception, $content, $options );
// override formatting.
$options['format_to'] = 'text';
}
// Remove new lines if not preserving them.
if ( ! $options['preserve_eol'] ) {
$result = preg_replace( '/[\n\r]+/u', ' ', $result );
}
// if there is limit set.
if ( $options['limit'] && $options['limit'] > 0 ) {
if ( 'HTML' === $options['format_to'] ) {
// We are formatting to HTML, so we will use the safe trim to avoid breaking the HTML.
$result = $converter->safeTrim( $result, $options['limit_by'], $options['limit'] );
} else {
$result_before_limit = $result;
// We are formatting to text.
$result = FormatTextUtils::limitTextBy( $result, $options['limit_by'], $options['limit'] );
// Add the elipsis if the text is trimmed.
if ( $result !== $result_before_limit ) {
$result = trim( $result ) . $options['elipsis'];
}
}
}
return apply_filters( 'wptelegram_prepare_content', $result, $content, $options );
}
/**
* Smartly trim the content inside <excerpt></excerpt> until the whole content is less than the limit.
*
* @param string $content The content to trim.
* @param array $options The options. See prepare_content() for more info.
*
* @return string The prepared content.
*/
public static function smart_trim_excerpt( $content, $options = [] ) {
if ( ! preg_match( self::EXCERPT_PATTERN, $content, $match ) ) {
return self::prepare_content( $content, $options );
}
$placeholder = '{:excerpt:}';
$delimiter = '{::excerpt::}';
$excerpt = $match[1];
/**
* Add a placeholder for the excerpt.
*
* $content: `This is the starting content. <excerpt>This is the excerpt.</excerpt> This is the rest of the content.`.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the content.`
*/
$result = preg_replace( self::EXCERPT_PATTERN, $placeholder, $content );
/**
* Place the excerpt at the end of the content separated by delimiter.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the content.{::excerpt::}This is the excerpt`
*/
$result = $result . $delimiter . $excerpt;
/**
* Since the excerpt is at the end, it will be trimmed first.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the content.{::excerpt::}This is…`
*/
$result = self::prepare_content( $result, $options );
/**
* If the delimiter is found, we will split the content to get the excerpt.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the content.{::excerpt::}This is…`
* OR
* $result: `This is the starting content. {:excerpt:} This is the rest of the…`
*/
if ( false !== strpos( $result, $delimiter ) ) {
/**
* The first part is the trimmed content and the second part is the trimmed excerpt.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the content.`
*
* $trimmed_excerpt: `This is…`
*/
list( $result, $trimmed_excerpt) = array_pad( explode( $delimiter, $result ), 2, '' );
} else {
/**
* Sorry, it was all nuked.
*
* $result: `This is the starting content. {:excerpt:} This is the rest of the…`
*/
$trimmed_excerpt = '';
}
// Escape the placeholder.
$placeholder = preg_quote( $placeholder, '/' );
// Remove the new line if the excerpt is empty.
$placeholder = '/' . ( $trimmed_excerpt ? $placeholder : $placeholder . '[\n\r]?' ) . '/ius';
/**
* Replace the placeholder with the trimmed excerpt.
*
* $result: `This is the starting content. This is… This is the rest of the content.`
* OR
* $result: `This is the starting content. This is the rest of the…`
*/
$result = preg_replace( $placeholder, FormatTextUtils::preparePregReplacement( $trimmed_excerpt ), $result );
return apply_filters( 'wptelegram_smart_trim_excerpt', $result, $content, $options );
}
/**
* Split content into multiple chunks in order to send to Telegram as multiple messages.
*
* @since 4.0.4
*
* @param string $content The content to split.
* @param string $parse_mode The parse mode.
*
* @return string[] The split content.
*/
public static function split_content( $content, $parse_mode = '' ) {
$limit = self::get_max_text_length( 'text' );
// Remove <excerpt>...</excerpt> tags.
$content = preg_replace( self::EXCERPT_PATTERN, '${1}', $content );
// break text after every nth character given by the limit and preserve words.
preg_match_all( '/.{1,' . $limit . '}(?:\s|$)/su', $content, $matches );
$parts = $matches[0];
// If the parse mode is HTML, we need to do some extra work
// to make sure the HTML tags are not broken.
if ( 'HTML' === $parse_mode ) {
$parts = array_map( 'force_balance_tags', $parts );
}
return $parts;
}
/**
* Get the maximum length of the text to send to Telegram.
*
* @since 4.0.0
*
* @param string $for The type of the text. Can be 'text' or 'caption' Default 'text'.
* @param int $padding Safety padding to add to the limit. Default 20 characters.
*
* @return int The maximum length of the text to send to Telegram.
*/
public static function get_max_text_length( $for = 'text', $padding = 20 ) {
$length = 'caption' === $for ? HtmlConverter::TG_CAPTION_MAX_LENGTH : HtmlConverter::TG_TEXT_MAX_LENGTH;
// Add the safety padding.
$length -= abs( (int) $padding );
return (int) apply_filters( 'wptelegram_max_text_length', $length, $for, $padding );
}
/**
* Get the attachment by the given limits.
*
* @param int $id The attachment ID.
* @param array $limits The limits to apply.
* @param string $return The return type. Can be 'path' or 'url'. Default null.
*
* @return string|false The attachment path or URL.
*/
public static function get_attachment_by_limits( $id, $limits, $return = null ) {
if ( null === $return ) {
$return = self::send_files_by_url() ? 'url' : 'path';
}
return parent::get_attachment_by_limits( $id, $limits, $return );
}
/**
* Get the attachment limited by the maximum size.
*
* @param int $id The attachment ID.
* @param int $filesize The maximum file size.
* @param string $return The return type. Can be 'path' or 'url'. Default null.
*
* @return string|false The attachment path or URL.
*/
public static function get_attachment_by_filesize( $id, $filesize, $return = null ) {
return parent::get_attachment_by_limits( $id, [ 'max_filesize' => $filesize ], $return );
}
/**
* Get the limits applied to images when sending to Telegram.
*
* @return array
*/
public static function get_image_limits() {
return [
'max_filesize' => self::get_image_size_limit(),
'max_dimensions' => self::IMAGE_DIMENSIONS_LIMIT,
'max_w2h_ratio' => self::IMAGE_MAX_W2H_RATIO,
];
}
/**
* Get the limit of the image size to send to Telegram.
*
* @return int
*/
public static function get_image_size_limit() {
return self::send_files_by_url() ? self::IMAGE_BY_URL_SIZE_LIMIT : self::IMAGE_BY_FILE_SIZE_LIMIT;
}
/**
* Whether to send files by URL.
*
* @since 4.0.14
*/
public static function send_files_by_url() {
$send_files_by_url = WPTG()->options()->get_path( 'advanced.send_files_by_url', true );
return (bool) apply_filters( 'wptelegram_send_files_by_url', $send_files_by_url );
}
}