PageRenderTime 93ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 1ms

/application/helpers/expressions/em_core_helper.php

https://bitbucket.org/machaven/limesurvey
PHP | 3533 lines | 2969 code | 130 blank | 434 comment | 460 complexity | 2623442eb3aa6827b4edec71caa1dc50 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause, GPL-3.0, LGPL-3.0
  1. <?php
  2. /**
  3. * LimeSurvey
  4. * Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
  5. * All rights reserved.
  6. * License: GNU/GPL License v2 or later, see LICENSE.php
  7. * LimeSurvey is free software. This version may have been modified pursuant
  8. * to the GNU General Public License, and as distributed it includes or
  9. * is derivative of works licensed under the GNU General Public License or
  10. * other free or open source software licenses.
  11. * See COPYRIGHT.php for copyright notices and details.
  12. *
  13. * $Id$
  14. */
  15. /**
  16. * Description of ExpressionManager
  17. * (1) Does safe evaluation of PHP expressions. Only registered Functions, and known Variables are allowed.
  18. * (a) Functions include any math, string processing, conditional, formatting, etc. functions
  19. * (2) This class replaces LimeSurvey's <= 1.91+ process of resolving strings that contain LimeReplacementFields
  20. * (a) String is split by expressions (by curly braces, but safely supporting strings and escaped curly braces)
  21. * (b) Expressions (things surrounded by curly braces) are evaluated - thereby doing LimeReplacementField substitution and/or more complex calculations
  22. * (c) Non-expressions are left intact
  23. * (d) The array of stringParts are re-joined to create the desired final string.
  24. * (3) The core of Expression Manager is a Recursive Descent Parser (RDP), based off of one build via JavaCC by TMSWhite in 1999.
  25. * (a) Functions that start with RDP_ should not be touched unless you really understand compiler design.
  26. *
  27. * @author Thomas M. White (TMSWhite)
  28. */
  29. class ExpressionManager {
  30. // These are the allowable suffixes for variables - each represents an attribute of a variable.
  31. static $RDP_regex_var_attr = 'code|gid|grelevance|gseq|jsName|mandatory|NAOK|qid|qseq|question|readWrite|relevanceStatus|relevance|rowdivid|sgqa|shown|type|valueNAOK|value';
  32. // These three variables are effectively static once constructed
  33. private $RDP_ExpressionRegex;
  34. private $RDP_TokenType;
  35. private $RDP_TokenizerRegex;
  36. private $RDP_CategorizeTokensRegex;
  37. private $RDP_ValidFunctions; // names and # params of valid functions
  38. // Thes variables are used while processing the equation
  39. private $RDP_expr; // the source expression
  40. private $RDP_tokens; // the list of generated tokens
  41. private $RDP_count; // total number of $RDP_tokens
  42. private $RDP_pos; // position within the $token array while processing equation
  43. private $RDP_errs; // array of syntax errors
  44. private $RDP_onlyparse;
  45. private $RDP_stack; // stack of intermediate results
  46. private $RDP_result; // final result of evaluating the expression;
  47. private $RDP_evalStatus; // true if $RDP_result is a valid result, and there are no serious errors
  48. private $varsUsed; // list of variables referenced in the equation
  49. // These variables are only used by sProcessStringContainingExpressions
  50. private $allVarsUsed; // full list of variables used within the string, even if contains multiple expressions
  51. private $prettyPrintSource; // HTML formatted output of running sProcessStringContainingExpressions
  52. private $substitutionNum; // Keeps track of number of substitions performed XXX
  53. private $substitutionInfo; // array of JavaScripts to managing dynamic substitution
  54. private $jsExpression; // caches computation of JavaScript equivalent for an Expression
  55. private $questionSeq; // sequence order of question - so can detect if try to use variable before it is set
  56. private $groupSeq; // sequence order of groups - so can detect if try to use variable before it is set
  57. private $surveyMode='group';
  58. // The following are only needed to enable click on variable names within pretty print and open new window to edit them
  59. private $sid=NULL; // the survey ID
  60. private $hyperlinkSyntaxHighlighting=true; // TODO - change this back to false
  61. private $sgqaNaming=false;
  62. function __construct()
  63. {
  64. // List of token-matching regular expressions
  65. // Note, this is effectively a Lexer using Regular Expressions. Don't change this unless you understand compiler design.
  66. $RDP_regex_dq_string = '(?<!\\\\)".*?(?<!\\\\)"';
  67. $RDP_regex_sq_string = '(?<!\\\\)\'.*?(?<!\\\\)\'';
  68. $RDP_regex_whitespace = '\s+';
  69. $RDP_regex_lparen = '\(';
  70. $RDP_regex_rparen = '\)';
  71. $RDP_regex_comma = ',';
  72. $RDP_regex_not = '!';
  73. $RDP_regex_inc_dec = '\+\+|--';
  74. $RDP_regex_binary = '[+*/-]';
  75. $RDP_regex_compare = '<=|<|>=|>|==|!=|\ble\b|\blt\b|\bge\b|\bgt\b|\beq\b|\bne\b';
  76. $RDP_regex_assign = '='; // '=|\+=|-=|\*=|/=';
  77. $RDP_regex_sgqa = '(?:INSERTANS:)?[0-9]+X[0-9]+X[0-9]+[A-Z0-9_]*\#?[01]?(?:\.(?:' . ExpressionManager::$RDP_regex_var_attr . '))?';
  78. $RDP_regex_word = '(?:TOKEN:)?(?:[A-Z][A-Z0-9_]*)?(?:\.(?:[A-Z][A-Z0-9_]*))*(?:\.(?:' . ExpressionManager::$RDP_regex_var_attr . '))?';
  79. $RDP_regex_number = '[0-9]+\.?[0-9]*|\.[0-9]+';
  80. $RDP_regex_andor = '\band\b|\bor\b|&&|\|\|';
  81. $RDP_regex_lcb = '{';
  82. $RDP_regex_rcb = '}';
  83. $RDP_regex_sq = '\'';
  84. $RDP_regex_dq= '"';
  85. $RDP_regex_bs = '\\\\';
  86. $RDP_StringSplitRegex = array(
  87. $RDP_regex_lcb,
  88. $RDP_regex_rcb,
  89. $RDP_regex_sq,
  90. $RDP_regex_dq,
  91. $RDP_regex_bs,
  92. );
  93. // RDP_ExpressionRegex is the regular expression that splits apart strings that contain curly braces in order to find expressions
  94. $this->RDP_ExpressionRegex = '#(' . implode('|',$RDP_StringSplitRegex) . ')#i';
  95. // asTokenRegex and RDP_TokenType must be kept in sync (same number and order)
  96. $RDP_TokenRegex = array(
  97. $RDP_regex_dq_string,
  98. $RDP_regex_sq_string,
  99. $RDP_regex_whitespace,
  100. $RDP_regex_lparen,
  101. $RDP_regex_rparen,
  102. $RDP_regex_comma,
  103. $RDP_regex_andor,
  104. $RDP_regex_compare,
  105. $RDP_regex_sgqa,
  106. $RDP_regex_word,
  107. $RDP_regex_number,
  108. $RDP_regex_not,
  109. $RDP_regex_inc_dec,
  110. $RDP_regex_assign,
  111. $RDP_regex_binary,
  112. );
  113. $this->RDP_TokenType = array(
  114. 'DQ_STRING',
  115. 'SQ_STRING',
  116. 'SPACE',
  117. 'LP',
  118. 'RP',
  119. 'COMMA',
  120. 'AND_OR',
  121. 'COMPARE',
  122. 'SGQA',
  123. 'WORD',
  124. 'NUMBER',
  125. 'NOT',
  126. 'OTHER',
  127. 'ASSIGN',
  128. 'BINARYOP',
  129. );
  130. // $RDP_TokenizerRegex - a single regex used to split and equation into tokens
  131. $this->RDP_TokenizerRegex = '#(' . implode('|',$RDP_TokenRegex) . ')#i';
  132. // $RDP_CategorizeTokensRegex - an array of patterns so can categorize the type of token found - would be nice if could get this from preg_split
  133. // Adding ability to capture 'OTHER' type, which indicates an error - unsupported syntax element
  134. $this->RDP_CategorizeTokensRegex = preg_replace("#^(.*)$#","#^$1$#i",$RDP_TokenRegex);
  135. $this->RDP_CategorizeTokensRegex[] = '/.+/';
  136. $this->RDP_TokenType[] = 'OTHER';
  137. // Each allowed function is a mapping from local name to external name + number of arguments
  138. // Functions can have a list of serveral allowable #s of arguments.
  139. // If the value is -1, the function must have a least one argument but can have an unlimited number of them
  140. // -2 means that at least one argument is required. -3 means at least two arguments are required, etc.
  141. $this->RDP_ValidFunctions = array(
  142. 'abs' => array('abs', 'Math.abs', $this->gT('Absolute value'), 'number abs(number)', 'http://www.php.net/manual/en/function.checkdate.php', 1),
  143. 'acos' => array('acos', 'Math.acos', $this->gT('Arc cosine'), 'number acos(number)', 'http://www.php.net/manual/en/function.acos.php', 1),
  144. 'addslashes' => array('addslashes', $this->gT('addslashes'), 'Quote string with slashes', 'string addslashes(string)', 'http://www.php.net/manual/en/function.addslashes.php', 1),
  145. 'asin' => array('asin', 'Math.asin', $this->gT('Arc sine'), 'number asin(number)', 'http://www.php.net/manual/en/function.asin.php', 1),
  146. 'atan' => array('atan', 'Math.atan', $this->gT('Arc tangent'), 'number atan(number)', 'http://www.php.net/manual/en/function.atan.php', 1),
  147. 'atan2' => array('atan2', 'Math.atan2', $this->gT('Arc tangent of two variables'), 'number atan2(number, number)', 'http://www.php.net/manual/en/function.atan2.php', 2),
  148. 'ceil' => array('ceil', 'Math.ceil', $this->gT('Round fractions up'), 'number ceil(number)', 'http://www.php.net/manual/en/function.ceil.php', 1),
  149. 'checkdate' => array('checkdate', 'checkdate', $this->gT('Returns true(1) if it is a valid date in gregorian calendar'), 'bool checkdate(month,day,year)', 'http://www.php.net/manual/en/function.checkdate.php', 3),
  150. 'cos' => array('cos', 'Math.cos', $this->gT('Cosine'), 'number cos(number)', 'http://www.php.net/manual/en/function.cos.php', 1),
  151. 'count' => array('exprmgr_count', 'LEMcount', $this->gT('Count the number of answered questions in the list'), 'number count(arg1, arg2, ... argN)', '', -1),
  152. 'countif' => array('exprmgr_countif', 'LEMcountif', $this->gT('Count the number of answered questions in the list equal the first argument'), 'number countif(matches, arg1, arg2, ... argN)', '', -2),
  153. 'countifop' => array('exprmgr_countifop', 'LEMcountifop', $this->gT('Count the number of answered questions in the list which pass the critiera (arg op value)'), 'number countifop(op, value, arg1, arg2, ... argN)', '', -3),
  154. 'date' => array('date', 'date', $this->gT('Format a local date/time'), 'string date(format [, timestamp=time()])', 'http://www.php.net/manual/en/function.date.php', 1,2),
  155. 'exp' => array('exp', 'Math.exp', $this->gT('Calculates the exponent of e'), 'number exp(number)', 'http://www.php.net/manual/en/function.exp.php', 1),
  156. 'fixnum' => array('exprmgr_fixnum', 'LEMfixnum', $this->gT('Display numbers with comma as decimal separator, if needed'), 'string fixnum(number)', '', 1),
  157. 'floor' => array('floor', 'Math.floor', $this->gT('Round fractions down'), 'number floor(number)', 'http://www.php.net/manual/en/function.floor.php', 1),
  158. 'gmdate' => array('gmdate', 'gmdate', $this->gT('Format a GMT date/time'), 'string gmdate(format [, timestamp=time()])', 'http://www.php.net/manual/en/function.gmdate.php', 1,2),
  159. 'html_entity_decode' => array('html_entity_decode', 'html_entity_decode', $this->gT('Convert all HTML entities to their applicable characters (always uses ENT_QUOTES and UTF-8)'), 'string html_entity_decode(string)', 'http://www.php.net/manual/en/function.html-entity-decode.php', 1),
  160. 'htmlentities' => array('htmlentities', 'htmlentities', $this->gT('Convert all applicable characters to HTML entities (always uses ENT_QUOTES and UTF-8)'), 'string htmlentities(string)', 'http://www.php.net/manual/en/function.htmlentities.php', 1),
  161. 'htmlspecialchars' => array('expr_mgr_htmlspecialchars', 'htmlspecialchars', $this->gT('Convert special characters to HTML entities (always uses ENT_QUOTES and UTF-8)'), 'string htmlspecialchars(string)', 'http://www.php.net/manual/en/function.htmlspecialchars.php', 1),
  162. 'htmlspecialchars_decode' => array('expr_mgr_htmlspecialchars_decode', 'htmlspecialchars_decode', $this->gT('Convert special HTML entities back to characters (always uses ENT_QUOTES and UTF-8)'), 'string htmlspecialchars_decode(string)', 'http://www.php.net/manual/en/function.htmlspecialchars-decode.php', 1),
  163. 'idate' => array('idate', 'idate', $this->gT('Format a local time/date as integer'), 'string idate(string [, timestamp=time()])', 'http://www.php.net/manual/en/function.idate.php', 1,2),
  164. 'if' => array('exprmgr_if', 'LEMif', $this->gT('Conditional processing'), 'if(test,result_if_true,result_if_false)', '', 3),
  165. 'implode' => array('exprmgr_implode', 'LEMimplode', $this->gT('Join array elements with a string'), 'string implode(glue,arg1,arg2,...,argN)', 'http://www.php.net/manual/en/function.implode.php', -2),
  166. 'intval' => array('intval', 'LEMintval', $this->gT('Get the integer value of a variable'), 'int intval(number [, base=10])', 'http://www.php.net/manual/en/function.intval.php', 1,2),
  167. 'is_empty' => array('exprmgr_empty', 'LEMempty', $this->gT('Determine whether a variable is considered to be empty'), 'bool is_empty(var)', 'http://www.php.net/manual/en/function.empty.php', 1),
  168. 'is_float' => array('is_float', 'LEMis_float', $this->gT('Finds whether the type of a variable is float'), 'bool is_float(var)', 'http://www.php.net/manual/en/function.is-float.php', 1),
  169. 'is_int' => array('is_int', 'LEMis_int', $this->gT('Find whether the type of a variable is integer'), 'bool is_int(var)', 'http://www.php.net/manual/en/function.is-int.php', 1),
  170. 'is_nan' => array('is_nan', 'isNaN', $this->gT('Finds whether a value is not a number'), 'bool is_nan(var)', 'http://www.php.net/manual/en/function.is-nan.php', 1),
  171. 'is_null' => array('is_null', 'LEMis_null', $this->gT('Finds whether a variable is NULL'), 'bool is_null(var)', 'http://www.php.net/manual/en/function.is-null.php', 1),
  172. 'is_numeric' => array('is_numeric', 'LEMis_numeric', $this->gT('Finds whether a variable is a number or a numeric string'), 'bool is_numeric(var)', 'http://www.php.net/manual/en/function.is-numeric.php', 1),
  173. 'is_string' => array('is_string', 'LEMis_string', $this->gT('Find whether the type of a variable is string'), 'bool is_string(var)', 'http://www.php.net/manual/en/function.is-string.php', 1),
  174. 'join' => array('exprmgr_join', 'LEMjoin', $this->gT('Join strings, return joined string.This function is an alias of implode("",argN)'), 'string join(arg1,arg2,...,argN)', '', -1),
  175. 'list' => array('exprmgr_list', 'LEMlist', $this->gT('Return comma-separated list of values'), 'string list(arg1, arg2, ... argN)', '', -2),
  176. 'log' => array('log', 'Math.log', $this->gT('Natural logarithm'), 'number log(number)', 'http://www.php.net/manual/en/function.log.php', 1),
  177. 'ltrim' => array('ltrim', 'ltrim', $this->gT('Strip whitespace (or other characters) from the beginning of a string'), 'string ltrim(string [, charlist])', 'http://www.php.net/manual/en/function.ltrim.php', 1,2),
  178. 'max' => array('max', 'Math.max', $this->gT('Find highest value'), 'number max(arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.max.php', -2),
  179. 'min' => array('min', 'Math.min', $this->gT('Find lowest value'), 'number min(arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.min.php', -2),
  180. 'mktime' => array('mktime', 'mktime', $this->gT('Get UNIX timestamp for a date (each of the 6 arguments are optional)'), 'number mktime([hour [, minute [, second [, month [, day [, year ]]]]]])', 'http://www.php.net/manual/en/function.mktime.php', 0,1,2,3,4,5,6),
  181. 'nl2br' => array('nl2br', 'nl2br', $this->gT('Inserts HTML line breaks before all newlines in a string'), 'string nl2br(string)', 'http://www.php.net/manual/en/function.nl2br.php', 1,1),
  182. 'number_format' => array('number_format', 'number_format', $this->gT('Format a number with grouped thousands'), 'string number_format(number)', 'http://www.php.net/manual/en/function.number-format.php', 1),
  183. 'pi' => array('pi', 'LEMpi', $this->gT('Get value of pi'), 'number pi()', '', 0),
  184. 'pow' => array('pow', 'Math.pow', $this->gT('Exponential expression'), 'number pow(base, exp)', 'http://www.php.net/manual/en/function.pow.php', 2),
  185. 'quoted_printable_decode' => array('quoted_printable_decode', 'quoted_printable_decode', $this->gT('Convert a quoted-printable string to an 8 bit string'), 'string quoted_printable_decode(string)', 'http://www.php.net/manual/en/function.quoted-printable-decode.php', 1),
  186. 'quoted_printable_encode' => array('quoted_printable_encode', 'quoted_printable_encode', $this->gT('Convert a 8 bit string to a quoted-printable string'), 'string quoted_printable_encode(string)', 'http://www.php.net/manual/en/function.quoted-printable-encode.php', 1),
  187. 'quotemeta' => array('quotemeta', 'quotemeta', $this->gT('Quote meta characters'), 'string quotemeta(string)', 'http://www.php.net/manual/en/function.quotemeta.php', 1),
  188. 'rand' => array('rand', 'rand', $this->gT('Generate a random integer'), 'int rand() OR int rand(min, max)', 'http://www.php.net/manual/en/function.rand.php', 0,2),
  189. 'regexMatch' => array('exprmgr_regexMatch', 'LEMregexMatch', $this->gT('Compare a string to a regular expression pattern'), 'bool regexMatch(pattern,input)', '', 2),
  190. 'round' => array('round', 'round', $this->gT('Rounds a number to an optional precision'), 'number round(val [, precision])', 'http://www.php.net/manual/en/function.round.php', 1,2),
  191. 'rtrim' => array('rtrim', 'rtrim', $this->gT('Strip whitespace (or other characters) from the end of a string'), 'string rtrim(string [, charlist])', 'http://www.php.net/manual/en/function.rtrim.php', 1,2),
  192. 'sin' => array('sin', 'Math.sin', $this->gT('Sine'), 'number sin(arg)', 'http://www.php.net/manual/en/function.sin.php', 1),
  193. 'sprintf' => array('sprintf', 'sprintf', $this->gT('Return a formatted string'), 'string sprintf(format, arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.sprintf.php', -2),
  194. 'sqrt' => array('sqrt', 'Math.sqrt', $this->gT('Square root'), 'number sqrt(arg)', 'http://www.php.net/manual/en/function.sqrt.php', 1),
  195. 'stddev' => array('exprmgr_stddev', 'LEMstddev', $this->gT('Calculate the Sample Standard Deviation for the list of numbers'), 'number stddev(arg1, arg2, ... argN)', '', -2),
  196. 'str_pad' => array('str_pad', 'str_pad', $this->gT('Pad a string to a certain length with another string'), 'string str_pad(input, pad_length [, pad_string])', 'http://www.php.net/manual/en/function.str-pad.php', 2,3),
  197. 'str_repeat' => array('str_repeat', 'str_repeat', $this->gT('Repeat a string'), 'string str_repeat(input, multiplier)', 'http://www.php.net/manual/en/function.str-repeat.php', 2),
  198. 'str_replace' => array('str_replace', 'LEMstr_replace', $this->gT('Replace all occurrences of the search string with the replacement string'), 'string str_replace(search, replace, subject)', 'http://www.php.net/manual/en/function.str-replace.php', 3),
  199. 'strcasecmp' => array('strcasecmp', 'strcasecmp', $this->gT('Binary safe case-insensitive string comparison'), 'int strcasecmp(str1, str2)', 'http://www.php.net/manual/en/function.strcasecmp.php', 2),
  200. 'strcmp' => array('strcmp', 'strcmp', $this->gT('Binary safe string comparison'), 'int strcmp(str1, str2)', 'http://www.php.net/manual/en/function.strcmp.php', 2),
  201. 'strip_tags' => array('strip_tags', 'strip_tags', $this->gT('Strip HTML and PHP tags from a string'), 'string strip_tags(str, allowable_tags)', 'http://www.php.net/manual/en/function.strip-tags.php', 1,2),
  202. 'stripos' => array('stripos', 'stripos', $this->gT('Find position of first occurrence of a case-insensitive string'), 'int stripos(haystack, needle [, offset=0])', 'http://www.php.net/manual/en/function.stripos.php', 2,3),
  203. 'stripslashes' => array('stripslashes', 'stripslashes', $this->gT('Un-quotes a quoted string'), 'string stripslashes(string)', 'http://www.php.net/manual/en/function.stripslashes.php', 1),
  204. 'stristr' => array('stristr', 'stristr', $this->gT('Case-insensitive strstr'), 'string stristr(haystack, needle [, before_needle=false])', 'http://www.php.net/manual/en/function.stristr.php', 2,3),
  205. 'strlen' => array('strlen', 'LEMstrlen', $this->gT('Get string length'), 'int strlen(string)', 'http://www.php.net/manual/en/function.strlen.php', 1),
  206. 'strpos' => array('strpos', 'LEMstrpos', $this->gT('Find position of first occurrence of a string'), 'int strpos(haystack, needle [ offset=0])', 'http://www.php.net/manual/en/function.strpos.php', 2,3),
  207. 'strrev' => array('strrev', 'strrev', $this->gT('Reverse a string'), 'string strrev(string)', 'http://www.php.net/manual/en/function.strrev.php', 1),
  208. 'strstr' => array('strstr', 'strstr', $this->gT('Find first occurrence of a string'), 'string strstr(haystack, needle)', 'http://www.php.net/manual/en/function.strstr.php', 2),
  209. 'strtolower' => array('strtolower', 'LEMstrtolower', $this->gT('Make a string lowercase'), 'string strtolower(string)', 'http://www.php.net/manual/en/function.strtolower.php', 1),
  210. 'strtoupper' => array('strtoupper', 'LEMstrtoupper', $this->gT('Make a string uppercase'), 'string strtoupper(string)', 'http://www.php.net/manual/en/function.strtoupper.php', 1),
  211. 'substr' => array('substr', 'substr', $this->gT('Return part of a string'), 'string substr(string, start [, length])', 'http://www.php.net/manual/en/function.substr.php', 2,3),
  212. 'sum' => array('array_sum', 'LEMsum', $this->gT('Calculate the sum of values in an array'), 'number sum(arg1, arg2, ... argN)', '', -2),
  213. 'sumifop' => array('exprmgr_sumifop', 'LEMsumifop', $this->gT('Sum the values of answered questions in the list which pass the critiera (arg op value)'), 'number sumifop(op, value, arg1, arg2, ... argN)', '', -3),
  214. 'tan' => array('tan', 'Math.tan', $this->gT('Tangent'), 'number tan(arg)', 'http://www.php.net/manual/en/function.tan.php', 1),
  215. 'time' => array('time', 'time', $this->gT('Return current UNIX timestamp'), 'number time()', 'http://www.php.net/manual/en/function.time.php', 0),
  216. 'trim' => array('trim', 'trim', $this->gT('Strip whitespace (or other characters) from the beginning and end of a string'), 'string trim(string [, charlist])', 'http://www.php.net/manual/en/function.trim.php', 1,2),
  217. 'ucwords' => array('ucwords', 'ucwords', $this->gT('Uppercase the first character of each word in a string'), 'string ucwords(string)', 'http://www.php.net/manual/en/function.ucwords.php', 1),
  218. 'unique' => array('exprmgr_unique', 'LEMunique', $this->gT('Returns true if all non-empty responses are unique'), 'boolean unique(arg1, ..., argN)', '', -1),
  219. );
  220. }
  221. /**
  222. * Add an error to the error log
  223. *
  224. * @param <type> $errMsg
  225. * @param <type> $token
  226. */
  227. private function RDP_AddError($errMsg, $token)
  228. {
  229. $this->RDP_errs[] = array($errMsg, $token);
  230. }
  231. /**
  232. * RDP_EvaluateBinary() computes binary expressions, such as (a or b), (c * d), popping the top two entries off the
  233. * stack and pushing the result back onto the stack.
  234. *
  235. * @param array $token
  236. * @return boolean - false if there is any error, else true
  237. */
  238. private function RDP_EvaluateBinary(array $token)
  239. {
  240. if (count($this->RDP_stack) < 2)
  241. {
  242. $this->RDP_AddError($this->gT("Unable to evaluate binary operator - fewer than 2 entries on stack"), $token);
  243. return false;
  244. }
  245. $arg2 = $this->RDP_StackPop();
  246. $arg1 = $this->RDP_StackPop();
  247. if (is_null($arg1) or is_null($arg2))
  248. {
  249. $this->RDP_AddError($this->gT("Invalid value(s) on the stack"), $token);
  250. return false;
  251. }
  252. // TODO: try to determine datatype?
  253. $bNumericArg1 = is_numeric($arg1[0]) || $arg1[0] == '';
  254. $bNumericArg2 = is_numeric($arg2[0]) || $arg2[0] == '';
  255. $bStringArg1 = !$bNumericArg1 || $arg1[0] == '';
  256. $bStringArg2 = !$bNumericArg2 || $arg2[0] == '';
  257. $bBothNumeric = ($bNumericArg1 && $bNumericArg2);
  258. $bBothString = ($bStringArg1 && $bStringArg2);
  259. $bMismatchType = (!$bBothNumeric && !$bBothString);
  260. switch(strtolower($token[0]))
  261. {
  262. case 'or':
  263. case '||':
  264. $result = array(($arg1[0] or $arg2[0]),$token[1],'NUMBER');
  265. break;
  266. case 'and':
  267. case '&&':
  268. $result = array(($arg1[0] and $arg2[0]),$token[1],'NUMBER');
  269. break;
  270. case '==':
  271. case 'eq':
  272. $result = array(($arg1[0] == $arg2[0]),$token[1],'NUMBER');
  273. break;
  274. case '!=':
  275. case 'ne':
  276. $result = array(($arg1[0] != $arg2[0]),$token[1],'NUMBER');
  277. break;
  278. case '<':
  279. case 'lt':
  280. if ($bMismatchType) {
  281. $result = array(false,$token[1],'NUMBER');
  282. }
  283. else {
  284. $result = array(($arg1[0] < $arg2[0]),$token[1],'NUMBER');
  285. }
  286. break;
  287. case '<=';
  288. case 'le':
  289. if ($bMismatchType) {
  290. $result = array(false,$token[1],'NUMBER');
  291. }
  292. else {
  293. // Need this explicit comparison in order to be in agreement with JavaScript
  294. if (($arg1[0] == '0' && $arg2[0] == '') || ($arg1[0] == '' && $arg2[0] == '0')) {
  295. $result = array(true,$token[1],'NUMBER');
  296. }
  297. else {
  298. $result = array(($arg1[0] <= $arg2[0]),$token[1],'NUMBER');
  299. }
  300. }
  301. break;
  302. case '>':
  303. case 'gt':
  304. if ($bMismatchType) {
  305. $result = array(false,$token[1],'NUMBER');
  306. }
  307. else {
  308. // Need this explicit comparison in order to be in agreement with JavaScript
  309. if (($arg1[0] == '0' && $arg2[0] == '') || ($arg1[0] == '' && $arg2[0] == '0')) {
  310. $result = array(false,$token[1],'NUMBER');
  311. }
  312. else {
  313. $result = array(($arg1[0] > $arg2[0]),$token[1],'NUMBER');
  314. }
  315. }
  316. break;
  317. case '>=';
  318. case 'ge':
  319. if ($bMismatchType) {
  320. $result = array(false,$token[1],'NUMBER');
  321. }
  322. else {
  323. $result = array(($arg1[0] >= $arg2[0]),$token[1],'NUMBER');
  324. }
  325. break;
  326. case '+':
  327. if ($bBothNumeric) {
  328. $result = array(($arg1[0] + $arg2[0]),$token[1],'NUMBER');
  329. }
  330. else {
  331. $result = array($arg1[0] . $arg2[0],$token[1],'STRING');
  332. }
  333. break;
  334. case '-':
  335. if ($bBothNumeric) {
  336. $result = array(($arg1[0] - $arg2[0]),$token[1],'NUMBER');
  337. }
  338. else {
  339. $result = array(NAN,$token[1],'NUMBER');
  340. }
  341. break;
  342. case '*':
  343. if ($bBothNumeric) {
  344. $result = array(($arg1[0] * $arg2[0]),$token[1],'NUMBER');
  345. }
  346. else {
  347. $result = array(NAN,$token[1],'NUMBER');
  348. }
  349. break;
  350. case '/';
  351. if ($bBothNumeric) {
  352. if ($arg2[0] == 0) {
  353. $result = array(NAN,$token[1],'NUMBER');
  354. }
  355. else {
  356. $result = array(($arg1[0] / $arg2[0]),$token[1],'NUMBER');
  357. }
  358. }
  359. else {
  360. $result = array(NAN,$token[1],'NUMBER');
  361. }
  362. break;
  363. }
  364. $this->RDP_StackPush($result);
  365. return true;
  366. }
  367. /**
  368. * Processes operations like +a, -b, !c
  369. * @param array $token
  370. * @return boolean - true if success, false if any error occurred
  371. */
  372. private function RDP_EvaluateUnary(array $token)
  373. {
  374. if (count($this->RDP_stack) < 1)
  375. {
  376. $this->RDP_AddError($this->gT("Unable to evaluate unary operator - no entries on stack"), $token);
  377. return false;
  378. }
  379. $arg1 = $this->RDP_StackPop();
  380. if (is_null($arg1))
  381. {
  382. $this->RDP_AddError($this->gT("Invalid value(s) on the stack"), $token);
  383. return false;
  384. }
  385. // TODO: try to determine datatype?
  386. switch($token[0])
  387. {
  388. case '+':
  389. $result = array((+$arg1[0]),$token[1],'NUMBER');
  390. break;
  391. case '-':
  392. $result = array((-$arg1[0]),$token[1],'NUMBER');
  393. break;
  394. case '!';
  395. $result = array((!$arg1[0]),$token[1],'NUMBER');
  396. break;
  397. }
  398. $this->RDP_StackPush($result);
  399. return true;
  400. }
  401. /**
  402. * Main entry function
  403. * @param <type> $expr
  404. * @param <type> $onlyparse - if true, then validate the syntax without computing an answer
  405. * @return boolean - true if success, false if any error occurred
  406. */
  407. public function RDP_Evaluate($expr, $onlyparse=false)
  408. {
  409. $this->RDP_expr = $expr;
  410. $this->RDP_tokens = $this->RDP_Tokenize($expr);
  411. $this->RDP_count = count($this->RDP_tokens);
  412. $this->RDP_pos = -1; // starting position within array (first act will be to increment it)
  413. $this->RDP_errs = array();
  414. $this->RDP_onlyparse = $onlyparse;
  415. $this->RDP_stack = array();
  416. $this->RDP_evalStatus = false;
  417. $this->RDP_result = NULL;
  418. $this->varsUsed = array();
  419. $this->jsExpression = NULL;
  420. if ($this->HasSyntaxErrors()) {
  421. return false;
  422. }
  423. elseif ($this->RDP_EvaluateExpressions())
  424. {
  425. if ($this->RDP_pos < $this->RDP_count)
  426. {
  427. $this->RDP_AddError($this->gT("Extra tokens found"), $this->RDP_tokens[$this->RDP_pos]);
  428. return false;
  429. }
  430. $this->RDP_result = $this->RDP_StackPop();
  431. if (is_null($this->RDP_result))
  432. {
  433. return false;
  434. }
  435. if (count($this->RDP_stack) == 0)
  436. {
  437. $this->RDP_evalStatus = true;
  438. return true;
  439. }
  440. else
  441. {
  442. $this-RDP_AddError($this->gT("Unbalanced equation - values left on stack"),NULL);
  443. return false;
  444. }
  445. }
  446. else
  447. {
  448. $this->RDP_AddError($this->gT("Not a valid expression"),NULL);
  449. return false;
  450. }
  451. }
  452. /**
  453. * Process "a op b" where op in (+,-,concatenate)
  454. * @return boolean - true if success, false if any error occurred
  455. */
  456. private function RDP_EvaluateAdditiveExpression()
  457. {
  458. if (!$this->RDP_EvaluateMultiplicativeExpression())
  459. {
  460. return false;
  461. }
  462. while (($this->RDP_pos + 1) < $this->RDP_count)
  463. {
  464. $token = $this->RDP_tokens[++$this->RDP_pos];
  465. if ($token[2] == 'BINARYOP')
  466. {
  467. switch ($token[0])
  468. {
  469. case '+':
  470. case '-';
  471. if ($this->RDP_EvaluateMultiplicativeExpression())
  472. {
  473. if (!$this->RDP_EvaluateBinary($token))
  474. {
  475. return false;
  476. }
  477. // else continue;
  478. }
  479. else
  480. {
  481. return false;
  482. }
  483. break;
  484. default:
  485. --$this->RDP_pos;
  486. return true;
  487. }
  488. }
  489. else
  490. {
  491. --$this->RDP_pos;
  492. return true;
  493. }
  494. }
  495. return true;
  496. }
  497. /**
  498. * Process a Constant (number of string), retrieve the value of a known variable, or process a function, returning result on the stack.
  499. * @return boolean - true if success, false if any error occurred
  500. */
  501. private function RDP_EvaluateConstantVarOrFunction()
  502. {
  503. if ($this->RDP_pos + 1 >= $this->RDP_count)
  504. {
  505. $this->RDP_AddError($this->gT("Poorly terminated expression - expected a constant or variable"), NULL);
  506. return false;
  507. }
  508. $token = $this->RDP_tokens[++$this->RDP_pos];
  509. switch ($token[2])
  510. {
  511. case 'NUMBER':
  512. case 'DQ_STRING':
  513. case 'SQ_STRING':
  514. $this->RDP_StackPush($token);
  515. return true;
  516. break;
  517. case 'WORD':
  518. case 'SGQA':
  519. if (($this->RDP_pos + 1) < $this->RDP_count and $this->RDP_tokens[($this->RDP_pos + 1)][2] == 'LP')
  520. {
  521. return $this->RDP_EvaluateFunction();
  522. }
  523. else
  524. {
  525. if ($this->RDP_isValidVariable($token[0]))
  526. {
  527. $this->varsUsed[] = $token[0]; // add this variable to list of those used in this equation
  528. if (preg_match("/\.(gid|grelevance|gseq|jsName|mandatory|qid|qseq|question|readWrite|relevance|rowdivid|sgqa|type)$/",$token[0]))
  529. {
  530. $relStatus=1; // static, so always relevant
  531. }
  532. else
  533. {
  534. $relStatus = $this->GetVarAttribute($token[0],'relevanceStatus',1);
  535. }
  536. if ($relStatus==1)
  537. {
  538. $result = array($this->GetVarAttribute($token[0],NULL,''),$token[1],'NUMBER');
  539. }
  540. else
  541. {
  542. $result = array(NULL,$token[1],'NUMBER'); // was 0 instead of NULL
  543. }
  544. $this->RDP_StackPush($result);
  545. // TODO - currently, will try to process value anyway, but want to show a potential error. Should it be a definitive error (e.g. prevent this behavior)?
  546. $groupSeq = $this->GetVarAttribute($token[0],'gseq',-1);
  547. if (($groupSeq != -1 && $this->groupSeq != -1) && ($groupSeq > $this->groupSeq))
  548. {
  549. $this->RDP_AddError($this->gT("Variable not declared until a later page"),$token);
  550. return false;
  551. }
  552. return true;
  553. }
  554. else
  555. {
  556. $this->RDP_AddError($this->gT("Undefined variable"), $token);
  557. return false;
  558. }
  559. }
  560. break;
  561. case 'COMMA':
  562. --$this->RDP_pos;
  563. $this->RDP_AddError($this->gT("Should never get to this line?"),$token);
  564. return false;
  565. default:
  566. return false;
  567. break;
  568. }
  569. }
  570. /**
  571. * Process "a == b", "a eq b", "a != b", "a ne b"
  572. * @return boolean - true if success, false if any error occurred
  573. */
  574. private function RDP_EvaluateEqualityExpression()
  575. {
  576. if (!$this->RDP_EvaluateRelationExpression())
  577. {
  578. return false;
  579. }
  580. while (($this->RDP_pos + 1) < $this->RDP_count)
  581. {
  582. $token = $this->RDP_tokens[++$this->RDP_pos];
  583. switch (strtolower($token[0]))
  584. {
  585. case '==':
  586. case 'eq':
  587. case '!=':
  588. case 'ne':
  589. if ($this->RDP_EvaluateRelationExpression())
  590. {
  591. if (!$this->RDP_EvaluateBinary($token))
  592. {
  593. return false;
  594. }
  595. // else continue;
  596. }
  597. else
  598. {
  599. return false;
  600. }
  601. break;
  602. default:
  603. --$this->RDP_pos;
  604. return true;
  605. }
  606. }
  607. return true;
  608. }
  609. /**
  610. * Process a single expression (e.g. without commas)
  611. * @return boolean - true if success, false if any error occurred
  612. */
  613. private function RDP_EvaluateExpression()
  614. {
  615. if ($this->RDP_pos + 2 < $this->RDP_count)
  616. {
  617. $token1 = $this->RDP_tokens[++$this->RDP_pos];
  618. $token2 = $this->RDP_tokens[++$this->RDP_pos];
  619. if ($token2[2] == 'ASSIGN')
  620. {
  621. if ($this->RDP_isValidVariable($token1[0]))
  622. {
  623. $this->varsUsed[] = $token1[0]; // add this variable to list of those used in this equation
  624. if ($this->RDP_isWritableVariable($token1[0]))
  625. {
  626. $evalStatus = $this->RDP_EvaluateLogicalOrExpression();
  627. if ($evalStatus)
  628. {
  629. $result = $this->RDP_StackPop();
  630. if (!is_null($result))
  631. {
  632. $newResult = $token2;
  633. $newResult[2] = 'NUMBER';
  634. $newResult[0] = $this->RDP_SetVariableValue($token2[0], $token1[0], $result[0]);
  635. $this->RDP_StackPush($newResult);
  636. }
  637. else
  638. {
  639. $evalStatus = false;
  640. }
  641. }
  642. return $evalStatus;
  643. }
  644. else
  645. {
  646. $this->RDP_AddError($this->gT('The value of this variable can not be changed'), $token1);
  647. return false;
  648. }
  649. }
  650. else
  651. {
  652. $this->RDP_AddError($this->gT('Only variables can be assigned values'), $token1);
  653. return false;
  654. }
  655. }
  656. else
  657. {
  658. // not an assignment expression, so try something else
  659. $this->RDP_pos -= 2;
  660. return $this->RDP_EvaluateLogicalOrExpression();
  661. }
  662. }
  663. else
  664. {
  665. return $this->RDP_EvaluateLogicalOrExpression();
  666. }
  667. }
  668. /**
  669. * Process "expression [, expression]*
  670. * @return boolean - true if success, false if any error occurred
  671. */
  672. private function RDP_EvaluateExpressions()
  673. {
  674. $evalStatus = $this->RDP_EvaluateExpression();
  675. if (!$evalStatus)
  676. {
  677. return false;
  678. }
  679. while (++$this->RDP_pos < $this->RDP_count) {
  680. $token = $this->RDP_tokens[$this->RDP_pos];
  681. if ($token[2] == 'RP')
  682. {
  683. return true; // presumbably the end of an expression
  684. }
  685. elseif ($token[2] == 'COMMA')
  686. {
  687. if ($this->RDP_EvaluateExpression())
  688. {
  689. $secondResult = $this->RDP_StackPop();
  690. $firstResult = $this->RDP_StackPop();
  691. if (is_null($firstResult))
  692. {
  693. return false;
  694. }
  695. $this->RDP_StackPush($secondResult);
  696. $evalStatus = true;
  697. }
  698. else
  699. {
  700. return false; // an error must have occurred
  701. }
  702. }
  703. else
  704. {
  705. $this->RDP_AddError($this->gT("Expected expressions separated by commas"),$token);
  706. $evalStatus = false;
  707. break;
  708. }
  709. }
  710. while (++$this->RDP_pos < $this->RDP_count)
  711. {
  712. $token = $this->RDP_tokens[$this->RDP_pos];
  713. $this->RDP_AddError($this->gT("Extra token found after expressions"),$token);
  714. $evalStatus = false;
  715. }
  716. return $evalStatus;
  717. }
  718. /**
  719. * Process a function call
  720. * @return boolean - true if success, false if any error occurred
  721. */
  722. private function RDP_EvaluateFunction()
  723. {
  724. $funcNameToken = $this->RDP_tokens[$this->RDP_pos]; // note that don't need to increment position for functions
  725. $funcName = $funcNameToken[0];
  726. if (!$this->RDP_isValidFunction($funcName))
  727. {
  728. $this->RDP_AddError($this->gT("Undefined function"), $funcNameToken);
  729. return false;
  730. }
  731. $token2 = $this->RDP_tokens[++$this->RDP_pos];
  732. if ($token2[2] != 'LP')
  733. {
  734. $this->RDP_AddError($this->gT("Expected left parentheses after function name"), $funcNameToken);
  735. }
  736. $params = array(); // will just store array of values, not tokens
  737. while ($this->RDP_pos + 1 < $this->RDP_count)
  738. {
  739. $token3 = $this->RDP_tokens[$this->RDP_pos + 1];
  740. if (count($params) > 0)
  741. {
  742. // should have COMMA or RP
  743. if ($token3[2] == 'COMMA')
  744. {
  745. ++$this->RDP_pos; // consume the token so can process next clause
  746. if ($this->RDP_EvaluateExpression())
  747. {
  748. $value = $this->RDP_StackPop();
  749. if (is_null($value))
  750. {
  751. return false;
  752. }
  753. $params[] = $value[0];
  754. continue;
  755. }
  756. else
  757. {
  758. $this->RDP_AddError($this->gT("Extra comma found in function"), $token3);
  759. return false;
  760. }
  761. }
  762. }
  763. if ($token3[2] == 'RP')
  764. {
  765. ++$this->RDP_pos; // consume the token so can process next clause
  766. return $this->RDP_RunFunction($funcNameToken,$params);
  767. }
  768. else
  769. {
  770. if ($this->RDP_EvaluateExpression())
  771. {
  772. $value = $this->RDP_StackPop();
  773. if (is_null($value))
  774. {
  775. return false;
  776. }
  777. $params[] = $value[0];
  778. continue;
  779. }
  780. else
  781. {
  782. return false;
  783. }
  784. }
  785. }
  786. }
  787. /**
  788. * Process "a && b" or "a and b"
  789. * @return boolean - true if success, false if any error occurred
  790. */
  791. private function RDP_EvaluateLogicalAndExpression()
  792. {
  793. if (!$this->RDP_EvaluateEqualityExpression())
  794. {
  795. return false;
  796. }
  797. while (($this->RDP_pos + 1) < $this->RDP_count)
  798. {
  799. $token = $this->RDP_tokens[++$this->RDP_pos];
  800. switch (strtolower($token[0]))
  801. {
  802. case '&&':
  803. case 'and':
  804. if ($this->RDP_EvaluateEqualityExpression())
  805. {
  806. if (!$this->RDP_EvaluateBinary($token))
  807. {
  808. return false;
  809. }
  810. // else continue
  811. }
  812. else
  813. {
  814. return false; // an error must have occurred
  815. }
  816. break;
  817. default:
  818. --$this->RDP_pos;
  819. return true;
  820. }
  821. }
  822. return true;
  823. }
  824. /**
  825. * Process "a || b" or "a or b"
  826. * @return boolean - true if success, false if any error occurred
  827. */
  828. private function RDP_EvaluateLogicalOrExpression()
  829. {
  830. if (!$this->RDP_EvaluateLogicalAndExpression())
  831. {
  832. return false;
  833. }
  834. while (($this->RDP_pos + 1) < $this->RDP_count)
  835. {
  836. $token = $this->RDP_tokens[++$this->RDP_pos];
  837. switch (strtolower($token[0]))
  838. {
  839. case '||':
  840. case 'or':
  841. if ($this->RDP_EvaluateLogicalAndExpression())
  842. {
  843. if (!$this->RDP_EvaluateBinary($token))
  844. {
  845. return false;
  846. }
  847. // else continue
  848. }
  849. else
  850. {
  851. // an error must have occurred
  852. return false;
  853. }
  854. break;
  855. default:
  856. // no more expressions being ORed together, so continue parsing
  857. --$this->RDP_pos;
  858. return true;
  859. }
  860. }
  861. // no more tokens to parse
  862. return true;
  863. }
  864. /**
  865. * Process "a op b" where op in (*,/)
  866. * @return boolean - true if success, false if any error occurred
  867. */
  868. private function RDP_EvaluateMultiplicativeExpression()
  869. {
  870. if (!$this->RDP_EvaluateUnaryExpression())
  871. {
  872. return false;
  873. }
  874. while (($this->RDP_pos + 1) < $this->RDP_count)
  875. {
  876. $token = $this->RDP_tokens[++$this->RDP_pos];
  877. if ($token[2] == 'BINARYOP')
  878. {
  879. switch ($token[0])
  880. {
  881. case '*':
  882. case '/';
  883. if ($this->RDP_EvaluateUnaryExpression())
  884. {
  885. if (!$this->RDP_EvaluateBinary($token))
  886. {
  887. return false;
  888. }
  889. // else continue
  890. }
  891. else
  892. {
  893. // an error must have occurred
  894. return false;
  895. }
  896. break;
  897. break;
  898. default:
  899. --$this->RDP_pos;
  900. return true;
  901. }
  902. }
  903. else
  904. {
  905. --$this->RDP_pos;
  906. return true;
  907. }
  908. }
  909. return true;
  910. }
  911. /**
  912. * Process expressions including functions and parenthesized blocks
  913. * @return boolean - true if success, false if any error occurred
  914. */
  915. private function RDP_EvaluatePrimaryExpression()
  916. {
  917. if (($this->RDP_pos + 1) >= $this->RDP_count) {
  918. $this->RDP_AddError($this->gT("Poorly terminated expression - expected a constant or variable"), NULL);
  919. return false;
  920. }
  921. $token = $this->RDP_tokens[++$this->RDP_pos];
  922. if ($token[2] == 'LP')
  923. {
  924. if (!$this->RDP_EvaluateExpressions())
  925. {
  926. return false;
  927. }
  928. $token = $this->RDP_tokens[$this->RDP_pos];
  929. if ($token[2] == 'RP')
  930. {
  931. return true;
  932. }
  933. else
  934. {
  935. $this->RDP_AddError($this->gT("Expected right parentheses"), $token);
  936. return false;
  937. }
  938. }
  939. else
  940. {
  941. --$this->RDP_pos;
  942. return $this->RDP_EvaluateConstantVarOrFunction();
  943. }
  944. }
  945. /**
  946. * Process "a op b" where op in (lt, gt, le, ge, <, >, <=, >=)
  947. * @return boolean - true if success, false if any error occurred
  948. */
  949. private function RDP_EvaluateRelationExpression()
  950. {
  951. if (!$this->RDP_EvaluateAdditiveExpression())
  952. {
  953. return false;
  954. }
  955. while (($this->RDP_pos + 1) < $this->RDP_count)
  956. {
  957. $token = $this->RDP_tokens[++$this->RDP_pos];
  958. switch (strtolower($token[0]))
  959. {
  960. case '<':
  961. case 'lt':
  962. case '<=';
  963. case 'le':
  964. case '>':
  965. case 'gt':
  966. case '>=';
  967. case 'ge':
  968. if ($this->RDP_EvaluateAdditiveExpression())
  969. {
  970. if (!$this->RDP_EvaluateBinary($token))
  971. {
  972. return false;
  973. }
  974. // else continue
  975. }
  976. else
  977. {
  978. // an error must have occurred
  979. return false;
  980. }
  981. break;
  982. default:
  983. --$this->RDP_pos;
  984. return true;
  985. }
  986. }
  987. return true;
  988. }
  989. /**
  990. * Process "op a" where op in (+,-,!)
  991. * @return boolean - true if success, false if any error occurred
  992. */
  993. private function RDP_EvaluateUnaryExpression()
  994. {
  995. if (($this->RDP_pos + 1) >= $this->RDP_count) {
  996. $this->RDP_AddError($this->gT("Poorly terminated expression - expected a constant or variable"), NULL);
  997. return false;
  998. }
  999. $token = $this->RDP_tokens[++$this->RDP_pos];
  1000. if ($token[2] == 'NOT' || $token[2] == 'BINARYOP')
  1001. {
  1002. switch ($token[0])
  1003. {
  1004. case '+':
  1005. case '-':
  1006. case '!':
  1007. if (!$this->RDP_EvaluatePrimaryExpression())
  1008. {
  1009. return false;
  1010. }
  1011. return $this->RDP_EvaluateUnary($token);
  1012. break;
  1013. default:
  1014. --$this->RDP_pos;
  1015. return $this->RDP_EvaluatePrimaryExpression();
  1016. }
  1017. }
  1018. else
  1019. {
  1020. --$this->RDP_pos;
  1021. return $this->RDP_EvaluatePrimaryExpression();
  1022. }
  1023. }
  1024. /**
  1025. * Returns array of all JavaScript-equivalent variable names used when parsing a string via sProcessStringContainingExpressions
  1026. * @return <type>
  1027. */
  1028. public function GetAllJsVarsUsed()
  1029. {
  1030. if (is_null($this->allVarsUsed)){
  1031. return array();
  1032. }
  1033. $names = array_unique($this->allVarsUsed);
  1034. if (is_null($names)) {
  1035. return array();
  1036. }
  1037. $jsNames = array();
  1038. foreach ($names as $name)
  1039. {
  1040. if (preg_match("/\.(gid|grelevance|gseq|jsName|mandatory|qid|qseq|question|readWrite|relevance|rowdivid|sgqa|type)$/",$name))
  1041. {
  1042. continue;
  1043. }
  1044. $val = $this->GetVarAttribute($name,'jsName','');
  1045. if ($val != '') {
  1046. $jsNames[] = $val;
  1047. }
  1048. }
  1049. return array_unique($jsNames);
  1050. }
  1051. /**
  1052. * Return the list of all of the JavaScript variables used by the most recent expression - only those that are set on the current page
  1053. * This is used to control static vs dynamic substitution. If an expression is entirely made up of off-page changes, it can be statically replaced.
  1054. * @return <type>
  1055. */
  1056. public function GetOnPageJsVarsUsed()
  1057. {
  1058. if (is_null($this->varsUsed)){
  1059. return array();
  1060. }
  1061. if ($this->surveyMode=='survey')
  1062. {
  1063. return $this->GetJsVarsUsed();
  1064. }
  1065. $names = array_unique($this->varsUsed);
  1066. if (is_null($names)) {
  1067. return array();
  1068. }
  1069. $jsNames = array();
  1070. foreach ($names as $name)
  1071. {
  1072. if (preg_match("/\.(gid|grelevance|gseq|jsName|mandatory|qid|qseq|question|readWrite|relevance|rowdivid|sgqa|type)$/",$name))
  1073. {
  1074. continue;
  1075. }
  1076. $val = $this->GetVarAttribute($name,'jsName','');
  1077. switch ($this->surveyMode)
  1078. {
  1079. case 'group':
  1080. $gseq = $this->GetVarAttribute($name,'gseq','');
  1081. $onpage = ($gseq == $this->groupSeq);
  1082. break;
  1083. case 'question':
  1084. $qseq = $this->GetVarAttribute($name,'qseq','');
  1085. $onpage = ($qseq == $this->questionSeq);
  1086. break;
  1087. case 'survey':
  1088. $onpage = true;
  1089. break;
  1090. }
  1091. if ($val != '' && $onpage) {
  1092. $jsNames[] = $val;
  1093. }
  1094. }
  1095. return array_unique($jsNames);
  1096. }
  1097. /**
  1098. * Return the list of all of the JavaScript variables used by the most recent expression
  1099. * @return <type>
  1100. */
  1101. public function GetJsVarsUsed()
  1102. {
  1103. if (is_null($this->varsUsed)){
  1104. return array();
  1105. }
  1106. $names = array_unique($this->varsUsed);
  1107. if (is_null($names)) {
  1108. return array();
  1109. }
  1110. $jsNames = array();
  1111. foreach ($names as $name)
  1112. {
  1113. if (preg_match("/\.(gid|grelevance|gseq|jsName|mandatory|qid|qseq|question|readWrite|relevance|rowdivid|sgqa|type)$/",$name))
  1114. {
  1115. continue;
  1116. }
  1117. $val = $this->GetVarAttribute($name,'jsName','');
  1118. if ($val != '') {
  1119. $jsNames[] = $val;
  1120. }
  1121. }
  1122. return array_unique($jsNames);
  1123. }
  1124. /**
  1125. * Return the JavaScript variable name for a named variable
  1126. * @param <type> $name
  1127. * @return <type>
  1128. */
  1129. public function GetJsVarFor($name)
  1130. {
  1131. return $this->GetVarAttribute($name,'jsName','');
  1132. }
  1133. /**
  1134. * Returns array of all variables used when parsing a string via sProcessStringContainingExpressions
  1135. * @return <type>
  1136. */
  1137. public function GetAllVarsUsed()
  1138. {
  1139. return array_unique($this->allVarsUsed);
  1140. }
  1141. /**
  1142. * Return the result of evaluating the equation - NULL if error
  1143. * @return mixed
  1144. */
  1145. public function GetResult()
  1146. {
  1147. return $this->RDP_result[0];
  1148. }
  1149. /**
  1150. * Return an array of errors
  1151. * @return array
  1152. */
  1153. public function GetErrors()
  1154. {
  1155. return $this->RDP_errs;
  1156. }
  1157. /**
  1158. * Converts the most recent expression into a valid JavaScript expression, mapping function and variable names and operators as needed.
  1159. * @return <type> the JavaScript expresssion
  1160. */
  1161. public function GetJavaScriptEquivalentOfExpression()
  1162. {
  1163. if (!is_null($this->jsExpression))
  1164. {
  1165. return $this->jsExpression;
  1166. }
  1167. if ($this->HasErrors())
  1168. {
  1169. $this->jsExpression = '';
  1170. return '';
  1171. }
  1172. $tokens = $this->RDP_tokens;
  1173. // TODOSHNOULLE
  1174. $stringParts=array();
  1175. $numTokens = count($tokens);
  1176. for ($i=0;$i<$numTokens;++$i)
  1177. {
  1178. $token = $tokens[$i];
  1179. // When do these need to be quoted?
  1180. switch ($token[2])
  1181. {
  1182. case 'DQ_STRING':
  1183. $stringParts[] = '"' . addcslashes($token[0],'\"') . '"'; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'";
  1184. break;
  1185. case 'SQ_STRING':
  1186. $stringParts[] = "'" . addcslashes($token[0],"\'") . "'"; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false) . "'";
  1187. break;
  1188. case 'SGQA':
  1189. case 'WORD':
  1190. if ($i+1<$numTokens && $tokens[$i+1][2] == 'LP')
  1191. {
  1192. // then word is a function name
  1193. $funcInfo = $this->RDP_ValidFunctions[$token[0]];
  1194. if ($funcInfo[1] == 'NA')
  1195. {
  1196. return ''; // to indicate that this is trying to use a undefined function. Need more graceful solution
  1197. }
  1198. $stringParts[] = $funcInfo[1]; // the PHP function name
  1199. }
  1200. elseif ($i+1<$numTokens && $tokens[$i+1][2] == 'ASSIGN')
  1201. {
  1202. $jsName = $this->GetVarAttribute($token[0],'jsName','');
  1203. $stringParts[] = "document.getElementById('" . $jsName . "').value";
  1204. if ($tokens[$i+1][0] == '+=')
  1205. {
  1206. // Javascript does concatenation unless both left and right side are numbers, so refactor the equation
  1207. $varName = $this->GetVarAttribute($token[0],'varName',$token[0]);
  1208. $stringParts[] = " = LEMval('" . $varName . "') + ";
  1209. ++$i;
  1210. }
  1211. }
  1212. else
  1213. {
  1214. $jsName = $this->GetVarAttribute($token[0],'jsName','');
  1215. $code = $this->GetVarAttribute($token[0],'code','');
  1216. if ($jsName != '')
  1217. {
  1218. $varName = $this->GetVarAttribute($token[0],'varName',$token[0]);
  1219. $stringParts[] = "LEMval('" . $varName . "') ";
  1220. }
  1221. else
  1222. {
  1223. $stringParts[] = is_numeric($code) ? $code : ("'" . addcslashes($code,"'") . "'"); // htmlspecialchars($code,ENT_QUOTES,'UTF-8',false) . "'");
  1224. }
  1225. }
  1226. break;
  1227. case 'LP':
  1228. case 'RP':
  1229. $stringParts[] = $token[0];
  1230. break;
  1231. case 'NUMBER':
  1232. $stringParts[] = is_numeric($token[0]) ? $token[0] : ("'" . $token[0] . "'");
  1233. break;
  1234. case 'COMMA':
  1235. $stringParts[] = $token[0] . ' ';
  1236. break;
  1237. default:
  1238. // don't need to check type of $token[2] here since already handling SQ_STRING and DQ_STRING above
  1239. switch (strtolower($token[0]))
  1240. {
  1241. case 'and': $stringParts[] = ' && '; break;
  1242. case 'or': $stringParts[] = ' || '; break;
  1243. case 'lt': $stringParts[] = ' < '; break;
  1244. case 'le': $stringParts[] = ' <= '; break;
  1245. case 'gt': $stringParts[] = ' > '; break;
  1246. case 'ge': $stringParts[] = ' >= '; break;
  1247. case 'eq': case '==': $stringParts[] = ' == '; break;
  1248. case 'ne': case '!=': $stringParts[] = ' != '; break;
  1249. default: $stringParts[] = ' ' . $token[0] . ' '; break;
  1250. }
  1251. break;
  1252. }
  1253. }
  1254. // for each variable that does not have a default value, add clause to throw error if any of them are NA
  1255. $nonNAvarsUsed = array();
  1256. foreach ($this->GetVarsUsed() as $var) // this function wants to see the NAOK suffix
  1257. {
  1258. if (!preg_match("/^.*\.(NAOK|relevanceStatus)$/", $var))
  1259. {
  1260. if ($this->GetVarAttribute($var,'jsName','') != '')
  1261. {
  1262. $nonNAvarsUsed[] = $var;
  1263. }
  1264. }
  1265. }
  1266. $mainClause = implode('', $stringParts);
  1267. $varsUsed = implode("', '", $nonNAvarsUsed);
  1268. if ($varsUsed != '')
  1269. {
  1270. $this->jsExpression = "LEMif(LEManyNA('" . $varsUsed . "'),'',(" . $mainClause . "))";
  1271. }
  1272. else
  1273. {
  1274. $this->jsExpression = '(' . $mainClause . ')';
  1275. }
  1276. return $this->jsExpression;
  1277. }
  1278. /**
  1279. * JavaScript Test function - simply writes the result of the current JavaScriptEquivalentFunction to the output buffer.
  1280. * @return <type>
  1281. */
  1282. public function GetJavascriptTestforExpression($expected,$num)
  1283. {
  1284. // assumes that the hidden variables have already been declared
  1285. $expr = $this->GetJavaScriptEquivalentOfExpression();
  1286. if (is_null($expr) || $expr == '') {
  1287. $expr = "'NULL'";
  1288. }
  1289. $jsmultiline_expr = str_replace("\n","\\\n",$expr);
  1290. $jsmultiline_expected = str_replace("\n","\\\n",addslashes($expected));
  1291. $jsParts = array();
  1292. $jsParts[] = "val = " . $jsmultiline_expr . ";\n";
  1293. $jsParts[] = "klass = (LEMeq(addslashes(val),'" . $jsmultiline_expected . "')) ? 'ok' : 'error';\n";
  1294. $jsParts[] = "document.getElementById('test_" . $num . "').innerHTML=(val);\n";
  1295. $jsParts[] = "document.getElementById('test_" . $num . "').className=klass;\n";
  1296. return implode('',$jsParts);
  1297. }
  1298. /**
  1299. * Generate the function needed to dynamically change the value of a <span> section
  1300. * @param <type> $name - the ID name for the function
  1301. * @return <type>
  1302. */
  1303. public function GetJavaScriptFunctionForReplacement($questionNum, $name,$eqn)
  1304. {
  1305. $jsParts = array();
  1306. // $jsParts[] = "\n // Tailor Question " . $questionNum . " - " . $name . ": { " . $eqn . " }\n";
  1307. $jsParts[] = " try{\n";
  1308. $jsParts[] = " document.getElementById('" . $name . "').innerHTML=LEMfixnum(\n ";
  1309. $jsParts[] = $this->GetJavaScriptEquivalentOfExpression();
  1310. $jsParts[] = ");\n";
  1311. $jsParts[] = " } catch (e) { }\n";
  1312. return implode('',$jsParts);
  1313. }
  1314. /**
  1315. * Returns the most recent PrettyPrint string generated by sProcessStringContainingExpressions
  1316. */
  1317. public function GetLastPrettyPrintExpression()
  1318. {
  1319. return $this->prettyPrintSource;
  1320. }
  1321. /**
  1322. * This is only used when there are no needed substitutions
  1323. * @param <type> $expr
  1324. */
  1325. public function SetPrettyPrintSource($expr)
  1326. {
  1327. $this->prettyPrintSource = $expr;
  1328. }
  1329. /**
  1330. * Color-codes Expressions (using HTML <span> tags), showing variable types and values.
  1331. * @return <type>
  1332. */
  1333. public function GetPrettyPrintString()
  1334. {
  1335. // color code the equation, showing not only errors, but also variable attributes
  1336. $errs = $this->RDP_errs;
  1337. $tokens = $this->RDP_tokens;
  1338. $errCount = count($errs);
  1339. $errIndex = 0;
  1340. if ($errCount > 0)
  1341. {
  1342. usort($errs,"cmpErrorTokens");
  1343. }
  1344. $errSpecificStyle= "style='border-style: solid; border-width: 2px; border-color: red;'";
  1345. $stringParts=array();
  1346. $numTokens = count($tokens);
  1347. $globalErrs=array();
  1348. while ($errIndex < $errCount)
  1349. {
  1350. if ($errs[$errIndex++][1][1]==0)
  1351. {
  1352. // General message, associated with position 0
  1353. $globalErrs[] = $errs[$errIndex-1][0];
  1354. }
  1355. else
  1356. {
  1357. --$errIndex;
  1358. break;
  1359. }
  1360. }
  1361. for ($i=0;$i<$numTokens;++$i)
  1362. {
  1363. $token = $tokens[$i];
  1364. $messages=array();
  1365. $thisTokenHasError=false;
  1366. if ($i==0 && count($globalErrs) > 0)
  1367. {
  1368. $messages = array_merge($messages,$globalErrs);
  1369. $thisTokenHasError=true;
  1370. }
  1371. if ($errIndex < $errCount && $token[1] == $errs[$errIndex][1][1])
  1372. {
  1373. $messages[] = $errs[$errIndex][0];
  1374. $thisTokenHasError=true;
  1375. }
  1376. if ($thisTokenHasError)
  1377. {
  1378. $stringParts[] = "<span title='" . implode('; ',$messages) . "' " . $errSpecificStyle . ">";
  1379. }
  1380. switch ($token[2])
  1381. {
  1382. case 'DQ_STRING':
  1383. $stringParts[] = "<span title='" . implode('; ',$messages) . "' style='color: gray'>\"";
  1384. $stringParts[] = $token[0]; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false);
  1385. $stringParts[] = "\"</span>";
  1386. break;
  1387. case 'SQ_STRING':
  1388. $stringParts[] = "<span title='" . implode('; ',$messages) . "' style='color: gray'>'";
  1389. $stringParts[] = $token[0]; // htmlspecialchars($token[0],ENT_QUOTES,'UTF-8',false);
  1390. $stringParts[] = "'</span>";
  1391. break;
  1392. case 'SGQA':
  1393. case 'WORD':
  1394. if ($i+1<$numTokens && $tokens[$i+1][2] == 'LP')
  1395. {
  1396. // then word is a function name
  1397. if ($this->RDP_isValidFunction($token[0])) {
  1398. $funcInfo = $this->RDP_ValidFunctions[$token[0]];
  1399. $messages[] = $funcInfo[2];
  1400. $messages[] = $funcInfo[3];
  1401. }
  1402. $stringParts[] = "<span title='" . implode('; ',$messages) . "' style='color: blue; font-weight: bold'>";
  1403. $stringParts[] = $token[0];
  1404. $stringParts[] = "</span>";
  1405. }
  1406. else
  1407. {
  1408. if (!$this->RDP_isValidVariable($token[0]))
  1409. {
  1410. $color = 'red';
  1411. $displayName = $token[0];
  1412. }
  1413. else
  1414. {
  1415. $jsName = $this->GetVarAttribute($token[0],'jsName','');
  1416. $code = $this->GetVarAttribute($token[0],'code','');
  1417. $question = $this->GetVarAttribute($token[0], 'question', '');
  1418. $qcode= $this->GetVarAttribute($token[0],'qcode','');
  1419. $questionSeq = $this->GetVarAttribute($token[0],'qseq',-1);
  1420. $groupSeq = $this->GetVarAttribute($token[0],'gseq',-1);
  1421. $ansList = $this->GetVarAttribute($token[0],'ansList','');
  1422. $gid = $this->GetVarAttribute($token[0],'gid',-1);
  1423. $qid = $this->GetVarAttribute($token[0],'qid',-1);
  1424. if ($jsName != '') {
  1425. $descriptor = '[' . $jsName . ']';
  1426. }
  1427. else {
  1428. $descriptor = '';
  1429. }
  1430. // Show variable name instead of SGQA code, if available
  1431. if ($qcode != '') {
  1432. if (preg_match('/^INSERTANS:/',$token[0])) {
  1433. $displayName = $qcode . '.shown';
  1434. $descriptor = '[' . $token[0] . ']';
  1435. }
  1436. else {
  1437. $args = explode('.',$token[0]);
  1438. if (count($args) == 2) {
  1439. $displayName = $qcode . '.' . $args[1];
  1440. }
  1441. else {
  1442. $displayName = $qcode;
  1443. }
  1444. }
  1445. }
  1446. else {
  1447. $displayName = $token[0];
  1448. }
  1449. if ($questionSeq != -1) {
  1450. $descriptor .= '[G:' . $groupSeq . ']';
  1451. }
  1452. if ($groupSeq != -1) {
  1453. $descriptor .= '[Q:' . $questionSeq . ']';
  1454. }
  1455. if (strlen($descriptor) > 0) {
  1456. $descriptor .= ': ';
  1457. }
  1458. if (version_compare(phpversion(), "5.2.3")>=0)
  1459. {
  1460. // 4th parameter to htmlspecialchars only became available in PHP version 5.2.3
  1461. $messages[] = $descriptor . htmlspecialchars($question,ENT_QUOTES,'UTF-8',false);
  1462. if ($ansList != '')
  1463. {
  1464. $messages[] = htmlspecialchars($ansList,ENT_QUOTES,'UTF-8',false);
  1465. }
  1466. if ($code != '') {
  1467. if ($token[2] == 'SGQA' && preg_match('/^INSERTANS:/',$token[0])) {
  1468. $shown = $this->GetVarAttribute($token[0], 'shown', '');
  1469. $messages[] = 'value=[' . htmlspecialchars($code,ENT_QUOTES,'UTF-8',false) . '] '
  1470. . htmlspecialchars($shown,ENT_QUOTES,'UTF-8',false);
  1471. }
  1472. else {
  1473. $messages[] = 'value=' . htmlspecialchars($code,ENT_QUOTES,'UTF-8',false);
  1474. }
  1475. }
  1476. }
  1477. else
  1478. {
  1479. $messages[] = $descriptor . htmlspecialchars($question,ENT_QUOTES,'UTF-8');
  1480. if ($ansList != '')
  1481. {
  1482. $messages[] = htmlspecialchars($ansList,ENT_QUOTES,'UTF-8');
  1483. }
  1484. if ($code != '') {
  1485. if ($token[2] == 'SGQA' && preg_match('/^INSERTANS:/',$token[0])) {
  1486. $shown = $this->GetVarAttribute($token[0], 'shown', '');
  1487. $messages[] = 'value=[' . htmlspecialchars($code,ENT_QUOTES,'UTF-8') . '] '
  1488. . htmlspecialchars($shown,ENT_QUOTES,'UTF-8');
  1489. }
  1490. else {
  1491. $messages[] = 'value=' . htmlspecialchars($code,ENT_QUOTES,'UTF-8');
  1492. }
  1493. }
  1494. }
  1495. if ($this->groupSeq == -1 || $groupSeq == -1 || $questionSeq == -1 || $this->questionSeq == -1) {
  1496. $color = '#996600'; // tan
  1497. }
  1498. else if ($groupSeq > $this->groupSeq) {
  1499. $color = '#FF00FF '; // pink a likely error
  1500. }
  1501. else if ($groupSeq < $this->groupSeq) {
  1502. $color = 'green';
  1503. }
  1504. else if ($questionSeq > $this->questionSeq) {
  1505. $color = 'maroon'; // #228b22 - warning
  1506. }
  1507. else {
  1508. $color = '#4C88BE'; // cyan that goes well with the background color
  1509. }
  1510. }
  1511. // prevent EM prcessing of messages within span
  1512. $message = implode('; ',$messages);
  1513. $message = str_replace(array('{','}'), array('{ ', ' }'), $message);
  1514. $stringParts[] = "<span title='" . $message . "' style='color: ". $color . "; font-weight: bold'";
  1515. if ($this->hyperlinkSyntaxHighlighting && isset($gid) && isset($qid)) {
  1516. // Modify this link to utilize a different framework
  1517. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $this->sid . '/gid/' . $gid . '/qid/' . $qid);
  1518. $stringParts[] = " onclick='window.open(\"" . $editlink . "\");'";
  1519. }
  1520. $stringParts[] = ">";
  1521. if ($this->sgqaNaming)
  1522. {
  1523. $sgqa = substr($jsName,4);
  1524. $nameParts = explode('.',$displayName);
  1525. if (count($nameParts)==2)
  1526. {
  1527. $sgqa .= '.' . $nameParts[1];
  1528. }
  1529. $stringParts[] = $sgqa;
  1530. }
  1531. else
  1532. {
  1533. $stringParts[] = $displayName;
  1534. }
  1535. $stringParts[] = "</span>";
  1536. }
  1537. break;
  1538. case 'ASSIGN':
  1539. $messages[] = 'Assigning a new value to a variable';
  1540. $stringParts[] = "<span title='" . implode('; ',$messages) . "' style='color: red; font-weight: bold'>";
  1541. $stringParts[] = $token[0];
  1542. $stringParts[] = "</span>";
  1543. break;
  1544. case 'COMMA':
  1545. $stringParts[] = $token[0] . ' ';
  1546. break;
  1547. case 'LP':
  1548. case 'RP':
  1549. case 'NUMBER':
  1550. $stringParts[] = $token[0];
  1551. break;
  1552. default:
  1553. $stringParts[] = ' ' . $token[0] . ' ';
  1554. break;
  1555. }
  1556. if ($thisTokenHasError)
  1557. {
  1558. $stringParts[] = "</span>";
  1559. ++$errIndex;
  1560. }
  1561. }
  1562. return "<span style='background-color: #eee8aa;'>" . implode('', $stringParts) . "</span>";
  1563. }
  1564. /**
  1565. * Get information about the variable, including JavaScript name, read-write status, and whether set on current page.
  1566. * @param <type> $varname
  1567. * @return <type>
  1568. */
  1569. private function GetVarAttribute($name,$attr,$default)
  1570. {
  1571. return LimeExpressionManager::GetVarAttribute($name,$attr,$default,$this->groupSeq,$this->questionSeq);
  1572. }
  1573. /**
  1574. * Return array of the list of variables used in the equation
  1575. * @return array
  1576. */
  1577. public function GetVarsUsed()
  1578. {
  1579. return array_unique($this->varsUsed);
  1580. }
  1581. /**
  1582. * Return true if there were syntax or processing errors
  1583. * @return boolean
  1584. */
  1585. public function HasErrors()
  1586. {
  1587. return (count($this->RDP_errs) > 0);
  1588. }
  1589. /**
  1590. * Return true if there are syntax errors
  1591. * @return boolean
  1592. */
  1593. private function HasSyntaxErrors()
  1594. {
  1595. // check for bad tokens
  1596. // check for unmatched parentheses
  1597. // check for undefined variables
  1598. // check for undefined functions (but can't easily check allowable # elements?)
  1599. $nesting = 0;
  1600. for ($i=0;$i<$this->RDP_count;++$i)
  1601. {
  1602. $token = $this->RDP_tokens[$i];
  1603. switch ($token[2])
  1604. {
  1605. case 'LP':
  1606. ++$nesting;
  1607. break;
  1608. case 'RP':
  1609. --$nesting;
  1610. if ($nesting < 0)
  1611. {
  1612. $this->RDP_AddError($this->gT("Extra right parentheses detected"), $token);
  1613. }
  1614. break;
  1615. case 'WORD':
  1616. case 'SGQA':
  1617. if ($i+1 < $this->RDP_count and $this->RDP_tokens[$i+1][2] == 'LP')
  1618. {
  1619. if (!$this->RDP_isValidFunction($token[0]))
  1620. {
  1621. $this->RDP_AddError($this->gT("Undefined function"), $token);
  1622. }
  1623. }
  1624. else
  1625. {
  1626. if (!($this->RDP_isValidVariable($token[0])))
  1627. {
  1628. $this->RDP_AddError($this->gT("Undefined variable"), $token);
  1629. }
  1630. }
  1631. break;
  1632. case 'OTHER':
  1633. $this->RDP_AddError($this->gT("Unsupported syntax"), $token);
  1634. break;
  1635. default:
  1636. break;
  1637. }
  1638. }
  1639. if ($nesting != 0)
  1640. {
  1641. $this->RDP_AddError(sprintf($this->gT("Missing %s closing right parentheses"),$nesting),NULL);
  1642. }
  1643. return (count($this->RDP_errs) > 0);
  1644. }
  1645. /**
  1646. * Return true if the function name is registered
  1647. * @param <type> $name
  1648. * @return boolean
  1649. */
  1650. private function RDP_isValidFunction($name)
  1651. {
  1652. return array_key_exists($name,$this->RDP_ValidFunctions);
  1653. }
  1654. /**
  1655. * Return true if the variable name is registered
  1656. * @param <type> $name
  1657. * @return boolean
  1658. */
  1659. private function RDP_isValidVariable($name)
  1660. {
  1661. $varName = preg_replace("/^(?:INSERTANS:)?(.*?)(?:\.(?:" . ExpressionManager::$RDP_regex_var_attr . "))?$/", "$1", $name);
  1662. return LimeExpressionManager::isValidVariable($varName);
  1663. }
  1664. /**
  1665. * Return true if the variable name is writable
  1666. * @param <type> $name
  1667. * @return <type>
  1668. */
  1669. private function RDP_isWritableVariable($name)
  1670. {
  1671. return ($this->GetVarAttribute($name, 'readWrite', 'N') == 'Y');
  1672. }
  1673. /**
  1674. * Process an expression and return its boolean value
  1675. * @param <type> $expr
  1676. * @param <type> $groupSeq - needed to determine whether using variables before they are declared
  1677. * @param <type> $questionSeq - needed to determine whether using variables before they are declared
  1678. * @return <type>
  1679. */
  1680. public function ProcessBooleanExpression($expr,$groupSeq=-1,$questionSeq=-1)
  1681. {
  1682. $this->groupSeq = $groupSeq;
  1683. $this->questionSeq = $questionSeq;
  1684. $expr = $this->ExpandThisVar($expr);
  1685. $status = $this->RDP_Evaluate($expr);
  1686. if (!$status) {
  1687. return false; // if there are errors in the expression, hide it?
  1688. }
  1689. $result = $this->GetResult();
  1690. if (is_null($result)) {
  1691. return false; // if there are errors in the expression, hide it?
  1692. }
  1693. // if ($result == 'false') {
  1694. // return false; // since the string 'false' is not considered boolean false, but an expression in JavaScript can return 'false'
  1695. // }
  1696. // return !empty($result);
  1697. // Check whether any variables are irrelevant - making this comparable to JavaScript which uses LEManyNA(varlist) to do the same thing
  1698. foreach ($this->GetVarsUsed() as $var) // this function wants to see the NAOK suffix
  1699. {
  1700. if (!preg_match("/^.*\.(NAOK|relevanceStatus)$/", $var))
  1701. {
  1702. if (!LimeExpressionManager::GetVarAttribute($var,'relevanceStatus',false,$groupSeq,$questionSeq))
  1703. {
  1704. return false;
  1705. }
  1706. }
  1707. }
  1708. return (boolean) $result;
  1709. }
  1710. /**
  1711. * Start processing a group of substitions - will be incrementally numbered
  1712. */
  1713. public function StartProcessingGroup($sid=NULL,$rooturl='',$hyperlinkSyntaxHighlighting=true)
  1714. {
  1715. $this->substitutionNum=0;
  1716. $this->substitutionInfo=array(); // array of JavaScripts for managing each substitution
  1717. $this->sid=$sid;
  1718. $this->hyperlinkSyntaxHighlighting=$hyperlinkSyntaxHighlighting;
  1719. }
  1720. /**
  1721. * Clear cache of tailoring content.
  1722. * When re-displaying same page, need to avoid generating double the amount of tailoring content.
  1723. */
  1724. public function ClearSubstitutionInfo()
  1725. {
  1726. $this->substitutionNum=0;
  1727. $this->substitutionInfo=array(); // array of JavaScripts for managing each substitution
  1728. }
  1729. /**
  1730. * Process multiple substitution iterations of a full string, containing multiple expressions delimited by {}, return a consolidated string
  1731. * @param <type> $src
  1732. * @param <type> $questionNum
  1733. * @param <type> $numRecursionLevels - number of levels of recursive substitution to perform
  1734. * @param <type> $whichPrettyPrintIteration - if recursing, specify which pretty-print iteration is desired
  1735. * @param <type> $groupSeq - needed to determine whether using variables before they are declared
  1736. * @param <type> $questionSeq - needed to determine whether using variables before they are declared
  1737. * @return <type>
  1738. */
  1739. public function sProcessStringContainingExpressions($src, $questionNum=0, $numRecursionLevels=1, $whichPrettyPrintIteration=1, $groupSeq=-1, $questionSeq=-1, $staticReplacement=false)
  1740. {
  1741. // tokenize string by the {} pattern, properly dealing with strings in quotations, and escaped curly brace values
  1742. $this->allVarsUsed = array();
  1743. $this->questionSeq = $questionSeq;
  1744. $this->groupSeq = $groupSeq;
  1745. $result = $src;
  1746. $prettyPrint = '';
  1747. $errors = array();
  1748. for($i=1;$i<=$numRecursionLevels;++$i)
  1749. {
  1750. // TODO - Since want to use <span> for dynamic substitution, what if there are recursive substititons?
  1751. $result = $this->sProcessStringContainingExpressionsHelper($result ,$questionNum, $staticReplacement);
  1752. if ($i == $whichPrettyPrintIteration)
  1753. {
  1754. $prettyPrint = $this->prettyPrintSource;
  1755. }
  1756. $errors = array_merge($errors, $this->RDP_errs);
  1757. }
  1758. $this->prettyPrintSource = $prettyPrint; // ensure that if doing recursive substition, can get original source to pretty print
  1759. $this->RDP_errs = $errors;
  1760. $result = str_replace(array('\{', '\}',), array('{', '}'), $result);
  1761. return $result;
  1762. }
  1763. /**
  1764. * Process one substitution iteration of a full string, containing multiple expressions delimited by {}, return a consolidated string
  1765. * @param <type> $src
  1766. * @param <type> $questionNum - used to generate substitution <span>s that indicate to which question they belong
  1767. * @return <type>
  1768. */
  1769. public function sProcessStringContainingExpressionsHelper($src, $questionNum, $staticReplacement=false)
  1770. {
  1771. // tokenize string by the {} pattern, properly dealing with strings in quotations, and escaped curly brace values
  1772. $stringParts = $this->asSplitStringOnExpressions($src);
  1773. $resolvedParts = array();
  1774. $prettyPrintParts = array();
  1775. $allErrors=array();
  1776. foreach ($stringParts as $stringPart)
  1777. {
  1778. if ($stringPart[2] == 'STRING') {
  1779. $resolvedParts[] = $stringPart[0];
  1780. $prettyPrintParts[] = $stringPart[0];
  1781. }
  1782. else {
  1783. ++$this->substitutionNum;
  1784. $expr = $this->ExpandThisVar(substr($stringPart[0],1,-1));
  1785. if ($this->RDP_Evaluate($expr))
  1786. {
  1787. $resolvedPart = $this->GetResult();
  1788. }
  1789. else
  1790. {
  1791. // show original and errors in-line
  1792. $resolvedPart = $this->GetPrettyPrintString();
  1793. $allErrors[] = $this->GetErrors();
  1794. }
  1795. $onpageJsVarsUsed = $this->GetOnPageJsVarsUsed();
  1796. $jsVarsUsed = $this->GetJsVarsUsed();
  1797. $prettyPrintParts[] = $this->GetPrettyPrintString();
  1798. $this->allVarsUsed = array_merge($this->allVarsUsed,$this->GetVarsUsed());
  1799. if (count($onpageJsVarsUsed) > 0 && !$staticReplacement)
  1800. {
  1801. $idName = "LEMtailor_Q_" . $questionNum . "_" . $this->substitutionNum;
  1802. // $resolvedParts[] = "<span id='" . $idName . "'>" . htmlspecialchars($resolvedPart,ENT_QUOTES,'UTF-8',false) . "</span>"; // TODO - encode within SPAN?
  1803. $resolvedParts[] = "<span id='" . $idName . "'>" . $resolvedPart . "</span>";
  1804. $this->substitutionVars[$idName] = 1;
  1805. $this->substitutionInfo[] = array(
  1806. 'questionNum' => $questionNum,
  1807. 'num' => $this->substitutionNum,
  1808. 'id' => $idName,
  1809. 'raw' => $stringPart[0],
  1810. 'result' => $resolvedPart,
  1811. 'vars' => implode('|',$jsVarsUsed),
  1812. 'js' => $this->GetJavaScriptFunctionForReplacement($questionNum, $idName, $expr),
  1813. );
  1814. }
  1815. else
  1816. {
  1817. $resolvedParts[] = $resolvedPart;
  1818. }
  1819. }
  1820. }
  1821. $result = implode('',$this->flatten_array($resolvedParts));
  1822. $this->prettyPrintSource = implode('',$this->flatten_array($prettyPrintParts));
  1823. $this->RDP_errs = $allErrors; // so that has all errors from this string
  1824. return $result; // recurse in case there are nested ones, avoiding infinite loops?
  1825. }
  1826. /**
  1827. * If the equation contains refernece to this, expand to comma separated list if needed.
  1828. * @param type $eqn
  1829. */
  1830. function ExpandThisVar($src)
  1831. {
  1832. $splitter = '(?:\b(?:self|that))(?:\.(?:[A-Z0-9_]+))*';
  1833. $parts = preg_split("/(" . $splitter . ")/i",$src,-1,(PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
  1834. $result = '';
  1835. foreach ($parts as $part)
  1836. {
  1837. if (preg_match("/" . $splitter . "/",$part))
  1838. {
  1839. $result .= LimeExpressionManager::GetAllVarNamesForQ($this->questionSeq,$part);
  1840. }
  1841. else
  1842. {
  1843. $result .= $part;
  1844. }
  1845. }
  1846. return $result;
  1847. }
  1848. /**
  1849. * Get info about all <span> elements needed for dynamic tailoring
  1850. * @return <type>
  1851. */
  1852. public function GetCurrentSubstitutionInfo()
  1853. {
  1854. return $this->substitutionInfo;
  1855. }
  1856. /**
  1857. * Flatten out an array, keeping it in the proper order
  1858. * @param array $a
  1859. * @return array
  1860. */
  1861. private function flatten_array(array $a) {
  1862. $i = 0;
  1863. while ($i < count($a)) {
  1864. if (is_array($a[$i])) {
  1865. array_splice($a, $i, 1, $a[$i]);
  1866. } else {
  1867. $i++;
  1868. }
  1869. }
  1870. return $a;
  1871. }
  1872. /**
  1873. * Run a registered function
  1874. * Some PHP functions require specific data types - those can be cast here.
  1875. * @param <type> $funcNameToken
  1876. * @param <type> $params
  1877. * @return boolean
  1878. */
  1879. private function RDP_RunFunction($funcNameToken,$params)
  1880. {
  1881. $name = $funcNameToken[0];
  1882. if (!$this->RDP_isValidFunction($name))
  1883. {
  1884. return false;
  1885. }
  1886. $func = $this->RDP_ValidFunctions[$name];
  1887. $funcName = $func[0];
  1888. $numArgs = count($params);
  1889. $result=1; // default value for $this->RDP_onlyparse
  1890. if (function_exists($funcName)) {
  1891. $numArgsAllowed = array_slice($func, 5); // get array of allowable argument counts from end of $func
  1892. $argsPassed = is_array($params) ? count($params) : 0;
  1893. // for unlimited # parameters (any value less than 0).
  1894. try
  1895. {
  1896. if ($numArgsAllowed[0] < 0) {
  1897. $minArgs = abs($numArgsAllowed[0] + 1); // so if value is -2, means that requires at least one argument
  1898. if ($argsPassed < $minArgs)
  1899. {
  1900. $this->RDP_AddError(sprintf($this->ngT("Function must have at least %s argument","Function must have at least %s arguments",$minArgs), $minArgs), $funcNameToken);
  1901. return false;
  1902. }
  1903. if (!$this->RDP_onlyparse) {
  1904. switch($funcName) {
  1905. case 'sprintf':
  1906. // PHP doesn't let you pass array of parameters to sprintf, so must use call_user_func_array
  1907. $result = call_user_func_array('sprintf',$params);
  1908. break;
  1909. default:
  1910. $result = $funcName($params);
  1911. break;
  1912. }
  1913. }
  1914. // Call function with the params passed
  1915. } elseif (in_array($argsPassed, $numArgsAllowed)) {
  1916. switch ($argsPassed) {
  1917. case 0:
  1918. if (!$this->RDP_onlyparse) {
  1919. $result = $funcName();
  1920. }
  1921. break;
  1922. case 1:
  1923. if (!$this->RDP_onlyparse) {
  1924. switch($funcName) {
  1925. case 'acos':
  1926. case 'asin':
  1927. case 'atan':
  1928. case 'cos':
  1929. case 'exp':
  1930. case 'is_nan':
  1931. case 'log':
  1932. case 'sin':
  1933. case 'sqrt':
  1934. case 'tan':
  1935. if (is_numeric($params[0]))
  1936. {
  1937. $result = $funcName(floatval($params[0]));
  1938. }
  1939. else
  1940. {
  1941. $result = NAN;
  1942. }
  1943. break;
  1944. default:
  1945. $result = $funcName($params[0]);
  1946. break;
  1947. }
  1948. }
  1949. break;
  1950. case 2:
  1951. if (!$this->RDP_onlyparse) {
  1952. switch($funcName) {
  1953. case 'atan2':
  1954. if (is_numeric($params[0]) && is_numeric($params[1]))
  1955. {
  1956. $result = $funcName(floatval($params[0]),floatval($params[1]));
  1957. }
  1958. else
  1959. {
  1960. $result = NAN;
  1961. }
  1962. break;
  1963. default:
  1964. $result = $funcName($params[0], $params[1]);
  1965. break;
  1966. }
  1967. }
  1968. break;
  1969. case 3:
  1970. if (!$this->RDP_onlyparse) {
  1971. $result = $funcName($params[0], $params[1], $params[2]);
  1972. }
  1973. break;
  1974. case 4:
  1975. if (!$this->RDP_onlyparse) {
  1976. $result = $funcName($params[0], $params[1], $params[2], $params[3]);
  1977. }
  1978. break;
  1979. case 5:
  1980. if (!$this->RDP_onlyparse) {
  1981. $result = $funcName($params[0], $params[1], $params[2], $params[3], $params[4]);
  1982. }
  1983. break;
  1984. case 6:
  1985. if (!$this->RDP_onlyparse) {
  1986. $result = $funcName($params[0], $params[1], $params[2], $params[3], $params[4], $params[5]);
  1987. }
  1988. break;
  1989. default:
  1990. $this->RDP_AddError(sprintf($this->gT("Unsupported number of arguments: %s", $argsPassed)), $funcNameToken);
  1991. return false;
  1992. }
  1993. } else {
  1994. $this->RDP_AddError(sprintf($this->gT("Function does not support %s arguments"), $argsPassed).' '
  1995. . sprintf($this->gT("Function supports this many arguments, where -1=unlimited: %s"), implode(',', $numArgsAllowed)), $funcNameToken);
  1996. return false;
  1997. }
  1998. }
  1999. catch (Exception $e)
  2000. {
  2001. $this->RDP_AddError($e->getMessage(),$funcNameToken);
  2002. return false;
  2003. }
  2004. $token = array($result,$funcNameToken[1],'NUMBER');
  2005. $this->RDP_StackPush($token);
  2006. return true;
  2007. }
  2008. }
  2009. /**
  2010. * Add user functions to array of allowable functions within the equation.
  2011. * $functions is an array of key to value mappings like this:
  2012. * See $this->RDP_ValidFunctions for examples of the syntax
  2013. * @param array $functions
  2014. */
  2015. public function RegisterFunctions(array $functions) {
  2016. $this->RDP_ValidFunctions= array_merge($this->RDP_ValidFunctions, $functions);
  2017. }
  2018. /**
  2019. * Set the value of a registered variable
  2020. * @param $op - the operator (=,*=,/=,+=,-=)
  2021. * @param <type> $name
  2022. * @param <type> $value
  2023. */
  2024. private function RDP_SetVariableValue($op,$name,$value)
  2025. {
  2026. if ($this->RDP_onlyparse)
  2027. {
  2028. return 1;
  2029. }
  2030. return LimeExpressionManager::SetVariableValue($op, $name, $value);
  2031. }
  2032. /**
  2033. * Split a soure string into STRING vs. EXPRESSION, where the latter is surrounded by unescaped curly braces.
  2034. * This verson properly handles nested curly braces and curly braces within strings within curly braces - both of which are needed to better support JavaScript
  2035. * Users still need to add a space or carriage return after opening braces (and ideally before closing braces too) to avoid having them treated as expressions.
  2036. * @param <type> $src
  2037. * @return string
  2038. */
  2039. public function asSplitStringOnExpressions($src)
  2040. {
  2041. $parts = preg_split($this->RDP_ExpressionRegex,$src,-1,(PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
  2042. $count = count($parts);
  2043. $tokens = array();
  2044. $inSQString=false;
  2045. $inDQString=false;
  2046. $curlyDepth=0;
  2047. $thistoken=array();
  2048. $offset=0;
  2049. for ($j=0;$j<$count;++$j)
  2050. {
  2051. switch($parts[$j])
  2052. {
  2053. case '{':
  2054. if ($j < ($count-1) && preg_match('/\s|\n|\r/',substr($parts[$j+1],0,1)))
  2055. {
  2056. // don't count this as an expression if the opening brace is followed by whitespace
  2057. $thistoken[] = '{';
  2058. $thistoken[] = $parts[++$j];
  2059. }
  2060. else if ($inDQString || $inSQString)
  2061. {
  2062. // just push the curly brace
  2063. $thistoken[] = '{';
  2064. }
  2065. else if ($curlyDepth>0)
  2066. {
  2067. // a nested curly brace - just push it
  2068. $thistoken[] = '{';
  2069. ++$curlyDepth;
  2070. }
  2071. else
  2072. {
  2073. // then starting an expression - save the out-of-expression string
  2074. if (count($thistoken) > 0)
  2075. {
  2076. $_token = implode('',$thistoken);
  2077. $tokens[] = array(
  2078. $_token,
  2079. $offset,
  2080. 'STRING'
  2081. );
  2082. $offset += strlen($_token);
  2083. }
  2084. $curlyDepth=1;
  2085. $thistoken = array();
  2086. $thistoken[] = '{';
  2087. }
  2088. break;
  2089. case '}':
  2090. // don't count this as an expression if the closing brace is preceded by whitespace
  2091. if ($j > 0 && preg_match('/\s|\n|\r/',substr($parts[$j-1],-1,1)))
  2092. {
  2093. $thistoken[] = '}';
  2094. }
  2095. else if ($curlyDepth==0)
  2096. {
  2097. // just push the token
  2098. $thistoken[] = '}';
  2099. }
  2100. else
  2101. {
  2102. if ($inSQString || $inDQString)
  2103. {
  2104. // just push the token
  2105. $thistoken[] = '}';
  2106. }
  2107. else
  2108. {
  2109. --$curlyDepth;
  2110. if ($curlyDepth==0)
  2111. {
  2112. // then closing expression
  2113. $thistoken[] = '}';
  2114. $_token = implode('',$thistoken);
  2115. $tokens[] = array(
  2116. $_token,
  2117. $offset,
  2118. 'EXPRESSION'
  2119. );
  2120. $offset += strlen($_token);
  2121. $thistoken=array();
  2122. }
  2123. else
  2124. {
  2125. // just push the token
  2126. $thistoken[] = '}';
  2127. }
  2128. }
  2129. }
  2130. break;
  2131. case '\'':
  2132. $thistoken[] = '\'';
  2133. if ($curlyDepth==0)
  2134. {
  2135. // only counts as part of a string if it is already within an expression
  2136. }
  2137. else
  2138. {
  2139. if ($inDQString)
  2140. {
  2141. // then just push the single quote
  2142. }
  2143. else
  2144. {
  2145. if ($inSQString) {
  2146. $inSQString=false; // finishing a single-quoted string
  2147. }
  2148. else {
  2149. $inSQString=true; // starting a single-quoted string
  2150. }
  2151. }
  2152. }
  2153. break;
  2154. case '"':
  2155. $thistoken[] = '"';
  2156. if ($curlyDepth==0)
  2157. {
  2158. // only counts as part of a string if it is already within an expression
  2159. }
  2160. else
  2161. {
  2162. if ($inSQString)
  2163. {
  2164. // then just push the double quote
  2165. }
  2166. else
  2167. {
  2168. if ($inDQString) {
  2169. $inDQString=false; // finishing a double-quoted string
  2170. }
  2171. else {
  2172. $inDQString=true; // starting a double-quoted string
  2173. }
  2174. }
  2175. }
  2176. break;
  2177. case '\\':
  2178. if ($j < ($count-1)) {
  2179. $thistoken[] = $parts[$j++];
  2180. $thistoken[] = $parts[$j];
  2181. }
  2182. break;
  2183. default:
  2184. $thistoken[] = $parts[$j];
  2185. break;
  2186. }
  2187. }
  2188. if (count($thistoken) > 0)
  2189. {
  2190. $tokens[] = array(
  2191. implode('',$thistoken),
  2192. $offset,
  2193. 'STRING',
  2194. );
  2195. }
  2196. return $tokens;
  2197. }
  2198. /**
  2199. * Specify the survey mode for this survey. Options are 'survey', 'group', and 'question'
  2200. * @param type $mode
  2201. */
  2202. public function SetSurveyMode($mode)
  2203. {
  2204. if (preg_match('/^group|question|survey$/',$mode))
  2205. {
  2206. $this->surveyMode = $mode;
  2207. }
  2208. }
  2209. /**
  2210. * Pop a value token off of the stack
  2211. * @return token
  2212. */
  2213. private function RDP_StackPop()
  2214. {
  2215. if (count($this->RDP_stack) > 0)
  2216. {
  2217. return array_pop($this->RDP_stack);
  2218. }
  2219. else
  2220. {
  2221. $this->RDP_AddError($this->gT("Tried to pop value off of empty stack"), NULL);
  2222. return NULL;
  2223. }
  2224. }
  2225. /**
  2226. * Stack only holds values (number, string), not operators
  2227. * @param array $token
  2228. */
  2229. private function RDP_StackPush(array $token)
  2230. {
  2231. if ($this->RDP_onlyparse)
  2232. {
  2233. // If only parsing, still want to validate syntax, so use "1" for all variables
  2234. switch($token[2])
  2235. {
  2236. case 'DQ_STRING':
  2237. case 'SQ_STRING':
  2238. $this->RDP_stack[] = array(1,$token[1],$token[2]);
  2239. break;
  2240. case 'NUMBER':
  2241. default:
  2242. $this->RDP_stack[] = array(1,$token[1],'NUMBER');
  2243. break;
  2244. }
  2245. }
  2246. else
  2247. {
  2248. $this->RDP_stack[] = $token;
  2249. }
  2250. }
  2251. /**
  2252. * Split the source string into tokens, removing whitespace, and categorizing them by type.
  2253. *
  2254. * @param $src
  2255. * @return array
  2256. */
  2257. private function RDP_Tokenize($src)
  2258. {
  2259. // $tokens0 = array of tokens from equation, showing value and offset position. Will include SPACE, which should be removed
  2260. $tokens0 = preg_split($this->RDP_TokenizerRegex,$src,-1,(PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE));
  2261. // $tokens = array of tokens from equation, showing value, offsete position, and type. Will not contain SPACE, but will contain OTHER
  2262. $tokens = array();
  2263. // Add token_type to $tokens: For each token, test each categorization in order - first match will be the best.
  2264. for ($j=0;$j<count($tokens0);++$j)
  2265. {
  2266. for ($i=0;$i<count($this->RDP_CategorizeTokensRegex);++$i)
  2267. {
  2268. $token = $tokens0[$j][0];
  2269. if (preg_match($this->RDP_CategorizeTokensRegex[$i],$token))
  2270. {
  2271. if ($this->RDP_TokenType[$i] !== 'SPACE') {
  2272. $tokens0[$j][2] = $this->RDP_TokenType[$i];
  2273. if ($this->RDP_TokenType[$i] == 'DQ_STRING' || $this->RDP_TokenType[$i] == 'SQ_STRING')
  2274. {
  2275. // remove outside quotes
  2276. // $unquotedToken = str_replace(array('\"',"\'","\\\\",'\n','\r','\t'),array('"',"'",'\\',"\n","\n","\t"),substr($token,1,-1));
  2277. $unquotedToken = str_replace(array('\"',"\'","\\\\"),array('"',"'",'\\'),substr($token,1,-1));
  2278. $tokens0[$j][0] = $unquotedToken;
  2279. }
  2280. $tokens[] = $tokens0[$j]; // get first matching non-SPACE token type and push onto $tokens array
  2281. }
  2282. break; // only get first matching token type
  2283. }
  2284. }
  2285. }
  2286. return $tokens;
  2287. }
  2288. /**
  2289. * Unit test the asSplitStringOnExpressions() function to ensure that accurately parses out all expressions
  2290. * surrounded by curly braces, allowing for strings and escaped curly braces.
  2291. */
  2292. static function UnitTestStringSplitter()
  2293. {
  2294. $tests = <<<EOD
  2295. This string does not contain an expression
  2296. "This is only a string"
  2297. "this is a string that contains {something in curly brace}"
  2298. How about nested curly braces, like {INSERTANS:{SGQ}}?
  2299. This example has escaped curly braces like \{this is not an equation\}
  2300. Should the parser check for unmatched { opening curly braces?
  2301. What about for unmatched } closing curly braces?
  2302. What if there is a { space after the opening brace?}
  2303. What about a {space before the closing brace }?
  2304. What about an { expression nested {within a string} that has white space after the opening brace}?
  2305. This {expression has {a nested curly brace { plus ones with whitespace around them} - they should be properly captured} into an expression with sub-expressions.
  2306. This {is a string {since it does not close } all of its curly} braces.
  2307. Can {expressions contain 'single' or "double" quoted strings}?
  2308. Can an expression contain a perl regular expression like this {'/^\d{3}-\d{2}-\d{4}$/'}?
  2309. [img src="images/mine_{Q1}.png"/]
  2310. [img src="images/mine_" + {Q1} + ".png"/]
  2311. [img src={implode('','"images/mine_',Q1,'.png"')}/]
  2312. [img src="images/mine_{if(Q1=="Y",'yes','no')}.png"/]
  2313. [img src="images/mine_{if(Q1=="Y",'sq with {nested braces}',"dq with {nested braces}")}.png"/]
  2314. {name}, you said that you are {age} years old, and that you have {numKids} {if((numKids==1),'child','children')} and {numPets} {if((numPets==1),'pet','pets')} running around the house. So, you have {numKids + numPets} wild {if((numKids + numPets ==1),'beast','beasts')} to chase around every day.
  2315. Since you have more {if((INSERT61764X1X3 > INSERT61764X1X4),'children','pets')} than you do {if((INSERT61764X1X3 > INSERT61764X1X4),'pets','children')}, do you feel that the {if((INSERT61764X1X3 > INSERT61764X1X4),'pets','children')} are at a disadvantage?
  2316. Here is a String that failed to parse prior to fixing the preg_split() command to avoid recursive search of sub-strings: [{((617167X9X3241 == "Y" or 617167X9X3242 == "Y" or 617167X9X3243 == "Y" or 617167X9X3244 == "Y" or 617167X9X3245 == "Y" or 617167X9X3246 == "Y" or 617167X9X3247 == "Y" or 617167X9X3248 == "Y" or 617167X9X3249 == "Y") and (617167X9X3301 == "Y" or 617167X9X3302 == "Y" or 617167X9X3303 == "Y" or 617167X9X3304 == "Y" or 617167X9X3305 == "Y" or 617167X9X3306 == "Y" or 617167X9X3307 == "Y" or 617167X9X3308 == "Y" or 617167X9X3309 == "Y"))}] Here is the question.
  2317. EOD;
  2318. // Here is a String that failed to parse prior to fixing the preg_split() command to avoid recursive search of sub-strings: [{((617167X9X3241 == "Y" or 617167X9X3242 == "Y" or 617167X9X3243 == "Y" or 617167X9X3244 == "Y" or 617167X9X3245 == "Y" or 617167X9X3246 == "Y" or 617167X9X3247 == "Y" or 617167X9X3248 == "Y" or 617167X9X3249 == "Y") and (617167X9X3301 == "Y" or 617167X9X3302 == "Y" or 617167X9X3303 == "Y" or 617167X9X3304 == "Y" or 617167X9X3305 == "Y" or 617167X9X3306 == "Y" or 617167X9X3307 == "Y" or 617167X9X3308 == "Y" or 617167X9X3309 == "Y"))}] Here is the question.
  2319. $em = new ExpressionManager();
  2320. $atests = explode("\n",$tests);
  2321. array_push($atests,'"hi\nthere\nhow\nare\nyou?\n"');
  2322. foreach($atests as $test)
  2323. {
  2324. $tokens = $em->asSplitStringOnExpressions($test);
  2325. print '<b>' . $test . '</b><hr/>';
  2326. print '<code>';
  2327. print implode("<br/>\n",explode("\n",print_r($tokens,TRUE)));
  2328. print '</code><hr/>';
  2329. }
  2330. }
  2331. /**
  2332. * Unit test the Tokenizer - Tokenize and generate a HTML-compatible print-out of a comprehensive set of test cases
  2333. */
  2334. static function UnitTestTokenizer()
  2335. {
  2336. // Comprehensive test cases for tokenizing
  2337. $tests = <<<EOD
  2338. String: "what about regular expressions, like for SSN (^\d{3}-\d{2}-\d{4}) or US phone# ((?:\(\d{3}\)\s*\d{3}-\d{4})"
  2339. String: "Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"
  2340. String: "can single quoted strings" . 'contain nested \'quoted sections\'?';
  2341. Parens: upcase('hello');
  2342. Numbers: 42 72.35 -15 +37 42A .5 0.7
  2343. And_Or: (this and that or the other); Sandles, sorting; (a && b || c)
  2344. Words: hi there, my name is C3PO!
  2345. UnaryOps: ++a, --b !b
  2346. BinaryOps: (a + b * c / d)
  2347. Comparators: > >= < <= == != gt ge lt le eq ne (target large gents built agile less equal)
  2348. Assign: = += -= *= /=
  2349. SGQA: 1X6X12 1X6X12ber1 1X6X12ber1_lab1 3583X84X249 12X3X5lab1_ber#1 1X6X12.NAOK 1X6X12ber1.NAOK 1X6X12ber1_lab1.NAOK 3583X84X249.NAOK 12X3X5lab1_ber#1.NAOK
  2350. Errors: Apt # 10C; (2 > 0) ? 'hi' : 'there'; array[30]; >>> <<< /* this is not a comment */ // neither is this
  2351. Words: q5pointChoice q5pointChoice.bogus q5pointChoice.code q5pointChoice.mandatory q5pointChoice.NAOK q5pointChoice.qid q5pointChoice.question q5pointChoice.relevance q5pointChoice.shown q5pointChoice.type
  2352. EOD;
  2353. $em = new ExpressionManager();
  2354. foreach(explode("\n",$tests) as $test)
  2355. {
  2356. $tokens = $em->RDP_Tokenize($test);
  2357. print '<b>' . $test . '</b><hr/>';
  2358. print '<code>';
  2359. print implode("<br/>\n",explode("\n",print_r($tokens,TRUE)));
  2360. print '</code><hr/>';
  2361. }
  2362. }
  2363. /**
  2364. * Show a table of allowable Expression Manager functions
  2365. * @return string
  2366. */
  2367. static function ShowAllowableFunctions()
  2368. {
  2369. $em = new ExpressionManager();
  2370. $output = "<h3>Functions Available within Expression Manager</h3>\n";
  2371. $output .= "<table border='1'><tr><th>Function</th><th>Meaning</th><th>Syntax</th><th>Reference</th></tr>\n";
  2372. foreach ($em->RDP_ValidFunctions as $name => $func) {
  2373. $output .= "<tr><td>" . $name . "</td><td>" . $func[2] . "</td><td>" . $func[3] . "</td><td><a href='" . $func[4] . "'>" . $func[4] . "</a>&nbsp;</td></tr>\n";
  2374. }
  2375. $output .= "</table>\n";
  2376. return $output;
  2377. }
  2378. /**
  2379. * Unit test the Evaluator, allowing for passing in of extra functions, variables, and tests
  2380. */
  2381. static function UnitTestEvaluator()
  2382. {
  2383. // Some test cases for Evaluator
  2384. $vars = array(
  2385. 'one' => array('sgqa'=>'one', 'code'=>1, 'jsName'=>'java_one', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>4),
  2386. 'two' => array('sgqa'=>'two', 'code'=>2, 'jsName'=>'java_two', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>4),
  2387. 'three' => array('sgqa'=>'three', 'code'=>3, 'jsName'=>'java_three', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>4),
  2388. 'four' => array('sgqa'=>'four', 'code'=>4, 'jsName'=>'java_four', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>1),
  2389. 'five' => array('sgqa'=>'five', 'code'=>5, 'jsName'=>'java_five', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>1),
  2390. 'six' => array('sgqa'=>'six', 'code'=>6, 'jsName'=>'java_six', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>1),
  2391. 'seven' => array('sgqa'=>'seven', 'code'=>7, 'jsName'=>'java_seven', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>5),
  2392. 'eight' => array('sgqa'=>'eight', 'code'=>8, 'jsName'=>'java_eight', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>5),
  2393. 'nine' => array('sgqa'=>'nine', 'code'=>9, 'jsName'=>'java_nine', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>5),
  2394. 'ten' => array('sgqa'=>'ten', 'code'=>10, 'jsName'=>'java_ten', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2395. 'half' => array('sgqa'=>'half', 'code'=>.5, 'jsName'=>'java_half', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2396. 'hi' => array('sgqa'=>'hi', 'code'=>'there', 'jsName'=>'java_hi', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2397. 'hello' => array('sgqa'=>'hello', 'code'=>"Tom", 'jsName'=>'java_hello', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2398. 'a' => array('sgqa'=>'a', 'code'=>0, 'jsName'=>'java_a', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>2),
  2399. 'b' => array('sgqa'=>'b', 'code'=>0, 'jsName'=>'java_b', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>2),
  2400. 'c' => array('sgqa'=>'c', 'code'=>0, 'jsName'=>'java_c', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>2),
  2401. 'd' => array('sgqa'=>'d', 'code'=>0, 'jsName'=>'java_d', 'readWrite'=>'Y', 'gseq'=>2,'qseq'=>2),
  2402. 'eleven' => array('sgqa'=>'eleven', 'code'=>11, 'jsName'=>'java_eleven', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2403. 'twelve' => array('sgqa'=>'twelve', 'code'=>12, 'jsName'=>'java_twelve', 'readWrite'=>'Y', 'gseq'=>1,'qseq'=>1),
  2404. // Constants
  2405. 'ASSESSMENT_HEADING' => array('sgqa'=>'ASSESSMENT_HEADING', 'code'=>'"Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"', 'jsName'=>'', 'readWrite'=>'N'),
  2406. 'QID' => array('sgqa'=>'QID', 'code'=>'value for {QID}', 'jsName'=>'', 'readWrite'=>'N'),
  2407. 'QUESTIONHELP' => array('sgqa'=>'QUESTIONHELP', 'code'=>'"can single quoted strings" . \'contain nested \'quoted sections\'?', 'jsName'=>'', 'readWrite'=>'N'),
  2408. 'QUESTION_HELP' => array('sgqa'=>'QUESTION_HELP', 'code'=>'Can strings have embedded <tags> like <html>, or even unbalanced "quotes or entities without terminal semicolons like &amp and &lt?', 'jsName'=>'', 'readWrite'=>'N'),
  2409. 'NUMBEROFQUESTIONS' => array('sgqa'=>'NUMBEROFQUESTIONS', 'code'=>'value for {NUMBEROFQUESTIONS}', 'jsName'=>'', 'readWrite'=>'N'),
  2410. 'THEREAREXQUESTIONS' => array('sgqa'=>'THEREAREXQUESTIONS', 'code'=>'value for {THEREAREXQUESTIONS}', 'jsName'=>'', 'readWrite'=>'N'),
  2411. 'TOKEN:FIRSTNAME' => array('sgqa'=>'TOKEN:FIRSTNAME', 'code' => 'value for {TOKEN:FIRSTNAME}', 'jsName' => '', 'readWrite' => 'N'),
  2412. 'WELCOME' => array('sgqa'=>'WELCOME', 'code'=>'value for {WELCOME}', 'jsName'=>'', 'readWrite'=>'N'),
  2413. // also include SGQA values and read-only variable attributes
  2414. '12X34X56' => array('sgqa'=>'12X34X56', 'code'=>5, 'jsName'=>'', 'readWrite'=>'N', 'gseq'=>1,'qseq'=>1),
  2415. '12X3X5lab1_ber' => array('sgqa'=>'12X3X5lab1_ber', 'code'=>10, 'jsName'=>'', 'readWrite'=>'N', 'gseq'=>1,'qseq'=>1),
  2416. 'q5pointChoice' => array('sgqa'=>'q5pointChoice', 'code'=>3, 'jsName'=>'java_q5pointChoice', 'readWrite'=>'N','shown'=>'Father', 'relevance'=>1, 'type'=>'5', 'question'=>'(question for q5pointChoice)', 'qid'=>14,'gseq'=>2,'qseq'=>14),
  2417. 'qArrayNumbers_ls1_min' => array('sgqa'=>'qArrayNumbers_ls1_min', 'code'=> 7, 'jsName'=>'java_qArrayNumbers_ls1_min', 'readWrite'=>'N','shown'=> 'I love LimeSurvey', 'relevance'=>1, 'type'=>'A', 'question'=>'(question for qArrayNumbers)', 'qid'=>6,'gseq'=>2,'qseq'=>6),
  2418. '12X3X5lab1_ber#1' => array('sgqa'=>'12X3X5lab1_ber#1', 'code'=> 15, 'jsName'=>'', 'readWrite'=>'N', 'gseq'=>1,'qseq'=>1),
  2419. 'zero' => array('sgqa'=>'zero', 'code'=>0, 'jsName'=>'java_zero', 'gseq'=>0,'qseq'=>0),
  2420. 'empty' => array('sgqa'=>'empty', 'code'=>'', 'jsName'=>'java_empty', 'gseq'=>0,'qseq'=>0),
  2421. 'BREAKS' => array('sgqa'=>'BREAKS', 'code'=>"1\n2\n3", 'jsName'=>'', 'readWrite'=>'N'),
  2422. );
  2423. // Syntax for $tests is
  2424. // expectedResult~expression
  2425. // if the expected result is an error, use NULL for the expected result
  2426. $tests = <<<EOD
  2427. <B>Empty Vs. Empty</B>~"<B>Empty Vs. Empty</B>"
  2428. 1~'' == ''
  2429. 0~'' != ''
  2430. 0~'' > ''
  2431. 1~'' >= ''
  2432. 0~'' < ''
  2433. 1~'' <= ''
  2434. 1~!''
  2435. ~('' and '')
  2436. ~('' or '')
  2437. <B>Empty Vs. Zero</B>~"<B>Empty Vs. Zero</B>"
  2438. 0~'' == 0
  2439. 1~'' != 0
  2440. 0~'' > 0
  2441. 0~'' >= 0
  2442. 0~'' < 0
  2443. 0~'' <= 0
  2444. 1~!''
  2445. 1~!0
  2446. 0~('' and 0)
  2447. 0~('' or 0)
  2448. <B>Empty Vs. Constant</B>~"<B>Empty Vs. Constant</B>"
  2449. 0~'' == 3
  2450. 1~'' != 3
  2451. 0~'' > 3
  2452. 0~'' >= 3
  2453. 0~'' < 3
  2454. 0~'' <= 3
  2455. 1~!''
  2456. 0~!3
  2457. 0~('' and 3)
  2458. 1~('' or 3)
  2459. <B>Empty Vs. Empty_Var</B>~"<B>Empty Vs. Empty_Var</B>"
  2460. 1~'' == empty
  2461. 0~'' != empty
  2462. 0~'' > empty
  2463. 1~'' >= empty
  2464. 0~'' < empty
  2465. 1~'' <= empty
  2466. 1~!''
  2467. 1~!empty
  2468. ~('' and empty)
  2469. ~('' or empty)
  2470. <B>Empty_Var Vs. Zero</B>~"<B>Empty_Var Vs. Zero</B>"
  2471. 0~empty == 0
  2472. 1~empty != 0
  2473. 0~empty > 0
  2474. 0~empty >= 0
  2475. 0~empty < 0
  2476. 0~empty <= 0
  2477. 1~!empty
  2478. 1~!0
  2479. 0~(empty and 0)
  2480. 0~(empty or 0)
  2481. <B>Empty_Var Vs. Zero</B>~"<B>Empty_Var Vs. Zero</B>"
  2482. 0~empty == zero
  2483. 1~empty != zero
  2484. 0~empty > zero
  2485. 0~empty >= zero
  2486. 0~empty < zero
  2487. 0~empty <= zero
  2488. 1~!empty
  2489. 1~!zero
  2490. 0~(empty and zero)
  2491. 0~(empty or zero)
  2492. <B>Empty_Var Vs. Constant</B>~"<B>Empty_Var Vs. Constant</B>"
  2493. 0~empty == 3
  2494. 1~empty != 3
  2495. 0~empty > 3
  2496. 0~empty >= 3
  2497. 0~empty < 3
  2498. 0~empty <= 3
  2499. 1~!empty
  2500. 0~!3
  2501. 0~(empty and 3)
  2502. 1~(empty or 3)
  2503. <B>Solution: Empty_Var Vs. Zero</B>~"<B>Solution: Empty_Var Vs. Zero</B>"
  2504. 0~!is_empty(empty) && (empty == 0)
  2505. 0~!is_empty(five) && (five == 0)
  2506. 1~!is_empty(zero) && (zero == 0)
  2507. 0~!is_empty(empty) && (empty > 0)
  2508. 0~!is_empty(empty) && (empty >= 0)
  2509. 0~!is_empty(empty) && (empty < 0)
  2510. 0~!is_empty(empty) && (empty <= 0)
  2511. 0~!is_empty(empty) && ((empty and 0))
  2512. 0~!is_empty(empty) && ((empty or 0))
  2513. <B>Solution: Empty_Var Vs. Zero</B>~"<B>Solution: Empty_Var Vs. Zero</B>"
  2514. 0~!is_empty(empty) && (empty == zero)
  2515. 0~!is_empty(five) && (five == zero)
  2516. 1~!is_empty(zero) && (zero == zero)
  2517. 0~!is_empty(empty) && (empty > zero)
  2518. 0~!is_empty(empty) && (empty >= zero)
  2519. 0~!is_empty(empty) && (empty < zero)
  2520. 0~!is_empty(empty) && (empty <= zero)
  2521. 0~!is_empty(empty) && ((empty and zero))
  2522. 0~!is_empty(empty) && ((empty or zero))
  2523. <B>Solution: Empty_Var Vs. Constant</B>~"<B>Solution: Empty_Var Vs. Constant</B>"
  2524. 0~!is_empty(empty) && (empty < 3)
  2525. 0~!is_empty(empty) && (empty <= 3)
  2526. <B>Solution: Empty_Var Vs. Variable</B>~"<B>Solution: Empty_Var Vs. Variable</B>"
  2527. 0~!is_empty(empty) && (empty < five)
  2528. 0~!is_empty(empty) && (empty <= five)
  2529. <B>Solution: The Hard One is Empty_Var != 0</B>~"<B>Solution: The Hard One is Empty_Var != 0</B>"
  2530. 1~(empty != 0)
  2531. 1~!is_empty(empty) && (empty != 0)
  2532. 1~is_empty(empty) || (empty != 0)
  2533. 1~is_empty(empty) || (empty != zero)
  2534. 0~is_empty(zero) || (zero != 0)
  2535. 1~is_empty(five) || (five != 0)
  2536. <b>SETUP</b>~'<b>SETUP</b>'
  2537. &quot;Can strings contain embedded \&quot;quoted passages\&quot; (and parentheses + other characters?)?&quot;~a=htmlspecialchars(ASSESSMENT_HEADING)
  2538. &quot;can single quoted strings&quot; . &#039;contain nested &#039;quoted sections&#039;?~b=htmlspecialchars(QUESTIONHELP)
  2539. Can strings have embedded &lt;tags&gt; like &lt;html&gt;, or even unbalanced &quot;quotes or entities without terminal semicolons like &amp;amp and &amp;lt?~c=htmlspecialchars(QUESTION_HELP)
  2540. <span id="d" style="border-style: solid; border-width: 2px; border-color: green">Hi there!</span>~d='<span id="d" style="border-style: solid; border-width: 2px; border-color: green">Hi there!</span>'
  2541. <b>FUNCTIONS</b>~'<b>FUNCTIONS</b>'
  2542. 5~abs(five)
  2543. 5~abs(-five)
  2544. 0.2~acos(cos(0.2))
  2545. 0~acos(cos(pi()))-pi()
  2546. &quot;Can strings contain embedded \\&quot;quoted passages\\&quot; (and parentheses + other characters?)?&quot;~addslashes(a)
  2547. &quot;can single quoted strings&quot; . &#039;contain nested &#039;quoted sections&#039;?~addslashes(b)
  2548. Can strings have embedded &lt;tags&gt; like &lt;html&gt;, or even unbalanced &quot;quotes or entities without terminal semicolons like &amp;amp and &amp;lt?~addslashes(c)
  2549. 0.2~asin(sin(0.2))
  2550. 0.2~atan(tan(0.2))
  2551. 0~atan2(0,1)
  2552. 1~ceil(0.3)
  2553. 1~ceil(0.7)
  2554. 0~ceil(-0.3)
  2555. 0~ceil(-0.7)
  2556. 10~ceil(9.1)
  2557. 1~checkdate(1,29,1967)
  2558. 0~checkdate(2,29,1967)
  2559. 0.2~cos(acos(0.2))
  2560. 5~count(1,2,3,4,5)
  2561. 0~count()
  2562. 5~count(one,two,three,four,five)
  2563. 2~count(a,'',c)
  2564. NULL~date('F j, Y, g:i a',time())
  2565. April 5, 2006, 1:02 am~date('F j, Y, g:i a',mktime(1,2,3,4,5,6))
  2566. 20~floor(exp(3))
  2567. 0~floor(asin(sin(pi())))
  2568. 9~floor(9.9)
  2569. 3~floor(pi())
  2570. January 12, 2012, 5:27 pm~date('F j, Y, g:i a',1326410867)
  2571. January 12, 2012, 11:27 pm~gmdate('F j, Y, g:i a',1326410867)
  2572. "Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"~html_entity_decode(a)
  2573. "can single quoted strings" . &#039;contain nested &#039;quoted sections&#039;?~html_entity_decode(b)
  2574. Can strings have embedded <tags> like <html>, or even unbalanced "quotes or entities without terminal semicolons like &amp and &lt?~html_entity_decode(c)
  2575. &quot;Can strings contain embedded \&quot;quoted passages\&quot; (and parentheses + other characters?)?&quot;~htmlentities(a)
  2576. &quot;can single quoted strings&quot; . &#039;contain nested &#039;quoted sections&#039;?~htmlentities(b)
  2577. Can strings have embedded &lt;tags&gt; like &lt;html&gt;, or even unbalanced &quot;quotes or entities without terminal semicolons like &amp;amp and &amp;lt?~htmlentities(c)
  2578. 1~c==htmlspecialchars(htmlspecialchars_decode(c))
  2579. 1~b==htmlspecialchars(htmlspecialchars_decode(b))
  2580. 1~a==htmlspecialchars(htmlspecialchars_decode(a))
  2581. "Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"~htmlspecialchars_decode(a)
  2582. "can single quoted strings" . 'contain nested 'quoted sections'?~htmlspecialchars_decode(b)
  2583. Can strings have embedded like , or even unbalanced "quotes or entities without terminal semicolons like & and <?~htmlspecialchars_decode(c)
  2584. "Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"~htmlspecialchars(a)
  2585. "can single quoted strings" . 'contain nested 'quoted sections'?~htmlspecialchars(b)
  2586. Can strings have embedded <tags> like <html>, or even unbalanced "quotes or entities without terminal semicolons like &amp and &lt?~htmlspecialchars(c)
  2587. 9~idate('B',1326410867)
  2588. 0~if('0',1,0)
  2589. 0~if(0,1,0)
  2590. 1~if(!0,1,0)
  2591. 0~if(!(!0),1,0)
  2592. 1~if('true',1,0)
  2593. 1~if('false',1,0)
  2594. 1~if('00',1,0)
  2595. 0~if('',1,0)
  2596. 1~if('A',1,0)
  2597. 0~if(empty,1,0)
  2598. 4~if(5 > 7,2,4)
  2599. 1~if(' ',1,0)
  2600. there~if((one > two),'hi','there')
  2601. 64~if((one < two),pow(2,6),pow(6,2))
  2602. H e l l o~implode(' ','H','e','l','l','o')
  2603. 1|2|3|4|5~implode('|',one,two,three,four,five)
  2604. 123~join(1,2,3)
  2605. 123 5~join(one,2,three," ",five)
  2606. 4~intval('4')
  2607. 4~intval('100',2)
  2608. 5~intval(5.7)
  2609. 0~is_empty(four)
  2610. 1~is_empty(empty)
  2611. 1~is_empty('')
  2612. 0~is_empty(0)
  2613. 0~is_empty('0')
  2614. 0~is_empty('false')
  2615. 0~is_empty('NULL')
  2616. 0~is_empty(1)
  2617. 1~is_empty(one==two)
  2618. 0~!is_empty(one==two)
  2619. 1~is_float(half)
  2620. 0~is_float(one)
  2621. 1~is_float(pi())
  2622. 0~is_float(5)
  2623. 0~is_int(half)
  2624. 1~is_int(one)
  2625. 0~is_nan(half)
  2626. 1~is_nan(WELCOME)
  2627. 1~is_null(sdfjskdfj)
  2628. 0~is_null(four)
  2629. 0~is_numeric(empty)
  2630. 1~is_numeric('1')
  2631. 1~is_numeric(four)
  2632. 0~is_numeric('hi')
  2633. 1~is_numeric(five)
  2634. 0~is_numeric(hi)
  2635. 0~is_string(four)
  2636. 1~is_string('hi')
  2637. 1~is_string(hi)
  2638. 1, 2, 3, 4, 5~list(one,two,three,min(four,five,six),max(three,four,five))
  2639. 11, 12~list(eleven,twelve)
  2640. 0, 1, 3, 5~list(0,one,'',three,'',five)
  2641. 1~log(exp(1))
  2642. 2~log(exp(2))
  2643. I was trimmed ~ltrim(' I was trimmed ')
  2644. 10~max(5,6,10,-20)
  2645. 6~max(five,(one + (two * four)- three))
  2646. 6~max((one + (two * four)- three))
  2647. 212~5 + max(1,(2+3),(4 + (5 + 6)),((7 + 8) + 9),((10 + 11), 12),(13 + (14 * 15) - 16))
  2648. 29~five + max(one, (two + three), (four + (five + six)),((seven + eight) + nine),((ten + eleven), twelve),(one + (two * three) - four))
  2649. 1024~max(one,(two*three),pow(four,five),six)
  2650. 2~max(one,two)
  2651. 5~max(one,two,three,four,five)
  2652. -5~min(-5,10,15,12,-3)
  2653. 1~min(five,four,one,two,three)
  2654. 1344765967~mktime(5,6,7,8)
  2655. 1144191723~mktime(1,2,3,4,5,6)
  2656. 1,000~number_format(1000)
  2657. 1,000.23~number_format(1000.23)
  2658. 1,234,567~number_format(1234567)
  2659. 315~ceil(100*pi())
  2660. 1~pi() == pi() * 2 - pi()
  2661. 4~pow(2,2)
  2662. 27~pow(3,3)
  2663. =~quoted_printable_decode(quoted_printable_encode('='))
  2664. \\$~quotemeta('$')
  2665. IGNORE THIS ERROR~rand(3,5)
  2666. 0~(a=rand())-a
  2667. 1~regexMatch('/embedded/',c)
  2668. 1~regexMatch('/^.*embedded.*$/',c)
  2669. 0~regexMatch('/joe/',c)
  2670. 1~regexMatch('/(?:dog|cat)food/','catfood stinks')
  2671. 1~regexMatch('/(?:dog|cat)food/','catfood stinks')
  2672. 1~regexMatch('/[0-9]{3}-[0-9]{2}-[0-9]{4}/','123-45-6789')
  2673. 1~regexMatch('/\d{3}-\d{2}-\d{4}/','123-45-6789')
  2674. 1~regexMatch('/(?:\(\d{3}\))\s*\d{3}-\d{4}/','(212) 555-1212')
  2675. 0~round(0.2)
  2676. 1~round(.8)
  2677. 0.07~0.01 + 0.06
  2678. 0.07~round(0.01 + 0.06,10)
  2679. I was trimmed~rtrim(' I was trimmed ')
  2680. 0.2~sin(asin(0.2))
  2681. 1~sin(pi()/2)
  2682. 1~sin(pi()/2) == sin(.5 * pi())
  2683. 1~sin(0.5 * pi())
  2684. hello,5~sprintf('%s,%d','hello',5)
  2685. 2~sqrt(4)
  2686. 158~round(stddev(4,5,6,7,8)*100)
  2687. hello-----~str_pad('hello',10,'-')
  2688. hello ~str_pad('hello',10)
  2689. hello~str_pad('hello',3)
  2690. testtesttest~str_repeat('test',3)
  2691. I am awesome~str_replace('You are','I am','You are awesome')
  2692. I love LimeSurvey~str_replace('like','love','I like LimeSurvey')
  2693. 1~0==strcasecmp('Hello','hello')
  2694. 0~0==strcasecmp('Hello','hi')
  2695. 1~0==strcmp('Hello','Hello')
  2696. 0~0==strcmp('Hello','hi')
  2697. Hi there!~c=strip_tags(d)
  2698. hello~strip_tags('<b>hello</b>')
  2699. 5~stripos('ABCDEFGHI','f')
  2700. hi~stripslashes('\\h\\i')
  2701. FGHI~stristr('ABCDEFGHI','fg')
  2702. 5~strlen('12345')
  2703. 5~strlen(hi)
  2704. 0~strpos('ABCDEFGHI','f')
  2705. 5~strpos('ABCDEFGHI','F')
  2706. 2~strpos('I like LimeSurvey','like')
  2707. 54321~strrev('12345')
  2708. 0~strstr('ABCDEFGHI','fg')
  2709. FGHI~strstr('ABCDEFGHI','FG')
  2710. hi there!~strtolower(c)
  2711. HI THERE!~strtoupper(c)
  2712. 678~substr('1234567890',5,3)
  2713. 15~sum(1,2,3,4,5)
  2714. 15~sum(one,two,three,four,five)
  2715. 0.2~tan(atan(0.2))
  2716. IGNORE THIS ERROR~time()
  2717. I was trimmed~trim(' I was trimmed ')
  2718. Hi There You~ucwords('hi there you')
  2719. <b>EXPRESSIONS</b>~'<b>EXPRESSIONS</b>'
  2720. 1~!'0'
  2721. 1~0 eq '0'
  2722. 0~0 ne '0'
  2723. 0~0 eq empty
  2724. 1~0 ne empty
  2725. 0~0 eq ''
  2726. 1~0 ne ''
  2727. 0~'' < 10
  2728. 0~0 < empty
  2729. 1~0 <= empty
  2730. 0~0 > empty
  2731. 1~0 >= empty
  2732. 0~'0' eq empty
  2733. 1~'0' ne empty
  2734. 0~'0' < empty
  2735. 1~'0' <= empty
  2736. 0~'0' > empty
  2737. 1~'0' >= empty
  2738. 1~empty eq empty
  2739. 0~empty ne empty
  2740. 0~'' > 0
  2741. 0~' ' > 0
  2742. 1~!0
  2743. 0~!' '
  2744. 0~!'A'
  2745. 0~!1
  2746. 0~!'1'
  2747. 1~!''
  2748. 1~!empty
  2749. 1~'0'==0
  2750. 0~'A'>0
  2751. 0~'A'<0
  2752. 0~'A'==0
  2753. 0~'A'>=0
  2754. 0~'A'<=0
  2755. 0~0>'A'
  2756. 0~0>='B'
  2757. 0~0=='C'
  2758. 0~0<'D'
  2759. 0~0<='E'
  2760. 1~0!='F'
  2761. 1~'A' or 'B'
  2762. 1~'A' and 'B'
  2763. 0~'A' eq 'B'
  2764. 1~'A' ne 'B'
  2765. 1~'A' < 'B'
  2766. 1~'A' <= 'B'
  2767. 0~'A' > 'B'
  2768. 0~'A' >= 'B'
  2769. AB~'A' + 'B'
  2770. NAN~'A' - 'B'
  2771. NAN~'A' * 'B'
  2772. NAN~'A' / 'B'
  2773. 1~'A' or empty
  2774. 0~'A' and empty
  2775. 0~'A' eq empty
  2776. 1~'A' ne empty
  2777. 0~'A' < empty
  2778. 0~'A' <= empty
  2779. 1~'A' > empty
  2780. 1~'A' >= empty
  2781. A~'A' + empty
  2782. NAN~'A' - empty
  2783. NAN~'A' * empty
  2784. NAN~'A' / empty
  2785. 0~0 or empty
  2786. 0~0 and empty
  2787. 0~0 + empty
  2788. 0~0 - empty
  2789. 0~0 * empty
  2790. NAN~0 / empty
  2791. 0~(-1 > 0)
  2792. 0~zero
  2793. ~empty
  2794. 1~five > zero
  2795. 1~five > empty
  2796. 1~empty < 16
  2797. 1~zero == empty
  2798. 3~q5pointChoice.code
  2799. 5~q5pointChoice.type
  2800. (question for q5pointChoice)~q5pointChoice.question
  2801. 1~q5pointChoice.relevance
  2802. 4~q5pointChoice.NAOK + 1
  2803. NULL~q5pointChoice.bogus
  2804. 14~q5pointChoice.qid
  2805. 7~qArrayNumbers_ls1_min.code
  2806. 1~(one * (two + (three - four) + five) / six)
  2807. 2.4~(one * two) + (three * four) / (five * six)
  2808. 50~12X34X56 * 12X3X5lab1_ber
  2809. 1~c == 'Hi there!'
  2810. 1~c == "Hi there!"
  2811. 3~a=three
  2812. 3~c=a
  2813. 12~c*=four
  2814. 15~c+=a
  2815. 5~c/=a
  2816. -1~c-=six
  2817. 24~one * two * three * four
  2818. -4~five - four - three - two
  2819. 0~two * three - two - two - two
  2820. 4~two * three - two
  2821. 105~5 + 1, 7 * 15
  2822. 7~7
  2823. 15~10 + 5
  2824. 24~12 * 2
  2825. 10~13 - 3
  2826. 3.5~14 / 4
  2827. 5~3 + 1 * 2
  2828. 1~one
  2829. there~hi
  2830. 6.25~one * two - three / four + five
  2831. 1~one + hi
  2832. 1~two > one
  2833. 1~two gt one
  2834. 1~three >= two
  2835. 1~three ge two
  2836. 0~four < three
  2837. 0~four lt three
  2838. 0~four <= three
  2839. 0~four le three
  2840. 0~four == three
  2841. 0~four eq three
  2842. 1~four != three
  2843. 0~four ne four
  2844. NAN~one * hi
  2845. 0~a='hello',b='',c=0
  2846. hello~a
  2847. 0~c
  2848. 0~one && 0
  2849. 0~two and 0
  2850. 1~five && 6
  2851. 1~seven && eight
  2852. 1~one or 0
  2853. 1~one || 0
  2854. 1~(one and 0) || (two and three)
  2855. value for {QID}~QID
  2856. "Can strings contain embedded \"quoted passages\" (and parentheses + other characters?)?"~ASSESSMENT_HEADING
  2857. "can single quoted strings" . 'contain nested 'quoted sections'?~QUESTIONHELP
  2858. Can strings have embedded <tags> like <html>, or even unbalanced "quotes or entities without terminal semicolons like &amp and &lt?~QUESTION_HELP
  2859. value for {TOKEN:FIRSTNAME}~TOKEN:FIRSTNAME
  2860. value for {THEREAREXQUESTIONS}~THEREAREXQUESTIONS
  2861. 15~12X3X5lab1_ber#1
  2862. 1~three == three
  2863. 1~three == 3
  2864. 11~eleven
  2865. 144~twelve * twelve
  2866. 0~!three
  2867. 8~five + + three
  2868. 2~five + - three
  2869. <b>SYNTAX ERRORS</b>~'<b>SYNTAX ERRORS</b>'
  2870. NULL~*
  2871. NULL~three +
  2872. NULL~four * / seven
  2873. NULL~(five - three
  2874. NULL~five + three)
  2875. NULL~seven + = four
  2876. NULL~>
  2877. NULL~five > > three
  2878. NULL~seven > = four
  2879. NULL~seven >=
  2880. NULL~three &&
  2881. NULL~three ||
  2882. NULL~three +
  2883. NULL~three >=
  2884. NULL~three +=
  2885. NULL~three !
  2886. NULL~three *
  2887. NULL~five ! three
  2888. NULL~(5 + 7) = 8
  2889. NULL~&& four
  2890. NULL~min(
  2891. NULL~max three, four, five)
  2892. NULL~three four
  2893. NULL~max(three,four,five) six
  2894. NULL~WELCOME='Good morning'
  2895. NULL~TOKEN:FIRSTNAME='Tom'
  2896. NULL~NUMBEROFQUESTIONS+=3
  2897. NULL~NUMBEROFQUESTIONS*=4
  2898. NULL~NUMBEROFQUESTIONS/=5
  2899. NULL~NUMBEROFQUESTIONS-=6
  2900. NULL~'Tom'='tired'
  2901. NULL~max()
  2902. EOD;
  2903. $atests = explode("\n",$tests);
  2904. $atests[] = "1\n2\n3~BREAKS";
  2905. $atests[] = "1<br />\n2<br />\n3~nl2br(BREAKS)";
  2906. $atests[] = "hi<br />\nthere<br />\nhow<br />\nare<br />\nyou?~nl2br('hi\\nthere\\nhow\\nare\\nyou?')";
  2907. $atests[] = "hi<br />\nthere,<br />\nuser!~nl2br(implode('\\n','hi','there,','user!'))";
  2908. $LEM =& LimeExpressionManager::singleton();
  2909. $em = new ExpressionManager();
  2910. $LEM->setTempVars($vars);
  2911. $LEMsessid = 'survey_' . Yii::app()->getConfig('surveyID');
  2912. // manually set relevance status
  2913. $_SESSION[$LEMsessid]['relevanceStatus'] = array();
  2914. foreach ($vars as $var) {
  2915. if (isset($var['qseq'])) {
  2916. $_SESSION[$LEMsessid]['relevanceStatus'][$var['qseq']] = 1;
  2917. }
  2918. }
  2919. $allJsVarnamesUsed = array();
  2920. $body = '';
  2921. $body .= '<table border="1"><tr><th>Expression</th><th>PHP Result</th><th>Expected</th><th>JavaScript Result</th><th>VarNames</th><th>JavaScript Eqn</th></tr>';
  2922. $i=0;
  2923. $javaScript = array();
  2924. foreach($atests as $test)
  2925. {
  2926. ++$i;
  2927. $values = explode("~",$test);
  2928. $expectedResult = array_shift($values);
  2929. $expr = implode("~",$values);
  2930. $resultStatus = 'ok';
  2931. $em->groupSeq=2;
  2932. $em->questionSeq=3;
  2933. $status = $em->RDP_Evaluate($expr);
  2934. if ($status)
  2935. {
  2936. $allJsVarnamesUsed = array_merge($allJsVarnamesUsed,$em->GetJsVarsUsed());
  2937. }
  2938. $result = $em->GetResult();
  2939. $valToShow = $result; // htmlspecialchars($result,ENT_QUOTES,'UTF-8',false);
  2940. $expectedToShow = $expectedResult; // htmlspecialchars($expectedResult,ENT_QUOTES,'UTF-8',false);
  2941. $body .= "<tr>";
  2942. $body .= "<td>" . $em->GetPrettyPrintString() . "</td>\n";
  2943. if (is_null($result)) {
  2944. $valToShow = "NULL";
  2945. }
  2946. if ($valToShow != $expectedToShow)
  2947. {
  2948. $resultStatus = 'error';
  2949. }
  2950. $body .= "<td class='" . $resultStatus . "'>" . $valToShow . "</td>\n";
  2951. $body .= '<td>' . $expectedToShow . "</td>\n";
  2952. $javaScript[] = $em->GetJavascriptTestforExpression($expectedToShow, $i);
  2953. $body .= "<td id='test_" . $i . "'>&nbsp;</td>\n";
  2954. $varsUsed = $em->GetVarsUsed();
  2955. if (is_array($varsUsed) and count($varsUsed) > 0) {
  2956. $varDesc = array();
  2957. foreach ($varsUsed as $v) {
  2958. $varDesc[] = $v;
  2959. }
  2960. $body .= '<td>' . implode(',<br/>', $varDesc) . "</td>\n";
  2961. }
  2962. else {
  2963. $body .= "<td>&nbsp;</td>\n";
  2964. }
  2965. $jsEqn = $em->GetJavaScriptEquivalentOfExpression();
  2966. if ($jsEqn == '')
  2967. {
  2968. $body .= "<td>&nbsp;</td>\n";
  2969. }
  2970. else
  2971. {
  2972. $body .= '<td>' . $jsEqn . "</td>\n";
  2973. }
  2974. $body .= '</tr>';
  2975. }
  2976. $body .= '</table>';
  2977. $body .= "<script type='text/javascript'>\n";
  2978. $body .= "<!--\n";
  2979. $body .= "var LEMgseq=2;\n";
  2980. $body .= "var LEMmode='group';\n";
  2981. $body .= "function recompute() {\n";
  2982. $body .= implode("\n",$javaScript);
  2983. $body .= "}\n//-->\n</script>\n";
  2984. $allJsVarnamesUsed = array_unique($allJsVarnamesUsed);
  2985. asort($allJsVarnamesUsed);
  2986. $pre = '';
  2987. $pre .= "<h3>Change some Relevance values to 0 to see how it affects computations</h3>\n";
  2988. $pre .= '<table border="1"><tr><th>#</th><th>JsVarname</th><th>Starting Value</th><th>Relevance</th></tr>';
  2989. $i=0;
  2990. $LEMvarNameAttr=array();
  2991. $LEMalias2varName=array();
  2992. foreach ($allJsVarnamesUsed as $jsVarName)
  2993. {
  2994. ++$i;
  2995. $pre .= "<tr><td>" . $i . "</td><td>" . $jsVarName;
  2996. foreach($vars as $k => $v) {
  2997. if ($v['jsName'] == $jsVarName)
  2998. {
  2999. $value = $v['code'];
  3000. }
  3001. }
  3002. $pre .= "</td><td>" . $value . "</td><td><input type='text' id='relevance" . $i . "' value='1' onchange='recompute()'/>\n";
  3003. $pre .= "<input type='hidden' id='" . $jsVarName . "' name='" . $jsVarName . "' value='" . $value . "'/>\n";
  3004. $pre .= "</td></tr>\n";
  3005. $LEMalias2varName[] = "'" . substr($jsVarName,5) . "':'" . $jsVarName . "'";
  3006. $LEMalias2varName[] = "'" . $jsVarName . "':'" . $jsVarName . "'";
  3007. $attrInfo = "'" . $jsVarName . "': {'jsName':'" . $jsVarName . "'";
  3008. $varInfo = $vars[substr($jsVarName,5)];
  3009. foreach ($varInfo as $k=>$v) {
  3010. if ($k == 'code') {
  3011. continue; // will access it from hidden node
  3012. }
  3013. if ($k == 'shown') {
  3014. $k = 'shown';
  3015. $v = htmlspecialchars(preg_replace("/[[:space:]]/",' ',$v),ENT_QUOTES);
  3016. }
  3017. if ($k == 'jsName') {
  3018. continue; // since already set
  3019. }
  3020. $attrInfo .= ", '" . $k . "':'" . $v . "'";
  3021. }
  3022. $attrInfo .= ",'qid':" . $i . "}";
  3023. $LEMvarNameAttr[] = $attrInfo;
  3024. }
  3025. $pre .= "</table>\n";
  3026. $pre .= "<script type='text/javascript'>\n";
  3027. $pre .= "<!--\n";
  3028. $pre .= "var LEMalias2varName= {". implode(",\n", $LEMalias2varName) ."};\n";
  3029. $pre .= "var LEMvarNameAttr= {" . implode(",\n", $LEMvarNameAttr) . "};\n";
  3030. $pre .= "var LEMradix = '.';\n";
  3031. $pre .= "//-->\n</script>\n";
  3032. print $pre;
  3033. print $body;
  3034. }
  3035. /**
  3036. * Stub to access LimeSurvey's functions for internationalizing strings
  3037. * @param <type> $string
  3038. * @return <type>
  3039. */
  3040. function gT($string)
  3041. {
  3042. // ultimately should call i8n functiouns
  3043. global $clang;
  3044. if (isset($clang)) {
  3045. return $clang->gT($string);
  3046. }
  3047. else {
  3048. return $string;
  3049. }
  3050. }
  3051. /**
  3052. * Stub to access LimeSurvey's functions for internationalizing strings
  3053. *
  3054. * @param string $single
  3055. * @param string $plural
  3056. * @param integer $number
  3057. */
  3058. function ngT($single, $plural, $number)
  3059. {
  3060. // ultimately should call i8n functiouns
  3061. global $clang;
  3062. if (isset($clang)) {
  3063. return $clang->ngT($single, $plural, $number);
  3064. }
  3065. else {
  3066. if ($number = 1) {
  3067. return $single;
  3068. } else {
  3069. return $plural;
  3070. }
  3071. }
  3072. }
  3073. }
  3074. /**
  3075. * Used by usort() to order Error tokens by their position within the string
  3076. * This must be outside of the class in order to work in PHP 5.2
  3077. * @param <type> $a
  3078. * @param <type> $b
  3079. * @return <type>
  3080. */
  3081. function cmpErrorTokens($a, $b)
  3082. {
  3083. if (is_null($a[1])) {
  3084. if (is_null($b[1])) {
  3085. return 0;
  3086. }
  3087. return 1;
  3088. }
  3089. if (is_null($b[1])) {
  3090. return -1;
  3091. }
  3092. if ($a[1][1] == $b[1][1]) {
  3093. return 0;
  3094. }
  3095. return ($a[1][1] < $b[1][1]) ? -1 : 1;
  3096. }
  3097. /**
  3098. * Count the number of answered questions (non-empty)
  3099. * @param <type> $args
  3100. * @return int
  3101. */
  3102. function exprmgr_count($args)
  3103. {
  3104. $j=0; // keep track of how many non-null values seen
  3105. foreach ($args as $arg)
  3106. {
  3107. if ($arg != '') {
  3108. ++$j;
  3109. }
  3110. }
  3111. return $j;
  3112. }
  3113. /**
  3114. * Count the number of answered questions (non-empty) which match the first argument
  3115. * @param <type> $args
  3116. * @return int
  3117. */
  3118. function exprmgr_countif($args)
  3119. {
  3120. $j=0; // keep track of how many non-null values seen
  3121. $match = array_shift($args);
  3122. foreach ($args as $arg)
  3123. {
  3124. if ($arg == $match) {
  3125. ++$j;
  3126. }
  3127. }
  3128. return $j;
  3129. }
  3130. /**
  3131. * Count the number of answered questions (non-empty) which meet the criteria (arg op value)
  3132. * @param <type> $args
  3133. * @return int
  3134. */
  3135. function exprmgr_countifop($args)
  3136. {
  3137. $j=0;
  3138. $op = array_shift($args);
  3139. $value = array_shift($args);
  3140. foreach ($args as $arg)
  3141. {
  3142. switch($op)
  3143. {
  3144. case '==': case 'eq': if ($arg == $value) { ++$j; } break;
  3145. case '>=': case 'ge': if ($arg >= $value) { ++$j; } break;
  3146. case '>': case 'gt': if ($arg > $value) { ++$j; } break;
  3147. case '<=': case 'le': if ($arg <= $value) { ++$j; } break;
  3148. case '<': case 'lt': if ($arg < $value) { ++$j; } break;
  3149. case '!=': case 'ne': if ($arg != $value) { ++$j; } break;
  3150. case 'RX':
  3151. try {
  3152. if (@preg_match($value, $arg))
  3153. {
  3154. ++$j;
  3155. }
  3156. }
  3157. catch (Exception $e) { }
  3158. break;
  3159. }
  3160. }
  3161. return $j;
  3162. }
  3163. /**
  3164. * Sum of values of answered questions which meet the criteria (arg op value)
  3165. * @param <type> $args
  3166. * @return int
  3167. */
  3168. function exprmgr_sumifop($args)
  3169. {
  3170. $result=0;
  3171. $op = array_shift($args);
  3172. $value = array_shift($args);
  3173. foreach ($args as $arg)
  3174. {
  3175. switch($op)
  3176. {
  3177. case '==': case 'eq': if ($arg == $value) { $result += $arg; } break;
  3178. case '>=': case 'ge': if ($arg >= $value) { $result += $arg; } break;
  3179. case '>': case 'gt': if ($arg > $value) { $result += $arg; } break;
  3180. case '<=': case 'le': if ($arg <= $value) { $result += $arg; } break;
  3181. case '<': case 'lt': if ($arg < $value) { $result += $arg; } break;
  3182. case '!=': case 'ne': if ($arg != $value) { $result += $arg; } break;
  3183. case 'RX':
  3184. try {
  3185. if (@preg_match($value, $arg))
  3186. {
  3187. $result += $arg;
  3188. }
  3189. }
  3190. catch (Exception $e) { }
  3191. break;
  3192. }
  3193. }
  3194. return $result;
  3195. }
  3196. /**
  3197. * If $test is true, return $ok, else return $error
  3198. * @param <type> $test
  3199. * @param <type> $ok
  3200. * @param <type> $error
  3201. * @return <type>
  3202. */
  3203. function exprmgr_if($test,$ok,$error)
  3204. {
  3205. if ($test)
  3206. {
  3207. return $ok;
  3208. }
  3209. else
  3210. {
  3211. return $error;
  3212. }
  3213. }
  3214. /**
  3215. * Join together $args[0-N] with ', '
  3216. * @param <type> $args
  3217. * @return <type>
  3218. */
  3219. function exprmgr_list($args)
  3220. {
  3221. $result="";
  3222. $j=1; // keep track of how many non-null values seen
  3223. foreach ($args as $arg)
  3224. {
  3225. if ($arg != '') {
  3226. if ($j > 1) {
  3227. $result .= ', ' . $arg;
  3228. }
  3229. else {
  3230. $result .= $arg;
  3231. }
  3232. ++$j;
  3233. }
  3234. }
  3235. return $result;
  3236. }
  3237. /**
  3238. * Join together $args[N]
  3239. * @param <type> $args
  3240. * @return <type>
  3241. */
  3242. function exprmgr_join($args)
  3243. {
  3244. return implode("",$args);
  3245. }
  3246. /**
  3247. * Join together $args[1-N] with $arg[0]
  3248. * @param <type> $args
  3249. * @return <type>
  3250. */
  3251. function exprmgr_implode($args)
  3252. {
  3253. if (count($args) <= 1)
  3254. {
  3255. return "";
  3256. }
  3257. $joiner = array_shift($args);
  3258. return implode($joiner,$args);
  3259. }
  3260. /**
  3261. * Return true if the variable is NULL or blank.
  3262. * @param <type> $arg
  3263. * @return <type>
  3264. */
  3265. function exprmgr_empty($arg)
  3266. {
  3267. if ($arg === NULL || $arg === "" || $arg === false) {
  3268. return true;
  3269. }
  3270. return false;
  3271. }
  3272. /**
  3273. * Compute the Sample Standard Deviation of a set of numbers ($args[0-N])
  3274. * @param <type> $args
  3275. * @return <type>
  3276. */
  3277. function exprmgr_stddev($args)
  3278. {
  3279. $vals = array();
  3280. foreach ($args as $arg)
  3281. {
  3282. if (is_numeric($arg)) {
  3283. $vals[] = $arg;
  3284. }
  3285. }
  3286. $count = count($vals);
  3287. if ($count <= 1) {
  3288. return 0; // what should default value be?
  3289. }
  3290. $sum = 0;
  3291. foreach ($vals as $val) {
  3292. $sum += $val;
  3293. }
  3294. $mean = $sum / $count;
  3295. $sumsqmeans = 0;
  3296. foreach ($vals as $val)
  3297. {
  3298. $sumsqmeans += ($val - $mean) * ($val - $mean);
  3299. }
  3300. $stddev = sqrt($sumsqmeans / ($count-1));
  3301. return $stddev;
  3302. }
  3303. /**
  3304. * Javascript equivalent does not cope well with ENT_QUOTES and related PHP constants, so set default to ENT_QUOTES
  3305. * @param <type> $string
  3306. * @return <type>
  3307. */
  3308. function expr_mgr_htmlspecialchars($string)
  3309. {
  3310. return htmlspecialchars($string,ENT_QUOTES);
  3311. }
  3312. /**
  3313. * Javascript equivalent does not cope well with ENT_QUOTES and related PHP constants, so set default to ENT_QUOTES
  3314. * @param <type> $string
  3315. * @return <type>
  3316. */
  3317. function expr_mgr_htmlspecialchars_decode($string)
  3318. {
  3319. return htmlspecialchars_decode($string,ENT_QUOTES);
  3320. }
  3321. /**
  3322. * Return true of $input matches the regular expression $pattern
  3323. * @param <type> $pattern
  3324. * @param <type> $input
  3325. * @return <type>
  3326. */
  3327. function exprmgr_regexMatch($pattern, $input)
  3328. {
  3329. try {
  3330. $result = @preg_match($pattern, $input);
  3331. } catch (Exception $e) {
  3332. $result = false;
  3333. // How should errors be logged?
  3334. echo sprintf($this->gT('Invalid PERL Regular Expression: %s'), htmlspecialchars($pattern));
  3335. }
  3336. return $result;
  3337. }
  3338. /**
  3339. * Display number with comma as radix separator, if needed
  3340. * @param type $value
  3341. * @return type
  3342. */
  3343. function exprmgr_fixnum($value)
  3344. {
  3345. if (LimeExpressionManager::usingCommaAsRadix())
  3346. {
  3347. $newval = implode(',',explode('.',$value));
  3348. return $newval;
  3349. }
  3350. return $value;
  3351. }
  3352. /**
  3353. * Returns true if all non-empty values are unique
  3354. * @param type $args
  3355. */
  3356. function exprmgr_unique($args)
  3357. {
  3358. $uniqs = array();
  3359. foreach ($args as $arg)
  3360. {
  3361. if (trim($arg)=='')
  3362. {
  3363. continue; // ignore blank answers
  3364. }
  3365. if (isset($uniqs[$arg]))
  3366. {
  3367. return false;
  3368. }
  3369. $uniqs[$arg]=1;
  3370. }
  3371. return true;
  3372. }
  3373. ?>