DateAgo
У меня возникли сложно с DateAgo, поэтому решил написать свой снипет, может кому пригодиться.
В чанке
format:
<?php
/**
* Форматирует дату публикации в относительном или полном формате с поддержкой мультиязычности
*/
if (!defined('MODX_CORE_PATH')) die('Unauthorized access');
$debug = (bool)$modx->getOption('debug', $scriptProperties, true);
if ($debug) {
$modx->setLogLevel(modX::LOG_LEVEL_INFO);
$modx->log(modX::LOG_LEVEL_INFO, 'formatDateRussian: Начало работы сниппета');
}
$date = $modx->getOption('publishedon', $scriptProperties, '');
$lang = htmlspecialchars($modx->getOption('lang', $scriptProperties, $modx->getOption('cultureKey', null, 'ru')), ENT_QUOTES, 'UTF-8');
$forceAbsolute = (bool)$modx->getOption('forceAbsolute', $scriptProperties, false);
$showYear = (bool)$modx->getOption('showYear', $scriptProperties, true);
$useCurrentDateFallback = (bool)$modx->getOption('useCurrentDateFallback', $scriptProperties, true);
$defaultOutput = $modx->getOption('defaultOutput', $scriptProperties, 'Нет даты');
// Валидация языка
$allowedLangs = ['ru', 'en'];
if (!in_array($lang, $allowedLangs)) {
$lang = 'ru';
if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Некорректный язык '$lang', установлен 'ru'");
}
if ($debug) {
$modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Параметры scriptProperties: " . print_r($scriptProperties, true));
$modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Получены параметры - Date: '$date'");
}
if (empty($date) && $useCurrentDateFallback) {
$date = date('Y-m-d H:i:s');
if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Использована текущая дата: '$date'");
}
$locales = [
'ru' => [
'months' => [
'01' => ['января', 'янв'], '02' => ['февраля', 'фев'],
'03' => ['марта', 'мар'], '04' => ['апреля', 'апр'],
'05' => ['мая', 'мая'], '06' => ['июня', 'июн'],
'07' => ['июля', 'июл'], '08' => ['августа', 'авг'],
'09' => ['сентября', 'сен'], '10' => ['октября', 'окт'],
'11' => ['ноября', 'ноя'], '12' => ['декабря', 'дек']
],
'relative' => [
'now' => 'Только что',
'yesterday' => 'Вчера',
'ago' => 'назад',
'units' => [
'minutes' => ['минуту', 'минуты', 'минут'],
'hours' => ['час', 'часа', 'часов'],
'days' => ['день', 'дня', 'дней']
]
]
],
'en' => [
'months' => [
'01' => ['January', 'Jan'], '02' => ['February', 'Feb'],
'03' => ['March', 'Mar'], '04' => ['April', 'Apr'],
'05' => ['May', 'May'], '06' => ['June', 'Jun'],
'07' => ['July', 'Jul'], '08' => ['August', 'Aug'],
'09' => ['September', 'Sep'], '10' => ['October', 'Oct'],
'11' => ['November', 'Nov'], '12' => ['December', 'Dec']
],
'relative' => [
'now' => 'Just now',
'yesterday' => 'Yesterday',
'ago' => 'ago',
'units' => [
'minutes' => ['minute', 'minutes', 'minutes'],
'hours' => ['hour', 'hours', 'hours'],
'days' => ['day', 'days', 'days']
]
]
]
];
$locale = $locales[$lang] ?? $locales['ru'];
$pluralForm = function($number, $unit) use ($locale) {
$forms = $locale['relative']['units'][$unit] ?? ['','',''];
$n = abs($number) % 100;
$remainder = $n % 10;
if ($n > 10 && $n < 20) return $forms[2];
if ($remainder > 1 && $remainder < 5) return $forms[1];
if ($remainder == 1) return $forms[0];
return $forms[2];
};
$timestamp = 0;
if (is_numeric($date) && $date > 0) {
$timestamp = (int)$date;
} elseif (!empty($date)) {
$timestamp = strtotime($date);
}
if ($timestamp <= 0) {
if ($debug) $modx->log(modX::LOG_LEVEL_WARN, "formatDateRussian: Некорректная дата. Input='$date'");
return $defaultOutput;
}
$now = time();
$diff = $now - $timestamp;
$days = floor($diff / 86400);
$formatAbsoluteDate = function($timestamp, $locale) use ($showYear) {
$monthIndex = date('m', $timestamp);
$monthNames = $locale['months'][$monthIndex] ?? ['', ''];
return date('d', $timestamp) . ' ' . $monthNames[0] . ($showYear ? ' ' . date('Y', $timestamp) : '');
};
if ($forceAbsolute || $timestamp > $now || $days > 7) {
$result = $formatAbsoluteDate($timestamp, $locale);
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Абсолютная дата, результат: '$result'");
return $result;
}
if ($diff < 60) {
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Менее минуты назад");
return $locale['relative']['now'];
}
if ($diff < 3600) {
$minutes = floor($diff/60);
if ($minutes == 1 && $lang === 'en') {
$result = "a minute {$locale['relative']['ago']}";
} else {
$unit = $pluralForm($minutes, 'minutes');
$result = "$minutes $unit {$locale['relative']['ago']}";
}
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Минуты, результат: '$result'");
return $result;
}
if ($diff < 86400) {
$hours = floor($diff/3600);
if ($hours == 1) {
$result = $lang === 'en' ? "an hour {$locale['relative']['ago']}" : "час {$locale['relative']['ago']}";
} else {
$unit = $pluralForm($hours, 'hours');
$result = "$hours $unit {$locale['relative']['ago']}";
}
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Часы, результат: '$result'");
return $result;
}
if ($days == 1) {
$result = $lang === 'en' ? "a day {$locale['relative']['ago']}" : $locale['relative']['yesterday'];
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Один день, результат: '$result'");
return $result;
}
$unit = $pluralForm($days, 'days');
$result = "$days $unit {$locale['relative']['ago']}";
if ($debug) $modx->log(modX::LOG_LEVEL_INFO, "formatDateRussian: Дни, результат: '$result'");
return $result;
Вывод на основании системных настроек{$_modx->resource.publishedon | formatDateRussian}
В чанке
{'formatDateRussian' | snippet : [
'publishedon' => $publishedon,
'format' => 'full',
'lang' => 'en',
'showYear' => true,
'debug' => 1
]}
Описаниеformat:
- relative (по умолчанию): выводит относительные интервалы («Только что», «5 минут назад», «Вчера» и т.д.).
- full: выводит полную дату («09 мая, 2025»).
- true (по умолчанию): включает год в полном формате.
- false: убирает год (например, «09 мая»).
- ru (по умолчанию через cultureKey): русский язык.
- en: английский язык.
Комментарии: 7
Перенес в готовые решения
showYear:погодите, а разве параметр dateFormat не закрывает этот момент?
true (по умолчанию): включает год в полном формате.
false: убирает год (например, «09 мая»).
там же какой формат укажешь такой и отобразится
добавьте у себя тоже возможность указания формата
if (!defined('MODX_CORE_PATH')) die('Unauthorized access');я конечно все понимаю, но реально зачем эта строка? — это же сниппет… Он без ядра в принципе не запустится или это типа защита от запуска файла напрямую?
if (!defined('MODX_CORE_PATH')) die('Unauthorized access'); — это мой З#**** (при загруженных проектах я часто разношу все) Если кто-то попытается обратиться к файлу напрямую (например, через URL вроде site.ru/assets/snippets/mysnippet.php.
можно использовать пространства имён и автозагрузку, но проверка MODX_CORE_PATH остаётся простым и надёжным способом защиты.
(просто как доп. уровень, это наверно из разряда xss csp)
погодите, а разве параметр dateFormat не закрывает этот момент?
Все верно вы говорите, просто я его еще не доделал. Изначально я хотел что при текущем годе показа года не было.
Вот сырой код, если хотите посмотреть, для понимания
можно использовать пространства имён и автозагрузку, но проверка MODX_CORE_PATH остаётся простым и надёжным способом защиты.
(просто как доп. уровень, это наверно из разряда xss csp)
погодите, а разве параметр dateFormat не закрывает этот момент?
Все верно вы говорите, просто я его еще не доделал. Изначально я хотел что при текущем годе показа года не было.
Вот сырой код, если хотите посмотреть, для понимания
$formatAbsoluteDate = function($timestamp, $locale) use ($showYear) {
$monthIndex = date('m', $timestamp);
$monthNames = $locale['months'][$monthIndex] ?? ['', ''];
$year = date('Y', $timestamp);
$currentYear = date('Y');
// !!!
$shouldShowYear = $showYear && ($year != $currentYear);
return date('d', $timestamp) . ' ' . $monthNames[0] . ($shouldShowYear ? ' ' . $year : '');
};
Можете просто убрать, также там есть лишний код («LOG_LEVEL_INFO») который, который по факту тоже никому не будет нужен, кроме как на этапе проверки.
Никогда не храните код .php или шаблоны .tpl в папке /assets, это прям нельзя.
Такие штуки не должны быть доступны пользователю через http запрос. php код может выполнится, что небезопасно, а код шаблона вообще просто отобразится или файл скачается.
Их нужно хранить или в директории core, которая должна быть закрыта от доступа через правила веб-сервера, или же вообще в идеале выше, чем public каталог.
ну и тогда не нужно парится насчет «Если кто-то попытается обратиться к файлу напрямую»
Такие штуки не должны быть доступны пользователю через http запрос. php код может выполнится, что небезопасно, а код шаблона вообще просто отобразится или файл скачается.
Их нужно хранить или в директории core, которая должна быть закрыта от доступа через правила веб-сервера, или же вообще в идеале выше, чем public каталог.
ну и тогда не нужно парится насчет «Если кто-то попытается обратиться к файлу напрямую»
Я опечатался, в assets, только tpl, а core закрыта htaccess (IfModule mod_authz_core.c)
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.