DateAgo

У меня возникли сложно с DateAgo, поэтому решил написать свой снипет, может кому пригодиться.

<?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»).
showYear:
  • true (по умолчанию): включает год в полном формате.
  • false: убирает год (например, «09 мая»).
lang:
  • ru (по умолчанию через cultureKey): русский язык.
  • en: английский язык.
Артур Сергеевич
09 мая 2025, 14:16
modx.pro
1
305
+5

Комментарии: 7

Николай Савин
09 мая 2025, 20:00
+1
Перенес в готовые решения
    Stepan
    11 мая 2025, 18:08
    0
    showYear:
    true (по умолчанию): включает год в полном формате.
    false: убирает год (например, «09 мая»).
    погодите, а разве параметр dateFormat не закрывает этот момент?

    там же какой формат укажешь такой и отобразится
    добавьте у себя тоже возможность указания формата
      Stepan
      11 мая 2025, 18:11
      0
      if (!defined('MODX_CORE_PATH')) die('Unauthorized access');
      я конечно все понимаю, но реально зачем эта строка? — это же сниппет… Он без ядра в принципе не запустится или это типа защита от запуска файла напрямую?
        Артур Сергеевич
        11 мая 2025, 18:30
        0
        if (!defined('MODX_CORE_PATH')) die('Unauthorized access'); — это мой З#**** (при загруженных проектах я часто разношу все) Если кто-то попытается обратиться к файлу напрямую (например, через URL вроде site.ru/assets/snippets/mysnippet.php.

        можно использовать пространства имён и автозагрузку, но проверка 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 : '');
        };
          Артур Сергеевич
          11 мая 2025, 18:35
          0
          Можете просто убрать, также там есть лишний код («LOG_LEVEL_INFO») который, который по факту тоже никому не будет нужен, кроме как на этапе проверки.
            Наумов Алексей
            11 мая 2025, 21:14
            0
            Никогда не храните код .php или шаблоны .tpl в папке /assets, это прям нельзя.
            Такие штуки не должны быть доступны пользователю через http запрос. php код может выполнится, что небезопасно, а код шаблона вообще просто отобразится или файл скачается.

            Их нужно хранить или в директории core, которая должна быть закрыта от доступа через правила веб-сервера, или же вообще в идеале выше, чем public каталог.

            ну и тогда не нужно парится насчет «Если кто-то попытается обратиться к файлу напрямую»
              Артур Сергеевич
              11 мая 2025, 21:48
              0
              Я опечатался, в assets, только tpl, а core закрыта htaccess (IfModule mod_authz_core.c)
          Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
          7