OpenXE/classes/Components/Util/StringUtil.php
2021-05-21 08:49:41 +02:00

408 lines
11 KiB
PHP

<?php
namespace Xentral\Components\Util;
use Exception;
use Xentral\Components\Util\Exception\InsecureRandomStringException;
use Xentral\Components\Util\Exception\InvalidArgumentException;
use Xentral\Components\Util\Exception\StringUtilException;
final class StringUtil
{
/** @var array $translate */
private static $transliteration = [
'/m\x{00B2}/u' => 'qm',
'/m\x{00B3}/u' => 'm3',
'/ä/' => 'ae',
'/ö/' => 'oe',
'/ü/' => 'ue',
'/Ä/' => 'Ae',
'/Ö/' => 'Oe',
'/Ü/' => 'Ue',
'/\x{00DF}/u' => 'ss', //ß
'/\x{20AC}/u' => 'EURO', //€
'/\x{0026}/u' => 'und', //&
];
/**
* Returns true if string starts with needle.
*
* @example startsWith('Apple', 'A') -> true
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function startsWith($haystack, $needle)
{
self::ensureString($haystack);
self::ensureString($needle);
return $needle === '' || strpos($haystack, $needle) === 0;
}
/**
* Returns true if string ends with needle.
*
* @example endsWith('Apple', 'e') -> true
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
{
self::ensureString($haystack);
self::ensureString($needle);
return $needle === substr($haystack, strlen($haystack) - strlen($needle), strlen($needle));
}
/**
* Returns true if needle appears anywhere in string.
*
* @example contains('Apple', 'pp') -> true
*
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function contains($haystack, $needle)
{
self::ensureString($haystack);
self::ensureString($needle);
return $needle === '' || strpos($haystack, $needle) !== false;
}
/**
* Pads a string up to a specific length.
*
* @example padLeft('a', 4, '-') -> '---a'
* @example padLeft('a', 4, '-+') -> '-+-a'
*
* @param string $string
* @param int $length
* @param string $pad
*
* @return string
*/
public static function padLeft($string, $length, $pad = ' ')
{
self::ensureString($string);
self::ensureInteger($length);
self::ensureString($pad);
return self::generatePadding($length - mb_strlen($string), $pad) . $string;
}
/**
* Pads a string up to a specific length.
*
* @example padRight('a', 4, '-') -> 'a---'
* @example padRight('a', 4, '-+') -> 'a-+-'
*
* @param string $string
* @param int $length
* @param string $pad
*
* @return string
*/
public static function padRight($string, $length, $pad = ' ')
{
self::ensureString($string);
self::ensureInteger($length);
self::ensureString($pad);
return $string . self::generatePadding($length - mb_strlen($string), $pad);
}
/**
* Generates a filesystem-friendly (win, mac, linux) file name from a given string
*
* @param string $value
*
* @return string file name
*/
public static function toFilename($value)
{
self::ensureString($value);
$sanitize = [
'/\s+/' => '_', //space
'/:\\\/' => '-', //drive name
'/\\\\\\\/' => '-', //unc path
'/[\\/\\\<>:|]/' => '-', //seperators and reserved
'/"/' => '\'',
'/[\*\?&]+/' => '', //other special chars
'/[\x{0000}-\x{001F}]/u' => '',
'/[\x{0080}-\x{FFFF}]/u' => '', //only ascii
];
$filename = preg_replace(array_keys(self::$transliteration), array_values(self::$transliteration), $value);
$filename = preg_replace(array_keys($sanitize), array_values($sanitize), $filename);
$filename = trim($filename, '.');
if ($filename === '') {
throw new StringUtilException(
sprintf('The specified string "%s" cannot be converted to a valid file name', $value)
);
}
return $filename;
}
/** Deletes all non-ASCII non-printable characters from a string
*
* Only characters between 32 to 127 (inclusive) remain.
*
* @param string $value
*
* @return string empty if whole string was non-ASCII
*/
public static function toAscii($value)
{
self::ensureString($value);
$value = preg_replace(['/[\x{0000}-\x{001F}]/u', '/[\x{0080}-\x{FFFF}]/u'], ['', ''], $value);
return $value;
}
/**
* Transforms a String to Title case.
*
* Use delimiter='' to only capitalize the first character.
*
* @example "foo Bar BaZ" => "Foo Bar Baz"
*
* @param string $value
* @param string $delimiters all characters that seperate words
*
* @return string
*/
public static function toTitleCase($value, $delimiters = '\s\v')
{
self::ensureString($value);
self::ensureString($delimiters);
$inWords = [$value];
$outWords = [];
if ($delimiters !== '') {
$regex = sprintf('/[%s]/', $delimiters);
$inWords = preg_split($regex, $value);
}
foreach ($inWords as $word) {
if ($word !== '') {
$outWords[] = mb_strtoupper(mb_substr($word, 0, 1))
. mb_strtolower(mb_substr($word, 1, mb_strlen($word)));
}
}
return implode(' ', $outWords);
}
/**
* Format a nubmer of Bytes to KB, MB etc.
*
* @example formatBytes(1024, ' ') -> '1,0 KB'
*
* @param integer $bytes
* @param string $separator
*
* @return string
*/
public static function formatBytes($bytes, $separator = '')
{
self::ensureInteger($bytes);
$bytes = (int)$bytes;
if ($bytes <= 0) {
return sprintf('0%sBytes', $separator);
}
$units = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
$exponent = (int)floor(log($bytes) / log(1024));
if ($exponent > count($units) - 1) {
$exponent = count($units) - 1;
}
$size = $bytes / pow(1024, $exponent);
return sprintf('%s%s%s',
number_format($size, 1, ',', '.'),
$separator,
$units[$exponent]
);
}
/**
* Parse PHP byte size Values to number of bytes.
*
* Shorthand Format is used in php_ini (e.g. post_max_size).
*
* @example parsePhpShorthandByte('1M') -> 1048576
*
* @param string $phpSize php shorthand byte format
*
* @return int
*/
public static function parsePhpByteSize($phpSize)
{
self::ensureString($phpSize);
preg_match('/^(\d+)([kKmMgG]?)$/', $phpSize, $matches);
if (empty($matches)) {
throw new InvalidArgumentException(sprintf('"%s" is not a PHP size format.', $phpSize));
}
$sizes = ['' => 0, 'K' => 1, 'M' => 2, 'G' => 3];
$number = (int)$matches[1];
$unit = strtoupper($matches[2]);
$exponent = $sizes[$unit];
$bytes = $number * pow(1024, $exponent);
return (int)$bytes;
}
/**
* Generates a random String with specified length
*
* @param int $length
* @param bool $secure true=create cryptographically secure string
*
* @throws InsecureRandomStringException
*
* @return string
*/
public static function random($length, $secure = false)
{
$random = self::randomByOpenSsl($length, $secure);
if ($random === false) {
$random = self::randomByRandomBytes($length);
}
if ($random !== false) {
return $random;
}
if ($random === false && $secure === true) {
throw new InsecureRandomStringException('Could not generate cryptographically secure random string.');
}
return self::randomByMd5($length);
}
/**
* @param int $length
* @param bool $secure
*
* @return string|false
*/
private static function randomByOpenSsl($length, $secure = false)
{
if (function_exists('openssl_random_pseudo_bytes')) {
$bytes = openssl_random_pseudo_bytes(ceil($length / 2), $cryptoStrong);
if ($bytes === false || ($secure === true && $cryptoStrong === false)) {
return false;
}
return substr(bin2hex($bytes), 0, $length);
}
return false;
}
/**
* random_bytes steht erst ab PHP7 zur Verfügung
*
* @param int $length
*
* @return string|false
*/
private static function randomByRandomBytes($length)
{
$random = false;
if (function_exists('random_bytes')) {
try {
$bytes = random_bytes(ceil($length / 2));
$random = substr(bin2hex($bytes), 0, $length);
} catch (Exception $e) {
}
}
return $random;
}
/**
* @param int $length
*
* @return string|false
*/
private static function randomByMd5($length)
{
$random = '';
for ($i = 0; $i * 32 <= $length; $i++) {
$val = mt_rand();
$random .= md5((string)$val);
}
$random = substr($random, 0, $length);
return $random;
}
/**
* @param int $lengthDelta
* @param string $sequence
*
* @return string
*/
private static function generatePadding($lengthDelta, $sequence)
{
if ($sequence === '' || $lengthDelta < 1) {
return '';
}
$padding = '';
for ($i = 0; $i < $lengthDelta; $i++) {
$delta = $lengthDelta - mb_strlen($padding);
$subSequence = mb_substr($sequence, 0, $delta);
$padding .= $subSequence;
}
return $padding;
}
/**
* @param mixed $value
*
* @throws InvalidArgumentException
*
* @return void
*/
private static function ensureString($value)
{
$type = gettype($value);
if ($type !== 'string') {
throw new InvalidArgumentException(sprintf('Wrong type "%s". Only "string" is allowed.', $type));
}
}
/**
* @param mixed $value
*
* @throws InvalidArgumentException
*
* @return void
*/
private static function ensureInteger($value)
{
$type = gettype($value);
if ($type !== 'integer') {
throw new InvalidArgumentException(sprintf('Wrong type "%s". Only "integer" allowed.', $type));
}
}
}