File src/include/lib/Garradin/UserTemplate/CommonModifiers.php artifact a952caa61f part of check-in 3aa5e09a301ba731b00f0b6a5122e813acc9cf1aec19d68a1502346815356394


<?php

namespace Garradin\UserTemplate;

use Garradin\Config;
use Garradin\Utils;

use Garradin\Web\Render\Markdown;

/**
 * Common modifiers used by Template (Smartyer) and UserTemplate
 */
class CommonModifiers
{
	const PHP_MODIFIERS_LIST = [
		'strtotime',
		'htmlentities',
		'htmlspecialchars',
		'trim',
		'ltrim',
		'rtrim',
		'md5',
		'sha1',
		'nl2br',
		'strlen',
		'strpos',
		'strrpos',
		'wordwrap',
		'strip_tags',
		'strlen',
		'boolval',
		'intval',
		'floatval',
		'strval',
		'substr',
		'http_build_query',
	];

	/**
	 * Used for PHP modifiers
	 */
	static public function __callStatic(string $name, array $arguments)
	{
		if (!in_array($name, self::PHP_MODIFIERS_LIST)) {
			throw new \Exception('Invalid method: ' . $name);
		}

		// That change sucks PHP :(
		// https://php.watch/versions/8.1/internal-func-non-nullable-null-deprecation
		if (PHP_VERSION_ID >= 80100) {
			foreach ($arguments as &$arg) {
				if (null === $arg) {
					$arg = '';
				}
			}

			unset($arg);
		}

		return call_user_func_array($name, $arguments);
	}

	const MODIFIERS_LIST = [
		'markdown',
		'money',
		'money_raw',
		'money_currency',
		'money_html',
		'money_currency_html',
		'relative_date',
		'relative_date_short',
		'date_short',
		'date_long',
		'date_hour',
		'date',
		'strftime',
		'size_in_bytes' => [Utils::class, 'format_bytes'],
		'typo',
		'css_hex_to_rgb',
		'toupper',
		'tolower',
		'ucwords',
		'ucfirst',
		'lcfirst',
		'abs',
	];

	static public function markdown($str): string
	{
		$md = new Markdown;
		return $md->render($str);
	}

	static public function money($number, bool $hide_empty = true, bool $force_sign = false, bool $html = false): string
	{
		if ($hide_empty && !$number) {
			return '';
		}

		$sign = ($force_sign && $number > 0) ? '+' : '';

		$out = $sign . Utils::money_format($number, ',', $html ? '&nbsp;' : ' ', $hide_empty);

		if ($html) {
			$out = sprintf('<span class="money">%s</span>', $out);
		}

		return $out;
	}

	static public function money_raw($number, bool $hide_empty = true): string
	{
		return Utils::money_format($number, ',', '', $hide_empty);
	}

	static public function money_currency($number, bool $hide_empty = true, bool $force_sign = false, bool $html = false): string
	{
		$out = self::money($number, $hide_empty, $force_sign, $html);

		if ($out !== '') {
			$out .= ($html ? '&nbsp;' : ' ') . Config::getInstance()->get('currency');
		}

		return $out;
	}

	static public function money_html($number, bool $hide_empty = true, bool $force_sign = false): string
	{
		return self::money($number, $hide_empty, $force_sign, true);
	}

	static public function money_currency_html($number, bool $hide_empty = true, bool $force_sign = false): string
	{
		return '<nobr>' . self::money_currency($number, $hide_empty, $force_sign, true) . '</nobr>';
	}

	static public function date_long($ts, bool $with_hour = false): ?string
	{
		return Utils::strftime_fr($ts, '%A %e %B %Y' . ($with_hour ? ' à %Hh%M' : ''));
	}

	static public function date_short($ts, bool $with_hour = false): ?string
	{
		return Utils::shortDate($ts, $with_hour);
	}

	static public function date_hour($ts, bool $minutes_only_if_required = false): ?string
	{
		$ts = Utils::get_datetime($ts);

		if (null === $ts) {
			return null;
		}

		if ($minutes_only_if_required && $ts->format('i') == '00') {
			return $ts->format('H\h');
		}
		else {
			return $ts->format('H\hi');
		}
	}

	static public function strftime($ts, string $format, string $locale = 'fr'): ?string
	{
		if ($locale == 'fr') {
			return Utils::strftime_fr($ts, $format);
		}

		$ts = Utils::get_datetime($ts);

		if (!$ts) {
			return $ts;
		}

		return @strftime($format, $ts->getTimestamp());
	}

