diff --git a/2fa.php b/2fa.php new file mode 100644 index 00000000..a7e3dbb2 --- /dev/null +++ b/2fa.php @@ -0,0 +1,64 @@ +getParameter('login'); +if ($cl_login == null && $request->isGet()) $cl_login = @$_COOKIE[LOGIN_COOKIE_NAME]; +$cl_password = $request->getParameter('password'); +$cl_auth_code = $request->getParameter('auth_code'); + +$form = new Form('twoFactorAuthForm'); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'login','value'=>$cl_login)); +$form->getElement('login')->setEnabled(false); +$form->addInput(array('type'=>'password','maxlength'=>'50','name'=>'password','value'=>$cl_password)); +$form->addInput(array('type'=>'text','maxlength'=>'100','name'=>'auth_code','value'=>$cl_auth_code)); +$form->addInput(array('type'=>'submit','name'=>'btn_login','value'=>$i18n->get('button.login'))); + +if ($request->isPost()) { + // Validate user input. + if (!ttValidString($cl_login)) $err->add($i18n->get('error.field'), $i18n->get('label.login')); + if (!ttValidString($cl_password)) $err->add($i18n->get('error.field'), $i18n->get('label.password')); + if (!ttValidString($cl_auth_code)) $err->add($i18n->get('error.field'), $i18n->get('form.2fa.2fa_code')); + + if ($err->no()) { + // Get user id. + $user_id = ttUserHelper::getUserIdByTmpRef($cl_auth_code); + if (!$user_id) + $err->add($i18n->get('error.2fa_code')); + + if ($err->no()) { + // Additionally check user password for better protection + // against brute force attacks guessing 2FA codes. + $user = new ttUser(null, $user_id); // Note: reusing $user from initialize.php. + // Check user password. + if (!$auth->doLogin($user->login, $cl_password)) + $err->add($i18n->get('error.auth')); + } + + if ($err->no()) { + // Redirect, depending on user role. + if ($user->isClient()) { + header('Location: reports.php'); + } else { + header('Location: time.php'); + } + exit(); + } + } +} // isPost + +$smarty->assign('forms', array($form->getName()=>$form->toArray())); +$smarty->assign('onload', 'onLoad="document.twoFactorAuthForm.auth_code.focus()"'); +$smarty->assign('title', $i18n->get('title.2fa')); +$smarty->assign('content_page_name', '2fa.tpl'); +$smarty->display('index.tpl'); 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/DateAndTime.class.php b/WEB-INF/lib/DateAndTime.class.php deleted file mode 100644 index e7224598..00000000 --- a/WEB-INF/lib/DateAndTime.class.php +++ /dev/null @@ -1,348 +0,0 @@ -$sDate parsed, or false on error. - */ - -function my_strptime($sDate, $sFormat) -{ - $aResult = array - ( - 'tm_sec' => 0, - 'tm_min' => 0, - 'tm_hour' => 0, - 'tm_mday' => 1, - 'tm_mon' => 0, - 'tm_year' => 0, - 'tm_wday' => 0, - 'tm_yday' => 0, - 'unparsed' => $sDate, - ); - - while($sFormat != "") - { - // ===== Search a %x element, Check the static string before the %x ===== - $nIdxFound = strpos($sFormat, '%'); - if($nIdxFound === false) - { - - // There is no more format. Check the last static string. - $aResult['unparsed'] = ($sFormat == $sDate) ? "" : $sDate; - break; - } - - $sFormatBefore = mb_substr($sFormat, 0, $nIdxFound); - $sDateBefore = mb_substr($sDate, 0, $nIdxFound); - - if($sFormatBefore != $sDateBefore) break; - - // ===== Read the value of the %x found ===== - $sFormat = mb_substr($sFormat, $nIdxFound); - $sDate = mb_substr($sDate, $nIdxFound); - - $aResult['unparsed'] = $sDate; - - $sFormatCurrent = mb_substr($sFormat, 0, 2); - $sFormatAfter = mb_substr($sFormat, 2); - - $nValue = -1; - $sDateAfter = ""; - switch($sFormatCurrent) - { - case '%S': // Seconds after the minute (0-59) - - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_sec'] = $nValue; - break; - - // ---------- - case '%M': // Minutes after the hour (0-59) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 59)) return false; - - $aResult['tm_min'] = $nValue; - break; - - // ---------- - case '%H': // Hour since midnight (0-23) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 0) || ($nValue > 23)) return false; - - $aResult['tm_hour'] = $nValue; - break; - - // ---------- - case '%d': // Day of the month (1-31) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 31)) return false; - - $aResult['tm_mday'] = $nValue; - break; - - // ---------- - case '%m': // Months since January (0-11) - sscanf($sDate, "%2d%[^\\n]", $nValue, $sDateAfter); - - if(($nValue < 1) || ($nValue > 12)) return false; - - $aResult['tm_mon'] = ($nValue - 1); - break; - - // ---------- - case '%Y': // Years since 1900 - sscanf($sDate, "%4d%[^\\n]", $nValue, $sDateAfter); - - if($nValue < 1900) return false; - - $aResult['tm_year'] = ($nValue - 1900); - break; - - // ---------- - default: - //sscanf($sDate, "%s%[^\\n]", $skip, $sDateAfter); - preg_match('/^(.+)(\s|$)/uU', $sDate, $matches); - if (isset($matches[1])) { - $sDateAfter = mb_substr($sDate, mb_strlen($matches[1])); - } else { - $sDateAfter = ''; - } - //break 2; // Break Switch and while - break; - } - - // ===== Next please ===== - $sFormat = $sFormatAfter; - $sDate = $sDateAfter; - - $aResult['unparsed'] = $sDate; - - } // END while($sFormat != "") - - - // ===== Create the other value of the result array ===== - $nParsedDateTimestamp = mktime($aResult['tm_hour'], $aResult['tm_min'], $aResult['tm_sec'], - $aResult['tm_mon'] + 1, $aResult['tm_mday'], $aResult['tm_year'] + 1900); - - // Before PHP 5.1 return -1 when error - if(($nParsedDateTimestamp === false) - ||($nParsedDateTimestamp === -1)) return false; - - $aResult['tm_wday'] = (int) strftime("%w", $nParsedDateTimestamp); // Days since Sunday (0-6) - $aResult['tm_yday'] = (strftime("%j", $nParsedDateTimestamp) - 1); // Days since January 1 (0-365) - - return $aResult; -} // END of function - -class DateAndTime { - var $mHour = 0; - var $mMinute = 0; - var $mSecond = 0; - var $mMonth; - var $mDay; // day of week - var $mDate; // day of month - var $mYear; - var $mIntrFormat = "%d.%m.%Y %H:%M:%S"; //29.02.2004 16:21:42 internal format date - var $mLocalFormat; - var $mParseResult = 0; - var $mAutoComplete = true; - - /** - * Constructor - * - * @param String $format - * @param String $strfDateTime - * @return DateAndTime - */ - function __construct($format="",$strfDateTime="") { - $this->mLocalFormat = ($format ? $format : $this->mIntrFormat); - $d = ($strfDateTime ? $strfDateTime : $this->do_strftime($this->mLocalFormat)); - $this->parseVal($d); - } - - function setFormat($format) { - $this->mLocalFormat = $format; - } - - function getFormat() { - return $this->mLocalFormat; - } - - //01 to 31 - function getDate() { return $this->mDate; } - - //0 (for Sunday) through 6 (for Saturday) - function getDay() { return $this->mDay; } - - //01 through 12 - function getMonth() { return $this->mMonth; } - - //1999 or 2003 - function getYear() { return $this->mYear; } - - function setDate($value) { $this->mDate = $value; } - function setMonth($value) { $this->mMonth = $value; } - function setYear($value) { $this->mYear = $value; } - - function setTimestamp($ts) { - $this->mDate = date("d",$ts); - $this->mDay = date("w",$ts); - $this->mMonth = date("m",$ts); - $this->mYear = date("Y",$ts); - $this->mHour = date("H",$ts); - $this->mMinute = date("i",$ts); - $this->mSecond = date("s",$ts); - } - - /** - * Return UNIX timestamp - */ - function getTimestamp() { - return @mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate, $this->mYear); - } - - function compare($datetime) { - $ts1 = $this->getTimestamp(); - $ts2 = $datetime->getTimestamp(); - if ($ts1<$ts2) return -1; - if ($ts1==$ts2) return 0; - if ($ts1>$ts2) return 1; - } - - function toString($format="") { - if ($this->mParseResult==0) { - if ($format) { - return $this->do_strftime($format, $this->getTimestamp()); - } else { - return $this->do_strftime($this->mLocalFormat, $this->getTimestamp()); - } - } else { - if ($format) { - return $this->do_strftime($format); - } else { - return $this->do_strftime($this->mLocalFormat); - } - } - } - - function parseVal($szDate, $format="") { - $useformat = ($format ? $format : $this->mLocalFormat); - $res = my_strptime($szDate, $useformat); - if ($res !== false) { - $this->mDate = $res['tm_mday']; - $this->mDay = $res['tm_wday']; - $this->mMonth = $res['tm_mon'] + 1; // tm_mon - Months since January (0-11) - $this->mYear = 1900 + $res['tm_year']; // tm_year - Years since 1900 - $this->mHour = $res['tm_hour']; - $this->mMinute = $res['tm_min']; - $this->mSecond = $res['tm_sec']; - $this->mParseResult = 0; - } elseif ($this->mAutoComplete) { - $this->setTimestamp(time()); - $this->mParseResult = 1; - } - } - - function isError() { - if ($this->mParseResult != 0) return true; - return false; - } - - function before(/*DateAndTime*/ $obj) { - if ($this->getTimestamp() < $obj->getTimestamp()) return true; - return false; - } - - function after(/*DateAndTime*/ $obj) { - if ($this->getTimestamp() > $obj->getTimestamp()) return true; - return false; - } - - function equals(/*DateAndTime*/ $obj) { - if ($this->getTimestamp() == $obj->getTimestamp()) return true; - return false; - } - - function decDay(/*int*/$days=1) { - $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate - $days, $this->mYear)); - } - - function incDay(/*int*/$days=1) { - $this->setTimestamp(@mktime($this->mHour, $this->mMinute, $this->mSecond, $this->mMonth, $this->mDate + $days, $this->mYear)); - } - - /** - * @param $format string Datetime format string - * @return string Preprocessed string with all locale-depended format - * characters replaced by localized i18n strings. - */ - function preprocessFormatString($format) { - global $i18n; - - // replace locale-dependent strings - $format = str_replace('%a', mb_substr($i18n->getWeekDayName($this->mDay), 0, 3, 'utf-8'), $format); - $format = str_replace('%A', $i18n->getWeekDayName($this->mDay), $format); - /* This block is commented out because we currently do not use these formatters. - $abbrev_month = mb_substr($i18n->monthNames[$this->mMonth], 0, 3, 'utf-8'); - $format = str_replace('%b', $abbrev_month, $format); - $format = str_replace('%h', $abbrev_month, $format); - $format = str_replace('%z', date('O'), $format); - $format = str_replace('%Z', date('O'), $format); // format as 'O' for consistency with JS strftime - if (strpos($format, '%c') !== false) { - $format = str_replace('%c', $this->preprocessFormatString('%a %d %b %Y %T %Z'), $format); - }*/ - return $format; - } - - function do_strftime($format, $timestamp = null) - { - if (!is_null($timestamp)) { - return strftime($this->preprocessFormatString($format), $timestamp); - } else { - return strftime($this->preprocessFormatString($format)); - } - } -} 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/Period.class.php b/WEB-INF/lib/Period.class.php deleted file mode 100644 index 57ea0e44..00000000 --- a/WEB-INF/lib/Period.class.php +++ /dev/null @@ -1,136 +0,0 @@ -getWeekStart(); - - $this->startDate = new DateAndTime(); - $this->startDate->setFormat($date_point->getFormat()); - $this->endDate = new DateAndTime(); - $this->endDate->setFormat($date_point->getFormat()); - $t_arr = localtime($date_point->getTimestamp()); - $t_arr[5] = $t_arr[5] + 1900; - - if ($t_arr[6] < $weekStartDay) { - $startWeekBias = $weekStartDay - 7; - } else { - $startWeekBias = $weekStartDay; - } - - switch ($period_type) { - case INTERVAL_THIS_DAY: - $this->startDate->setTimestamp($date_point->getTimestamp()); - $this->endDate->setTimestamp($date_point->getTimestamp()); - break; - - case INTERVAL_LAST_DAY: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-1,$t_arr[5])); - break; - - case INTERVAL_THIS_WEEK: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+$startWeekBias,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]+6+$startWeekBias,$t_arr[5])); - break; - case INTERVAL_LAST_WEEK: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-7+$startWeekBias,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,$t_arr[3]-$t_arr[6]-1+$startWeekBias,$t_arr[5])); - break; - case INTERVAL_THIS_MONTH: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+2,0,$t_arr[5])); - break; - case INTERVAL_LAST_MONTH: - $this->startDate->setTimestamp(mktime(0,0,0,$t_arr[4],1,$t_arr[5])); - $this->endDate->setTimestamp(mktime(0,0,0,$t_arr[4]+1,0,$t_arr[5])); - break; - - case INTERVAL_THIS_YEAR: - $this->startDate->setTimestamp(mktime(0, 0, 0, 1, 1, $t_arr[5])); - $this->endDate->setTimestamp(mktime(0, 0, 0, 12, 31, $t_arr[5])); - break; - } - } - - function setPeriod($b_date, $e_date) { - $this->startDate = $b_date; - $this->endDate = $e_date; - } - - // return date string - function getStartDate($format="") { - return $this->startDate->toString($format); - } - - // return date string - function getEndDate($format="") { - return $this->endDate->toString($format); - } -} diff --git a/WEB-INF/lib/common.lib.php b/WEB-INF/lib/common.lib.php index db00923a..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", " "<\/", "