diff --git a/core/lexicon/en/tv_widget.inc.php b/core/lexicon/en/tv_widget.inc.php
index ca16427662f..79abff2a150 100644
--- a/core/lexicon/en/tv_widget.inc.php
+++ b/core/lexicon/en/tv_widget.inc.php
@@ -48,7 +48,7 @@
$_lang['combo_typeahead_delay_desc'] = 'Milliseconds before a matched option is shown. (Default: 250)';
$_lang['date'] = 'Date';
$_lang['date_format'] = 'Date Format';
-$_lang['date_format_desc'] = 'Enter a format using php’s strftime syntax.
+$_lang['date_format_desc'] = 'Enter a format using php’s datetime syntax.
Common examples include:
- [[+example_1a]] ([[+example_1b]]) (default format)
diff --git a/core/src/Revolution/File/modFile.php b/core/src/Revolution/File/modFile.php
index 8f96776ffc3..9f3d73034eb 100644
--- a/core/src/Revolution/File/modFile.php
+++ b/core/src/Revolution/File/modFile.php
@@ -1,4 +1,5 @@
fileHandler->modx->getService('archive', 'compression.xPDOZip', XPDO_CORE_PATH, $this->path)) {
@@ -184,25 +183,25 @@ public function getSize()
/**
* Gets the last accessed time of the file
*
- * @param string $timeFormat The format, in strftime format, of the time
+ * @param string $timeFormat The format, in datetime format, of the time
*
* @return string The formatted time
*/
- public function getLastAccessed($timeFormat = '%b %d, %Y %I:%M:%S %p')
+ public function getLastAccessed($timeFormat = 'M d, Y h:i:s A')
{
- return strftime($timeFormat, fileatime($this->path));
+ return $this->fileHandler->formatter->format(fileatime($this->path), $timeFormat);
}
/**
* Gets the last modified time of the file
*
- * @param string $timeFormat The format, in strftime format, of the time
+ * @param string $timeFormat The format, in datetime format, of the time
*
* @return string The formatted time
*/
- public function getLastModified($timeFormat = '%b %d, %Y %I:%M:%S %p')
+ public function getLastModified($timeFormat = 'M d, Y h:i:s A')
{
- return strftime($timeFormat, filemtime($this->path));
+ return $this->fileHandler->formatter->format(filemtime($this->path), $timeFormat);
}
/**
diff --git a/core/src/Revolution/File/modFileHandler.php b/core/src/Revolution/File/modFileHandler.php
index b2ea85028a6..3134fcf082e 100644
--- a/core/src/Revolution/File/modFileHandler.php
+++ b/core/src/Revolution/File/modFileHandler.php
@@ -1,4 +1,5 @@
modx =& $modx;
$this->config = array_merge($this->config, $this->modx->_userConfig, $config);
if (!isset($this->config['context'])) {
$this->config['context'] = $this->modx->context->get('key');
}
$this->context = $this->modx->getContext($this->config['context']);
+ $this->formatter = $this->modx->services->get(modManagerDateFormatter::class);
}
/**
@@ -57,7 +63,8 @@ function __construct(modX &$modx, array $config = []) {
* of the object as the specified class.
* @return mixed The appropriate modFile/modDirectory object
*/
- public function make($path, array $options = [], $overrideClass = '') {
+ public function make($path, array $options = [], $overrideClass = '')
+ {
$path = $this->sanitizePath($path);
if (!empty($overrideClass)) {
@@ -79,7 +86,8 @@ public function make($path, array $options = [], $overrideClass = '') {
*
* @return string The base path
*/
- public function getBasePath() {
+ public function getBasePath()
+ {
$basePath = '';
/* expand placeholders */
@@ -100,7 +108,8 @@ public function getBasePath() {
*
* @return string The base URL
*/
- public function getBaseUrl() {
+ public function getBaseUrl()
+ {
$baseUrl = '';
/* expand placeholders */
@@ -122,7 +131,8 @@ public function getBaseUrl() {
* @param string $path The path to clean
* @return string The sanitized path
*/
- public function sanitizePath($path) {
+ public function sanitizePath($path)
+ {
return preg_replace(["/\.*[\/|\\\]/i", "/[\/|\\\]+/i"], ['/', '/'], $path);
}
@@ -132,7 +142,8 @@ public function sanitizePath($path) {
* @param string $path
* @return string The postfixed path
*/
- public function postfixSlash($path) {
+ public function postfixSlash($path)
+ {
$len = strlen($path);
if (substr($path, $len - 1, $len) != '/') {
$path .= '/';
@@ -146,7 +157,8 @@ public function postfixSlash($path) {
* @param string $fileName The path for a file
* @return string The directory path of the given file
*/
- public function getDirectoryFromFile($fileName) {
+ public function getDirectoryFromFile($fileName)
+ {
$dir = dirname($fileName);
return $this->postfixSlash($dir);
}
@@ -157,7 +169,8 @@ public function getDirectoryFromFile($fileName) {
* @param string $file
* @return boolean True if a binary file.
*/
- public function isBinary($file) {
+ public function isBinary($file)
+ {
if (!file_exists($file) || !is_file($file)) {
return false;
}
diff --git a/core/src/Revolution/Filters/modOutputFilter.php b/core/src/Revolution/Filters/modOutputFilter.php
index 146af8dafbe..caea05b1cb3 100644
--- a/core/src/Revolution/Filters/modOutputFilter.php
+++ b/core/src/Revolution/Filters/modOutputFilter.php
@@ -1,4 +1,5 @@
modx = &$modx;
}
@@ -45,20 +48,30 @@ function __construct(modX &$modx)
*/
public function filter(&$element)
{
- $usemb = function_exists('mb_strlen') && (boolean)$this->modx->getOption('use_multibyte', null, false);
+ $usemb = function_exists('mb_strlen') && (bool)$this->modx->getOption('use_multibyte', null, false);
$encoding = $this->modx->getOption('modx_charset', null, 'UTF-8');
$output = &$element->_output;
$inputFilter = $element->getInputFilter();
if ($inputFilter !== null && $inputFilter->hasCommands()) {
- $modifier_cmd = $inputFilter->getCommands();
+ $modifier_cmd = array_map('trim', $inputFilter->getCommands());
$modifier_value = $inputFilter->getModifiers();
$count = count($modifier_cmd);
$condition = [];
- for ($i = 0; $i < $count; $i++) {
+ // Load lexicon for filters that potentially require translation
+ if (count(array_intersect($modifier_cmd, ['date', 'idate', 'strftime', 'fuzzydate', 'ago'])) > 0) {
+ $cultureKey = $this->modx->getOption('cultureKey', null, 'en');
+ $locale = $this->modx->config['locale'];
+ $lang = !empty($locale) && strlen($locale) >= 2 ? substr($locale, 0, 2) : $cultureKey ;
+ if (empty($this->modx->lexicon)) {
+ $this->modx->getService('lexicon', 'modLexicon');
+ }
+ $this->modx->lexicon->load("{$lang}:core:filters");
+ }
- $m_cmd = trim($modifier_cmd[$i]);
+ for ($i = 0; $i < $count; $i++) {
+ $m_cmd = $modifier_cmd[$i];
$m_val = $modifier_value[$i];
$this->log('Processing Modifier: ' . $m_cmd . ' (parameters: ' . $m_val . ')');
@@ -123,7 +136,7 @@ public function filter(&$element)
$condition[] = intval(stripos($output, $m_val) !== false);
break;
case 'containsnot':
- $condition[] = intval(stripos($output, $m_val) === false);;
+ $condition[] = intval(stripos($output, $m_val) === false);
break;
case 'ismember':
case 'memberof':
@@ -324,12 +337,16 @@ public function filter(&$element)
if ($limit < 0) {
$limit = 0;
}
- $breakpoint = $usemb ? mb_strpos($output, " ", $limit, $encoding) : strpos($output, " ",
- $limit);
+ $breakpoint = $usemb
+ ? mb_strpos($output, ' ', $limit, $encoding)
+ : strpos($output, ' ', $limit)
+ ;
if (false !== $breakpoint) {
if ($breakpoint < $len - 1) {
- $partial = $usemb ? mb_substr($output, 0, $breakpoint, $encoding) : substr($output,
- 0, $breakpoint);
+ $partial = $usemb
+ ? mb_substr($output, 0, $breakpoint, $encoding)
+ : substr($output, 0, $breakpoint)
+ ;
$output = $partial . $pad;
}
}
@@ -444,18 +461,29 @@ public function filter(&$element)
/* See PHP's nl2br - http://www.php.net/manual/en/function.nl2br.php */
$output = nl2br($output);
break;
+
+ case 'tabs2spaces':
+ $spacesPerTab = !empty($m_val) ? (int)$m_val : 2 ;
+ if (strpos($output, "\t") !== false) {
+ $replacement = '';
+ $i = 0;
+ while ($i < $spacesPerTab) {
+ $i++;
+ $replacement .= ' ';
+ }
+ $output = str_replace("\t", $replacement, $output);
+ }
+ break;
- case 'strftime':
+ case 'strftime': /** @deprecated Removal of strftime filter option tbd */
case 'date':
- /* See PHP's strftime - http://www.php.net/manual/en/function.strftime.php */
- if (empty($m_val)) {
- $m_val = "%A, %d %B %Y %H:%M:%S";
- /* @todo this should be modx default date/time format? Lexicon? */
- }
- if (($value = filter_var($output, FILTER_VALIDATE_INT)) === false) {
- $value = strtotime($output);
- }
- $output = ($value !== false) ? strftime($m_val, $value) : '';
+ $format = !empty($m_val) ? $m_val : '%A, %d %B %Y %H:%M:%S' ;
+ $formatter = new modFrontendDateFormatter($this->modx);
+ $formatter->setSourceFormat($m_val);
+ $output = ($output !== false)
+ ? $formatter->format($output, $format)
+ : ''
+ ;
break;
case 'strtotime':
@@ -466,39 +494,42 @@ public function filter(&$element)
$output = '';
}
break;
+
case 'fuzzydate':
- /* displays a "fuzzy" date reference */
- if (empty($this->modx->lexicon)) {
- $this->modx->getService('lexicon', 'modLexicon');
- }
- $this->modx->lexicon->load('filters');
- if (empty($m_val)) {
- $m_val = '%b %e';
- }
if (!empty($output)) {
+ $relativeFormat = !empty($m_val) ? $m_val : '%I:%M %p' ;
$time = strtotime($output);
+ $formatter = new modFrontendDateFormatter($this->modx);
+ $formatter->setSourceFormat($relativeFormat);
if ($time >= strtotime('today')) {
- $output = $this->modx->lexicon('today_at', ['time' => strftime('%I:%M %p', $time)]);
+ $output = $this->modx->lexicon(
+ 'today_at',
+ ['time' => $formatter->format($time, $relativeFormat)],
+ $lang
+ );
} elseif ($time >= strtotime('yesterday')) {
- $output = $this->modx->lexicon('yesterday_at',
- ['time' => strftime('%I:%M %p', $time)]);
+ $output = $this->modx->lexicon(
+ 'yesterday_at',
+ ['time' => $formatter->format($time, $relativeFormat)],
+ $lang
+ );
} else {
- $output = strftime($m_val, $time);
+ if (empty($m_val)) {
+ $m_val = '%b %e';
+ }
+ $formatter->setSourceFormat($m_val);
+ $output = $formatter->format($time, $m_val);
}
} else {
$output = '—';
}
break;
+
case 'ago':
/* calculates relative time ago from a timestamp */
if (empty($output)) {
break;
}
- if (empty($this->modx->lexicon)) {
- $this->modx->getService('lexicon', 'modLexicon');
- }
- $this->modx->lexicon->load('filters');
-
$agoTS = [];
$uts['start'] = strtotime($output);
$uts['end'] = time();
@@ -551,35 +582,56 @@ public function filter(&$element)
$ago = [];
if (!empty($agoTS['years'])) {
- $ago[] = $this->modx->lexicon(($agoTS['years'] > 1 ? 'ago_years' : 'ago_year'),
- ['time' => $agoTS['years']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['years'] > 1 ? 'ago_years' : 'ago_year'),
+ ['time' => $agoTS['years']],
+ $lang
+ );
}
if (!empty($agoTS['months'])) {
- $ago[] = $this->modx->lexicon(($agoTS['months'] > 1 ? 'ago_months' : 'ago_month'),
- ['time' => $agoTS['months']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['months'] > 1 ? 'ago_months' : 'ago_month'),
+ ['time' => $agoTS['months']],
+ $lang
+ );
}
if (!empty($agoTS['weeks']) && empty($agoTS['years'])) {
- $ago[] = $this->modx->lexicon(($agoTS['weeks'] > 1 ? 'ago_weeks' : 'ago_week'),
- ['time' => $agoTS['weeks']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['weeks'] > 1 ? 'ago_weeks' : 'ago_week'),
+ ['time' => $agoTS['weeks']],
+ $lang
+ );
}
if (!empty($agoTS['days']) && empty($agoTS['months']) && empty($agoTS['years'])) {
- $ago[] = $this->modx->lexicon(($agoTS['days'] > 1 ? 'ago_days' : 'ago_day'),
- ['time' => $agoTS['days']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['days'] > 1 ? 'ago_days' : 'ago_day'),
+ ['time' => $agoTS['days']],
+ $lang
+ );
}
if (!empty($agoTS['hours']) && empty($agoTS['weeks']) && empty($agoTS['months']) && empty($agoTS['years'])) {
- $ago[] = $this->modx->lexicon(($agoTS['hours'] > 1 ? 'ago_hours' : 'ago_hour'),
- ['time' => $agoTS['hours']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['hours'] > 1 ? 'ago_hours' : 'ago_hour'),
+ ['time' => $agoTS['hours']],
+ $lang
+ );
}
if (!empty($agoTS['minutes']) && empty($agoTS['days']) && empty($agoTS['weeks']) && empty($agoTS['months']) && empty($agoTS['years'])) {
- $ago[] = $this->modx->lexicon($agoTS['minutes'] == 1 ? 'ago_minute' : 'ago_minutes',
- ['time' => $agoTS['minutes']]);
+ $ago[] = $this->modx->lexicon(
+ ($agoTS['minutes'] == 1 ? 'ago_minute' : 'ago_minutes'),
+ ['time' => $agoTS['minutes']],
+ $lang
+ );
}
if (empty($ago)) { /* handle <1 min */
- $ago[] = $this->modx->lexicon('ago_seconds',
- ['time' => !empty($agoTS['seconds']) ? $agoTS['seconds'] : 0]);
+ $ago[] = $this->modx->lexicon(
+ 'ago_seconds',
+ ['time' => !empty($agoTS['seconds']) ? $agoTS['seconds'] : 0],
+ $lang
+ );
}
$output = implode(', ', $ago);
- $output = $this->modx->lexicon('ago', ['time' => $output]);
+ $output = $this->modx->lexicon('ago', ['time' => $output], $lang);
break;
case 'md5':
/* See PHP's md5 - http://www.php.net/manual/en/function.md5.php */
@@ -615,12 +667,9 @@ public function filter(&$element)
if ($user = $this->modx->getObjectGraph(modUser::class, '{"Profile":{}}', $output)) {
$userData = array_merge($user->toArray(), $user->Profile->toArray());
unset($userData['cachepwd'], $userData['salt'], $userData['sessionid'], $userData['password'], $userData['session_stale']);
- if (strpos($key, 'extended.') === 0 && isset($userData['extended'][substr($key,
- 9)])) {
+ if (strpos($key, 'extended.') === 0 && isset($userData['extended'][substr($key, 9)])) {
$userInfo = $userData['extended'][substr($key, 9)];
- } elseif (strpos($key,
- 'remote_data.') === 0 && isset($userData['remote_data'][substr($key,
- 12)])) {
+ } elseif (strpos($key, 'remote_data.') === 0 && isset($userData['remote_data'][substr($key, 12)])) {
$userInfo = $userData['remote_data'][substr($key, 12)];
} elseif (isset($userData[$key])) {
$userInfo = $userData[$key];
@@ -791,11 +840,9 @@ private static function parseConditions($conditions, $value = null, $default = n
return $default;
}
-
if (!$m_con) {
return $value;
}
-
} catch (Exception $e) {
}
diff --git a/core/src/Revolution/Formatter/modDateFormatConverter.php b/core/src/Revolution/Formatter/modDateFormatConverter.php
new file mode 100644
index 00000000000..a33ed14fa99
--- /dev/null
+++ b/core/src/Revolution/Formatter/modDateFormatConverter.php
@@ -0,0 +1,168 @@
+ [
+ 'datetime' => 'strftimeToDatetime',
+ 'intl' => 'strftimeToIntl'
+ ],
+ 'datetime' => [
+ 'intl' => 'datetimeToIntl'
+ ]
+ ];
+
+ private ?string $mapName;
+ private array $map = [];
+
+ public function __construct(modX $modx, string $conversionRule = 'strftime->intl')
+ {
+ $this->modx =& $modx;
+ $conversionRule = trim($conversionRule);
+ if (empty($conversionRule)) {
+ // log warn
+ return;
+ }
+ if (strpos($conversionRule, '->') === false) {
+ // log warn
+ return;
+ }
+ $rule = explode('->', $conversionRule);
+ $this->fromFormat = $rule[0];
+ $this->toFormat = $rule[1];
+ }
+
+ public function apply(string $format): string
+ {
+ $format = trim($format);
+ $this->originalFormat = $format;
+ if (!$this->getConversionMap()) {
+ // log err
+ return $format;
+ }
+ $method = $this->getConversionMethod();
+ if (!empty($method) && method_exists($this, $method)) {
+ $format = $this->$method($format);
+ $this->modx->log(modX::LOG_LEVEL_ERROR, "\rNew format = {$format}");
+ return $format;
+ }
+ // log warn
+ return $format;
+ }
+
+ private function getConversionMap(): bool
+ {
+ $this->mapName = array_key_exists($this->fromFormat, self::FORMAT_CONVERTERS_MAP) && array_key_exists($this->toFormat, self::FORMAT_CONVERTERS_MAP[$this->fromFormat])
+ ? self::FORMAT_CONVERTERS_MAP[$this->fromFormat][$this->toFormat]
+ : null
+ ;
+ if (!$this->mapName) {
+ // log err
+ return false;
+ }
+ $file = $this->mapName . '.map.php';
+ $filePath = __DIR__ . '/' . ltrim($file, '/');
+ if (!file_exists($filePath)) {
+ $this->modx->log(modX::LOG_LEVEL_ERROR, "\rMap file at {$filePath} not found, aborting!");
+ return false;
+ }
+ // $this->modx->log(modX::LOG_LEVEL_ERROR, "\rGetting map file {$file} from {$filePath}...");
+ $this->map = require $file;
+ // $this->modx->log(modX::LOG_LEVEL_ERROR, "\rGot conversion map!");
+ return true;
+ }
+
+ private function getConversionMethod(): string
+ {
+ if (!$this->fromFormat || !$this->toFormat) {
+ // log err
+ return '';
+ }
+ return strtolower($this->fromFormat) . 'To' . ucfirst(strtolower($this->toFormat));
+ }
+
+ private function strftimeToIntl(string $format): string
+ {
+ $this->prepareEscapedFormatting($format);
+ if (preg_match_all('/%[\w]/', $format, $parts, PREG_PATTERN_ORDER)) {
+ foreach ($parts[0] as $part) {
+ $replacement = $this->map[$part];
+ // Handle pre-defined patterns, defined by {predef:const1:const2}
+ if (in_array($part, ['%X', '%x', '%c'])) {
+ $replacement = '{predef:' . implode(':', $replacement) . '}';
+ /*
+ Intl pre-defined format equivalents can not also contain other
+ patterns or characters. Here, if a strftime pre-defined pattern is
+ found, all other information in the original format is discarded
+ to ensure a valid mapping is created.
+ */
+ if (strlen($format) > 2) {
+ $msg = "[Make into lexicon] A pre-defined strftime format ({$part}) was found in the original format string to be converted ({$this->originalFormat}). Other characters and/or formats in the original format were discarded to ensure a valid mapping to Intl.";
+ $this->modx->log(modX::LOG_LEVEL_WARN, $msg);
+ }
+ $format = $replacement;
+ break;
+ }
+ $format = str_replace($part, $replacement, $format);
+ }
+ }
+ return $format;
+ }
+
+ private function strftimeToDatetime(string $format): string
+ {
+ $this->prepareEscapedFormatting($format);
+ if (preg_match_all('/%[\w]/', $format, $parts, PREG_PATTERN_ORDER)) {
+ foreach ($parts[0] as $part) {
+ $replacement = $this->map[$part];
+ $format = str_replace($part, $replacement, $format);
+ }
+ }
+ return $format;
+ }
+
+ /**
+ * Provide basic transformation of string literals in formatting pattern
+ */
+ private function prepareEscapedFormatting(string &$format): void
+ {
+ if (strpos($format, '%%') !== false) {
+ preg_match_all('/%%[\w]/', $format, $escapedParts, PREG_PATTERN_ORDER);
+ foreach ($escapedParts[0] as $escapedPart) {
+ $replacement = $this->toFormat === 'intl'
+ ? "'{$escapedPart[0]}{$escapedPart[2]}'"
+ : $escapedPart[0] . "\\" . $escapedPart[2]
+ ;
+ $format = str_replace($escapedPart, $replacement, $format);
+ }
+ // If any '%%' sequences remain, they indicate a literal '%'
+ if (strpos($format, '%%') !== false) {
+ $format = str_replace('%%', '%', $format);
+ }
+ }
+ }
+}
diff --git a/core/src/Revolution/Formatter/modFrontendDateFormatter.php b/core/src/Revolution/Formatter/modFrontendDateFormatter.php
new file mode 100644
index 00000000000..80ac102922f
--- /dev/null
+++ b/core/src/Revolution/Formatter/modFrontendDateFormatter.php
@@ -0,0 +1,130 @@
+autoConvertStrftime = version_compare(phpversion(), '9.0.0', '>=');
+ // Hard code testing vals ...
+ // $this->autoConvertStrftime = true;
+ // $this->hasIntlDateExt = false;
+ // End HCV
+ $lc = setlocale(LC_ALL, null);
+ $msg = <<hasIntlDateExt}
+ autoConvertStrftime: {$this->autoConvertStrftime}
+ locale: {$lc}
+ culture: {$this->modx->cultureKey}
+ session lang: {$_SESSION['manager_language']}
+ LOG;
+ $this->modx->log(modX::LOG_LEVEL_ERROR, "\r{$msg}");
+ // $this->modx->log(modX::LOG_LEVEL_ERROR, "\rSession: " . print_r($_SESSION, true));
+ }
+
+ /**
+ * Transforms a date/time-related value using the specified DateTime format
+ * @param string|int $value The value to transform (a Unix timestamp or mysql-format string)
+ * @param string $format The custom format to use when formatting the $value
+ * @param bool $useOffset Whether to use the offset time (system setting) in the date calculation
+ * @param string|null $emptyValue The text to show when the $value passed is empty
+ * @return string The formatted date
+ */
+ public function format($value, string $format, bool $useOffset = false, ?string $emptyValue = null): string
+ {
+ $msg = <<sourceFormat}
+ sourceFormatType: {$this->sourceFormatType}
+ LOG;
+ $this->modx->log(modX::LOG_LEVEL_ERROR, "\r{$msg}");
+ if (!$this->autoConvertStrftime) {
+ $this->setDateFn($this->sourceFormatType);
+ } else {
+ if ($this->sourceFormatType === 'strftime') {
+ if ($this->hasIntlDateExt) {
+ $this->setDateFn('intl');
+ $this->setConversionRule();
+ } else {
+ $this->setDateFn('datetime');
+ $this->setConversionRule('strftime->datetime');
+ }
+ $this->getConverter();
+ // $this->modx->log(modX::LOG_LEVEL_ERROR, "\rGot converter!");
+ $format = $this->converter->apply($format);
+ }
+ }
+ return parent::format($value, $format);
+ }
+
+ /**
+ * Sets this class's $conversionRule property value
+ * @param string $rule A string in the form of 'fromPatternType->toPatternType'
+ * that specifies the source to destination conversion
+ */
+ public function setConversionRule(string $rule = 'strftime->intl'): void
+ {
+ $this->conversionRule = $rule;
+ }
+
+ /**
+ * Preserves the original formatting string for reference
+ * @param string $sourceFormat The formatting pattern originally passed in
+ * to this class's format method (before transformations, if any)
+ */
+ public function setSourceFormat(string $sourceFormat): void
+ {
+ if (strpos($sourceFormat, '%') !== false) {
+ $this->setSourceFormatType('strftime');
+ }
+ $this->sourceFormat = trim($sourceFormat);
+ }
+
+ public function setSourceFormatType(string $formatType): void
+ {
+ // optional: set 'strftime' or 'datetime', could be 'intl' but that's typically going to be the preferred destination format
+ $this->sourceFormatType = trim($formatType);
+ }
+
+ public function setDestinationFormatType(string $formatType): void
+ {
+ // optional: set 'intl' or 'datetime'
+ $this->destinationFormatType = trim($formatType);
+ }
+
+ /**
+ * Gets an instance of modDateFormatConverter
+ */
+ private function getConverter(): void
+ {
+ $this->converter = new modDateFormatConverter($this->modx, $this->conversionRule);
+ }
+}
diff --git a/core/src/Revolution/Formatter/modManagerDateFormatter.php b/core/src/Revolution/Formatter/modManagerDateFormatter.php
index b89a0fa421a..a1910af518c 100644
--- a/core/src/Revolution/Formatter/modManagerDateFormatter.php
+++ b/core/src/Revolution/Formatter/modManagerDateFormatter.php
@@ -11,6 +11,7 @@
namespace MODX\Revolution\Formatter;
+use IntlDateFormatter;
use MODX\Revolution\modX;
/**
@@ -57,18 +58,32 @@ class modManagerDateFormatter
null
];
+ /**
+ * @var bool $hasIntlDateExt Whether the Intl extension's IntlDateFormatter is available
+ */
+ protected bool $hasIntlDateExt = false;
+
+ /**
+ * @var string $dateFn An identifier specifying which date formatting function to use
+ */
+ protected string $dateFn = 'date';
+
+
/**
* @var string $managerDateEmptyDisplay The text (if any) to show for empty dates
*/
private string $managerDateEmptyDisplay = '';
-
public function __construct(modX $modx)
{
$this->modx =& $modx;
$this->managerDateFormat = $this->modx->getOption('manager_date_format', null, 'Y-m-d', true);
$this->managerTimeFormat = $this->modx->getOption('manager_time_format', null, 'H:i', true);
$this->managerDateEmptyDisplay = $this->modx->getOption('manager_datetime_empty_value', null, '–', true);
+
+ if ($this->modx->hasIntlExtension && class_exists('IntlDateFormatter')) {
+ $this->hasIntlDateExt = true;
+ }
}
public function isEmpty($value): bool
@@ -111,6 +126,17 @@ protected function parseValue($value, bool $useOffset = false): ?int
return $value + $offset;
}
+ /**
+ * Sets an identifier for specifying which date formatting function to use
+ * @param string $formatType The formatting pattern type (datetime, strftime, or intl [unicode/ICU])
+ */
+ protected function setDateFn(string $formatType): void
+ {
+ $formatType = trim($formatType);
+ $fnId = $formatType === 'datetime' ? 'date' : $formatType ;
+ $this->dateFn = $fnId;
+ }
+
/**
* Transforms a date/time-related value using the specified DateTime format
* @param string|int $value The value to transform (a Unix timestamp or mysql-format string)
@@ -127,7 +153,72 @@ public function format($value, string $format, bool $useOffset = false, ?string
return $emptyValue === null ? $this->managerDateEmptyDisplay : $emptyValue;
}
- return date($format, $value);
+ // Handle replacement of space-related patterns in format
+ if (preg_match('/\*[nt]/', $format)) {
+ $spacingFormats = ['*n', '*t'];
+ foreach ($spacingFormats as $spacer) {
+ $replacementSpacer = "";
+ if (strpos($format, $spacer) !== false) {
+ $newFormat = "";
+ $replacementSpacer = $spacer === '*n' ? "\n" : "\t" ;
+ $parts = explode($spacer, $format);
+ $numParts = count($parts);
+ for ($i = 0; $i < $numParts; $i++) {
+ $newFormat .= "{$parts[$i]}";
+ if ($i < $numParts - 1) {
+ $newFormat .= $replacementSpacer;
+ }
+ }
+ $format = $newFormat;
+ }
+ }
+ }
+
+ if ($this->dateFn === 'intl') {
+ $useIntlPredefinedFormats = false;
+ $locale = class_exists('Locale')
+ ? \Locale::getDefault()
+ : 'en_US'
+ ;
+ if (strpos($format, '{predef') === 0) {
+ $useIntlPredefinedFormats = true;
+ $formatsConfig = trim($format, '{}');
+ $formatsConfig = str_replace('predef:', '', $formatsConfig);
+ $formatsConfig = explode(':', $formatsConfig);
+ }
+ $predefinedFormats = $useIntlPredefinedFormats && count($formatsConfig) === 2
+ ? [(int)$formatsConfig[0], (int)$formatsConfig[1]]
+ : [IntlDateFormatter::NONE, IntlDateFormatter::NONE]
+ ;
+ $args = [$locale, ...$predefinedFormats];
+ try {
+ $formatter = new IntlDateFormatter(...$args);
+ $timezone = $formatter->getTimeZoneId();
+ $formatter->setTimeZone($timezone);
+ if (!$useIntlPredefinedFormats) {
+ $formatter->setPattern($format);
+ }
+ // $msg = <<modx->log(modX::LOG_LEVEL_ERROR, "\r{$msg}");
+ return $formatter->format($value);
+ } catch (\Exception $e) {
+ $msg = 'There was a problem initializing IntlDateFormatter with the following arguments: ' . print_r($args, true);
+ $this->modx->log(modX::LOG_LEVEL_ERROR, "\r{$msg}");
+ return '**';
+ }
+ } else {
+ /**
+ * While strftime is still present in modx-supported versions of php, use this
+ * strategy for dynamically specifying one of two functions: strftime or date
+ */
+ // $this->modx->log(modX::LOG_LEVEL_ERROR, "\rFormatting [{$format}] with {$this->dateFn}");
+ return call_user_func($this->dateFn, $format, $value);
+ }
}
/**
diff --git a/core/src/Revolution/Formatter/modStrftimeToIntlConverter.php b/core/src/Revolution/Formatter/modStrftimeToIntlConverter.php
new file mode 100644
index 00000000000..a09a8bbac75
--- /dev/null
+++ b/core/src/Revolution/Formatter/modStrftimeToIntlConverter.php
@@ -0,0 +1,26 @@
+ 'D',
+ '%A' => 'l',
+ '%d' => 'd',
+ '%e' => 'j',
+ '%j' => 'z', // 001 to 366 => 0 to 365
+ '%u' => 'N',
+ '%w' => 'w',
+ '%U' => 'W', // general match, see strftime
+ '%V' => 'W', // general match, see strftime
+ '%W' => 'W',
+ '%b' => 'M',
+ '%h' => 'M', // general match, %h is localized version of %b
+ '%B' => 'F',
+ '%m' => 'm',
+ '%C' => '**', // 2-digit century, no datetime equivalent
+ '%g' => 'y', // general match, see strftime
+ '%G' => 'Y', // general match, see strftime
+ '%y' => 'y',
+ '%Y' => 'Y',
+ '%H' => 'H',
+ '%k' => 'G',
+ '%I' => 'h',
+ '%l' => 'g',
+ '%M' => 'i',
+ '%p' => 'A',
+ '%P' => 'a',
+ '%S' => 's',
+ '%z' => 'Z',
+ '%Z' => 'T',
+ '%s' => 'U',
+ // compound formats
+ '%r' => 'h:i:s A',
+ '%R' => 'H:i',
+ '%T' => 'H:i:s',
+ '%X' => 'h:i:s', // locale unsupported in datetime, see strftime
+ '%c' => 'c', // locale unsupported in datetime, see strftime
+ '%D' => 'm/d/y',
+ '%F' => 'Y-m-d',
+ '%x' => 'm/d/y', // locale unsupported in datetime, see strftime
+ // characters
+ '%n' => '*n', // newline, \n only works within double quoted string
+ '%t' => '*t', // tab, \t only works within double quoted string
+ '%%' => '%'
+];
diff --git a/core/src/Revolution/Formatter/strftimeToIntl.map.php b/core/src/Revolution/Formatter/strftimeToIntl.map.php
new file mode 100644
index 00000000000..ef9b25af472
--- /dev/null
+++ b/core/src/Revolution/Formatter/strftimeToIntl.map.php
@@ -0,0 +1,47 @@
+ 'D',
+ '%A' => 'EEEE',
+ '%d' => 'dd',
+ '%e' => 'd',
+ '%j' => 'DDD',
+ '%u' => 'e', // general match, 1-7 (Mon-Sun) => 1-7 (Sun-Sat)
+ '%w' => 'e', // general match, 0-6 (Sun-Sat) => 1-7 (Sun-Sat)
+ '%U' => 'ww', // general match, see strftime
+ '%V' => 'ww', // close match, except some leap years
+ '%W' => 'ww', // general match, see strftime
+ '%b' => 'MMM',
+ '%h' => 'MMM',
+ '%B' => 'MMMM',
+ '%m' => 'MM',
+ '%C' => '**', // 2-digit century, no Intl equivalent (would have to be calculated from y)
+ '%g' => 'YY',
+ '%G' => 'Y',
+ '%y' => 'yy',
+ '%Y' => 'y',
+ '%H' => 'HH',
+ '%k' => 'H',
+ '%I' => 'hh',
+ '%l' => 'h',
+ '%M' => 'mm',
+ '%p' => 'a',
+ '%P' => 'a', // 'b' is specified in ICU, but doesn't seem to work, using 'a'
+ '%S' => 'ss',
+ '%z' => 'Z',
+ '%Z' => 'z',
+ '%s' => '**', // no Intl equivalent to display UNIX timestamp
+ // compound formats
+ '%r' => 'hh:mm:ss a',
+ '%R' => 'HH:mm',
+ '%T' => 'HH:mm:ss',
+ '%X' => ['date' => \IntlDateFormatter::NONE, 'time' => \IntlDateFormatter::LONG],
+ '%c' => ['date' => \IntlDateFormatter::MEDIUM, 'time' => \IntlDateFormatter::LONG],
+ '%D' => 'MM/dd/yy',
+ '%F' => 'y-MM-dd',
+ '%x' => ['date' => \IntlDateFormatter::SHORT, 'time' => \IntlDateFormatter::NONE],
+ // characters
+ '%n' => '*n', // newline, \n only works within double quoted string
+ '%t' => '*t', // tab, \t only works within double quoted string
+ '%%' => "'%'"
+];
diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php
index 0272710ee87..15f01ac68eb 100644
--- a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php
+++ b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php
@@ -11,6 +11,7 @@
namespace MODX\Revolution\Processors\Element\TemplateVar\Configs;
+use MODX\Revolution\Formatter\modFrontendDateFormatter;
use MODX\Revolution\modNamespace;
use MODX\Revolution\Processors\Processor;
use MODX\Revolution\modTemplateVar;
@@ -29,7 +30,6 @@
*/
class GetInputPropertyConfigs extends Processor
{
-
public $propertiesKey = 'input_properties';
public $configDirectory = 'inputproperties';
public $onPropertiesListEvent = 'OnTVInputPropertiesList';
@@ -70,6 +70,8 @@ public function setHelpContent(array $fieldKeys, bool $expandHelp)
private function setExampleData()
{
/* Date example */
+ $now = time();
+ $formatter = new modFrontendDateFormatter($this->modx);
$formatDefault = 'Y-m-d';
$formatCurrent = $this->modx->getOption('manager_date_format');
$seps = '-/. ';
@@ -85,6 +87,14 @@ private function setExampleData()
$timestampAheadAlt = strtotime('+3 months 8 days');
$nextYear = date('Y') + 1;
+ /*
+ Some usages of date kept here (instead of using the $formatter),
+ as evenutal localization of these values would have no effect
+ */
+ $formatter->setSourceFormatType('strftime');
+ $format2 = '%B %e'; // Intl: 'MMMM d' | datetime: 'F jS' (S is for ordinal [st, nd, rd], only available in non-localized datetime format)
+ $format4 = '%B %Y'; // Intl: 'MMMM y' | datetime: 'F Y'
+
$this->exampleData['disabled_dates_desc'] = [
'format_current' => date($formatCurrent),
'format_default' => date($formatDefault),
@@ -92,31 +102,40 @@ private function setExampleData()
',' . date($formatDefault, strtotime("+7 days")),
'example_2a' => date($formatWithoutYear, $timestampAheadOneMonth) .
',' . date($formatWithoutYear, $timestampAheadAlt),
- 'example_2b' => date("F jS", $timestampAheadOneMonth),
- 'example_2c' => date("F jS", $timestampAheadAlt),
+ 'example_2b' => $formatter->format($timestampAheadOneMonth, $format2),
+ 'example_2c' => $formatter->format($timestampAheadAlt, $format2),
'example_3a' => '^' . date("Y"),
'example_3b' => date("Y"),
'example_4a' => date($formatRegexAllDays, $timestampAheadOneMonth),
- 'example_4b' => date("F Y", $timestampAheadOneMonth),
+ 'example_4b' => $formatter->format($timestampAheadOneMonth, $format4),
'example_5' => '03-..$',
'example_6a' => $nextYear . '.03.15',
'example_6b' => $nextYear . '\\\.03\\\.15'
];
+
+ $format1 = '%A, %d %B %Y'; // datetime: 'l, d F Y'
+ $format2 = '%a, %b %e, %Y'; // datetime: 'D, M j, Y'
+ $format3 = '%m/%d/%Y'; // datetime: 'm/d/Y'
+ $format4 = '%Y-%m-%d'; // datetime: 'Y-m-d'
+ $format5 = '%Y-%m-%d %H:%M:%S'; // datetime: 'Y-m-d H:i:s'
+ $format6 = '%b %e, %Y'; // datetime: 'M j, Y'
+ $format7 = '%e %b %Y %l:%M %p'; // datetime: 'j M Y g:i A'
+
$this->exampleData['date_format_desc'] = [
- 'example_1a' => '%A %d, %B %Y',
- 'example_1b' => strftime('%A %d, %B %Y'),
- 'example_2a' => '%a, %b %e, %Y',
- 'example_2b' => strftime('%a, %b %e, %Y'),
- 'example_3a' => '%m/%d/%Y',
- 'example_3b' => strftime('%m/%d/%Y'),
- 'example_4a' => '%Y-%m-%d',
- 'example_4b' => strftime('%Y-%m-%d'),
- 'example_5a' => '%Y-%m-%d %T',
- 'example_5b' => strftime('%Y-%m-%d %T'),
- 'example_6a' => '%b %e, %Y',
- 'example_6b' => strftime('%b %e, %Y'),
- 'example_7a' => '%e %h %Y %l:%M %p',
- 'example_7b' => strftime('%e %h %Y %l:%M %p')
+ 'example_1a' => $format1,
+ 'example_1b' => $formatter->format($now, $format1),
+ 'example_2a' => $format2,
+ 'example_2b' => $formatter->format($now, $format2),
+ 'example_3a' => $format3,
+ 'example_3b' => $formatter->format($now, $format3),
+ 'example_4a' => $format4,
+ 'example_4b' => $formatter->format($now, $format4),
+ 'example_5a' => $format5,
+ 'example_5b' => $formatter->format($now, $format5),
+ 'example_6a' => $format6,
+ 'example_6b' => $formatter->format($now, $format6),
+ 'example_7a' => $format7,
+ 'example_7b' => $formatter->format($now, $format7)
];
/* Resource list example */
diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/date.php b/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/date.php
index c8a2c722b05..9cb1b644bee 100644
--- a/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/date.php
+++ b/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/date.php
@@ -15,14 +15,14 @@
# Set values
$useDefault = $params['default'] === 'true' || $params['default'] === 1 ? 'true' : 'false' ;
-$defaultFormat = "'%A %d, %B %Y'";
+$defaultFormat = "'l, d F Y'";
$format = !empty($params['format']) ? json_encode($params['format']) : $defaultFormat ;
/*
The date and string output properties share the same 'format' parameter, which is
problematic when switching between the two; reset to the default value
in this case.
*/
-$format = strpos($format, '%') === false ? $defaultFormat : $format ;
+$format = in_array($params['format'], ['Upper Case', 'Lower Case', 'Sentence Case', 'Capitalize']) ? $defaultFormat : $format ;
# Set help descriptions
$descKeys = [
diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/string.php b/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/string.php
index 610683e2026..2fe68702f2f 100644
--- a/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/string.php
+++ b/core/src/Revolution/Processors/Element/TemplateVar/Configs/mgr/properties/string.php
@@ -20,7 +20,7 @@
problematic when switching between the two; reset to the default value
in this case.
*/
-$format = strpos($format, '%') !== false ? "''" : $format ;
+$format = !in_array($params['format'], ['Upper Case', 'Lower Case', 'Sentence Case', 'Capitalize']) ? "''" : $format ;
# Set help descriptions
$descKeys = [
diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Renders/web/output/date.class.php b/core/src/Revolution/Processors/Element/TemplateVar/Renders/web/output/date.class.php
index 12b170e3f32..0591a57e842 100644
--- a/core/src/Revolution/Processors/Element/TemplateVar/Renders/web/output/date.class.php
+++ b/core/src/Revolution/Processors/Element/TemplateVar/Renders/web/output/date.class.php
@@ -1,4 +1,5 @@
tv->parseInput($value);
+ $value = $this->tv->parseInput($value);
/* if not using current time and no value, return */
- if (empty($value) && empty($params['default'])) return '';
-
+ if (empty($value) && empty($params['default'])) {
+ return '';
+ }
/* if using current, and value empty, get current time */
if (!empty($params['default']) && empty($value)) {
$timestamp = time();
} else { /* otherwise get timestamp */
- $timestamp= strtotime($value);
+ $timestamp = strtotime($value);
}
/* return formatted time */
- return strftime($params['format'],$timestamp);
+ $formatter = new modFrontendDateFormatter($this->modx);
+ $formatter->setSourceFormat($params['format']);
+ return $formatter->format($timestamp, $params['format']);
}
}
return 'modTemplateVarOutputRenderDate';
diff --git a/core/src/Revolution/Transport/modTransportProvider.php b/core/src/Revolution/Transport/modTransportProvider.php
index ccd5a804267..516c0364782 100644
--- a/core/src/Revolution/Transport/modTransportProvider.php
+++ b/core/src/Revolution/Transport/modTransportProvider.php
@@ -2,6 +2,7 @@
namespace MODX\Revolution\Transport;
+use MODX\Revolution\Formatter\modManagerDateFormatter;
use MODX\Revolution\modX;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
@@ -151,7 +152,7 @@ public function stats(array $args = [])
'url' => (string)$xml->url,
'id' => (string)$package->id,
'name' => (string)$package->name,
- 'downloads' => number_format((integer)$package->downloads, 0),
+ 'downloads' => number_format((int)$package->downloads, 0),
];
}
/** @var SimpleXMLElement $package */
@@ -334,6 +335,8 @@ public function transfer($signature, $target = null, array $args = [])
*/
public function find(array $search = [])
{
+ $formatter = $this->xpdo->services->get(modManagerDateFormatter::class);
+
$results = [];
$where = array_merge([
@@ -342,7 +345,6 @@ public function find(array $search = [])
'sorter' => false,
'start' => 0,
'limit' => 10,
- 'dateFormat' => '%b %d, %Y',
'supportsSeparator' => ', ',
], $search);
$where['page'] = !empty($where['start']) ? round($where['start'] / $where['limit']) : 0;
@@ -364,7 +366,7 @@ public function find(array $search = [])
$installed = $this->xpdo->getObject(modTransportPackage::class, (string)$package->signature);
$versionCompiled = rtrim((string)$package->version . '-' . (string)$package->release, '-');
- $releasedon = strftime($this->arg('dateFormat', $where), strtotime((string)$package->releasedon));
+ $releasedon = $formatter->formatDate(strtotime((string)$package->releasedon));
$supports = '';
foreach ($package->supports as $support) {
@@ -382,13 +384,13 @@ public function find(array $search = [])
'createdon' => (string)$package->createdon,
'editedon' => (string)$package->editedon,
'name' => (string)$package->name,
- 'downloads' => number_format((integer)$package->downloads, 0),
+ 'downloads' => number_format((int)$package->downloads, 0),
'releasedon' => $releasedon,
'screenshot' => (string)$package->screenshot,
'thumbnail' => !empty($package->thumbnail) ? (string)$package->thumbnail : (string)$package->screenshot,
'license' => (string)$package->license,
'minimum_supports' => (string)$package->minimum_supports,
- 'breaks_at' => (integer)$package->breaks_at != 10000000 ? (string)$package->breaks_at : '',
+ 'breaks_at' => (int)$package->breaks_at != 10000000 ? (string)$package->breaks_at : '',
'supports_db' => (string)$package->supports_db,
'location' => (string)$package->location,
'version-compiled' => $versionCompiled,
@@ -474,8 +476,11 @@ protected function args(array $args = [])
'supports' => $this->xpdo->version['code_name'] . '-' . $this->xpdo->version['full_version'],
'http_host' => $this->xpdo->getOption('http_host'),
'php_version' => PHP_VERSION,
- 'language' => $this->xpdo->getOption('manager_language', $_SESSION,
- $this->xpdo->getOption('cultureKey', null, 'en')),
+ 'language' => $this->xpdo->getOption(
+ 'manager_language',
+ $_SESSION,
+ $this->xpdo->getOption('cultureKey', null, 'en')
+ ),
];
return array_merge($baseArgs, $args);
@@ -512,7 +517,7 @@ public function request(string $path, string $method = 'GET', array $params = []
// Add params to the body if this is a POST request
if ($method === 'POST') {
- $request = $request->withHeader('Content-Type','application/x-www-form-urlencoded');
+ $request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');
$request->getBody()->write(http_build_query($params));
}
diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php
index 086ffd04485..508c738f3a3 100644
--- a/core/src/Revolution/modX.php
+++ b/core/src/Revolution/modX.php
@@ -292,6 +292,9 @@ class modX extends xPDO {
*/
private $_deprecations = [];
+ /** Indicates whether the php internationalization extension is available */
+ public bool $hasIntlExtension = false;
+
/**
* Harden the environment against common security flaws.
*
@@ -475,6 +478,7 @@ public function __construct($configPath= '', $options = null, $driverOptions = n
$this->addPackage('MODX\Revolution\Registry\Db', MODX_CORE_PATH . 'src/', null, 'MODX\\');
$this->addPackage('MODX\Revolution\Sources', MODX_CORE_PATH . 'src/', null, 'MODX\\');
$this->addPackage('MODX\Revolution\Transport', MODX_CORE_PATH . 'src/', null, 'MODX\\');
+ $this->hasIntlExtension = extension_loaded('intl');
} catch (xPDOException $xe) {
$this->sendError('unavailable', ['error_message' => $xe->getMessage()]);
} catch (Exception $e) {
@@ -2603,13 +2607,18 @@ protected function _initContext($contextKey, $regenerate = false, $options = nul
$this->config= array_merge($this->_systemConfig, $this->context->config);
$iniTZ = ini_get('date.timezone');
$cfgTZ = $this->getOption('date_timezone', $options, '');
- if (!empty($cfgTZ)) {
- if (empty($iniTZ) || $iniTZ !== $cfgTZ) {
- date_default_timezone_set($cfgTZ);
- }
- } elseif (empty($iniTZ)) {
- date_default_timezone_set('UTC');
+ $targetTZ = empty($iniTZ) && empty($cfgTZ) ? 'UTC' : $iniTZ ;
+ if (!empty($cfgTZ) && $cfgTZ !== $iniTZ) {
+ $targetTZ = $cfgTZ;
+ }
+
+ // $this->log(modX::LOG_LEVEL_ERROR, "Context: {$this->context->key}; targetTZ: {$targetTZ}; iniTZ: {$iniTZ}; cfgTZ: {$cfgTZ}");
+
+ if (!date_default_timezone_set($targetTZ)) {
+ $msg = '[_initContext] Failed to set default timezone for the [[+key]] context because the target timezone value [[+targetTZ]] is invalid.';
+ $this->log(modX::LOG_LEVEL_ERROR, "\r{$msg}");
}
+
if ($this->_initialized) {
$this->user = null;
$this->getUser();
@@ -2678,6 +2687,12 @@ protected function _initCulture($options = null) {
if ($result === false) {
$this->log(modX::LOG_LEVEL_ERROR, 'Could not set the locale. Please check if the locale ' . $this->getOption('locale', null, $locale) . ' exists on your system');
}
+
+ // $this->log(modX::LOG_LEVEL_ERROR, 'Value for targetLocale: '.$targetLocale);
+
+ if ($this->hasIntlExtension && class_exists('Locale')) {
+ \Locale::setDefault($targetLocale);
+ }
}
$this->services->add('lexicon', new modLexicon($this));
diff --git a/manager/controllers/default/resource/data.class.php b/manager/controllers/default/resource/data.class.php
index 887fb1965c2..7e468bc0f3a 100644
--- a/manager/controllers/default/resource/data.class.php
+++ b/manager/controllers/default/resource/data.class.php
@@ -9,6 +9,7 @@
* files found in the top-level directory of this distribution.
*/
+use MODX\Revolution\Formatter\modManagerDateFormatter;
use MODX\Revolution\modResource;
use xPDO\xPDO;
use xPDO\Cache\xPDOCacheManager;
@@ -28,6 +29,7 @@ class ResourceDataManagerController extends ResourceManagerController
/** @var string $previewUrl */
public $previewUrl;
+ private modManagerDateFormatter $formatter;
/**
* Check for any permissions or requirements to load page
@@ -78,6 +80,7 @@ public function loadCustomCssJs()
*/
public function process(array $scriptProperties = [])
{
+ $this->formatter = $this->modx->services->get(modManagerDateFormatter::class);
$placeholders = [];
$id = (int)$this->scriptProperties['id'];
@@ -96,8 +99,16 @@ public function process(array $scriptProperties = [])
$this->resource->getOne('Template');
$server_offset_time = intval($this->modx->getOption('server_offset_time', null, 0));
- $this->resource->set('createdon_adjusted', strftime('%c', $this->resource->get('createdon') + $server_offset_time));
- $this->resource->set('editedon_adjusted', strftime('%c', $this->resource->get('editedon') + $server_offset_time));
+ $this->resource->set('createdon_adjusted', $this->formatter->formatResourceDate(
+ $this->resource->get('createdon'),
+ 'created',
+ false
+ ));
+ $this->resource->set('editedon_adjusted', $this->formatter->formatResourceDate(
+ $this->resource->get('editedon'),
+ 'edited',
+ false
+ ));
$this->resource->_contextKey = $this->resource->get('context_key');
$buffer = $this->modx->cacheManager->get($this->resource->getCacheKey(), [