	static public function date($ts, string $format = null, string $locale = 'fr'): ?string
	{
		if (null === $format) {
			$format = 'd/m/Y à H:i';
		}
		elseif (preg_match('/^DATE_[\w\d]+$/', $format)) {
			$format = constant('DateTime::' . $format);
		}

		if ($locale == 'fr') {
			return Utils::date_fr($ts, $format);
		}

		$ts = Utils::get_datetime($ts);
		return date($format, $ts);
	}

	static public function relative_date($ts, bool $with_hour = false): string
	{
		$day = null;

		if (null === $ts) {
			return '';
		}

		$date = Utils::get_datetime($ts);

		if ($date->format('Ymd') == date('Ymd'))
		{
			$day = 'aujourd\'hui';
		}
		elseif ($date->format('Ymd') == date('Ymd', strtotime('yesterday')))
		{
			$day = 'hier';
		}
		elseif ($date->format('Ymd') == date('Ymd', strtotime('tomorrow')))
		{
			$day = 'demain';
		}
		elseif ($date->getTimestamp() > time() - 3600*24*7 && $date->getTimestamp() < time()) {
			$day = sprintf('il y a %d jours', round((time() - $date->getTimestamp()) / (3600*24)));
		}
		elseif ($date->format('Y') == date('Y'))
		{
			$day = strtolower(Utils::strftime_fr($date, '%A %e %B'));
		}
		else
		{
			$day = strtolower(Utils::strftime_fr($date, '%e %B %Y'));
		}

		if ($with_hour)
		{
			$hour = $date->format('H\hi');
			return sprintf('%s, %s', $day, $hour);
		}

		return $day;
	}

	static public function relative_date_short($ts, bool $with_hour = false): string
	{
		$day = null;

		if (null === $ts) {
			return '';
		}

		$date = Utils::get_datetime($ts);

		if ($date->format('Ymd') == date('Ymd'))
		{
			$day = 'aujourd\'hui';
		}
		elseif ($date->format('Ymd') == date('Ymd', strtotime('yesterday')))
		{
			$day = 'hier';
		}
		elseif ($date->format('Ymd') == date('Ymd', strtotime('tomorrow')))
		{
			$day = 'demain';
		}
		elseif ($date->getTimestamp() > time() - 3600*24*7) {
			$day = sprintf('il y a %d jours', round((time() - $date->getTimestamp()) / (3600*24)));
		}
		elseif ($date->format('Y') == date('Y'))
		{
			$day = strtolower(Utils::strftime_fr($date, '%e %B'));
		}
		else
		{
			$day = strtolower(Utils::strftime_fr($date, '%d/%m/%Y'));
		}

		if ($with_hour)
		{
			$hour = $date->format('H\hi');
			return sprintf('%s, %s', $day, $hour);
		}

		return $day;
	}

	static public function typo($str, $locale = 'fr')
	{
		$str = preg_replace('/[\h]*([?!:»])(?=\s|$)/us', "\xc2\xa0\\1", $str);
		$str = preg_replace('/(?=^|\s)([«])[\h]*/u', "\\1\xc2\xa0", $str);
		return $str;
	}

	static public function css_hex_to_rgb($str): ?string {
		$hex = sscanf((string)$str, '#%02x%02x%02x');

		if (empty($hex)) {
			return null;
		}

		return implode(', ', $hex);
	}

	static public function toupper($str): string
	{
		return function_exists('mb_strtoupper') ? mb_strtoupper($str) : strtoupper($str);
	}

	static public function tolower($str): string
	{
		return function_exists('mb_strtolower') ? mb_strtolower($str) : strtolower($str);
	}

	static public function ucwords($str): string
	{
		return function_exists('mb_convert_case') ? mb_convert_case($str, \MB_CASE_TITLE) : ucwords($str);
	}

	static public function ucfirst($str): string
	{
		return function_exists('mb_strtoupper') ? mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1) : ucfirst($str);
	}

	static public function lcfirst($str): string
	{
		return function_exists('mb_strtolower') ? mb_strtolower(mb_substr($str, 0, 1)) . mb_substr($str, 1) : ucfirst($str);
	}

	static public function abs($in)
	{
		if (false !== strpos((string)$in, '.')) {
			$in = (float) $in;
		}
		else {
			$in = (int) $in;
		}

		return abs($in);
	}
}