diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt
index 08a4d856c9..40a1a93015 100644
--- a/emhttp/languages/en_US/helptext.txt
+++ b/emhttp/languages/en_US/helptext.txt
@@ -2155,8 +2155,19 @@ Click on the counters to either acknowledge or view the unread notifications.
:end
:notifications_display_position_help:
-Choose the position of where notifications appear on screen in *Detailed* view. Multiple notifications are stacked, bottom-to-top or
-top-to-bottom depending on the selected placement.
+Choose the position of where notification popups appear on screen.
+:end
+
+:notifications_stack_help:
+When enabled, multiple notifications are stacked to conserve screen space.
+:end
+
+:notifications_duration_help:
+Time in milliseconds before a notification automatically closes.
+:end
+
+:notifications_max_help:
+Maximum number of notifications shown on screen at once.
:end
:notifications_auto_close_help:
diff --git a/emhttp/plugins/dynamix/Notifications.page b/emhttp/plugins/dynamix/Notifications.page
index b6ffafba72..8d4aa24d97 100644
--- a/emhttp/plugins/dynamix/Notifications.page
+++ b/emhttp/plugins/dynamix/Notifications.page
@@ -112,17 +112,44 @@ $(function(){
+_(Store notifications to flash)_:
+:
+ =mk_option($notify['path'], "/tmp/notifications", _("No"))?>
+ =mk_option($notify['path'], "/boot/config/plugins/dynamix/notifications", _("Yes"))?>
+
+
+:notifications_store_flash_help:
+
_(Display position)_:
:
=mk_option($notify['position'], "top-left", _("top-left"))?>
=mk_option($notify['position'], "top-right", _("top-right"))?>
=mk_option($notify['position'], "bottom-left", _("bottom-left"))?>
=mk_option($notify['position'], "bottom-right", _("bottom-right"))?>
- =mk_option($notify['position'], "center", _("center"))?>
+ =mk_option($notify['position'], "bottom-center", _("bottom-center"))?>
+ =mk_option($notify['position'], "top-center", _("top-center"))?>
:notifications_display_position_help:
+_(Stack notification popups)_:
+:
+ =mk_option($notify['expand'] ?? 'true', "true", _("Yes"))?>
+ =mk_option($notify['expand'] ?? 'true', "false", _("No"))?>
+
+
+:notifications_stack_help:
+
+_(Notification popup duration)_:
+:
+
+:notifications_duration_help:
+
+_(Max notification popups)_:
+:
+
+:notifications_max_help:
+
_(Date format)_:
:
=mk_option($notify['date'], "d-m-Y", _("DD-MM-YYYY"))?>
@@ -140,14 +167,6 @@ _(Time format)_:
:notifications_time_format_help:
-_(Store notifications to flash)_:
-:
- =mk_option($notify['path'], "/tmp/notifications", _("No"))?>
- =mk_option($notify['path'], "/boot/config/plugins/dynamix/notifications", _("Yes"))?>
-
-
-:notifications_store_flash_help:
-
_(System notifications)_:
:
=mk_option($notify['system'], "", _("Disabled"))?>
diff --git a/emhttp/plugins/dynamix/default.cfg b/emhttp/plugins/dynamix/default.cfg
index 81147abe5d..a555d54756 100644
--- a/emhttp/plugins/dynamix/default.cfg
+++ b/emhttp/plugins/dynamix/default.cfg
@@ -45,6 +45,9 @@ day="0"
cron=""
write="NOCORRECT"
[notify]
+expand="true"
+duration="5000"
+max="3"
date="d-m-Y"
time="H:i"
position="top-right"
diff --git a/emhttp/plugins/dynamix/include/Notify.php b/emhttp/plugins/dynamix/include/Notify.php
index e692b41b03..f4b51115b0 100644
--- a/emhttp/plugins/dynamix/include/Notify.php
+++ b/emhttp/plugins/dynamix/include/Notify.php
@@ -36,6 +36,10 @@
case 'm':
$notify .= " -{$option} ".escapeshellarg($value);
break;
+ case 'u':
+ $notify .= " -{$option} ".escapeshellarg($value);
+ break;
+
case 'x':
case 't':
$notify .= " -{$option}";
diff --git a/emhttp/plugins/dynamix/include/Translations.php b/emhttp/plugins/dynamix/include/Translations.php
index c3bd47382d..5f6c8bb924 100644
--- a/emhttp/plugins/dynamix/include/Translations.php
+++ b/emhttp/plugins/dynamix/include/Translations.php
@@ -73,9 +73,9 @@ function parse_plugin($plugin) {
global $docroot,$language,$locale;
$plugin = strtolower($plugin);
$text = "$docroot/languages/$locale/$plugin.txt";
- if (file_exists($text)) {
+ if (file_exists($text)) {
$store = "$docroot/languages/$locale/$plugin.dot";
- if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
+ clearstatcache(); if (!file_exists($store) || filemtime($text) > filemtime($store)) file_put_contents($store,serialize(parse_lang_file($text)));
$language = array_merge($language,unserialize(file_get_contents($store)));
}
}
@@ -133,7 +133,7 @@ function translate($key) {
if (file_exists($text)) {
$store = "$docroot/languages/$locale/translations.dot";
// global translations
- if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
+ clearstatcache(); if (!file_exists($store) || filemtime($text) > filemtime($store)) file_put_contents($store,serialize(parse_lang_file($text)));
$language = unserialize(file_get_contents($store));
}
if (file_exists("$docroot/languages/$locale/helptext.txt")) {
@@ -163,7 +163,7 @@ function translate($key) {
if (file_exists($text)) {
// additional translations
$store = "$docroot/languages/$locale/$more.dot";
- if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
+ clearstatcache(); if (!file_exists($store) || filemtime($text) > filemtime($store)) file_put_contents($store,serialize(parse_lang_file($text)));
$language = array_merge($language,unserialize(file_get_contents($store)));
}
}
diff --git a/emhttp/plugins/dynamix/scripts/notify b/emhttp/plugins/dynamix/scripts/notify
index 1c4db802b5..e9db2df506 100755
--- a/emhttp/plugins/dynamix/scripts/notify
+++ b/emhttp/plugins/dynamix/scripts/notify
@@ -31,6 +31,7 @@ notify [-e "event"] [-s "subject"] [-d "description"] [-i "normal|warning|alert"
use -r to specify recipients and not use default
use -t to force send email only (for testing)
use -b to NOT send a browser notification
+ use -u to specify a custom filename (API use only)
all options are optional
notify init
@@ -87,12 +88,13 @@ function generate_email($event, $subject, $description, $importance, $message, $
return mail($to, $subj, implode("\n", $body), implode("\n", $headers));
}
-function safe_filename($string) {
+function safe_filename($string, $maxLength=255) {
$special_chars = ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"];
$string = trim(str_replace($special_chars, "", $string));
- $string = preg_replace('~[^0-9a-z -_]~i', '', $string);
+ $string = preg_replace('~[^0-9a-z -_.]~i', '', $string);
$string = preg_replace('~[- ]~i', '_', $string);
- return trim($string);
+ // limit filename length to $maxLength characters
+ return substr(trim($string), 0, $maxLength);
}
/*
@@ -112,7 +114,7 @@ function ini_encode_value($value) {
if (is_int($value) || is_float($value)) return $value;
if (is_bool($value)) return $value ? 'true' : 'false';
$value = (string)$value;
- return '"'.strtr($value, ["\\"=>"\\\\", '"' => '\\"']).'"';
+ return '"'.strtr($value, ["\\" => "\\\\", '"' => '\\"']).'"';
}
function build_ini_string(array $data) {
@@ -135,6 +137,7 @@ function ini_decode_value($value) {
return $value;
}
+
// start
if ($argc == 1) exit(usage());
@@ -209,8 +212,9 @@ case 'add':
$mailtest = false;
$overrule = false;
$noBrowser = false;
+ $customFilename = false;
- $options = getopt("l:e:s:d:i:m:r:xtb");
+ $options = getopt("l:e:s:d:i:m:r:u:xtb");
foreach ($options as $option => $value) {
switch ($option) {
case 'e':
@@ -246,11 +250,26 @@ case 'add':
$link = $value;
$fqdnlink = (strpos($link,"http") === 0) ? $link : ($nginx['NGINX_DEFAULTURL']??'').$link;
break;
+ case 'u':
+ $customFilename = $value;
+ break;
}
}
- $unread = "{$unread}/".safe_filename("{$event}-{$ticket}.notify");
- $archive = "{$archive}/".safe_filename("{$event}-{$ticket}.notify");
+ if ($customFilename) {
+ $filename = safe_filename($customFilename);
+ } else {
+ // suffix length: _{timestamp}.notify = 1+10+7 = 18 chars.
+ $suffix = "_{$ticket}.notify";
+ $max_name_len = 255 - strlen($suffix);
+ // sanitize event, truncating it to leave room for suffix
+ $clean_name = safe_filename($event, $max_name_len);
+ // construct filename with suffix (underscore separator matches safe_filename behavior)
+ $filename = "{$clean_name}{$suffix}";
+ }
+
+ $unread = "{$unread}/{$filename}";
+ $archive = "{$archive}/{$filename}";
if (file_exists($archive)) break;
$entity = $overrule===false ? $notify[$importance] : $overrule;
$cleanSubject = clean_subject($subject);
@@ -274,8 +293,8 @@ case 'add':
];
file_put_contents($unread, build_ini_string($unreadData));
}
- if (($entity & 2)==2 || $mailtest) generate_email($event, $cleanSubject, str_replace(' ','. ',$description), $importance, $message, $recipients, $fqdnlink);
- if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg($cleanSubject)." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}};
+ if (($entity & 2)==2 || $mailtest) generate_email($event, clean_subject($subject), str_replace(' ','. ',$description), $importance, $message, $recipients, $fqdnlink);
+ if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg(clean_subject($subject))." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}};
break;
case 'get':