Sindbad~EG File Manager
<?php
/*
@noinspection PhpComposerExtensionStubsInspection */
/* @noinspection RegExpRedundantEscape */
namespace WPDRMS\ASL\Utils;
if ( !defined('ABSPATH') ) {
die("You can't access this file directly.");
}
class Str {
/**
* Performs a safe sanitation and escape for strings and numeric values in LIKE type queries.
* This is not to be used on whole queries, only values.
*
* @param mixed $input
* @param bool $remove_quotes
* @param string $remove
* @return array|mixed
* @uses wd_mysql_escape_mimic()
*/
public static function escape( $input, bool $remove_quotes = false, string $remove = '' ) {
// recursively go through if it is an array
if ( is_array($input) ) {
foreach ( $input as $k => $v ) {
$input[ $k ] = self::escape($v, $remove_quotes, $remove);
}
return $input;
}
if ( is_float( $input ) || is_int( $input ) ) {
return $input;
}
if ( $remove_quotes ) {
$input = str_replace(
array(
chr(145),
chr(146),
chr(147),
chr(148),
chr(150),
chr(151),
chr(133),
"'",
'"',
),
'',
$input
);
}
if ( !empty($remove) ) {
$input = str_replace(str_split($remove), '', $input);
}
if ( function_exists( 'esc_sql' ) ) {
return esc_sql( $input );
}
// Okay, what? Not one function is present, use the one we have
return wd_mysql_escape_mimic($input);
}
/**
* Checks if the given date format matches the pattern YYYY-MM-DD
*
* It does not validate the date, only the format.
*
* @param string $date
* @return bool
*/
public static function checkDate( string $date ): bool {
if ( MB::strlen( $date ) !== 10 ) {
return false;
}
return (bool) preg_match('/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}\z/', $date);
}
/**
* Converts a string to number, array of strings to array of numbers
*
* Since esc_like() does not escape numeric values, casting them is the easiest way to go
*
* @param mixed $number
* @return int[]|float[]|int|float
*/
public static function forceNumeric( $number ) {
if ( is_array($number) ) {
foreach ( $number as $k => $v ) {
$number[ $k ] = self::forceNumeric($v);
}
return $number;
} else {
if ( is_int($number) || is_float($number) ) {
return $number;
}
if ( $number === null ) {
return 0;
}
// Replace any non-numeric and decimal point character
$number = preg_replace('/[^0-9\.\-]+/', '', $number);
// PHP 7.x+ 'feature' - empty string, or non-numeric value throws an error
// ...in this case it is either a number or an empty string at this point, so check for empty string
$number = empty($number) ? 0 : $number; // Check explanation above
// Still something fishy?
$number = !is_numeric($number) ?
strpos($number, '.') !== false ? floatval($number) : intval($number) :
$number;
$number = $number + 0;
}
return $number;
}
/**
* Generates a string reverse, support multibite strings, plus fallback if mbstring not avail
*
* @param string $s
* @return string
*/
public static function reverse( string $s ): string {
/*
* Not sure if using extension_loaded(...) is enough.
*/
if (
function_exists('mb_detect_encoding') &&
function_exists('mb_strlen') &&
function_exists('mb_substr')
) {
// Using mbstring
$encoding = mb_detect_encoding($s);
$encoding = $encoding === false ? null : $encoding;
$length = mb_strlen($s, $encoding); // @phpstan-ignore-line
$reversed = '';
while ( $length-- > 0 ) {
$reversed .= mb_substr($s, $length, 1, $encoding); // @phpstan-ignore-line
}
return $reversed;
} else {
// Good old regex method, still supporting fully UFT8
preg_match_all('/./us', $s, $ar);
return implode(array_reverse($ar[0]));
}
}
/**
* Converts anything to string
*
* @param mixed $any
* @param string $separator
* @param int $level
* @return string
*/
public static function anyToString( $any, string $separator = ' ', int $level = 0 ): string {
$str = '';
if ( is_string($any) && $level === 0 ) {
$any = maybe_unserialize($any);
/**
* String check is required again, as only string is accepted for json_validate
* and at this point it can be an array or object.
*/
if ( is_string($any) && function_exists('json_validate') && json_validate($any) ) {
$any = json_decode($any, true);
}
}
if ( is_array( $any ) ) {
$str_arr = array();
foreach ( $any as $sub_arr ) {
$str_arr[] = self::anyToString( $sub_arr, $separator, $level + 1 );
}
$str_arr = array_filter( $str_arr, fn( $value ) => $value !== '' );
$str = implode( $separator, $str_arr );
} else {
// Invalid values
if ( is_bool($any) || is_null($any) || ( is_float($any) && is_nan($any) ) ) {
return '';
}
// Check for objects, as those yield a fatal error when converted to strings
if ( !is_object($any) ) {
$str = (string) $any;
}
}
return $str;
}
/**
* Replaces the first occurrence of the $find string with $replace within the $subject.
*
* @param string $find
* @param string $replace
* @param string $subject
* @return string
* @since 4.11
*/
public static function replaceFirst( string $find, string $replace, string $subject ): string {
if ( $find === '' ) {
return $subject;
}
// From the comments at PHP.net/str_replace
// Splits $subject into an array of 2 items by $find,
// and then joins the array with $replace
return implode($replace, explode($find, $subject, 2));
}
/**
* @param string $s
* @return string
*/
public static function fixSSLURLs( string $s ): string {
if ( ASL_SITE_IS_PROBABLY_SSL ) {
/** @noinspection HttpUrlsUsage */
return str_replace('http://', 'https://', $s);
}
return $s;
}
/**
* Clears and trims a search phrase from extra slashes and extra space characters
*
* @param string $s
* @return string
*/
public static function clear( string $s ): string {
// Step 2: Remove all control (non-printable) characters except for whitespace characters
// Control characters range from \x00 to \x1F and \x7F
// We'll preserve whitespace characters like space, tab, newline, etc.
$cleaned = preg_replace('/[\x00-\x08\x0B-\x1F\x7F]/u', '', $s);
if ( $cleaned === null ) {
return '';
}
// Step 1: Remove all backslashes
$stripped = str_replace('\\', '', $cleaned);
// Step 3: Trim leading and trailing whitespace
$trimmed = trim($stripped);
// Step 4: Replace multiple whitespace characters with a single space
$res = preg_replace('/\s+/', ' ', $trimmed);
// Step 5: Ensure the result is a string
return is_string($res) ? $res : '';
}
/**
* PHP 7.4 way of json_validate()
*
* @param string $str
* @return bool
*/
public static function isJson( string $str ): bool {
$json = json_decode($str);
return $json && $str !== $json;
}
/**
* Strips given HTML tags and all of their contents from a text
*
* Similar to strip_tags() but the contents are also removed
*
* @param string|string[] $text String or array of strings
* @param string[] $tags
* @return string|string[] Concatenated text
* @noinspection PhpMissingParamTypeInspection
*/
public static function stripTagsWithContent( $text, $tags = array() ) {
foreach ( $tags as $tag ) {
$text = preg_replace('/<' . $tag . '>(.*?)<\/' . $tag . '>/s', '', $text);
if ( $text === null ) {
return '';
}
$text = preg_replace('/<\\/?' . $tag . '(.|\\s)*?>/', '', $text);
if ( $text === null ) {
return '';
}
// New line is required to split non-blank lines
$text = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $text);
if ( $text === null ) {
return '';
}
}
return $text;
}
/**
* Removes WordPress Gutenberg editor blocks with their contents from the given string by block names array
*
* @param string $content
* @param string[] $block_names
* @return string
*/
public static function removeGutenbergBlocks( string $content, array $block_names = array( 'core-embed/*' ) ): string {
foreach ( $block_names as &$block_name ) {
$block_name = str_replace('*', 'AAAAAAAAAAAAAAA', $block_name);
$block_name = preg_quote($block_name, '~'); // Escaping with delimiter
$block_name = str_replace('AAAAAAAAAAAAAAA', '.*?', $block_name);
}
$block_names = implode('|', $block_names);
// Use '~' as the regex delimiter to avoid escaping '/'
$ret = preg_replace(
"~<!--\s+wp:($block_names)(\s+\{.*?\})?\s+-->(.*?)<!--\s+\/wp:($block_names)\s+-->~s",
'',
$content
);
return $ret === null ? '' : $ret;
}
/**
* Removes accents via transliterator and the WordPress core remove_accents function
*
* @param string $str
* @return string
*/
public static function removeAccents( string $str ): string {
// Hebrew transliteration does not work correctly, no vowels.
if ( preg_match('/\p{Hebrew}/u', $str) ) {
return $str;
}
$s = $str;
if (
class_exists('\\Transliterator') &&
method_exists('\\Transliterator', 'transliterate')
) {
$transliterator = \Transliterator::createFromRules(
':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;',
\Transliterator::FORWARD
);
if ( $transliterator !== null ) {
$s = $transliterator->transliterate($s);
if ( $s === false ) {
$s = $str;
}
}
}
return remove_accents($s);
}
/**
* Resolves content with bracket syntax (such as no results fields etc...)
*
* @param $content
* @param array $fields fieldN=>contentN keypairs for replacements
* @param bool $empty_on_missing if any of the field contents is empty, leave the whole expression empty
* @return string
*/
public static function resolveBracketSyntax( $content, array $fields = array(), bool $empty_on_missing = false ): string {
if ( empty($fields) ) {
return $content;
}
// Find conditional patterns, like [prefix {field} suffix]
preg_match_all( '/(\[.*?\])/', $content, $matches );
if ( isset( $matches[0] ) && isset( $matches[1] ) && is_array( $matches[1] ) ) {
foreach ( $matches[1] as $fieldset ) {
// Pass on each section to this function again, the code will never get here
$stripped_fieldset = str_replace(array( '[', ']' ), '', $fieldset);
$processed_content = self::resolveBracketSyntax($stripped_fieldset, $fields, true);
// Replace the original with the processed version, first occurrence, in case of duplicates
$content = self::replaceFirst($fieldset, $processed_content, $content);
}
}
preg_match_all( '/{(.*?)}/', $content, $matches );
if ( isset( $matches[0] ) && isset( $matches[1] ) && is_array( $matches[1] ) ) {
foreach ( $matches[1] as $field ) {
$val = $fields[ $field ] ?? '';
// For the recursive call to break, if any of the fields is empty
if ( $empty_on_missing && $val === '' ) {
return '';
}
$content = str_replace( '{' . $field . '}', $val, $content );
}
}
return $content;
}
/**
* Checks if a string is Base64 encoded
*
* @param string $s
* @return bool
*/
public static function isBase64Encoded( string $s ): bool {
if ( (bool) preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s) === false ) {
return false;
}
$decoded = base64_decode($s, true);
if ( $decoded === false ) {
return false;
}
if ( function_exists('mb_detect_encoding') ) {
$encoding = mb_detect_encoding($decoded);
if ( !in_array($encoding, array( 'UTF-8', 'ASCII' ), true) ) {
return false;
}
}
return $decoded !== false && base64_encode($decoded) === $s;
}
public static function getContext( $content, $length, $depth, $phrase, $phrase_arr ): string {
// Try for an exact match
if ( count( $phrase_arr ) > 0 && $phrase != '' ) {
$_ex_content = self::contextFind(
$content,
$phrase,
floor($length / 6),
$length,
$depth,
true
);
if ( $_ex_content === false ) {
// No exact match, go with the first keyword
$content = self::contextFind(
$content,
$phrase_arr[0],
floor($length / 6),
$length,
$depth
);
} else {
$content = $_ex_content;
}
}
return $content;
}
/**
* Returns the context of a phrase within a text.
* Uses preg_split method to iterate through strings.
*
* @param $str - string context
* @param $needle - string context
* @param $context - int length of the context
* @param $maxlength - int maximum length of the string in characters
* @param $str_length_limit - source string maximum length
* @return string
* @noinspection PhpMissingParamTypeInspection
*/
public static function contextFind( $str, $needle, $context, $maxlength, $str_length_limit = 10000, $false_on_no_match = false ) {
$haystack = remove_accents(' ' . trim($str) . ' ');
// To prevent memory overflow, we need to limit the hay to relatively low count
$haystack = wd_substr_at_word(MB::strtolower($haystack), $str_length_limit, '');
$needle = remove_accents(MB::strtolower($needle));
if ( $needle == '' ) {
if ( MB::strlen($str) > $maxlength ) {
return wd_substr_at_word($str, $maxlength);
} else {
return $str;
}
}
/**
* This is an interesting issue. Turns out mb_substr($hay, $start, 1) is very ineffective.
* the preg_split(...) method is far more efficient in terms of speed, however it needs much more
* memory. In our case speed is the top priority. However, to prevent memory overflow, the haystack
* is reduced to 10000 characters (roughly 1500 words) first.
*
* Reference ticket: https://wp-dreams.com/forums/topic/search-speed/
* Speed tests: http://stackoverflow.com/questions/3666306/how-to-iterate-utf-8-string-in-php
*/
$chrArray = preg_split('//u', $haystack, -1, PREG_SPLIT_NO_EMPTY);
$hay_length = count($chrArray) - 1;
if ( $i = MB::strpos($haystack, $needle) ) {
$start =$i;
$end =$i;
$spaces =0;
while ( $spaces < ( (int) $context /2 ) && $start > 0 ) {
--$start;
if ( $chrArray[ $start ] == ' ' ) {
++$spaces;
}
}
while ( $spaces < ( $context +1 ) && $end < $hay_length ) {
++$end;
if ( $chrArray[ $end ] == ' ' ) {
++$spaces;
}
}
while ( $spaces < ( $context +1 ) && $start > 0 ) {
--$start;
if ( $chrArray[ $start ] == ' ' ) {
++$spaces;
}
}
$str_start = ( $start - 1 ) < 0 ? 0 : ( $start -1 );
$str_end = ( $end - 1 ) < 0 ? 0 : ( $end -1 );
$result = trim( MB::substr($str, $str_start, ( $str_end - $str_start )) );
// Somewhere inbetween..
if ( $start != 0 && $end < $hay_length ) {
return '... ' . $result . ' ...';
}
// Beginning
if ( $start == 0 && $end < $hay_length ) {
return $result . ' ...';
}
// End
if ( $start != 0 && $end == $hay_length ) {
return '... ' . $result;
}
// If it is too long, strip it
if ( MB::strlen($result) > $maxlength ) {
return wd_substr_at_word( $result, $maxlength );
}
// Else, it is the whole
return $result;
} else {
if ( $false_on_no_match ) {
return false;
}
// If it is too long, strip it
if ( MB::strlen($str) > $maxlength ) {
return wd_substr_at_word( $str, $maxlength );
}
return $str;
}
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists