From 4c677ff07d6fe9c6f0677db409ead331c66023a4 Mon Sep 17 00:00:00 2001 From: Lapiu Dev Date: Thu, 8 Jun 2023 07:50:49 -0600 Subject: [PATCH] Fixed issue #18401: Unable to use checkdate function if debug set checkdate wrap added. --- .../helpers/expressions/em_core_helper.php | 160 +++++++++--------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/application/helpers/expressions/em_core_helper.php b/application/helpers/expressions/em_core_helper.php index f847d60f5d8..f975146cd48 100644 --- a/application/helpers/expressions/em_core_helper.php +++ b/application/helpers/expressions/em_core_helper.php @@ -39,23 +39,20 @@ class ExpressionManager 'valueNAOK', 'value', ); - /* var string[] allowable static suffixes for variables - each represents an attribute of a variable that can not be updated on same page - * @see LimeExpressionManager->knownVars definition - */ + // These are the allowable static suffixes for variables - each represents an attribute of a variable that can not be updated on same page private $aRDP_regexpStaticAttribute = array( 'qid', - 'gid', - 'question', - 'sgqa', - 'type', - 'relevance', 'grelevance', - 'qseq', 'gseq', 'jsName', - 'jsName_on', 'mandatory', + 'qid', + 'qseq', + 'question', + 'relevance', 'rowdivid', + 'sgqa', + 'type', ); // These three variables are effectively static once constructed private $RDP_ExpressionRegex; @@ -202,7 +199,7 @@ function __construct() 'atan' => array('atan', 'Decimal.asNum.atan', gT('Arc tangent'), 'number atan(number)', 'http://php.net/atan', 1), 'atan2' => array('atan2', 'Decimal.asNum.atan2', gT('Arc tangent of two variables'), 'number atan2(number, number)', 'http://php.net/atan2', 2), 'ceil' => array('ceil', 'Decimal.asNum.ceil', gT('Round fractions up'), 'number ceil(number)', 'http://php.net/ceil', 1), - 'checkdate' => array('checkdate', 'checkdate', gT('Returns true(1) if it is a valid date in gregorian calendar'), 'bool checkdate(month,day,year)', 'http://php.net/checkdate', 3), + 'checkdate' => array('exprmgr_checkdate', 'checkdate', gT('Returns true(1) if it is a valid date in gregorian calendar'), 'bool checkdate(month,day,year)', 'http://php.net/checkdate', 3), 'cos' => array('cos', 'Decimal.asNum.cos', gT('Cosine'), 'number cos(number)', 'http://php.net/cos', 1), 'count' => array('exprmgr_count', 'LEMcount', gT('Count the number of answered questions in the list'), 'number count(arg1, arg2, ... argN)', '', -1), 'countif' => array('exprmgr_countif', 'LEMcountif', gT('Count the number of answered questions in the list equal the first argument'), 'number countif(matches, arg1, arg2, ... argN)', '', -2), @@ -358,8 +355,8 @@ private function getMismatchInformation(array $arg1, array $arg2) { /* When value come from DB : it's set to 1.000000 (DECIMAL) : must be fixed see #11163. Response::model() must fix this . or not ? */ /* Don't return true always : user can entre non numeric value in a numeric value : we must compare as string then */ - $arg1[0] = ($arg1[2] == "NUMBER" && strpos((string) $arg1[0], ".")) ? rtrim(rtrim((string) $arg1[0], "0"), ".") : $arg1[0]; - $arg2[0] = ($arg2[2] == "NUMBER" && strpos((string) $arg2[0], ".")) ? rtrim(rtrim((string) $arg2[0], "0"), ".") : $arg2[0]; + $arg1[0] = ($arg1[2] == "NUMBER" && strpos($arg1[0], ".")) ? rtrim(rtrim($arg1[0], "0"), ".") : $arg1[0]; + $arg2[0] = ($arg2[2] == "NUMBER" && strpos($arg2[0], ".")) ? rtrim(rtrim($arg2[0], "0"), ".") : $arg2[0]; $bNumericArg1 = $arg1[0] !== "" && (!$arg1[0] || strval(floatval($arg1[0])) == strval($arg1[0])); $bNumericArg2 = $arg2[0] !== "" && (!$arg2[0] || strval(floatval($arg2[0])) == strval($arg2[0])); @@ -410,7 +407,7 @@ public function RDP_EvaluateBinary(array $token) } } - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case 'or': case '||': $result = array(($arg1[0] or $arg2[0]), $token[1], 'NUMBER'); @@ -438,7 +435,7 @@ public function RDP_EvaluateBinary(array $token) if ($isForcedString) { $this->RDP_AddWarning(new EMWarningInvalidComparison($token)); } - $result = array(strcmp((string) $arg1[0], (string) $arg2[0]) < 0, $token[1], 'NUMBER'); + $result = array(strcmp($arg1[0], $arg2[0]) < 0, $token[1], 'NUMBER'); } else { $result = array(($arg1[0] < $arg2[0]), $token[1], 'NUMBER'); } @@ -458,7 +455,7 @@ public function RDP_EvaluateBinary(array $token) if ($isForcedString) { $this->RDP_AddWarning(new EMWarningInvalidComparison($token)); } - $result = array(strcmp((string) $arg1[0], (string) $arg2[0]) <= 0, $token[1], 'NUMBER'); + $result = array(strcmp($arg1[0], $arg2[0]) <= 0, $token[1], 'NUMBER'); } else { $result = array(($arg1[0] <= $arg2[0]), $token[1], 'NUMBER'); } @@ -479,7 +476,7 @@ public function RDP_EvaluateBinary(array $token) if ($isForcedString) { $this->RDP_AddWarning(new EMWarningInvalidComparison($token)); } - $result = array(strcmp((string) $arg1[0], (string) $arg2[0]) > 0, $token[1], 'NUMBER'); + $result = array(strcmp($arg1[0], $arg2[0]) > 0, $token[1], 'NUMBER'); } else { $result = array(($arg1[0] > $arg2[0]), $token[1], 'NUMBER'); } @@ -496,7 +493,7 @@ public function RDP_EvaluateBinary(array $token) if ($isForcedString) { $this->RDP_AddWarning(new EMWarningInvalidComparison($token)); } - $result = array(strcmp((string) $arg1[0], (string) $arg2[0]) >= 0, $token[1], 'NUMBER'); + $result = array(strcmp($arg1[0], $arg2[0]) >= 0, $token[1], 'NUMBER'); } else { $result = array(($arg1[0] >= $arg2[0]), $token[1], 'NUMBER'); } @@ -690,7 +687,7 @@ private function RDP_EvaluateConstantVarOrFunction() } else { if ($this->RDP_isValidVariable($token[0])) { $this->varsUsed[] = $token[0]; // add this variable to list of those used in this equation - if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $token[0])) { + if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $token[0])) { $relStatus = 1; // static, so always relevant } else { $relStatus = $this->GetVarAttribute($token[0], 'relevanceStatus', 1); @@ -731,7 +728,7 @@ private function RDP_EvaluateEqualityExpression() } while (($this->RDP_pos + 1) < $this->RDP_count) { $token = $this->RDP_tokens[++$this->RDP_pos]; - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case '==': case 'eq': case '!=': @@ -906,7 +903,7 @@ private function RDP_EvaluateLogicalAndExpression() } while (($this->RDP_pos + 1) < $this->RDP_count) { $token = $this->RDP_tokens[++$this->RDP_pos]; - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case '&&': case 'and': if ($this->RDP_EvaluateEqualityExpression()) { @@ -937,7 +934,7 @@ private function RDP_EvaluateLogicalOrExpression() } while (($this->RDP_pos + 1) < $this->RDP_count) { $token = $this->RDP_tokens[++$this->RDP_pos]; - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case '||': case 'or': if ($this->RDP_EvaluateLogicalAndExpression()) { @@ -1038,7 +1035,7 @@ private function RDP_EvaluateRelationExpression() } while (($this->RDP_pos + 1) < $this->RDP_count) { $token = $this->RDP_tokens[++$this->RDP_pos]; - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case '<': case 'lt': case '<='; @@ -1112,7 +1109,7 @@ public function GetAllJsVarsUsed() } $jsNames = array(); foreach ($names as $name) { - if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $name)) { + if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $name)) { continue; } $val = $this->GetVarAttribute($name, 'jsName', ''); @@ -1142,7 +1139,7 @@ public function GetOnPageJsVarsUsed() } $jsNames = array(); foreach ($names as $name) { - if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $name)) { + if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $name)) { continue; } $val = $this->GetVarAttribute($name, 'jsName', ''); @@ -1181,7 +1178,7 @@ public function GetJsVarsUsed() } $jsNames = array(); foreach ($names as $name) { - if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $name)) { + if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $name)) { continue; } $val = $this->GetVarAttribute($name, 'jsName', ''); @@ -1251,15 +1248,11 @@ public function GetJavaScriptEquivalentOfExpression() return ''; } $tokens = $this->RDP_tokens; - /* @var string|null used for ASSIGN expression */ - $idToSet = null; - /* @var string[] the final expression line by line (to be join at end) */ $stringParts = array(); $numTokens = count($tokens); - /* @var integer bracket count for static function management */ + /* Static function management */ $bracket = 0; - /* @var string static string to be parsed bedfore send to JS */ $staticStringToParse = ""; for ($i = 0; $i < $numTokens; ++$i) { $token = $tokens[$i]; // When do these need to be quoted? @@ -1294,10 +1287,10 @@ public function GetJavaScriptEquivalentOfExpression() } else { switch ($token[2]) { case 'DQ_STRING': - $stringParts[] = '"' . addcslashes((string) $token[0], '\"') . '"'; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'"; + $stringParts[] = '"' . addcslashes($token[0], '\"') . '"'; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'"; break; case 'SQ_STRING': - $stringParts[] = "'" . addcslashes((string) $token[0], "\'") . "'"; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'"; + $stringParts[] = "'" . addcslashes($token[0], "\'") . "'"; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'"; break; case 'SGQA': case 'WORD': @@ -1313,18 +1306,15 @@ public function GetJavaScriptEquivalentOfExpression() } } elseif ($i + 1 < $numTokens && $tokens[$i + 1][2] == 'ASSIGN') { $jsName = $this->GetVarAttribute($token[0], 'jsName', ''); - /* Value is in the page : can not set */ - if (!empty($jsName)) { - $idToSet = $jsName; - if ($tokens[$i + 1][0] == '+=') { - // Javascript does concatenation unless both left and right side are numbers, so refactor the equation - $varName = $this->GetVarAttribute($token[0], 'varName', $token[0]); - $stringParts[] = " = LEMval('" . $varName . "') + "; - ++$i; - } + $stringParts[] = "document.getElementById('" . $jsName . "').value"; + if ($tokens[$i + 1][0] == '+=') { + // Javascript does concatenation unless both left and right side are numbers, so refactor the equation + $varName = $this->GetVarAttribute($token[0], 'varName', $token[0]); + $stringParts[] = " = LEMval('" . $varName . "') + "; + ++$i; } } else { - if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $token[0])) { + if (preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $token[0])) { /* This is a static variables : set as static */ $static = $this->sProcessStringContainingExpressions("{" . $token[0] . "}", 0, 1, 1, -1, -1, true); $stringParts[] = "'" . addcslashes($static, "'") . "'"; @@ -1352,7 +1342,7 @@ public function GetJavaScriptEquivalentOfExpression() break; default: // don't need to check type of $token[2] here since already handling SQ_STRING and DQ_STRING above - switch (strtolower((string) $token[0])) { + switch (strtolower($token[0])) { case 'and': $stringParts[] = ' && '; break; @@ -1379,9 +1369,6 @@ public function GetJavaScriptEquivalentOfExpression() case '!=': $stringParts[] = ' != '; break; - case '=': - /* ASSIGN : usage jquery: don't add anything (disable default) */; - break; default: $stringParts[] = ' ' . $token[0] . ' '; break; @@ -1398,17 +1385,13 @@ public function GetJavaScriptEquivalentOfExpression() * see https://bugs.limesurvey.org/view.php?id=18008 for issue about sgqa and question * See https://bugs.limesurvey.org/view.php?id=14818 for feature */ - if (!preg_match("/^.*\.(NAOK|valueNAOK|shown|relevanceStatus)$/", (string) $var) && !preg_match("/^.*\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $var)) { + if (!preg_match("/^.*\.(NAOK|valueNAOK|shown|relevanceStatus)$/", $var) && !preg_match("/^.*\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $var)) { if ($this->GetVarAttribute($var, 'jsName', '') != '') { $nonNAvarsUsed[] = $var; } } } $mainClause = implode('', $stringParts); - if ($idToSet) { - /* If there are an id to set (assign) : set it via jquery */ - $mainClause = "$('#{$idToSet}').val({$mainClause})"; - } $varsUsed = implode("', '", $nonNAvarsUsed); if ($varsUsed != '') { $this->jsExpression = "LEMif(LEManyNA('" . $varsUsed . "'),'',(" . $mainClause . "))"; @@ -1584,11 +1567,11 @@ public function GetPrettyPrintString() } // Show variable name instead of SGQA code, if available if ($qcode != '') { - if (preg_match('/^INSERTANS:/', (string) $token[0])) { + if (preg_match('/^INSERTANS:/', $token[0])) { $displayName = $qcode . '.shown'; $descriptor = '[' . $token[0] . ']'; } else { - $args = explode('.', (string) $token[0]); + $args = explode('.', $token[0]); if (count($args) == 2) { $displayName = $qcode . '.' . $args[1]; } else { @@ -1613,7 +1596,7 @@ public function GetPrettyPrintString() $messages[] = $ansList; } if ($code != '') { - if ($token[2] == 'SGQA' && preg_match('/^INSERTANS:/', (string) $token[0])) { + if ($token[2] == 'SGQA' && preg_match('/^INSERTANS:/', $token[0])) { $shown = $this->GetVarAttribute($token[0], 'shown', ''); $messages[] = 'value=[' . $code . '] ' . $shown; @@ -1645,8 +1628,8 @@ public function GetPrettyPrintString() $stringParts[] = ""; } if ($this->sgqaNaming) { - $sgqa = substr((string) $jsName, 4); - $nameParts = explode('.', (string) $displayName); + $sgqa = substr($jsName, 4); + $nameParts = explode('.', $displayName); if (count($nameParts) == 2) { $sgqa .= '.' . $nameParts[1]; } @@ -1916,7 +1899,7 @@ public function ProcessBooleanExpression($expr, $groupSeq = -1, $questionSeq = - /* this function wants to see the NAOK suffix : NAOK|valueNAOK|shown|relevanceStatus * Static suffix are always OK (no need NAOK) */ - if (!preg_match("/^.*\.(NAOK|valueNAOK|shown|relevanceStatus)$/", (string) $var) && ! preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", (string) $var)) { + if (!preg_match("/^.*\.(NAOK|valueNAOK|shown|relevanceStatus)$/", $var) && ! preg_match("/\.(" . $this->getRegexpStaticValidAttributes() . ")$/", $var)) { if (!LimeExpressionManager::GetVarAttribute($var, 'relevanceStatus', false, $groupSeq, $questionSeq)) { return false; } @@ -2010,7 +1993,7 @@ public function sProcessStringContainingExpressionsHelper($src, $questionNum, $s $prettyPrintParts[] = $stringPart[0]; } else { ++$this->substitutionNum; - $expr = $this->ExpandThisVar(substr((string) $stringPart[0], 1, -1)); + $expr = $this->ExpandThisVar(substr($stringPart[0], 1, -1)); if ($this->RDP_Evaluate($expr, false, $this->resetErrorsAndWarningsOnEachPart)) { $resolvedPart = $this->GetResult(); } else { @@ -2072,7 +2055,7 @@ function ExpandThisVar($src) case 'SGQA': case 'WORD': $splitter = '(?:\b(?:self|that))(?:\.(?:[A-Z0-9_]+))*'; // self or that, optionaly followed by dot and alnum - if (preg_match("/" . $splitter . "/", (string) $token[0])) { + if (preg_match("/" . $splitter . "/", $token[0])) { $setInCache = false; $expandedVar .= LimeExpressionManager::GetAllVarNamesForQ($this->questionSeq, $token[0]); } else { @@ -2723,7 +2706,7 @@ function exprmgr_countifop($args) break; case 'RX': try { - if (@preg_match($value, (string) $arg)) { + if (@preg_match($value, $arg)) { ++$j; } } catch (Exception $e) { @@ -2872,7 +2855,7 @@ function exprmgr_sumifop($args) break; case 'RX': try { - if (@preg_match($value, (string) $arg)) { + if (@preg_match($value, $arg)) { $result += $arg; } } catch (Exception $e) { @@ -2884,6 +2867,28 @@ function exprmgr_sumifop($args) return $result; } +/** + * Validate a Gregorian date + * @see https://www.php.net/checkdate + * Check if all params are valid before send it to PHP checkdate to avoid PHP Warning + * + * @param mixed $month + * @param mixed $day + * @param mixed $year + * @return boolean + */ +function exprmgr_checkdate($month, $day, $year) +{ + if ( + (!ctype_digit($month) && !is_int($month)) + || (!ctype_digit($day) && !is_int($day)) + || (!ctype_digit($year) && !is_int($year)) + ) { + return false; + } + return checkdate(intval($month), intval($day), intval($year)); +} + /** * Find the closest matching Numerical input values in a list an replace it by the * corresponding value within another list @@ -2938,7 +2943,7 @@ function exprmgr_convert_value($fValueToReplace, $iStrict, $sTranslateFromList, */ function exprmgr_date($format, $timestamp = null) { - $timestamp = $timestamp ?? time(); + $timestamp = isset($timestamp) ? $timestamp : time(); if (!is_numeric($timestamp)) { return false; } @@ -3017,10 +3022,10 @@ function exprmgr_listifop($args) $glue = array_shift($args); $validAttributes = "/" . LimeExpressionManager::getRegexpValidAttributes() . "/"; - if (! preg_match($validAttributes, (string) $cmpAttr)) { + if (! preg_match($validAttributes, $cmpAttr)) { return $cmpAttr . " not recognized ?!"; } - if (! preg_match($validAttributes, (string) $retAttr)) { + if (! preg_match($validAttributes, $retAttr)) { return $retAttr . " not recognized ?!"; } @@ -3055,7 +3060,7 @@ function exprmgr_listifop($args) break; case 'RX': try { - $match = preg_match($value, (string) $cmpVal); + $match = preg_match($value, $cmpVal); } catch (Exception $ex) { return "Invalid RegEx"; } @@ -3088,7 +3093,7 @@ function exprmgr_log($args) if (!is_numeric($number)) { return NAN; } - $base = $args[1] ?? exp(1); + $base = (isset($args[1])) ? $args[1] : exp(1); if (!is_numeric($base)) { return NAN; } @@ -3111,13 +3116,13 @@ function exprmgr_log($args) */ function exprmgr_mktime($hour = null, $minute = null, $second = null, $month = null, $day = null, $year = null) { - $hour = $hour ?? date("H"); - $minute = $minute ?? date("i"); - $second = $second ?? date("s"); - $month = $month ?? date("n"); - $day = $day ?? date("j"); - $year = $year ?? date("Y"); - $hour = $hour ?? date("H"); + $hour = isset($hour) ? $hour : date("H"); + $minute = isset($minute) ? $minute : date("i"); + $second = isset($second) ? $second : date("s"); + $month = isset($month) ? $month : date("n"); + $day = isset($day) ? $day : date("j"); + $year = isset($year) ? $year : date("Y"); + $hour = isset($hour) ? $hour : date("H"); $iInvalidArg = count(array_filter(array($hour, $minute, $second, $month, $day, $year), function ($timeValue) { return !is_numeric($timeValue); /* This allow get by string like "01.000" , same than javascript with 2.72.6 and default PHP(5.6) function*/ })); @@ -3224,7 +3229,7 @@ function expr_mgr_htmlspecialchars_decode($string) function exprmgr_regexMatch($pattern, $input) { // Test the regexp pattern agains null : must always return 0, false if error happen - if (@preg_match($pattern . 'u', '') === false) { + if (@preg_match($pattern . 'u', null) === false) { return false; // invalid : true or false ? } // 'u' is the regexp modifier for unicode so that non-ASCII string will be validated properly @@ -3239,7 +3244,7 @@ function exprmgr_regexMatch($pattern, $input) function geterrors_exprmgr_regexMatch($pattern, $input) { // @todo : use set_error_handler to get the preg_last_error - if (@preg_match($pattern . 'u', '') === false) { + if (@preg_match($pattern . 'u', null) === false) { return sprintf(ExpressionManager::gT('Invalid PERL Regular Expression: %s'), htmlspecialchars($pattern)); } } @@ -3266,7 +3271,7 @@ function exprmgr_unique($args) { $uniqs = array(); foreach ($args as $arg) { - if (trim((string) $arg) == '') { + if (trim($arg) == '') { continue; // ignore blank answers } if (isset($uniqs[$arg])) { @@ -3276,3 +3281,4 @@ function exprmgr_unique($args) } return true; } +