318 lines
8.2 KiB
PHP
318 lines
8.2 KiB
PHP
<?php
|
||
/**
|
||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||
*
|
||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||
* @license The MIT License (MIT)
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace ForkBB;
|
||
|
||
use ForkBB\Core\Container;
|
||
use DateTime;
|
||
use DateTimeZone;
|
||
use InvalidArgumentException;
|
||
|
||
/**
|
||
* Инициализирует другие функции (передача контейнера)
|
||
*/
|
||
function _init(Container $c): void
|
||
{
|
||
__([$c]);
|
||
dt(0, true, '', '', true, true, $c);
|
||
}
|
||
|
||
/**
|
||
* Переводит строку с подстановкой аргументов
|
||
* Защита от дурака отсутствует, ловим ошибки/исключения
|
||
*/
|
||
function __(string|array $arg): string
|
||
{
|
||
static $c, $lang;
|
||
|
||
if (null === $lang) {
|
||
if (null === $c) {
|
||
$c = \reset($arg);
|
||
|
||
if (! $c instanceof Container) {
|
||
throw new InvalidArgumentException('Container expected');
|
||
}
|
||
|
||
return '';
|
||
} else {
|
||
$lang = $c->Lang;
|
||
}
|
||
}
|
||
|
||
if (\is_array($arg)) {
|
||
$str = \array_shift($arg);
|
||
$tr = $lang->get($str);
|
||
|
||
if (null === $tr) {
|
||
$tr = e($str);
|
||
} elseif (\is_array($tr)) {
|
||
$num = \array_shift($arg);
|
||
$tr = $lang->getForm($tr, $num);
|
||
}
|
||
|
||
if (empty($arg)) {
|
||
return $tr;
|
||
} elseif (\is_array(\reset($arg))) {
|
||
return \strtr($tr, \array_map('\\ForkBB\\e', \reset($arg)));
|
||
} else {
|
||
$arg = \array_map('\\ForkBB\\e', $arg);
|
||
return \sprintf($tr, ...$arg);
|
||
}
|
||
} else {
|
||
return $lang->get($arg) ?? e($arg);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Экранирует спецсимволов HTML-сущностями
|
||
*/
|
||
function e(string $arg): string
|
||
{
|
||
return \htmlspecialchars($arg, \ENT_HTML5 | \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
|
||
/**
|
||
* Возвращает число в формате текущего пользователя
|
||
*/
|
||
function num(mixed $number, int $decimals = 0): string
|
||
{
|
||
return \is_numeric($number)
|
||
? \number_format((float) $number, $decimals, __('lang_decimal_point'), __('lang_thousands_sep'))
|
||
: '-';
|
||
}
|
||
|
||
/**
|
||
* Возвращает дату/время в формате текущего пользователя
|
||
*/
|
||
function dt(int $arg, bool $dateOnly = false, string $dateFormat = null, string $timeFormat = null, bool $timeOnly = false, bool $noText = false, Container $container = null): string
|
||
{
|
||
static $c, $offset;
|
||
|
||
if (null !== $container) {
|
||
$c = $container;
|
||
return '';
|
||
}
|
||
|
||
if (empty($arg)) {
|
||
return __('Never');
|
||
}
|
||
|
||
if (null === $offset) {
|
||
if (\in_array($c->user->timezone, DateTimeZone::listIdentifiers(), true)) {
|
||
$dateTimeZone = new DateTimeZone($c->user->timezone);
|
||
$dateTime = new DateTime('now', $dateTimeZone);
|
||
$offset = $dateTime->getOffset();
|
||
} else {
|
||
$offset = 0;
|
||
}
|
||
}
|
||
|
||
$arg += $offset;
|
||
|
||
if (null === $dateFormat) {
|
||
$dateFormat = $c->DATE_FORMATS[$c->user->date_format];
|
||
}
|
||
if (null === $timeFormat) {
|
||
$timeFormat = $c->TIME_FORMATS[$c->user->time_format];
|
||
}
|
||
|
||
$date = \gmdate($dateFormat, $arg);
|
||
|
||
if (! $noText) {
|
||
$now = \time() + $offset;
|
||
|
||
if ($date == \gmdate($dateFormat, $now)) {
|
||
$date = __('Today');
|
||
} elseif ($date == \gmdate($dateFormat, $now - 86400)) {
|
||
$date = __('Yesterday');
|
||
}
|
||
}
|
||
|
||
if ($dateOnly) {
|
||
return $date;
|
||
} elseif ($timeOnly) {
|
||
return \gmdate($timeFormat, $arg);
|
||
} else {
|
||
return $date . ' ' . \gmdate($timeFormat, $arg);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Возвращает размер в байтах, Кбайтах, ...
|
||
*/
|
||
function size(int $size): string
|
||
{
|
||
$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
||
|
||
for ($i = 0; $size >= 1024; ++$i) {
|
||
$size /= 1024;
|
||
}
|
||
|
||
$decimals = $size - (int) $size < 0.005 ? 0 : 2;
|
||
|
||
return __(['%s ' . $units[$i], num($size, $decimals)]);
|
||
}
|
||
|
||
/**
|
||
* Возвращает нормализованный (частично) url или пустую строку
|
||
*/
|
||
function url(string $url): string
|
||
{
|
||
if (
|
||
! isset($url[1])
|
||
|| \preg_match('%^\s%u', $url)
|
||
) {
|
||
return '';
|
||
}
|
||
|
||
switch ($url[0]) {
|
||
case '/':
|
||
if ('/' === $url[1]) {
|
||
$schemeOn = false;
|
||
$hostOn = true;
|
||
$url = 'http:' . $url;
|
||
|
||
break;
|
||
}
|
||
case '?':
|
||
case '#':
|
||
$schemeOn = false;
|
||
$hostOn = false;
|
||
$url = 'http://a.a' . $url;
|
||
|
||
break;
|
||
default:
|
||
$hostOn = true;
|
||
|
||
if (\preg_match('%^([a-z][a-z0-9+.-]*):(\S)?%i', $url, $m)) {
|
||
if (
|
||
! isset($m[2])
|
||
|| isset($m[1][10])
|
||
) {
|
||
return '';
|
||
}
|
||
|
||
$schemeOn = true;
|
||
} else {
|
||
$schemeOn = false;
|
||
$url = 'http://' . $url;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
$p = \parse_url($url);
|
||
|
||
if (! \is_array($p)) {
|
||
return '';
|
||
}
|
||
|
||
$scheme = \strtolower($p['scheme'] ?? '');
|
||
$result = $schemeOn && $scheme ? $scheme . ':' : '';
|
||
|
||
switch ($scheme) {
|
||
case 'javascript':
|
||
return '';
|
||
case 'mailto':
|
||
if (
|
||
isset($p['host'])
|
||
|| ! isset($p['path'])
|
||
|| ! \preg_match('%^([^\x00-\x1F]+)@([^\x00-\x1F\s@]++)$%Du', $p['path'], $m)
|
||
) {
|
||
return '';
|
||
}
|
||
|
||
$result .= \rawurlencode(\rawurldecode($m[1])) . '@' . \rawurlencode(\rawurldecode($m[2]));
|
||
|
||
break;
|
||
case 'tel':
|
||
if (
|
||
isset($p['host'])
|
||
|| ! isset($p['path'])
|
||
|| ! \preg_match('%^\+?[0-9.()-]+$%D', $p['path'])
|
||
) {
|
||
return '';
|
||
}
|
||
|
||
$result .= $p['path'];
|
||
|
||
break;
|
||
default:
|
||
if ($hostOn && isset($p['host'])) {
|
||
$result .= '//';
|
||
|
||
if (isset($p['user'])) {
|
||
$result .= \rawurlencode(\rawurldecode($p['user']));
|
||
|
||
if (isset($p['pass'])) {
|
||
$result .= ':' . \rawurlencode(\rawurldecode($p['pass']));
|
||
}
|
||
|
||
$result .= '@';
|
||
}
|
||
|
||
if (\preg_match('%[\x80-\xFF]%', $p['host'])) {
|
||
$p['host'] = \idn_to_ascii($p['host'], 0, \INTL_IDNA_VARIANT_UTS46);
|
||
}
|
||
|
||
$host = \filter_var($p['host'], \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME);
|
||
|
||
if (false !== $host) {
|
||
$result .= $host;
|
||
} elseif (
|
||
'[' === $p['host'][0]
|
||
&& ']' === $p['host'][-1]
|
||
&& \filter_var(\substr($p['host'], 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
|
||
) {
|
||
$result .= $p['host'];
|
||
} else {
|
||
return '';
|
||
}
|
||
|
||
if (isset($p['port'])) {
|
||
$result .= ':' . $p['port'];
|
||
}
|
||
}
|
||
|
||
if (isset($p['path'])) {
|
||
$result .= \preg_replace_callback(
|
||
'%[^/\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
|
||
function($m) {
|
||
return \rawurlencode($m[0]);
|
||
},
|
||
$p['path']
|
||
);
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
if (isset($p['query'])) {
|
||
$result .= '?' . \preg_replace_callback(
|
||
'%[^=&\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
|
||
function($m) {
|
||
return \rawurlencode($m[0]);
|
||
},
|
||
$p['query']
|
||
);
|
||
}
|
||
|
||
if (isset($p['fragment'])) {
|
||
$result .= '#' . \preg_replace_callback(
|
||
'%[^\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
|
||
function($m) {
|
||
return \rawurlencode($m[0]);
|
||
},
|
||
$p['fragment']
|
||
);
|
||
}
|
||
|
||
return $result;
|
||
}
|