Files
f3-simple-blog/app/Helpers/App.php
2026-03-29 01:49:25 +01:00

211 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
function app_timezone(): string
{
$timezone = trim((string) Base::instance()->get('app.timezone'));
return ($timezone !== '' && in_array($timezone, DateTimeZone::listIdentifiers(), true)) ? $timezone : 'UTC';
}
function app_now(): string
{
return gmdate('Y-m-d H:i:s');
}
function app_is_prod(): bool
{
return Base::instance()->get('app.env') === 'prod';
}
function app_ensure_dir(string $path): void
{
if (!is_dir($path)) {
mkdir($path, 0775, true);
}
}
function app_unique_slug(string $value, callable $exists): string
{
$base = Web::instance()->slug(trim($value));
if ($base === '') {
$base = 'article';
}
if (!$exists($base)) {
return $base;
}
for ($i = 2; $i <= 1000; $i++) {
$candidate = $base . '-' . $i;
if (!$exists($candidate)) {
return $candidate;
}
}
throw new RuntimeException('Impossible de générer un slug unique.');
}
function app_format_datetime_fr(string $value): string
{
static $utc, $formatter;
$value = trim($value);
if ($value === '') {
return '';
}
try {
$utc ??= new DateTimeZone('UTC');
$date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value, $utc);
if (!$date instanceof DateTimeImmutable) {
$date = new DateTimeImmutable($value, $utc);
}
$date = $date->setTimezone(new DateTimeZone(date_default_timezone_get()));
$formatter ??= new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::LONG,
IntlDateFormatter::SHORT,
date_default_timezone_get(),
IntlDateFormatter::GREGORIAN,
"d MMMM yyyy 'à' HH:mm"
);
$formatted = $formatter->format($date);
return is_string($formatted) && $formatted !== '' ? $formatted : $value;
} catch (Throwable) {
return $value;
}
}
function app_trusted_proxies(): array
{
$value = Base::instance()->get('app.trusted_proxies');
if (is_array($value)) {
$items = [];
array_walk_recursive($value, static function (mixed $item) use (&$items): void {
if (is_string($item) || is_numeric($item)) {
$items[] = (string) $item;
}
});
} else {
$raw = trim((string) $value);
if ($raw === '') {
return [];
}
$items = preg_split('/[\s,]+/', $raw) ?: [];
}
$normalized = [];
foreach ($items as $item) {
foreach (preg_split('/[\s,]+/', trim((string) $item)) ?: [] as $part) {
$part = trim($part);
if ($part !== '') {
$normalized[] = $part;
}
}
}
return array_values(array_unique($normalized));
}
function app_is_trusted_proxy(?string $ip = null): bool
{
$ip = trim((string) ($ip ?? Base::instance()->get('SERVER.REMOTE_ADDR')));
if ($ip === '' || filter_var($ip, FILTER_VALIDATE_IP) === false) {
return false;
}
foreach (app_trusted_proxies() as $proxy) {
if (app_ip_matches_proxy($ip, $proxy)) {
return true;
}
}
return false;
}
function app_request_scheme(): string
{
$f3 = Base::instance();
$https = strtolower(trim((string) $f3->get('SERVER.HTTPS')));
if ($https !== '' && $https !== 'off' && $https !== '0') {
return 'https';
}
$scheme = strtolower(trim((string) $f3->get('SCHEME')));
if ($scheme === 'https') {
return 'https';
}
// Derrière un reverse proxy de confiance, on accepte le proto transmis.
if (app_is_trusted_proxy()) {
$forwardedProto = trim((string) $f3->get('SERVER.HTTP_X_FORWARDED_PROTO'));
if ($forwardedProto !== '') {
$forwardedProto = strtolower(trim(explode(',', $forwardedProto)[0]));
return $forwardedProto === 'https' ? 'https' : 'http';
}
$forwarded = trim((string) $f3->get('SERVER.HTTP_FORWARDED'));
if ($forwarded !== '' && preg_match('/(?:^|[;,]\s*)proto=(https?)/i', $forwarded, $matches) === 1) {
return strtolower($matches[1]) === 'https' ? 'https' : 'http';
}
}
return 'http';
}
function app_ip_matches_proxy(string $ip, string $proxy): bool
{
$proxy = trim($proxy);
if ($proxy === '') {
return false;
}
if (!str_contains($proxy, '/')) {
return filter_var($proxy, FILTER_VALIDATE_IP) !== false && strcasecmp($ip, $proxy) === 0;
}
[$subnet, $prefix] = explode('/', $proxy, 2);
$subnet = trim($subnet);
$prefix = trim($prefix);
$ipBinary = inet_pton($ip);
$subnetBinary = inet_pton($subnet);
if ($ipBinary === false || $subnetBinary === false || strlen($ipBinary) !== strlen($subnetBinary)) {
return false;
}
if (!ctype_digit($prefix)) {
return false;
}
$prefixLength = (int) $prefix;
$maxBits = strlen($ipBinary) * 8;
if ($prefixLength < 0 || $prefixLength > $maxBits) {
return false;
}
$fullBytes = intdiv($prefixLength, 8);
if ($fullBytes > 0 && substr($ipBinary, 0, $fullBytes) !== substr($subnetBinary, 0, $fullBytes)) {
return false;
}
$remainingBits = $prefixLength % 8;
if ($remainingBits === 0) {
return true;
}
$mask = (0xFF << (8 - $remainingBits)) & 0xFF;
return (ord($ipBinary[$fullBytes]) & $mask) === (ord($subnetBinary[$fullBytes]) & $mask);
}