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", " "<\/", "