diff --git a/README.md b/README.md
index cf2f8601..022c0c03 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,9 @@
## About
Anuko [Time Tracker](https://www.anuko.com/time-tracker/index.htm) is an open source, web-based time tracking application written in PHP. It allows you to track the time that employees or colleagues spend working on projects and tasks. It needs a web server such as Apache, IIS, etc. to run on, and a database to keep the data in, such as MySQL.
+## Terminal Illness of the Owner
+December 28, 2023: Nik Okuntseff, the owner and lead software developer at Anuko, developed a terminal illness. Anuko, the company behind Time Tracker, will cease to exist at soon.
+
## Free Hosting
[Anuko](https://www.anuko.com) provides [free hosting](https://www.anuko.com/time-tracker/free-hosting/index.htm) of Time Tracker to individuals and small groups up to 5 users. To start using Time Tracker immediately, create a group at https://timetracker.anuko.com
diff --git a/WEB-INF/lib/I18n.class.php b/WEB-INF/lib/I18n.class.php
index 1760da48..bd4e9ffd 100644
--- a/WEB-INF/lib/I18n.class.php
+++ b/WEB-INF/lib/I18n.class.php
@@ -51,9 +51,16 @@ function get($key) {
return $value;
}
+ // get - keyExists determines if a key exists.
+ function keyExists($key) {
+ $value = $this->get($key);
+ return ($value !== null);
+ }
+
// load - loads localized strings into $keys array by first going through the default file (en.lang.php)
- // and then through the requested language file (which is supplied as parameter).
- // This means we end up with default English strings when keys are missing in the translation file.
+ // and then through the requested language file (which is supplied as parameter),
+ // (this means we end up with default English strings when keys are missing in the translation file),
+ // and then from a group custom translation field, if available.
function load($langName) {
// Load default English keys first.
$defaultFileName = RESOURCE_DIR . '/' . $this->defaultLang . '.lang.php';
@@ -105,6 +112,35 @@ function load($langName) {
}
}
}
+
+ // Now load custom translation for group.
+ global $user;
+ $customTranslation = $user->getCustomTranslation();
+ if ($customTranslation != null) {
+ $lines = preg_split("/\r\n|\n|\r/", $customTranslation);
+ for ($i = 0; $i < count($lines); $i++) {
+ $parts = explode('=', $lines[$i]);
+ if (count($parts) != 2) continue;
+
+ $key = trim($parts[0]);
+ $value = trim($parts[1]);
+ // Escape single quotes and backslashes.
+ $value = addcslashes($value, "'\\");
+ $value = htmlspecialchars($value);
+
+ $pos = strpos($key, ".");
+ if (!($pos === false)) {
+ $p = explode(".", $key);
+ $str = "";
+ foreach ($p as $w) {
+ $str .= "[\"".$w."\"]";
+ }
+ eval("\$this->keys".$str."='".$value."';");
+ } else {
+ $this->keys[$key] = $value;
+ }
+ }
+ }
}
// hasLang determines if a file for requested language exists.
diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php
index 2d8e287d..79aba106 100644
--- a/WEB-INF/lib/common.lib.php
+++ b/WEB-INF/lib/common.lib.php
@@ -126,7 +126,7 @@ function isTrue($val)
}
// ttValidString is used to check user input to validate a string.
-function ttValidString($val, $emptyValid = false)
+function ttValidString($val, $emptyValid = false, $maxChars = 0)
{
if (is_null($val)) {
return $emptyValid ? true : false;
@@ -140,6 +140,10 @@ function ttValidString($val, $emptyValid = false)
if (stristr($val, '';
} elseif ($encode === 'javascript_charcode') {
- $string = '' . $text . '';
for ($x = 0, $_length = strlen($string); $x < $_length; $x++) {
$ord[] = ord($string[ $x ]);
}
- return '';
+ return '';
} elseif ($encode === 'hex') {
preg_match('!^(.*)(\?.*)$!', $address, $match);
if (!empty($match[ 2 ])) {
@@ -129,6 +137,6 @@ function smarty_function_mailto($params)
return '' . $text_encode . '';
} else {
// no encoding
- return '' . $text . '';
+ return $string;
}
}
diff --git a/WEB-INF/lib/smarty/plugins/function.math.php b/WEB-INF/lib/smarty/plugins/function.math.php
index fd5b3d16..f9cf67fe 100644
--- a/WEB-INF/lib/smarty/plugins/function.math.php
+++ b/WEB-INF/lib/smarty/plugins/function.math.php
@@ -69,8 +69,8 @@ function smarty_function_math($params, $template)
// Adapted from https://www.php.net/manual/en/function.eval.php#107377
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))';
- $operators = '[+\/*\^%-]'; // Allowed math operators
- $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)+\)|\((?1)+\)))(?:'.$operators.'(?1))?)+$/';
+ $operators = '[,+\/*\^%-]'; // Allowed math operators
+ $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)*\)|\((?1)*\)))(?:'.$operators.'(?1))?)+$/';
if (!preg_match($regexp, $equation)) {
trigger_error("math: illegal characters", E_USER_WARNING);
diff --git a/WEB-INF/lib/smarty/plugins/modifier.capitalize.php b/WEB-INF/lib/smarty/plugins/modifier.capitalize.php
index c5fc400a..2903d61d 100644
--- a/WEB-INF/lib/smarty/plugins/modifier.capitalize.php
+++ b/WEB-INF/lib/smarty/plugins/modifier.capitalize.php
@@ -22,6 +22,8 @@
*/
function smarty_modifier_capitalize($string, $uc_digits = false, $lc_rest = false)
{
+ $string = (string) $string;
+
if (Smarty::$_MBSTRING) {
if ($lc_rest) {
// uppercase (including hyphenated words)
diff --git a/WEB-INF/lib/smarty/plugins/modifier.count.php b/WEB-INF/lib/smarty/plugins/modifier.count.php
new file mode 100644
index 00000000..ca35fc11
--- /dev/null
+++ b/WEB-INF/lib/smarty/plugins/modifier.count.php
@@ -0,0 +1,36 @@
+ Prior to PHP 8.0.0, if the parameter was neither an array nor an object that implements the Countable interface,
+ * > 1 would be returned, unless value was null, in which case 0 would be returned.
+ */
+
+ if ($arrayOrObject instanceof Countable || is_array($arrayOrObject)) {
+ return count($arrayOrObject, (int) $mode);
+ } elseif ($arrayOrObject === null) {
+ return 0;
+ }
+ return 1;
+}
diff --git a/WEB-INF/lib/smarty/plugins/modifier.date_format.php b/WEB-INF/lib/smarty/plugins/modifier.date_format.php
index 8e7e0b6e..e3589fd0 100644
--- a/WEB-INF/lib/smarty/plugins/modifier.date_format.php
+++ b/WEB-INF/lib/smarty/plugins/modifier.date_format.php
@@ -78,7 +78,8 @@ function smarty_modifier_date_format($string, $format = null, $default_date = ''
}
$format = str_replace($_win_from, $_win_to, $format);
}
- return strftime($format, $timestamp);
+ // @ to suppress deprecation errors when running in PHP8.1 or higher.
+ return @strftime($format, $timestamp);
} else {
return date($format, $timestamp);
}
diff --git a/WEB-INF/lib/smarty/plugins/modifier.escape.php b/WEB-INF/lib/smarty/plugins/modifier.escape.php
index 47489aa9..11e44682 100644
--- a/WEB-INF/lib/smarty/plugins/modifier.escape.php
+++ b/WEB-INF/lib/smarty/plugins/modifier.escape.php
@@ -23,95 +23,25 @@
*/
function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $double_encode = true)
{
- static $_double_encode = true;
static $is_loaded_1 = false;
static $is_loaded_2 = false;
if (!$char_set) {
$char_set = Smarty::$_CHARSET;
}
+
+ $string = (string)$string;
+
switch ($esc_type) {
case 'html':
- if ($_double_encode) {
- // php >=5.3.2 - go native
- return htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode);
- } else {
- if ($double_encode) {
- // php <5.2.3 - only handle double encoding
- return htmlspecialchars($string, ENT_QUOTES, $char_set);
- } else {
- // php <5.2.3 - prevent double encoding
- $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string);
- $string = htmlspecialchars($string, ENT_QUOTES, $char_set);
- $string = str_replace(
- array(
- '%%%SMARTY_START%%%',
- '%%%SMARTY_END%%%'
- ),
- array(
- '&',
- ';'
- ),
- $string
- );
- return $string;
- }
- }
+ return htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode);
// no break
case 'htmlall':
if (Smarty::$_MBSTRING) {
- // mb_convert_encoding ignores htmlspecialchars()
- if ($_double_encode) {
- // php >=5.3.2 - go native
- $string = htmlspecialchars($string, ENT_QUOTES, $char_set, $double_encode);
- } else {
- if ($double_encode) {
- // php <5.2.3 - only handle double encoding
- $string = htmlspecialchars($string, ENT_QUOTES, $char_set);
- } else {
- // php <5.2.3 - prevent double encoding
- $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string);
- $string = htmlspecialchars($string, ENT_QUOTES, $char_set);
- $string =
- str_replace(
- array(
- '%%%SMARTY_START%%%',
- '%%%SMARTY_END%%%'
- ),
- array(
- '&',
- ';'
- ),
- $string
- );
- return $string;
- }
- }
- // htmlentities() won't convert everything, so use mb_convert_encoding
- return mb_convert_encoding($string, 'HTML-ENTITIES', $char_set);
+ $string = mb_convert_encoding($string, 'UTF-8', $char_set);
+ return htmlentities($string, ENT_QUOTES, 'UTF-8', $double_encode);
}
// no MBString fallback
- if ($_double_encode) {
- return htmlentities($string, ENT_QUOTES, $char_set, $double_encode);
- } else {
- if ($double_encode) {
- return htmlentities($string, ENT_QUOTES, $char_set);
- } else {
- $string = preg_replace('!&(#?\w+);!', '%%%SMARTY_START%%%\\1%%%SMARTY_END%%%', $string);
- $string = htmlentities($string, ENT_QUOTES, $char_set);
- $string = str_replace(
- array(
- '%%%SMARTY_START%%%',
- '%%%SMARTY_END%%%'
- ),
- array(
- '&',
- ';'
- ),
- $string
- );
- return $string;
- }
- }
+ return htmlentities($string, ENT_QUOTES, $char_set, $double_encode);
// no break
case 'url':
return rawurlencode($string);
diff --git a/WEB-INF/lib/smarty/plugins/modifier.explode.php b/WEB-INF/lib/smarty/plugins/modifier.explode.php
new file mode 100644
index 00000000..5186fde3
--- /dev/null
+++ b/WEB-INF/lib/smarty/plugins/modifier.explode.php
@@ -0,0 +1,25 @@
+=8.1
+ return explode($separator, $string ?? '', $limit ?? PHP_INT_MAX);
+}
diff --git a/WEB-INF/lib/smarty/plugins/modifier.number_format.php b/WEB-INF/lib/smarty/plugins/modifier.number_format.php
new file mode 100644
index 00000000..8c612601
--- /dev/null
+++ b/WEB-INF/lib/smarty/plugins/modifier.number_format.php
@@ -0,0 +1,26 @@
+=8.1
+ return number_format($num ?? 0.0, $decimals, $decimal_separator, $thousands_separator);
+}
diff --git a/WEB-INF/lib/smarty/plugins/modifier.truncate.php b/WEB-INF/lib/smarty/plugins/modifier.truncate.php
index 33e7e53a..80dcdb53 100644
--- a/WEB-INF/lib/smarty/plugins/modifier.truncate.php
+++ b/WEB-INF/lib/smarty/plugins/modifier.truncate.php
@@ -42,8 +42,8 @@ function smarty_modifier_truncate($string, $length = 80, $etc = '...', $break_wo
if (!$middle) {
return mb_substr($string, 0, $length, Smarty::$_CHARSET) . $etc;
}
- return mb_substr($string, 0, $length / 2, Smarty::$_CHARSET) . $etc .
- mb_substr($string, -$length / 2, $length, Smarty::$_CHARSET);
+ return mb_substr($string, 0, intval($length / 2), Smarty::$_CHARSET) . $etc .
+ mb_substr($string, -intval($length / 2), $length, Smarty::$_CHARSET);
}
return $string;
}
@@ -56,7 +56,7 @@ function smarty_modifier_truncate($string, $length = 80, $etc = '...', $break_wo
if (!$middle) {
return substr($string, 0, $length) . $etc;
}
- return substr($string, 0, $length / 2) . $etc . substr($string, -$length / 2);
+ return substr($string, 0, intval($length / 2)) . $etc . substr($string, -intval($length / 2));
}
return $string;
}
diff --git a/WEB-INF/lib/smarty/plugins/modifiercompiler.escape.php b/WEB-INF/lib/smarty/plugins/modifiercompiler.escape.php
index 70b95cc9..602c3dbf 100644
--- a/WEB-INF/lib/smarty/plugins/modifiercompiler.escape.php
+++ b/WEB-INF/lib/smarty/plugins/modifiercompiler.escape.php
@@ -18,12 +18,10 @@
* @param Smarty_Internal_TemplateCompilerBase $compiler
*
* @return string with compiled code
- * @throws \SmartyException
+ * @throws SmartyException
*/
function smarty_modifiercompiler_escape($params, Smarty_Internal_TemplateCompilerBase $compiler)
{
- static $_double_encode = true;
- static $is_loaded = false;
$compiler->template->_checkPlugins(
array(
array(
@@ -41,53 +39,30 @@ function smarty_modifiercompiler_escape($params, Smarty_Internal_TemplateCompile
}
switch ($esc_type) {
case 'html':
- if ($_double_encode) {
- return 'htmlspecialchars(' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
- var_export($double_encode, true) . ')';
- } elseif ($double_encode) {
- return 'htmlspecialchars(' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ')';
- } else {
- // fall back to modifier.escape.php
- }
+ return 'htmlspecialchars((string)' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
+ var_export($double_encode, true) . ')';
// no break
case 'htmlall':
if (Smarty::$_MBSTRING) {
- if ($_double_encode) {
- // php >=5.2.3 - go native
- return 'mb_convert_encoding(htmlspecialchars(' . $params[ 0 ] . ', ENT_QUOTES, ' .
- var_export($char_set, true) . ', ' . var_export($double_encode, true) .
- '), "HTML-ENTITIES", ' . var_export($char_set, true) . ')';
- } elseif ($double_encode) {
- // php <5.2.3 - only handle double encoding
- return 'mb_convert_encoding(htmlspecialchars(' . $params[ 0 ] . ', ENT_QUOTES, ' .
- var_export($char_set, true) . '), "HTML-ENTITIES", ' . var_export($char_set, true) . ')';
- } else {
- // fall back to modifier.escape.php
- }
+ return 'htmlentities(mb_convert_encoding((string)' . $params[ 0 ] . ', \'UTF-8\', ' .
+ var_export($char_set, true) . '), ENT_QUOTES, \'UTF-8\', ' .
+ var_export($double_encode, true) . ')';
}
// no MBString fallback
- if ($_double_encode) {
- // php >=5.2.3 - go native
- return 'htmlentities(' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
- var_export($double_encode, true) . ')';
- } elseif ($double_encode) {
- // php <5.2.3 - only handle double encoding
- return 'htmlentities(' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ')';
- } else {
- // fall back to modifier.escape.php
- }
+ return 'htmlentities((string)' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
+ var_export($double_encode, true) . ')';
// no break
case 'url':
- return 'rawurlencode(' . $params[ 0 ] . ')';
+ return 'rawurlencode((string)' . $params[ 0 ] . ')';
case 'urlpathinfo':
- return 'str_replace("%2F", "/", rawurlencode(' . $params[ 0 ] . '))';
+ return 'str_replace("%2F", "/", rawurlencode((string)' . $params[ 0 ] . '))';
case 'quotes':
// escape unescaped single quotes
- return 'preg_replace("%(? "\\\\\\\\", "\'" => "\\\\\'", "\"" => "\\\\\"", "\\r" => "\\\\r", "\\n" => "\\\n", "" => "<\/", "