mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-25 14:10:28 +01:00
408 lines
11 KiB
PHP
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));
|
||
|
}
|
||
|
}
|
||
|
}
|