PageRenderTime 115ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 2ms

/application/helpers/expressions/em_manager_helper.php

https://bitbucket.org/sammousa/valuematchbv-ls2
PHP | 8992 lines | 7236 code | 497 blank | 1259 comment | 1548 complexity | eca4d861e985af8c0f33b9b29a4cae84 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 LimeExpressionManager
  17. * This is a wrapper class around ExpressionManager that implements a Singleton and eases
  18. * passing of LimeSurvey variable values into ExpressionManager
  19. *
  20. * @author Thomas M. White (TMSWhite)
  21. */
  22. include_once('em_core_helper.php');
  23. Yii::app()->loadHelper('database');
  24. Yii::app()->loadHelper('frontend');
  25. Yii::import("application.libraries.Date_Time_Converter");
  26. define('LEM_DEBUG_TIMING',1);
  27. define('LEM_DEBUG_VALIDATION_SUMMARY',2); // also includes SQL error messages
  28. define('LEM_DEBUG_VALIDATION_DETAIL',4);
  29. define('LEM_PRETTY_PRINT_ALL_SYNTAX',32);
  30. define('LEM_DEFAULT_PRECISION',12);
  31. class LimeExpressionManager {
  32. /**
  33. * LimeExpressionManager is a singleton. $instance is its storage location.
  34. * @var LimeExpressionManager
  35. */
  36. private static $instance;
  37. /**
  38. * Implements the recursive descent parser that processes expressions
  39. * @var ExpressionManager
  40. */
  41. private $em;
  42. /**
  43. *
  44. * @var type
  45. */
  46. private $groupRelevanceInfo;
  47. /**
  48. * The survey ID
  49. * @var integer
  50. */
  51. private $sid;
  52. /**
  53. * sum of LEM_DEBUG constants - use bitwise AND comparisons to identify which parts to use
  54. * @var type
  55. */
  56. private $debugLevel=0;
  57. /**
  58. * Collection of variable attributes, indexed by SGQA code
  59. *
  60. * Actual variables are stored in this structure:
  61. * $knownVars[$sgqa] = array(
  62. * 'jsName_on' => // the name of the javascript variable if it is defined on the current page - often 'answerSGQA'
  63. * 'jsName' => // the name of the javascript variable when referenced on different pages - usually 'javaSGQA'
  64. * 'readWrite' => // 'Y' for yes, 'N' for no - currently not used
  65. * 'hidden' => // 1 if the question attribute 'hidden' is true, otherwise 0
  66. * 'question' => // the text of the question (or sub-question)
  67. * 'qid' => // the numeric question id - e.g. the Q part of the SGQA name
  68. * 'gid' => // the numeric group id - e.g. the G part of the SGQA name
  69. * 'grelevance' => // the group level relevance string
  70. * 'relevance' => // the question level relevance string
  71. * 'qcode' => // the qcode-style variable name for this question (or sub-question)
  72. * 'qseq' => // the 0-based index of the question within the survey
  73. * 'gseq' => // the 0-based index of the group within the survey
  74. * 'type' => // the single character type code for the question
  75. * 'sgqa' => // the SGQA name for the variable
  76. * 'ansList' => // ansArray converted to a JavaScript fragment - e.g. ",'answers':{ 'M':'Male','F':'Female'}"
  77. * 'ansArray' => // PHP array of answer strings, keyed on the answer code = e.g. array['M']='Male';
  78. * 'scale_id' => // '0' for most answers. '1' for second scale within dual-scale questions
  79. * 'rootVarName' => // the root code / name / title for the question, without any sub-question or answer-level suffix. This is from the title column in the questions table
  80. * 'subqtext' => // the sub-question text
  81. * 'rowdivid' => // the JavaScript ID of the row identifier for a question. This is used to show/hide entire question rows
  82. * 'onlynum' => // 1 if only numbers are allowed for this variable. If so, then extra processing is needed to ensure that can use comma as a decimal separator
  83. * );
  84. *
  85. * Reserved variables (e.g. TOKEN:xxxx) are stored with this structure:
  86. * $knownVars[$token] = array(
  87. * 'code' => // the static value for the variable
  88. * 'type' => // ''
  89. * 'jsName_on' => // ''
  90. * 'jsName' => // ''
  91. * 'readWrite' => // 'N' - since these are always read-only variables
  92. * );
  93. *
  94. * @var type
  95. */
  96. private $knownVars;
  97. /**
  98. * maps qcode varname to SGQA code
  99. *
  100. * @example ['gender'] = '38612X10X145'
  101. * @var type
  102. */
  103. private $qcode2sgqa;
  104. /**
  105. * variables temporarily set for substitution purposes
  106. *
  107. * These are typically the LimeReplacement Fields passed in via templatereplace()
  108. * Each has the following structure: array(
  109. * 'code' => // the static value of the variable
  110. * 'jsName_on' => // ''
  111. * 'jsName' => // ''
  112. * 'readWrite' => // 'N'
  113. * );
  114. *
  115. * @var type
  116. */
  117. private $tempVars;
  118. /**
  119. * Array of relevance information for each page (gseq), indexed by gseq.
  120. * Within a page, it contains a sequential list of the results of each relevance equation processed
  121. * array(
  122. * 'qid' => // question id -- e.g. 154
  123. * 'gseq' => // 0-based group sequence -- e.g. 2
  124. * 'eqn' => // the raw relevance equation parsed -- e.g. "!is_empty(p2_sex)"
  125. * 'result' => // the Boolean result of parsing that equation in the current context -- e.g. 0
  126. * 'numJsVars' => // the number of dynamic JavaScript variables used in that equation -- e.g. 1
  127. * 'relevancejs' => // the actual JavaScript to insert for that relevance equation -- e.g. "LEMif(LEManyNA('p2_sex'),'',( ! LEMempty(LEMval('p2_sex') )))"
  128. * 'relevanceVars' => // a pipe-delimited list of JavaScript variables upon which that equation depends -- e.g. "java38612X12X153"
  129. * 'jsResultVar' => // the JavaScript variable in which that result will be stored -- e.g. "java38612X12X154"
  130. * 'type' => // the single character type of the question -- e.g. 'S'
  131. * 'hidden' => // 1 if the question should always be hidden
  132. * 'hasErrors' => // 1 if there were parsing errors processing that relevance equation
  133. * @var type
  134. */
  135. private $pageRelevanceInfo;
  136. /**
  137. *
  138. * @var type
  139. */
  140. private $pageTailorInfo;
  141. /**
  142. * internally set to true (1) for survey.php so get group-specific logging but keep javascript variable namings consistent on the page.
  143. * @var type
  144. */
  145. private $allOnOnePage=false;
  146. /**
  147. * survey mode. One of 'survey', 'group', or 'question'
  148. * @var string
  149. */
  150. private $surveyMode='group';
  151. /**
  152. * a set of global survey options passed from LimeSurvey
  153. *
  154. * For example, array(
  155. * 'rooturl' => // URL prefix needed to be able to click on a syntax-highlighted variable name and have it open the needed editting window
  156. * 'hyperlinkSyntaxHighlighting' => // true if should be able to click on variables to edit them
  157. * 'active' => // 0 for inactive, 1 for active survey
  158. * 'allowsave' => // 0 for do not allow save; 1 for allow save
  159. * 'anonymized' => // 1 for anonymous
  160. * 'assessments' => // 1 for use assessments
  161. * 'datestamp' => // 1 for use date stamps
  162. * 'ipaddr' => // 1 for capture IP address
  163. * 'radix' => // '.' for use period as decimal separator; ',' for use comma as decimal separator
  164. * 'savetimings' => // "Y" if should save survey timings
  165. * 'startlanguage' => // the starting language -- e.g. 'en'
  166. * 'surveyls_dateformat' => // the index of the language specific date format -- e.g. 1
  167. * 'tablename' => // the name of the table storing the survey data, if active -- e.g. lime_survey_38612
  168. * 'target' => // the path for uploading files -- e.g. '/temp/files/'
  169. * 'timeadjust' => // the time offset -- e.g. 0
  170. * 'tempdir' => // the temporary directory for uploading files -- e.g. '/temp/'
  171. * );
  172. *
  173. * @var type
  174. */
  175. private $surveyOptions=array();
  176. /**
  177. * array of mappings of Question # (qid) to pipe-delimited list of SGQA codes used within it
  178. *
  179. * @example [150] = "38612X11X150|38612X11X150other"
  180. * @var type
  181. */
  182. private $qid2code;
  183. /**
  184. * array of mappings of JavaScript Variable names to Question number (qid)
  185. *
  186. * @example ['java38612X13X161other'] = '161'
  187. * @var type
  188. */
  189. private $jsVar2qid;
  190. /**
  191. * maps name of the variable to the SGQ name (without the A suffix)
  192. *
  193. * @example ['p1_sex'] = "38612X10X147"
  194. * @example ['afDS_sq1_1'] = "26626X37X705sq1#1"
  195. * @var type
  196. */
  197. private $qcode2sgq;
  198. /**
  199. * array of mappings of knownVar aliases to the JavaScript variable names.
  200. * This maps both the SGQA and qcode alias names to the same 2 dimensional array
  201. *
  202. * @example ['p1_sex'] = array(
  203. * 'jsName' => // the JavaScript variable name used by EM -- e.g. "java38612X11X147"
  204. * 'jsPart' => // the JavaScript fragment used in EM's ____ array -- e.g. "'p1_sex':'java38612X11X147'"
  205. * );
  206. * @example ['afDS_sq1_1] = array(
  207. * 'jsName' => "java26626X37X705sq1#1"
  208. * 'jsPart' => "'afDS_sq1_1':'java26626X37X705sq1#1'"
  209. * );
  210. * @var type
  211. */
  212. private $alias2varName;
  213. /**
  214. * JavaScript array of mappings of canonical JavaScript variable name to key attributes.
  215. * These fragments are used to create the JavaScript varNameAttr array.
  216. *
  217. * @example ['java38612X11X147'] = "'java38612X11X147':{ 'jsName':'java38612X11X147','jsName_on':'java38612X11X147','sgqa':'38612X11X147','qid':147,'gid':11,'type':'G','default':'','rowdivid':'','onlynum':'','gseq':1,'answers':{ 'M':'Male','F':'Female'}}"
  218. * @example ['java26626X37X705sq1#1'] = "'java26626X37X705sq1#1':{ 'jsName':'java26626X37X705sq1#1','jsName_on':'java26626X37X705sq1#1','sgqa':'26626X37X705sq1#1','qid':705,'gid':37,'type':'1','default':'','rowdivid':'26626X37X705sq1','onlynum':'','gseq':1,'answers':{ '0~1':'1|Low','0~2':'2|Medium','0~3':'3|High','1~1':'1|Never','1~2':'2|Sometimes','1~3':'3|Always'}}"
  219. *
  220. * @var type
  221. */
  222. private $varNameAttr;
  223. /**
  224. * array of enumerated answer lists indexed by qid
  225. * These use a tilde syntax to indicate which scale the answer is part of.
  226. *
  227. * @example ['0~4'] = "4|Child" // this means that code 4 in scale 0 has a coded value of 4 and a display value of 'Child'
  228. * @example (for [705]): ['1~2'] = '2|Sometimes' // this means that the second scale for this question uses the coded value of 2 to represent 'Sometimes'
  229. * @example // TODO - add example from survey using assessments
  230. *
  231. * @var type
  232. */
  233. private $qans;
  234. /**
  235. * map of gid to 0-based sequence number of groups
  236. *
  237. * @example [10] = 0 // means that the first group (gseq=0) has gid=10
  238. *
  239. * @var type
  240. */
  241. private $groupId2groupSeq;
  242. /**
  243. * map question # to an incremental count of question order across the whole survey
  244. *
  245. * @example [157] = 13 // means that that 14th question in the survey has qid=157
  246. *
  247. * @var type
  248. */
  249. private $questionId2questionSeq;
  250. /**
  251. * map question # to the group it is within, using an incremental count of group order
  252. *
  253. * @example [157] = 2 // means that qid 157 is in the 3rd page of questions (gseq = 2)
  254. *
  255. * @var type
  256. */
  257. private $questionId2groupSeq;
  258. /**
  259. * array of info about each Group, indexed by GroupSeq
  260. *
  261. * @example [2] = array(
  262. * 'qstart' => 9 // the first qseq within that group
  263. * 'qend' => 13 //the last qseq within that group
  264. * );
  265. *
  266. * @var type
  267. */
  268. private $groupSeqInfo;
  269. /**
  270. * tracks which groups have at least one relevant, non-hidden question
  271. *
  272. * @example [2] = 0 // means that the third group (gseq==2) is currently irrelevant
  273. *
  274. * @var type
  275. */
  276. private $gseq2relevanceStatus;
  277. /**
  278. * maps question # to the validation equation(s) for that question.
  279. * These are grouped by qid then validation type, such as 'value_range', and 'num_answers'
  280. *
  281. * @example [703] = array(
  282. * 'eqn' => array(
  283. * 'value_range' = "((is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK >= (0)) and (is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK <= (5)))"
  284. * ),
  285. * 'tips' => array(
  286. * 'value_range' = "Each answer must be between {fixnum(0)} and {fixnum(5)}"
  287. * ),
  288. * 'subqValidEqns' = array(
  289. * [] = array(
  290. * 'subqValidSelector' => '' //
  291. * 'subqValidEqn' => "(is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK >= (0)) && (is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK <= (5))"
  292. * ),
  293. * 'sumEqn' => '' // the equation to compute the current sum of the responses
  294. * 'sumRemainingEqn' => '' // the equation to how much is left (for the question attribute that lets you specify the exact value of the sum of the answers)
  295. * );
  296. *
  297. * @var type
  298. */
  299. private $qid2validationEqn;
  300. /**
  301. * keeps relevance in proper sequence so can minimize relevance processing to see what should be see on page and in indexes
  302. * Array is indexed on qseq
  303. *
  304. * @example [3] = array(
  305. * 'relevance' => "!is_empty(num)" // the question-level relevance equation
  306. * 'grelevance' => "" // the group-level relevance equation
  307. * 'qid' => "699" // the question id
  308. * 'qseq' => 3 // the 0-index question sequence
  309. * 'gseq' => 0 // the 0-index group sequence
  310. * 'jsResultVar_on' => 'answer26626X34X699' // the javascript variable holding the input value
  311. * 'jsResultVar' => 'java26226X34X699' // the javascript variable (often hidden) holding the value to be submitted
  312. * 'type' => 'N' // the one character question type
  313. * 'hidden' => 0 // 1 if it should be always_hidden
  314. * 'gid' => "34" // group id
  315. * 'mandatory' => 'N' // 'Y' if mandatory
  316. * 'eqn' => "" // TODO ??
  317. * 'help' => "" // the help text
  318. * 'qtext' => "Enter a larger number than {num}" // the question text
  319. * 'code' => 'afDS_sq5_1' // the full variable name
  320. * 'other' => 'N' // whether the question supports the 'other' option - 'Y' if true
  321. * 'rowdivid' => '2626X37X705sq5' // the javascript id for the row - in this case, the 5th sub-question
  322. * 'aid' => 'sq5' // the answer id
  323. * 'sqid' => '791' // the sub-question's qid (only populated for some question types)
  324. * );
  325. *
  326. * @var type
  327. */
  328. private $questionSeq2relevance;
  329. /**
  330. * current Group sequence (0-based index)
  331. * @example 1
  332. * @var integer
  333. */
  334. private $currentGroupSeq;
  335. /**
  336. * for Question-by-Question mode, the 0-based index
  337. * @example 3
  338. * @var integer
  339. */
  340. private $currentQuestionSeq;
  341. /**
  342. * used in Question-by-Question mode
  343. * @var integer
  344. */
  345. private $currentQID;
  346. /**
  347. * set of the current set of questions to be displayed, indexed by QID - at least one must be relevant
  348. *
  349. * The array has N entries, where N is the number if qids in the Qset. Each has the following contents:
  350. * @example [705] = array(
  351. * 'info' => array() // this is an exact copy of $questionSeq2relevance[$qseq] -- TODO - remove redundancy
  352. * 'relevant' => 1 // 1 if the question is currently relevant
  353. * 'hidden' => 0 // 1 if the question is always hidden
  354. * 'relEqn' => '' // the relevance equation -- TODO - how different from ['info']['relevance']?
  355. * 'sgqa' => // pipe-separated list of SGQA codes for this question -- e.g. "26626X37X705sq1#0|26626X37X705sq1#1|26626X37X705sq2#0|26626X37X705sq2#1|26626X37X705sq3#0|26626X37X705sq3#1|26626X37X705sq4#0|26626X37X705sq4#1|26626X37X705sq5#0|26626X37X705sq5#1"
  356. * 'unansweredSQs' => // pipe-separated list of currently unanswered SGQA codes for this question -- e.g. "26626X37X705sq1#0|26626X37X705sq1#1|26626X37X705sq3#0|26626X37X705sq3#1|26626X37X705sq5#0|26626X37X705sq5#1"
  357. * 'valid' => 0 // 1 if the current answers pass all of the validation criteria for the question
  358. * 'validEqn' => // the auto-generated validation criteria, based upon advanced question attributes -- e.g. "((count(if(count(26626X37X705sq1#0.NAOK,26626X37X705sq1#1.NAOK)==2,1,''), if(count(26626X37X705sq2#0.NAOK,26626X37X705sq2#1.NAOK)==2,1,''), if(count(26626X37X705sq3#0.NAOK,26626X37X705sq3#1.NAOK)==2,1,''), if(count(26626X37X705sq4#0.NAOK,26626X37X705sq4#1.NAOK)==2,1,''), if(count(26626X37X705sq5#0.NAOK,26626X37X705sq5#1.NAOK)==2,1,'')) >= (minSelect)) and (count(if(count(26626X37X705sq1#0.NAOK,26626X37X705sq1#1.NAOK)==2,1,''), if(count(26626X37X705sq2#0.NAOK,26626X37X705sq2#1.NAOK)==2,1,''), if(count(26626X37X705sq3#0.NAOK,26626X37X705sq3#1.NAOK)==2,1,''), if(count(26626X37X705sq4#0.NAOK,26626X37X705sq4#1.NAOK)==2,1,''), if(count(26626X37X705sq5#0.NAOK,26626X37X705sq5#1.NAOK)==2,1,'')) <= (maxSelect)))"
  359. * 'prettyValidEqn' => // syntax-highlighted version of validEqn, only showing syntax errors
  360. * 'validTip' => // html fragment to insert for the validation tip -- e.g. "<div id='vmsg_705_num_answers' class='em_num_answers'>Please select between 1 and 3 answer(s)</div>"
  361. * 'prettyValidTip' => // version of validTip that can be parsed by EM to create dynmamic validation -- e.g. "<div id='vmsg_705_num_answers' class='em_num_answers'>Please select between {fixnum(minSelect)} and {fixnum(maxSelect)} answer(s)</div>"
  362. * 'validJS' => // JavaScript fragment that can perform validation. This is the result of parsing validEqn -- e.g. "LEMif(LEManyNA('minSelect', 'maxSelect'),'',(((LEMcount(LEMif(LEMcount(LEMval('26626X37X705sq1#0.NAOK') , LEMval('26626X37X705sq1#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq2#0.NAOK') , LEMval('26626X37X705sq2#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq3#0.NAOK') , LEMval('26626X37X705sq3#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq4#0.NAOK') , LEMval('26626X37X705sq4#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq5#0.NAOK') , LEMval('26626X37X705sq5#1.NAOK') ) == 2, 1, '')) >= (LEMval('minSelect') )) && (LEMcount(LEMif(LEMcount(LEMval('26626X37X705sq1#0.NAOK') , LEMval('26626X37X705sq1#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq2#0.NAOK') , LEMval('26626X37X705sq2#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq3#0.NAOK') , LEMval('26626X37X705sq3#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq4#0.NAOK') , LEMval('26626X37X705sq4#1.NAOK') ) == 2, 1, ''), LEMif(LEMcount(LEMval('26626X37X705sq5#0.NAOK') , LEMval('26626X37X705sq5#1.NAOK') ) == 2, 1, '')) <= (LEMval('maxSelect') )))))"
  363. * 'invalidSQs' => // current list of subquestions that fail validation criteria -- e.g. "26626X37X705sq1#0|26626X37X705sq1#1|26626X37X705sq2#0|26626X37X705sq2#1|26626X37X705sq3#0|26626X37X705sq3#1|26626X37X705sq4#0|26626X37X705sq4#1|26626X37X705sq5#0|26626X37X705sq5#1"
  364. * 'relevantSQs' => // current list of subquestions that are relevant -- e.g. "26626X37X705sq1#0|26626X37X705sq1#1|26626X37X705sq2#0|26626X37X705sq2#1|26626X37X705sq3#0|26626X37X705sq3#1|26626X37X705sq4#0|26626X37X705sq4#1|26626X37X705sq5#0|26626X37X705sq5#1"
  365. * 'irrelevantSQs' => // current list of subquestions that are irrelevant -- e.g. "26626X37X705sq2#0|26626X37X705sq2#1|26626X37X705sq4#0|26626X37X705sq4#1"
  366. * 'subQrelEqn' => // TODO - ??
  367. * 'mandViolation' => 0 // 1 if the question is mandatory and fails the mandatory criteria
  368. * 'anyUnanswered' => 1 // 1 if any parts of the question are unanswered
  369. * 'mandTip' => '' // message to display if the question fails mandatory criteria
  370. * 'message' => '' // TODO ??
  371. * 'updatedValues' => // array of values that should be updated for this question, as [$sgqa] = $value
  372. * 'sumEqn' => '' //
  373. * 'sumRemainingEqn' => '' //
  374. * );
  375. *
  376. * @var type
  377. */
  378. private $currentQset=NULL;
  379. /**
  380. * last result of NavigateForwards, NavigateBackwards, or JumpTo
  381. * Array of status information about last movement, whether at question, group, or survey level
  382. *
  383. * @example = array(
  384. * 'finished' => 0 // 1 if the survey has been completed and needs to be finalized
  385. * 'message' => '' // any error message that needs to be displayed
  386. * 'seq' => 1 // the sequence count, using gseq, or qseq units if in 'group' or 'question' mode, respectively
  387. * 'mandViolation' => 0 // whether there was any violation of mandatory constraints in the last movement
  388. * 'valid' => 0 // 1 if the last movement passed all validation constraints. 0 if there were any validation errors
  389. * 'unansweredSQs' => // pipe-separated list of any sub-questions that were not answered
  390. * 'invalidSQs' => // pipe-separated list of any sub-questions that failed validation constraints
  391. * );
  392. *
  393. * @var type
  394. */
  395. private $lastMoveResult=NULL;
  396. /**
  397. * array of information needed to generate navigation index in question-by-question mode
  398. * One entry for each question, indexed by qseq
  399. *
  400. * @example [4] = array(
  401. * 'qid' => "700" // the question id
  402. * 'qtext' => 'How old are you?' // the question text
  403. * 'qcode' => 'age' // the variable name
  404. * 'qhelp' => '' // the help text
  405. * 'anyUnanswered' => 0 // 1 if there are any sub-questions answered. Used for index display
  406. * 'anyErrors' => 0 // 1 if there are any errors among the sub-questions. Could be used for index display
  407. * 'show' => 1 // 1 if there are any relevant, non-hidden sub-questions. Only if so, then display the index entry
  408. * 'gseq' => 0 // the group sequence
  409. * 'gtext' => // text description for the group
  410. * 'gname' => 'G1' // the group title
  411. * 'gid' => "34" // the group id
  412. * 'mandViolation' => 0 // 1 if the question as a whole fails the mandatory criteria
  413. * 'valid' => 1 // 0 if any part of the question fails validation criteria.
  414. * );
  415. *
  416. * @var type
  417. */
  418. private $indexQseq;
  419. /**
  420. * array of information needed to generate navigation index in group-by-group mode
  421. * One entry for each group, indexed by gseq
  422. *
  423. * @example [0] = array(
  424. * 'gtext' => // the description for the group
  425. * 'gname' => 'G1' // the group title
  426. * 'gid' => '34' // the group id
  427. * 'anyUnanswered' => 0 // 1 if any questions within the group are unanswered
  428. * 'anyErrors' => 0 // 1 if any of the questions within the group fail either validity or mandatory constraints
  429. * 'valid' => 1 // 1 if at least question in the group is relevant and non-hidden
  430. * 'mandViolation' => 0 // 1 if at least one relevant, non-hidden question in the group fails mandatory constraints
  431. * 'show' => 1 // 1 if there is at least one relevant, non-hidden question within the group
  432. * );
  433. *
  434. * @var type
  435. */
  436. private $indexGseq;
  437. /**
  438. * array of group sequence number to static info
  439. * One entry per group, indexed on gseq
  440. *
  441. * @example [0] = array(
  442. * 'group_order' => 0 // gseq
  443. * 'gid' => "34" // group id
  444. * 'group_name' => 'G2' // the group title
  445. * 'description' => // the description of the group (e.g. gtitle)
  446. * 'grelevance' => '' // the group-level relevance
  447. * );
  448. *
  449. * @var type
  450. */
  451. private $gseq2info;
  452. /**
  453. * the maximum groupSeq reached - this is needed for Index
  454. * @var type
  455. */
  456. private $maxGroupSeq;
  457. /**
  458. * mapping of questions to information about their subquestions.
  459. * One entry per question, indexed on qid
  460. *
  461. * @example [702] = array(
  462. * 'qid' => 702 // the question id
  463. * 'qseq' => 6 // the question sequence
  464. * 'gseq' => 0 // the group sequence
  465. * 'sgqa' => '26626X34X702' // the root of the SGQA code (reallly just the SGQ)
  466. * 'varName' => 'afSrcFilter_sq1' // the full qcode variable name - note, if there are sub-questions, don't use this one.
  467. * 'type' => 'M' // the one-letter question type
  468. * 'fieldname' => '26626X34X702sq1' // the fieldname (used as JavaScript variable name, and also as database column name
  469. * 'rootVarName' => 'afDS' // the root variable name
  470. * 'preg' => '/[A-Z]+/' // regular expression validation equation, if any
  471. * 'subqs' => array() of sub-questions, where each contains:
  472. * 'rowdivid' => '26626X34X702sq1' // the javascript id identifying the question row (so array_filter can hide rows)
  473. * 'varName' => 'afSrcFilter_sq1' // the full variable name for the sub-question
  474. * 'jsVarName_on' => 'java26626X34X702sq1' // the JavaScript variable name if the variable is defined on the current page
  475. * 'jsVarName' => 'java26626X34X702sq1' // the JavaScript variable name to use if the variable is defined on a different page
  476. * 'csuffix' => 'sq1' // the SGQ suffix to use for a fieldname
  477. * 'sqsuffix' => '_sq1' // the suffix to use for a qcode variable name
  478. * );
  479. *
  480. * @var type
  481. */
  482. private $q2subqInfo;
  483. /**
  484. * array of advanced question attributes for each question
  485. * Indexed by qid; available for all quetions
  486. *
  487. * @example [784] = array(
  488. * 'array_filter_exclude' => 'afSrcFilter'
  489. * 'exclude_all_others' => 'sq5'
  490. * 'max_answers' => '3'
  491. * 'min_answers' => '1'
  492. * 'other_replace_text' => '{afSrcFilter_other}'
  493. * );
  494. *
  495. * @var type
  496. */
  497. private $qattr;
  498. /**
  499. * list of needed sub-question relevance (e.g. array_filter)
  500. * Indexed by qid then sgqa; only generated for current group of questions
  501. *
  502. * @example [708][26626X37X708sq2] = array(
  503. * 'qid' => '708' // the question id
  504. * 'eqn' => "((26626X34X702sq2 != ''))" // the auto-generated sub-question-level relevance equation
  505. * 'prettyPrintEqn' => '' // only generated if there errors - shows syntax highlighting of them
  506. * 'result' => 0 // result of processing the sub-question-level relevance equation in the current context
  507. * 'numJsVars' => 1 // the number of on-page javascript variables in 'eqn'
  508. * 'relevancejs' => // the generated javascript from 'eqn' -- e.g. "LEMif(LEManyNA('26626X34X702sq2'),'',(((LEMval('26626X34X702sq2') != ""))))"
  509. * 'relevanceVars' => "java26626X34X702sq2" // the pipe-separated list of on-page javascript variables in 'eqn'
  510. * 'rowdivid' => "26626X37X708sq2" // the javascript id of the question row (so can apply array_filter)
  511. * 'type' => 'array_filter' // semicolon delimited list of types of subquestion relevance filters applied
  512. * 'qtype' => 'A' // the single character question type
  513. * 'sgqa' => "26626X37X708" // the SGQ portion of the fieldname
  514. * 'hasErrors' => 0 // 1 if there are any parse errors in the sub-question validation equations
  515. * );
  516. *
  517. * @var type
  518. */
  519. private $subQrelInfo=array();
  520. /**
  521. * array of Group-level relevance status
  522. * Indexed by gseq; only shows groups that have been visited
  523. *
  524. * @example [1] = array(
  525. * 'gseq' => 1 // group sequence
  526. * 'eqn' => '' // the group-level relevance
  527. * 'result' => 1 // result of processing the group-level relevance
  528. * 'numJsVars' => 0 // the number of on-page javascript variables in the group-level relevance equation
  529. * 'relevancejs' => '' // the javascript version of the relevance equation
  530. * 'relevanceVars' => '' // the pipe-delimited list of on-page javascript variable names used within the group-level relevance equation
  531. * 'prettyPrint' => '' // a pretty-print version of the group-level relevance equation, only if there are errors
  532. * );
  533. *
  534. * @var type
  535. */
  536. private $gRelInfo=array();
  537. /**
  538. * Array of timing information to debug how long it takes for portions of LEM to run.
  539. * Array of timing information (in seconds) for EM to help with debugging
  540. *
  541. * @example [1] = array(
  542. * [0]="LimeExpressionManager::NavigateForwards"
  543. * [1]=1.7079849243164
  544. * );
  545. *
  546. * @var type
  547. */
  548. private $runtimeTimings=array();
  549. /**
  550. * True (1) if calling LimeExpressionManager functions between StartSurvey and FinishProcessingPage
  551. * Used (mostly deprecated) to detect calls to LEM which happen outside of the normal processing scope
  552. * @var Boolean
  553. */
  554. private $initialized=false;
  555. /**
  556. * True (1) if have already processed the relevance equations (so don't need to do it again)
  557. *
  558. * @var Boolean
  559. */
  560. private $processedRelevance=false;
  561. /**
  562. * Message generated to show debug timing values, if debugLevel includes LEM_DEBUG_TIMING
  563. * @var type
  564. */
  565. private $debugTimingMsg='';
  566. /**
  567. * temporary variable to reduce need to parse same equation multiple times. Used for relevance and validation
  568. * Array, indexed on equation, providing the following information:
  569. *
  570. * @example ['!is_empty(num)'] = array(
  571. * 'result' => 1 // result of processing the equation in the current scope
  572. * 'prettyPrint' => '' // syntax-highlighted version of equation if there are any errors
  573. * 'hasErrors' => 0 // 1 if there are any syntax errors
  574. * );
  575. *
  576. * @var type
  577. */
  578. private $ParseResultCache;
  579. /**
  580. * array of 2nd scale answer lists for types ':' and ';' -- needed for convenient print of logic file
  581. * Indexed on qid; available for all questions
  582. *
  583. * @example [706] = array(
  584. * '1~1' => '1|Never',
  585. * '1~2' => '2|Sometimes',
  586. * '1~3' => '3|Always'
  587. * );
  588. *
  589. * @var type
  590. */
  591. private $multiflexiAnswers;
  592. /**
  593. * used to specify whether to generate equations using SGQA codes or qcodes
  594. * Default is to convert all qcode naming to sgqa naming when generating javascript, as that provides the greatest backwards compatibility
  595. * TSV export of survey structure sets this to false so as to force use of qcode naming
  596. *
  597. * @var Boolean
  598. */
  599. private $sgqaNaming = true;
  600. /**
  601. * Number of groups in survey (number of possible pages to display)
  602. * @var integer
  603. */
  604. private $numGroups=0;
  605. /**
  606. * Numer of questions in survey (counting display-only ones?)
  607. * @var integer
  608. */
  609. private $numQuestions=0;
  610. /**
  611. * String identifier for the active session
  612. * @var type
  613. */
  614. private $sessid;
  615. /**
  616. * Linked list of array filters
  617. * @var array
  618. */
  619. private $qrootVarName2arrayFilter = array();
  620. /**
  621. * Array, keyed on qid, to JavaScript and list of variables needed to implement exclude_all_others_auto
  622. * @var type
  623. */
  624. private $qid2exclusiveAuto = array();
  625. /**
  626. * Array of values to be updated
  627. * @var type
  628. */
  629. private $updatedValues = array();
  630. /**
  631. * A private constructor; prevents direct creation of object
  632. */
  633. private function __construct()
  634. {
  635. self::$instance =& $this;
  636. $this->em = new ExpressionManager();
  637. if (!isset($_SESSION['LEMlang'])) {
  638. $_SESSION['LEMlang'] = 'en'; // so that there is a default
  639. }
  640. }
  641. /**
  642. * Ensures there is only one instances of LEM. Note, if switch between surveys, have to clear this cache
  643. * @return LimeExpressionManager
  644. */
  645. public static function &singleton()
  646. {
  647. $now = microtime(true);
  648. if (isset($_SESSION['LEMdirtyFlag'])) {
  649. $c = __CLASS__;
  650. self::$instance = new $c;
  651. unset($_SESSION['LEMdirtyFlag']);
  652. }
  653. else if (!isset(self::$instance)) {
  654. if (isset($_SESSION['LEMsingleton'])) {
  655. self::$instance = unserialize($_SESSION['LEMsingleton']);
  656. }
  657. else {
  658. $c = __CLASS__;
  659. self::$instance = new $c;
  660. }
  661. }
  662. else {
  663. // does exist, and OK to cache
  664. return self::$instance;
  665. }
  666. // only record duration if have to create new (or unserialize) an instance
  667. self::$instance->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  668. return self::$instance;
  669. }
  670. /**
  671. * Prevent users to clone the instance
  672. */
  673. public function __clone()
  674. {
  675. trigger_error('Clone is not allowed.', E_USER_ERROR);
  676. }
  677. /**
  678. * Tells Expression Manager that something has changed enough that needs to eliminate internal caching
  679. */
  680. public static function SetDirtyFlag()
  681. {
  682. $_SESSION['LEMdirtyFlag'] = true;
  683. $_SESSION['LEMforceRefresh'] = true;
  684. }
  685. /**
  686. * Set the SurveyId - really checks whether the survey you're about to work with is new, and if so, clears the LEM cache
  687. * @param <integer> $sid
  688. */
  689. public static function SetSurveyId($sid=NULL)
  690. {
  691. if (!is_null($sid)) {
  692. if (isset($_SESSION['LEMsid']) && $sid != $_SESSION['LEMsid']) {
  693. // then trying to use a new survey - so clear the LEM cache
  694. self::SetDirtyFlag();
  695. }
  696. $_SESSION['LEMsid'] = $sid;
  697. }
  698. }
  699. /**
  700. * Sets the language for Expression Manager. If the language has changed, then EM cache must be invalidated and refreshed
  701. * @param <string> $lang
  702. */
  703. public static function SetEMLanguage($lang=NULL)
  704. {
  705. if (is_null($lang)) {
  706. return; // should never happen
  707. }
  708. if (!isset($_SESSION['LEMlang'])) {
  709. $_SESSION['LEMlang'] = $lang;
  710. }
  711. if ($_SESSION['LEMlang'] != $lang) {
  712. // then changing languages, so clear cache
  713. self::SetDirtyFlag();
  714. }
  715. $_SESSION['LEMlang'] = $lang;
  716. }
  717. /**
  718. * Do bulk-update/save of Conditions to Relevance
  719. * @param <integer> $surveyId - if NULL, processes the entire database, otherwise just the specified survey
  720. * @param <integer> $qid - if specified, just updates that one question
  721. * @return array of query strings
  722. */
  723. public static function UpgradeConditionsToRelevance($surveyId=NULL, $qid=NULL)
  724. {
  725. LimeExpressionManager::SetDirtyFlag(); // set dirty flag even if not conditions, since must have had a DB change
  726. // Cheat and upgrade question attributes here too.
  727. self::UpgradeQuestionAttributes(true,$surveyId,$qid);
  728. $releqns = self::ConvertConditionsToRelevance($surveyId,$qid);
  729. $num = count($releqns);
  730. if ($num == 0) {
  731. return NULL;
  732. }
  733. $queries = array();
  734. foreach ($releqns as $key=>$value) {
  735. $query = "UPDATE {{questions}} SET relevance=".Yii::app()->db->quoteValue($value)." WHERE qid=".$key;
  736. dbExecuteAssoc($query);
  737. $queries[] = $query;
  738. }
  739. LimeExpressionManager::SetDirtyFlag();
  740. return $queries;
  741. }
  742. /**
  743. * This reverses UpgradeConditionsToRelevance(). It removes Relevance for questions that have Conditions
  744. * @param <integer> $surveyId
  745. * @param <integer> $qid
  746. */
  747. public static function RevertUpgradeConditionsToRelevance($surveyId=NULL, $qid=NULL)
  748. {
  749. LimeExpressionManager::SetDirtyFlag(); // set dirty flag even if not conditions, since must have had a DB change
  750. $releqns = self::ConvertConditionsToRelevance($surveyId,$qid);
  751. $num = count($releqns);
  752. if ($num == 0) {
  753. return NULL;
  754. }
  755. foreach ($releqns as $key=>$value) {
  756. $query = "UPDATE {{questions}} SET relevance=1 WHERE qid=".$key;
  757. dbExecuteAssoc($query);
  758. }
  759. return count($releqns);
  760. }
  761. /**
  762. * If $qid is set, returns the relevance equation generated from conditions (or NULL if there are no conditions for that $qid)
  763. * If $qid is NULL, returns an array of relevance equations generated from Conditions, keyed on the question ID
  764. * @param <integer> $surveyId
  765. * @param <integer> $qid - if passed, only generates relevance equation for that question - otherwise genereates for all questions with conditions
  766. * @return array of generated relevance strings, indexed by $qid
  767. */
  768. public static function ConvertConditionsToRelevance($surveyId=NULL, $qid=NULL)
  769. {
  770. $query = LimeExpressionManager::getConditionsForEM($surveyId,$qid);
  771. $_qid = -1;
  772. $relevanceEqns = array();
  773. $scenarios = array();
  774. $relAndList = array();
  775. $relOrList = array();
  776. foreach($query->readAll() as $row)
  777. {
  778. $row['method']=trim($row['method']); //For Postgres
  779. if ($row['qid'] != $_qid)
  780. {
  781. // output the values for prior question is there was one
  782. if ($_qid != -1)
  783. {
  784. if (count($relOrList) > 0)
  785. {
  786. $relAndList[] = '(' . implode(' or ', $relOrList) . ')';
  787. }
  788. if (count($relAndList) > 0)
  789. {
  790. $scenarios[] = '(' . implode(' and ', $relAndList) . ')';
  791. }
  792. $relevanceEqn = implode(' or ', $scenarios);
  793. $relevanceEqns[$_qid] = $relevanceEqn;
  794. }
  795. // clear for next question
  796. $_qid = $row['qid'];
  797. $_scenario = $row['scenario'];
  798. $_cqid = $row['cqid'];
  799. $_subqid = -1;
  800. $relAndList = array();
  801. $relOrList = array();
  802. $scenarios = array();
  803. $releqn = '';
  804. }
  805. if ($row['scenario'] != $_scenario)
  806. {
  807. if (count($relOrList) > 0)
  808. {
  809. $relAndList[] = '(' . implode(' or ', $relOrList) . ')';
  810. }
  811. $scenarios[] = '(' . implode(' and ', $relAndList) . ')';
  812. $relAndList = array();
  813. $relOrList = array();
  814. $_scenario = $row['scenario'];
  815. $_cqid = $row['cqid'];
  816. $_subqid = -1;
  817. }
  818. if ($row['cqid'] != $_cqid)
  819. {
  820. $relAndList[] = '(' . implode(' or ', $relOrList) . ')';
  821. $relOrList = array();
  822. $_cqid = $row['cqid'];
  823. $_subqid = -1;
  824. }
  825. // fix fieldnames
  826. if ($row['type'] == '' && preg_match('/^{.+}$/',$row['cfieldname'])) {
  827. $fieldname = substr($row['cfieldname'],1,-1); // {TOKEN:xxxx}
  828. $subqid = $fieldname;
  829. $value = $row['value'];
  830. }
  831. else if ($row['type'] == 'M' || $row['type'] == 'P') {
  832. if (substr($row['cfieldname'],0,1) == '+') {
  833. // if prefixed with +, then a fully resolved name
  834. $fieldname = substr($row['cfieldname'],1) . '.NAOK';
  835. $subqid = $fieldname;
  836. $value = $row['value'];
  837. }
  838. else {
  839. // else create name by concatenating two parts together
  840. $fieldname = $row['cfieldname'] . $row['value'] . '.NAOK';
  841. $subqid = $row['cfieldname'];
  842. $value = 'Y';
  843. }
  844. }
  845. else {
  846. $fieldname = $row['cfieldname'] . '.NAOK';
  847. $subqid = $fieldname;
  848. $value = $row['value'];
  849. }
  850. if ($_subqid != -1 && $_subqid != $subqid)
  851. {
  852. $relAndList[] = '(' . implode(' or ', $relOrList) . ')';
  853. $relOrList = array();
  854. }
  855. $_subqid = $subqid;
  856. // fix values
  857. if (preg_match('/^@\d+X\d+X\d+.*@$/',$value)) {
  858. $value = substr($value,1,-1);
  859. }
  860. else if (preg_match('/^{.+}$/',$value)) {
  861. $value = substr($value,1,-1);
  862. }
  863. else if ($row['method'] == 'RX') {
  864. if (!preg_match('#^/.*/$#',$value))
  865. {
  866. $value = '"/' . $value . '/"'; // if not surrounded by slashes, add them.
  867. }
  868. }
  869. else {
  870. $value = '"' . $value . '"';
  871. }
  872. // add equation
  873. if ($row['method'] == 'RX')
  874. {
  875. $relOrList[] = "regexMatch(" . $value . "," . $fieldname . ")";
  876. }
  877. else
  878. {
  879. // Conditions uses ' ' to mean not answered, but internally it is really stored as ''. Fix this
  880. if ($value === '" "' || $value == '""') {
  881. if ($row['method'] == '==')
  882. {
  883. $relOrList[] = "is_empty(" . $fieldname . ")";
  884. }
  885. else if ($row['method'] == '!=')
  886. {
  887. $relOrList[] = "!is_empty(" . $fieldname . ")";
  888. }
  889. else
  890. {
  891. $relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
  892. }
  893. }
  894. else
  895. {
  896. if ($value == '"0"' || !preg_match('/^".+"$/',$value))
  897. {
  898. switch ($row['method'])
  899. {
  900. case '==':
  901. case '<':
  902. case '<=':
  903. case '>=':
  904. $relOrList[] = '(!is_empty(' . $fieldname . ') && (' . $fieldname . " " . $row['method'] . " " . $value . '))';
  905. break;
  906. case '!=':
  907. $relOrList[] = '(is_empty(' . $fieldname . ') || (' . $fieldname . " != " . $value . '))';
  908. break;
  909. default:
  910. $relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
  911. break;
  912. }
  913. }
  914. else
  915. {
  916. switch ($row['method'])
  917. {
  918. case '<':
  919. case '<=':
  920. $relOrList[] = '(!is_empty(' . $fieldname . ') && (' . $fieldname . " " . $row['method'] . " " . $value . '))';
  921. break;
  922. default:
  923. $relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
  924. break;
  925. }
  926. }
  927. }
  928. }
  929. if ($row['cqid'] == 0 || substr($row['cfieldname'],0,1) == '+') {
  930. $_cqid = -1; // forces this statement to be ANDed instead of being part of a cqid OR group
  931. }
  932. }
  933. // output last one
  934. if ($_qid != -1)
  935. {
  936. if (count($relOrList) > 0)
  937. {
  938. $relAndList[] = '(' . implode(' or ', $relOrList) . ')';
  939. }
  940. if (count($relAndList) > 0)
  941. {
  942. $scenarios[] = '(' . implode(' and ', $relAndList) . ')';
  943. }
  944. $relevanceEqn = implode(' or ', $scenarios);
  945. $relevanceEqns[$_qid] = $relevanceEqn;
  946. }
  947. if (is_null($qid)) {
  948. return $relevanceEqns;
  949. }
  950. else {
  951. if (isset($relevanceEqns[$qid]))
  952. {
  953. $result = array();
  954. $result[$qid] = $relevanceEqns[$qid];
  955. return $result;
  956. }
  957. else
  958. {
  959. return NULL;
  960. }
  961. }
  962. }
  963. /**
  964. * Return list of relevance equations generated from conditions
  965. * @param <integer> $surveyId
  966. * @param <integer> $qid
  967. * @return array of relevance equations, indexed by $qid
  968. */
  969. public static function UnitTestConvertConditionsToRelevance($surveyId=NULL, $qid=NULL)
  970. {
  971. $LEM =& LimeExpressionManager::singleton();
  972. return $LEM->ConvertConditionsToRelevance($surveyId, $qid);
  973. }
  974. /**
  975. * Process all question attributes that apply to EM
  976. * (1) Sub-question-level relevance: e.g. array_filter, array_filter_exclude
  977. * (2) Validations: e.g. min/max number of answers; min/max/eq sum of answers
  978. * @param <integer> $onlyThisQseq - only process these attributes for the specified question
  979. */
  980. public function _CreateSubQLevelRelevanceAndValidationEqns($onlyThisQseq=NULL)
  981. {
  982. // $now = microtime(true);
  983. $this->subQrelInfo=array(); // reset it each time this is called
  984. $subQrels = array(); // array of sub-question-level relevance equations
  985. $validationEqn = array();
  986. $validationTips = array(); // array of visible tips for validation criteria, indexed by $qid
  987. // Associate these with $qid so that can be nested under appropriate question-level relevance
  988. foreach ($this->q2subqInfo as $qinfo)
  989. {
  990. if (!is_null($onlyThisQseq) && $onlyThisQseq != $qinfo['qseq']) {
  991. continue;
  992. }
  993. else if (!$this->allOnOnePage && $this->currentGroupSeq != $qinfo['gseq']) {
  994. continue; // only need subq relevance for current page.
  995. }
  996. $questionNum = $qinfo['qid'];
  997. $type = $qinfo['type'];
  998. $hasSubqs = (isset($qinfo['subqs']) && count($qinfo['subqs'] > 0));
  999. $qattr = isset($this->qattr[$questionNum]) ? $this->qattr[$questionNum] : array();
  1000. if (isset($qattr['input_boxes']) && $qattr['input_boxes'] == '1')
  1001. {
  1002. $input_boxes='1';
  1003. }
  1004. else
  1005. {
  1006. $input_boxes='';
  1007. }
  1008. if (isset($qattr['value_range_allows_missing']) && $qattr['value_range_allows_missing'] == '1')
  1009. {
  1010. $value_range_allows_missing = true;
  1011. }
  1012. else
  1013. {
  1014. $value_range_allows_missing = false;
  1015. }
  1016. // array_filter
  1017. // If want to filter question Q2 on Q1, where each have subquestions SQ1-SQ3, this is equivalent to relevance equations of:
  1018. // relevance for Q2_SQ1 is Q1_SQ1!=''
  1019. $array_filter = NULL;
  1020. if (isset($qattr['array_filter']) && trim($qattr['array_filter']) != '')
  1021. {
  1022. $array_filter = $qattr['array_filter'];
  1023. $this->qrootVarName2arrayFilter[$qinfo['rootVarName']]['array_filter'] = $array_filter;
  1024. }
  1025. // array_filter_exclude
  1026. // If want to filter question Q2 on Q1, where each have subquestions SQ1-SQ3, this is equivalent to relevance equations of:
  1027. // relevance for Q2_SQ1 is Q1_SQ1==''
  1028. $array_filter_exclude = NULL;
  1029. if (isset($qattr['array_filter_exclude']) && trim($qattr['array_filter_exclude']) != '')
  1030. {
  1031. $array_filter_exclude = $qattr['array_filter_exclude'];
  1032. $this->qrootVarName2arrayFilter[$qinfo['rootVarName']]['array_filter_exclude'] = $array_filter_exclude;
  1033. }
  1034. // array_filter and array_filter_exclude get processed together
  1035. if (!is_null($array_filter) || !is_null($array_filter_exclude))
  1036. {
  1037. if ($hasSubqs) {
  1038. $cascadedAF = array();
  1039. $cascadedAFE = array();
  1040. list($cascadedAF, $cascadedAFE) = $this->_recursivelyFindAntecdentArrayFilters($qinfo['rootVarName'],array(),array());
  1041. $cascadedAF = array_reverse($cascadedAF);
  1042. $cascadedAFE = array_reverse($cascadedAFE);
  1043. $subqs = $qinfo['subqs'];
  1044. if ($type == 'R') {
  1045. $subqs = array();
  1046. foreach ($this->qans[$qinfo['qid']] as $k=>$v)
  1047. {
  1048. $_code = explode('~',$k);
  1049. $subqs[] = array(
  1050. 'rowdivid'=>$qinfo['sgqa'] . $_code[1],
  1051. 'sqsuffix'=>'_' . $_code[1],
  1052. );
  1053. }
  1054. }
  1055. $last_rowdivid = '--';
  1056. foreach ($subqs as $sq) {
  1057. if ($sq['rowdivid'] == $last_rowdivid)
  1058. {
  1059. continue;
  1060. }
  1061. $last_rowdivid = $sq['rowdivid'];
  1062. $af_names = array();
  1063. $afe_names = array();
  1064. switch ($type)
  1065. {
  1066. case '1': //Array (Flexible Labels) dual scale
  1067. case ':': //ARRAY (Multi Flexi) 1 to 10
  1068. case ';': //ARRAY (Multi Flexi) Text
  1069. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  1070. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  1071. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  1072. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  1073. case 'F': //ARRAY (Flexible) - Row Format
  1074. case 'L': //LIST drop-down/radio-button list
  1075. case 'M': //Multiple choice checkbox
  1076. case 'P': //Multiple choice with comments checkbox + text
  1077. case 'K': //MULTIPLE NUMERICAL QUESTION
  1078. case 'Q': //MULTIPLE SHORT TEXT
  1079. case 'R': //Ranking
  1080. // if ($this->sgqaNaming)
  1081. // {
  1082. foreach ($cascadedAF as $_caf)
  1083. {
  1084. $sgq = ((isset($this->qcode2sgq[$_caf])) ? $this->qcode2sgq[$_caf] : $_caf);
  1085. $fqid = explode('X',$sgq);
  1086. if (!isset($fqid[2]))
  1087. {
  1088. continue;
  1089. }
  1090. $fqid = $fqid[2];
  1091. if ($this->q2subqInfo[$fqid]['type'] == 'R')
  1092. {
  1093. $rankables = array();
  1094. foreach ($this->qans[$fqid] as $k=>$v)
  1095. {
  1096. $rankable = explode('~',$k);
  1097. $rankables[] = '_' . $rankable[1];
  1098. }
  1099. if (array_search($sq['sqsuffix'],$rankables) === false)
  1100. {
  1101. continue;
  1102. }
  1103. }
  1104. $fsqs = array();
  1105. foreach ($this->q2subqInfo[$fqid]['subqs'] as $fsq)
  1106. {
  1107. if ($this->q2subqInfo[$fqid]['type'] == 'R')
  1108. {
  1109. // we know the suffix exists
  1110. $fsqs[] = '(' . $sgq . $fsq['csuffix'] . ".NAOK == '" . substr($sq['sqsuffix'],1) . "')";
  1111. }
  1112. else if ($this->q2subqInfo[$fqid]['type'] == ':' && isset($this->qattr[$fqid]['multiflexible_checkbox']) && $this->qattr[$fqid]['multiflexible_checkbox']=='1')
  1113. {
  1114. if ($fsq['sqsuffix'] == $sq['sqsuffix'])
  1115. {
  1116. $fsqs[] = $sgq . $fsq['csuffix'] . '.NAOK=="1"';
  1117. }
  1118. }
  1119. else
  1120. {
  1121. if ($fsq['sqsuffix'] == $sq['sqsuffix'])
  1122. {
  1123. $fsqs[] = '!is_empty(' . $sgq . $fsq['csuffix'] . '.NAOK)';
  1124. }
  1125. }
  1126. }
  1127. if (count($fsqs) > 0)
  1128. {
  1129. $af_names[] = '(' . implode(' or ', $fsqs) . ')';
  1130. }
  1131. }
  1132. foreach ($cascadedAFE as $_cafe)
  1133. {
  1134. $sgq = ((isset($this->qcode2sgq[$_cafe])) ? $this->qcode2sgq[$_cafe] : $_cafe);
  1135. $fqid = explode('X',$sgq);
  1136. if (!isset($fqid[2]))
  1137. {
  1138. continue;
  1139. }
  1140. $fqid = $fqid[2];
  1141. if ($this->q2subqInfo[$fqid]['type'] == 'R')
  1142. {
  1143. $rankables = array();
  1144. foreach ($this->qans[$fqid] as $k=>$v)
  1145. {
  1146. $rankable = explode('~',$k);
  1147. $rankables[] = '_' . $rankable[1];
  1148. }
  1149. if (array_search($sq['sqsuffix'],$rankables) === false)
  1150. {
  1151. continue;
  1152. }
  1153. }
  1154. $fsqs = array();
  1155. foreach ($this->q2subqInfo[$fqid]['subqs'] as $fsq)
  1156. {
  1157. if ($this->q2subqInfo[$fqid]['type'] == 'R')
  1158. {
  1159. // we know the suffix exists
  1160. $fsqs[] = '(' . $sgq . $fsq['csuffix'] . ".NAOK != '" . substr($sq['sqsuffix'],1) . "')";
  1161. }
  1162. else if ($this->q2subqInfo[$fqid]['type'] == ':' && isset($this->qattr[$fqid]['multiflexible_checkbox']) && $this->qattr[$fqid]['multiflexible_checkbox']=='1')
  1163. {
  1164. if ($fsq['sqsuffix'] == $sq['sqsuffix'])
  1165. {
  1166. $fsqs[] = $sgq . $fsq['csuffix'] . '.NAOK!="1"';
  1167. }
  1168. }
  1169. else
  1170. {
  1171. if ($fsq['sqsuffix'] == $sq['sqsuffix'])
  1172. {
  1173. $fsqs[] = 'is_empty(' . $sgq . $fsq['csuffix'] . '.NAOK)';
  1174. }
  1175. }
  1176. }
  1177. if (count($fsqs) > 0)
  1178. {
  1179. $afe_names[] = '(' . implode(' and ', $fsqs) . ')';
  1180. }
  1181. }
  1182. // }
  1183. // else // TODO - implement qcode naming for this
  1184. // {
  1185. // foreach ($cascadedAF as $_caf)
  1186. // {
  1187. // $sgq = $_caf . $sq['sqsuffix'];
  1188. // if (isset($this->knownVars[$sgq]))
  1189. // {
  1190. // $af_names[] = $sgq . '.NAOK';
  1191. // }
  1192. // }
  1193. // foreach ($cascadedAFE as $_cafe)
  1194. // {
  1195. // $sgq = $_cafe . $sq['sqsuffix'];
  1196. // if (isset($this->knownVars[$sgq]))
  1197. // {
  1198. // $afe_names[] = $sgq . '.NAOK';
  1199. // }
  1200. // }
  1201. // }
  1202. break;
  1203. default:
  1204. break;
  1205. }
  1206. $af_names = array_unique($af_names);
  1207. $afe_names= array_unique($afe_names);
  1208. if (count($af_names) > 0 || count($afe_names) > 0) {
  1209. $afs_eqn = '';
  1210. if (count($af_names) > 0)
  1211. {
  1212. $afs_eqn .= implode(' && ', $af_names);
  1213. }
  1214. if (count($afe_names) > 0)
  1215. {
  1216. if ($afs_eqn != '')
  1217. {
  1218. $afs_eqn .= ' && ';
  1219. }
  1220. $afs_eqn .= implode(' && ', $afe_names);
  1221. }
  1222. $subQrels[] = array(
  1223. 'qtype' => $type,
  1224. 'type' => 'array_filter',
  1225. 'rowdivid' => $sq['rowdivid'],
  1226. 'eqn' => '(' . $afs_eqn . ')',
  1227. 'qid' => $questionNum,
  1228. 'sgqa' => $qinfo['sgqa'],
  1229. );
  1230. }
  1231. }
  1232. }
  1233. }
  1234. // code_filter: WZ
  1235. // This can be skipped, since question types 'W' (list-dropdown-flexible) and 'Z'(list-radio-flexible) are no longer supported
  1236. // equals_num_value
  1237. // Validation:= sum(sq1,...,sqN) == value (which could be an expression).
  1238. if (isset($qattr['equals_num_value']) && trim($qattr['equals_num_value']) != '')
  1239. {
  1240. $equals_num_value = $qattr['equals_num_value'];
  1241. if ($hasSubqs) {
  1242. $subqs = $qinfo['subqs'];
  1243. $sq_names = array();
  1244. foreach ($subqs as $sq) {
  1245. $sq_name = NULL;
  1246. switch ($type)
  1247. {
  1248. case 'K': //MULTIPLE NUMERICAL QUESTION
  1249. if ($this->sgqaNaming)
  1250. {
  1251. $sq_name = $sq['rowdivid'] . '.NAOK';
  1252. }
  1253. else
  1254. {
  1255. $sq_name = $sq['varName'] . '.NAOK';
  1256. }
  1257. break;
  1258. default:
  1259. break;
  1260. }
  1261. if (!is_null($sq_name)) {
  1262. $sq_names[] = $sq_name;
  1263. }
  1264. }
  1265. if (count($sq_names) > 0) {
  1266. if (!isset($validationEqn[$questionNum]))
  1267. {
  1268. $validationEqn[$questionNum] = array();
  1269. }
  1270. // sumEqn and sumRemainingEqn may need to be rounded if using sliders
  1271. $precision=LEM_DEFAULT_PRECISION; // default is not to round
  1272. if (isset($qattr['slider_layout']) && $qattr['slider_layout']=='1')
  1273. {
  1274. $precision=0; // default is to round to whole numbers
  1275. if (isset($qattr['slider_accuracy']) && trim($qattr['slider_accuracy'])!='')
  1276. {
  1277. $slider_accuracy = $qattr['slider_accuracy'];
  1278. $_parts = explode('.',$slider_accuracy);
  1279. if (isset($_parts[1]))
  1280. {
  1281. $precision = strlen($_parts[1]); // number of digits after mantissa
  1282. }
  1283. }
  1284. }
  1285. $sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
  1286. $sumRemainingEqn = '(' . $equals_num_value . ' - sum(' . implode(', ', $sq_names) . '))';
  1287. $mainEqn = 'sum(' . implode(', ', $sq_names) . ')';
  1288. if (!is_null($precision))
  1289. {
  1290. $sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
  1291. $sumRemainingEqn = 'round(' . $sumRemainingEqn . ', ' . $precision . ')';
  1292. $mainEqn = 'round(' . $mainEqn . ', ' . $precision . ')';
  1293. }
  1294. $noanswer_option = '';
  1295. if ($value_range_allows_missing)
  1296. {
  1297. $noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
  1298. }
  1299. $validationEqn[$questionNum][] = array(
  1300. 'qtype' => $type,
  1301. 'type' => 'equals_num_value',
  1302. 'class' => 'sum_range',
  1303. 'eqn' => ($qinfo['mandatory']=='Y')?'(' . $mainEqn . ' == (' . $equals_num_value . '))':'(' . $mainEqn . ' == (' . $equals_num_value . ')' . $noanswer_option . ')',
  1304. 'qid' => $questionNum,
  1305. 'sumEqn' => $sumEqn,
  1306. 'sumRemainingEqn' => $sumRemainingEqn,
  1307. );
  1308. }
  1309. }
  1310. }
  1311. else
  1312. {
  1313. $equals_num_value='';
  1314. }
  1315. // exclude_all_others
  1316. // If any excluded options are true (and relevant), then disable all other input elements for that question
  1317. if (isset($qattr['exclude_all_others']) && trim($qattr['exclude_all_others']) != '')
  1318. {
  1319. $exclusive_options = explode(';',$qattr['exclude_all_others']);
  1320. if ($hasSubqs) {
  1321. foreach ($exclusive_options as $exclusive_option)
  1322. {
  1323. $exclusive_option = trim($exclusive_option);
  1324. if ($exclusive_option == '') {
  1325. continue;
  1326. }
  1327. $subqs = $qinfo['subqs'];
  1328. $sq_names = array();
  1329. foreach ($subqs as $sq) {
  1330. $sq_name = NULL;
  1331. if ($sq['csuffix'] == $exclusive_option)
  1332. {
  1333. continue; // so don't make the excluded option irrelevant
  1334. }
  1335. switch ($type)
  1336. {
  1337. case ':': //ARRAY (Multi Flexi) 1 to 10
  1338. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  1339. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  1340. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  1341. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  1342. case 'F': //ARRAY (Flexible) - Row Format
  1343. case 'M': //Multiple choice checkbox
  1344. case 'P': //Multiple choice with comments checkbox + text
  1345. case 'K': //MULTIPLE NUMERICAL QUESTION
  1346. case 'Q': //MULTIPLE SHORT TEXT
  1347. if ($this->sgqaNaming)
  1348. {
  1349. $sq_name = $qinfo['sgqa'] . trim($exclusive_option) . '.NAOK';
  1350. }
  1351. else
  1352. {
  1353. $sq_name = $qinfo['sgqa'] . trim($exclusive_option) . '.NAOK';
  1354. }
  1355. break;
  1356. default:
  1357. break;
  1358. }
  1359. if (!is_null($sq_name)) {
  1360. $subQrels[] = array(
  1361. 'qtype' => $type,
  1362. 'type' => 'exclude_all_others',
  1363. 'rowdivid' => $sq['rowdivid'],
  1364. 'eqn' => 'is_empty(' . $sq_name . ')',
  1365. 'qid' => $questionNum,
  1366. 'sgqa' => $qinfo['sgqa'],
  1367. );
  1368. }
  1369. }
  1370. }
  1371. }
  1372. }
  1373. else
  1374. {
  1375. $exclusive_option = '';
  1376. }
  1377. // exclude_all_others_auto
  1378. // if (count(this.relevanceStatus) == count(this)) { set exclusive option value to "Y" and call checkconditions() }
  1379. // However, note that would need to blank the values, not use relevance, otherwise can't unclick the _auto option without having it re-enable itself
  1380. if (isset($qattr['exclude_all_others_auto']) && trim($qattr['exclude_all_others_auto']) == '1'
  1381. && isset($qattr['exclude_all_others']) && trim($qattr['exclude_all_others']) != '' && count(explode(';',trim($qattr['exclude_all_others']))) == 1)
  1382. {
  1383. $exclusive_option = trim($qattr['exclude_all_others']);
  1384. if ($hasSubqs) {
  1385. $subqs = $qinfo['subqs'];
  1386. $sq_names = array();
  1387. foreach ($subqs as $sq) {
  1388. $sq_name = NULL;
  1389. switch ($type)
  1390. {
  1391. case 'M': //Multiple choice checkbox
  1392. case 'P': //Multiple choice with comments checkbox + text
  1393. if ($this->sgqaNaming)
  1394. {
  1395. $sq_name = substr($sq['jsVarName'],4);
  1396. }
  1397. else
  1398. {
  1399. $sq_name = $sq['varName'];
  1400. }
  1401. break;
  1402. default:
  1403. break;
  1404. }
  1405. if (!is_null($sq_name))
  1406. {
  1407. if ($sq['csuffix'] == $exclusive_option)
  1408. {
  1409. $eoVarName = substr($sq['jsVarName'],4);
  1410. }
  1411. else
  1412. {
  1413. $sq_names[] = $sq_name;
  1414. }
  1415. }
  1416. }
  1417. if (count($sq_names) > 0) {
  1418. $relpart = "sum(" . implode(".relevanceStatus, ", $sq_names) . ".relevanceStatus)";
  1419. $checkedpart = "count(" . implode(".NAOK, ", $sq_names) . ".NAOK)";
  1420. $eoRelevantAndUnchecked = "(" . $eoVarName . ".relevanceStatus && is_empty(" . $eoVarName . "))";
  1421. $eoEqn = "(" . $eoRelevantAndUnchecked . " && (" . $relpart . " == " . $checkedpart . "))";
  1422. $this->em->ProcessBooleanExpression($eoEqn, $qinfo['gseq'], $qinfo['qseq']);
  1423. $relevanceVars = implode('|',$this->em->GetJSVarsUsed());
  1424. $relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
  1425. // Unset all checkboxes and hidden values for this question (irregardless of whether they are array filtered)
  1426. $eosaJS = "if (" . $relevanceJS . ") {\n";
  1427. $eosaJS .=" $('#question" . $questionNum . " [type=checkbox]').attr('checked',false);\n";
  1428. $eosaJS .=" $('#java" . $qinfo['sgqa'] . "other').val('');\n";
  1429. $eosaJS .=" $('#answer" . $qinfo['sgqa'] . "other').val('');\n";
  1430. $eosaJS .=" $('[id^=java" . $qinfo['sgqa'] . "]').val('');\n";
  1431. $eosaJS .=" $('#answer" . $eoVarName . "').attr('checked',true);\n";
  1432. $eosaJS .=" $('#java" . $eoVarName . "').val('Y');\n";
  1433. $eosaJS .=" LEMrel" . $questionNum . "();\n";
  1434. $eosaJS .=" relChange" . $questionNum ."=true;\n";
  1435. $eosaJS .="}\n";
  1436. $this->qid2exclusiveAuto[$questionNum] = array(
  1437. 'js'=>$eosaJS,
  1438. 'relevanceVars'=>$relevanceVars, // so that EM knows which variables to declare
  1439. 'rowdivid'=>$eoVarName, // to ensure that EM creates a hidden relevanceSGQA input for the exclusive option
  1440. );
  1441. }
  1442. }
  1443. }
  1444. // min_answers
  1445. // Validation:= count(sq1,...,sqN) >= value (which could be an expression).
  1446. if (isset($qattr['min_answers']) && trim($qattr['min_answers']) != '')
  1447. {
  1448. $min_answers = $qattr['min_answers'];
  1449. if ($hasSubqs) {
  1450. $subqs = $qinfo['subqs'];
  1451. $sq_names = array();
  1452. foreach ($subqs as $sq) {
  1453. $sq_name = NULL;
  1454. switch ($type)
  1455. {
  1456. case '1': //Array (Flexible Labels) dual scale
  1457. if (substr($sq['varName'],-1,1) == '0')
  1458. {
  1459. if ($this->sgqaNaming)
  1460. {
  1461. $base = $sq['rowdivid']."#";
  1462. $sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
  1463. }
  1464. else
  1465. {
  1466. $base = substr($sq['varName'],0,-1);
  1467. $sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
  1468. }
  1469. }
  1470. break;
  1471. case ':': //ARRAY (Multi Flexi) 1 to 10
  1472. case ';': //ARRAY (Multi Flexi) Text
  1473. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  1474. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  1475. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  1476. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  1477. case 'F': //ARRAY (Flexible) - Row Format
  1478. case 'K': //MULTIPLE NUMERICAL QUESTION
  1479. case 'Q': //MULTIPLE SHORT TEXT
  1480. case 'M': //Multiple choice checkbox
  1481. case 'R': //RANKING STYLE
  1482. if ($this->sgqaNaming)
  1483. {
  1484. $sq_name = substr($sq['jsVarName'],4) . '.NAOK';
  1485. }
  1486. else
  1487. {
  1488. $sq_name = $sq['varName'] . '.NAOK';
  1489. }
  1490. break;
  1491. case 'P': //Multiple choice with comments checkbox + text
  1492. if (!preg_match('/comment$/',$sq['varName'])) {
  1493. if ($this->sgqaNaming)
  1494. {
  1495. $sq_name = $sq['rowdivid'] . '.NAOK';
  1496. }
  1497. else
  1498. {
  1499. $sq_name = $sq['rowdivid'] . '.NAOK';
  1500. }
  1501. }
  1502. break;
  1503. default:
  1504. break;
  1505. }
  1506. if (!is_null($sq_name)) {
  1507. $sq_names[] = $sq_name;
  1508. }
  1509. }
  1510. if (count($sq_names) > 0) {
  1511. if (!isset($validationEqn[$questionNum]))
  1512. {
  1513. $validationEqn[$questionNum] = array();
  1514. }
  1515. $validationEqn[$questionNum][] = array(
  1516. 'qtype' => $type,
  1517. 'type' => 'min_answers',
  1518. 'class' => 'num_answers',
  1519. 'eqn' => 'if(is_empty('.$min_answers.'),1,(count(' . implode(', ', $sq_names) . ') >= (' . $min_answers . ')))',
  1520. 'qid' => $questionNum,
  1521. );
  1522. }
  1523. }
  1524. }
  1525. else
  1526. {
  1527. $min_answers='';
  1528. }
  1529. // max_answers
  1530. // Validation:= count(sq1,...,sqN) <= value (which could be an expression).
  1531. if (isset($qattr['max_answers']) && trim($qattr['max_answers']) != '')
  1532. {
  1533. $max_answers = $qattr['max_answers'];
  1534. if ($hasSubqs) {
  1535. $subqs = $qinfo['subqs'];
  1536. $sq_names = array();
  1537. foreach ($subqs as $sq) {
  1538. $sq_name = NULL;
  1539. switch ($type)
  1540. {
  1541. case '1': //Array (Flexible Labels) dual scale
  1542. if (substr($sq['varName'],-1,1) == '0')
  1543. {
  1544. if ($this->sgqaNaming)
  1545. {
  1546. $base = $sq['rowdivid']."#";
  1547. $sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
  1548. }
  1549. else
  1550. {
  1551. $base = substr($sq['varName'],0,-1);
  1552. $sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
  1553. }
  1554. }
  1555. break;
  1556. case ':': //ARRAY (Multi Flexi) 1 to 10
  1557. case ';': //ARRAY (Multi Flexi) Text
  1558. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  1559. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  1560. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  1561. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  1562. case 'F': //ARRAY (Flexible) - Row Format
  1563. case 'K': //MULTIPLE NUMERICAL QUESTION
  1564. case 'Q': //MULTIPLE SHORT TEXT
  1565. case 'M': //Multiple choice checkbox
  1566. case 'R': //RANKING STYLE
  1567. if ($this->sgqaNaming)
  1568. {
  1569. $sq_name = substr($sq['jsVarName'],4) . '.NAOK';
  1570. }
  1571. else
  1572. {
  1573. $sq_name = $sq['varName'] . '.NAOK';
  1574. }
  1575. break;
  1576. case 'P': //Multiple choice with comments checkbox + text
  1577. if (!preg_match('/comment$/',$sq['varName'])) {
  1578. if ($this->sgqaNaming)
  1579. {
  1580. $sq_name = $sq['rowdivid'] . '.NAOK';
  1581. }
  1582. else
  1583. {
  1584. $sq_name = $sq['varName'] . '.NAOK';
  1585. }
  1586. }
  1587. break;
  1588. default:
  1589. break;
  1590. }
  1591. if (!is_null($sq_name)) {
  1592. $sq_names[] = $sq_name;
  1593. }
  1594. }
  1595. if (count($sq_names) > 0) {
  1596. if (!isset($validationEqn[$questionNum]))
  1597. {
  1598. $validationEqn[$questionNum] = array();
  1599. }
  1600. $validationEqn[$questionNum][] = array(
  1601. 'qtype' => $type,
  1602. 'type' => 'max_answers',
  1603. 'class' => 'num_answers',
  1604. 'eqn' => '(if(is_empty('.$max_answers.'),1,count(' . implode(', ', $sq_names) . ') <= (' . $max_answers . ')))',
  1605. 'qid' => $questionNum,
  1606. );
  1607. }
  1608. }
  1609. }
  1610. else
  1611. {
  1612. $max_answers='';
  1613. }
  1614. // min_num_value_n
  1615. // Validation:= N >= value (which could be an expression).
  1616. if (isset($qattr['min_num_value_n']) && trim($qattr['min_num_value_n']) != '')
  1617. {
  1618. $min_num_value_n = $qattr['min_num_value_n'];
  1619. if ($hasSubqs) {
  1620. $subqs = $qinfo['subqs'];
  1621. $sq_names = array();
  1622. $subqValidEqns = array();
  1623. foreach ($subqs as $sq) {
  1624. $sq_name = NULL;
  1625. switch ($type)
  1626. {
  1627. case 'K': //MULTIPLE NUMERICAL QUESTION
  1628. if ($this->sgqaNaming)
  1629. {
  1630. if(($qinfo['mandatory']=='Y')){
  1631. $sq_name = '('. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
  1632. }else{
  1633. $sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
  1634. }
  1635. }
  1636. else
  1637. {
  1638. if(($qinfo['mandatory']=='Y')){
  1639. $sq_name = '('. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
  1640. }else{
  1641. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
  1642. }
  1643. }
  1644. $subqValidSelector = $sq['jsVarName_on'];
  1645. break;
  1646. case 'N': //NUMERICAL QUESTION TYPE
  1647. if ($this->sgqaNaming)
  1648. {
  1649. if(($qinfo['mandatory']=='Y')){
  1650. $sq_name = '('. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
  1651. }else{
  1652. $sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
  1653. }
  1654. }
  1655. else
  1656. {
  1657. if(($qinfo['mandatory']=='Y')){
  1658. $sq_name = '('. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
  1659. }else{
  1660. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
  1661. }
  1662. }
  1663. $subqValidSelector = '';
  1664. break;
  1665. default:
  1666. break;
  1667. }
  1668. if (!is_null($sq_name)) {
  1669. $sq_names[] = $sq_name;
  1670. $subqValidEqns[$subqValidSelector] = array(
  1671. 'subqValidEqn' => $sq_name,
  1672. 'subqValidSelector' => $subqValidSelector,
  1673. );
  1674. }
  1675. }
  1676. if (count($sq_names) > 0) {
  1677. if (!isset($validationEqn[$questionNum]))
  1678. {
  1679. $validationEqn[$questionNum] = array();
  1680. }
  1681. $validationEqn[$questionNum][] = array(
  1682. 'qtype' => $type,
  1683. 'type' => 'min_num_value_n',
  1684. 'class' => 'value_range',
  1685. 'eqn' => implode(' && ', $sq_names),
  1686. 'qid' => $questionNum,
  1687. 'subqValidEqns' => $subqValidEqns,
  1688. );
  1689. }
  1690. }
  1691. }
  1692. else
  1693. {
  1694. $min_num_value_n='';
  1695. }
  1696. // max_num_value_n
  1697. // Validation:= N <= value (which could be an expression).
  1698. if (isset($qattr['max_num_value_n']) && trim($qattr['max_num_value_n']) != '')
  1699. {
  1700. $max_num_value_n = $qattr['max_num_value_n'];
  1701. if ($hasSubqs) {
  1702. $subqs = $qinfo['subqs'];
  1703. $sq_names = array();
  1704. $subqValidEqns = array();
  1705. foreach ($subqs as $sq) {
  1706. $sq_name = NULL;
  1707. switch ($type)
  1708. {
  1709. case 'K': //MULTIPLE NUMERICAL QUESTION
  1710. if ($this->sgqaNaming)
  1711. {
  1712. $sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK <= (' . $max_num_value_n . '))';
  1713. }
  1714. else
  1715. {
  1716. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK <= (' . $max_num_value_n . '))';
  1717. }
  1718. $subqValidSelector = $sq['jsVarName_on'];
  1719. break;
  1720. case 'N': //NUMERICAL QUESTION TYPE
  1721. if ($this->sgqaNaming)
  1722. {
  1723. $sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK <= (' . $max_num_value_n . '))';
  1724. }
  1725. else
  1726. {
  1727. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK <= (' . $max_num_value_n . '))';
  1728. }
  1729. $subqValidSelector = '';
  1730. break;
  1731. default:
  1732. break;
  1733. }
  1734. if (!is_null($sq_name)) {
  1735. $sq_names[] = $sq_name;
  1736. $subqValidEqns[$subqValidSelector] = array(
  1737. 'subqValidEqn' => $sq_name,
  1738. 'subqValidSelector' => $subqValidSelector,
  1739. );
  1740. }
  1741. }
  1742. if (count($sq_names) > 0) {
  1743. if (!isset($validationEqn[$questionNum]))
  1744. {
  1745. $validationEqn[$questionNum] = array();
  1746. }
  1747. $validationEqn[$questionNum][] = array(
  1748. 'qtype' => $type,
  1749. 'type' => 'max_num_value_n',
  1750. 'class' => 'value_range',
  1751. 'eqn' => implode(' && ', $sq_names),
  1752. 'qid' => $questionNum,
  1753. 'subqValidEqns' => $subqValidEqns,
  1754. );
  1755. }
  1756. }
  1757. }
  1758. else
  1759. {
  1760. $max_num_value_n='';
  1761. }
  1762. // min_num_value
  1763. // Validation:= sum(sq1,...,sqN) >= value (which could be an expression).
  1764. if (isset($qattr['min_num_value']) && trim($qattr['min_num_value']) != '')
  1765. {
  1766. $min_num_value = $qattr['min_num_value'];
  1767. if ($hasSubqs) {
  1768. $subqs = $qinfo['subqs'];
  1769. $sq_names = array();
  1770. foreach ($subqs as $sq) {
  1771. $sq_name = NULL;
  1772. switch ($type)
  1773. {
  1774. case 'K': //MULTIPLE NUMERICAL QUESTION
  1775. if ($this->sgqaNaming)
  1776. {
  1777. $sq_name = $sq['rowdivid'] . '.NAOK';
  1778. }
  1779. else
  1780. {
  1781. $sq_name = $sq['varName'] . '.NAOK';
  1782. }
  1783. break;
  1784. default:
  1785. break;
  1786. }
  1787. if (!is_null($sq_name)) {
  1788. $sq_names[] = $sq_name;
  1789. }
  1790. }
  1791. if (count($sq_names) > 0) {
  1792. if (!isset($validationEqn[$questionNum]))
  1793. {
  1794. $validationEqn[$questionNum] = array();
  1795. }
  1796. $sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
  1797. $precision = LEM_DEFAULT_PRECISION;
  1798. if (!is_null($precision))
  1799. {
  1800. $sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
  1801. }
  1802. $noanswer_option = '';
  1803. if ($value_range_allows_missing)
  1804. {
  1805. $noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
  1806. }
  1807. $validationEqn[$questionNum][] = array(
  1808. 'qtype' => $type,
  1809. 'type' => 'min_num_value',
  1810. 'class' => 'sum_range',
  1811. 'eqn' => '(sum(' . implode(', ', $sq_names) . ') >= (' . $min_num_value . ')' . $noanswer_option . ')',
  1812. 'qid' => $questionNum,
  1813. 'sumEqn' => $sumEqn,
  1814. );
  1815. }
  1816. }
  1817. }
  1818. else
  1819. {
  1820. $min_num_value='';
  1821. }
  1822. // max_num_value
  1823. // Validation:= sum(sq1,...,sqN) <= value (which could be an expression).
  1824. if (isset($qattr['max_num_value']) && trim($qattr['max_num_value']) != '')
  1825. {
  1826. $max_num_value = $qattr['max_num_value'];
  1827. if ($hasSubqs) {
  1828. $subqs = $qinfo['subqs'];
  1829. $sq_names = array();
  1830. foreach ($subqs as $sq) {
  1831. $sq_name = NULL;
  1832. switch ($type)
  1833. {
  1834. case 'K': //MULTIPLE NUMERICAL QUESTION
  1835. if ($this->sgqaNaming)
  1836. {
  1837. $sq_name = $sq['rowdivid'] . '.NAOK';
  1838. }
  1839. else
  1840. {
  1841. $sq_name = $sq['varName'] . '.NAOK';
  1842. }
  1843. break;
  1844. default:
  1845. break;
  1846. }
  1847. if (!is_null($sq_name)) {
  1848. $sq_names[] = $sq_name;
  1849. }
  1850. }
  1851. if (count($sq_names) > 0) {
  1852. if (!isset($validationEqn[$questionNum]))
  1853. {
  1854. $validationEqn[$questionNum] = array();
  1855. }
  1856. $sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
  1857. $precision = LEM_DEFAULT_PRECISION;
  1858. if (!is_null($precision))
  1859. {
  1860. $sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
  1861. }
  1862. $noanswer_option = '';
  1863. if ($value_range_allows_missing)
  1864. {
  1865. $noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
  1866. }
  1867. $validationEqn[$questionNum][] = array(
  1868. 'qtype' => $type,
  1869. 'type' => 'max_num_value',
  1870. 'class' => 'sum_range',
  1871. 'eqn' => '(sum(' . implode(', ', $sq_names) . ') <= (' . $max_num_value . ')' . $noanswer_option . ')',
  1872. 'qid' => $questionNum,
  1873. 'sumEqn' => $sumEqn,
  1874. );
  1875. }
  1876. }
  1877. }
  1878. else
  1879. {
  1880. $max_num_value='';
  1881. }
  1882. // multiflexible_min
  1883. // Validation:= sqN >= value (which could be an expression).
  1884. if (isset($qattr['multiflexible_min']) && trim($qattr['multiflexible_min']) != '' && $input_boxes=='1')
  1885. {
  1886. $multiflexible_min = $qattr['multiflexible_min'];
  1887. if ($hasSubqs) {
  1888. $subqs = $qinfo['subqs'];
  1889. $sq_names = array();
  1890. $subqValidEqns = array();
  1891. foreach ($subqs as $sq) {
  1892. $sq_name = NULL;
  1893. switch ($type)
  1894. {
  1895. case ':': //MULTIPLE NUMERICAL QUESTION
  1896. if ($this->sgqaNaming)
  1897. {
  1898. $sgqa = substr($sq['jsVarName'],4);
  1899. $sq_name = '(is_empty(' . $sgqa . '.NAOK) || ' . $sgqa . '.NAOK >= (' . $multiflexible_min . '))';
  1900. }
  1901. else
  1902. {
  1903. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || ' . $sq['varName'] . '.NAOK >= (' . $multiflexible_min . '))';
  1904. }
  1905. $subqValidSelector = $sq['jsVarName_on'];
  1906. break;
  1907. default:
  1908. break;
  1909. }
  1910. if (!is_null($sq_name)) {
  1911. $sq_names[] = $sq_name;
  1912. $subqValidEqns[$subqValidSelector] = array(
  1913. 'subqValidEqn' => $sq_name,
  1914. 'subqValidSelector' => $subqValidSelector,
  1915. );
  1916. }
  1917. }
  1918. if (count($sq_names) > 0) {
  1919. if (!isset($validationEqn[$questionNum]))
  1920. {
  1921. $validationEqn[$questionNum] = array();
  1922. }
  1923. $validationEqn[$questionNum][] = array(
  1924. 'qtype' => $type,
  1925. 'type' => 'multiflexible_min',
  1926. 'class' => 'value_range',
  1927. 'eqn' => implode(' && ', $sq_names),
  1928. 'qid' => $questionNum,
  1929. 'subqValidEqns' => $subqValidEqns,
  1930. );
  1931. }
  1932. }
  1933. }
  1934. else
  1935. {
  1936. $multiflexible_min='';
  1937. }
  1938. // multiflexible_max
  1939. // Validation:= sqN <= value (which could be an expression).
  1940. if (isset($qattr['multiflexible_max']) && trim($qattr['multiflexible_max']) != '' && $input_boxes=='1')
  1941. {
  1942. $multiflexible_max = $qattr['multiflexible_max'];
  1943. if ($hasSubqs) {
  1944. $subqs = $qinfo['subqs'];
  1945. $sq_names = array();
  1946. $subqValidEqns = array();
  1947. foreach ($subqs as $sq) {
  1948. $sq_name = NULL;
  1949. switch ($type)
  1950. {
  1951. case ':': //MULTIPLE NUMERICAL QUESTION
  1952. if ($this->sgqaNaming)
  1953. {
  1954. $sgqa = substr($sq['jsVarName'],4);
  1955. $sq_name = '(is_empty(' . $sgqa . '.NAOK) || ' . $sgqa . '.NAOK <= (' . $multiflexible_max . '))';
  1956. }
  1957. else
  1958. {
  1959. $sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || ' . $sq['varName'] . '.NAOK <= (' . $multiflexible_max . '))';
  1960. }
  1961. $subqValidSelector = $sq['jsVarName_on'];
  1962. break;
  1963. default:
  1964. break;
  1965. }
  1966. if (!is_null($sq_name)) {
  1967. $sq_names[] = $sq_name;
  1968. $subqValidEqns[$subqValidSelector] = array(
  1969. 'subqValidEqn' => $sq_name,
  1970. 'subqValidSelector' => $subqValidSelector,
  1971. );
  1972. }
  1973. }
  1974. if (count($sq_names) > 0) {
  1975. if (!isset($validationEqn[$questionNum]))
  1976. {
  1977. $validationEqn[$questionNum] = array();
  1978. }
  1979. $validationEqn[$questionNum][] = array(
  1980. 'qtype' => $type,
  1981. 'type' => 'multiflexible_max',
  1982. 'class' => 'value_range',
  1983. 'eqn' => implode(' && ', $sq_names),
  1984. 'qid' => $questionNum,
  1985. 'subqValidEqns' => $subqValidEqns,
  1986. );
  1987. }
  1988. }
  1989. }
  1990. else
  1991. {
  1992. $multiflexible_max='';
  1993. }
  1994. // min_num_of_files
  1995. // Validation:= sq_filecount >= value (which could be an expression).
  1996. if (isset($qattr['min_num_of_files']) && trim($qattr['min_num_of_files']) != '')
  1997. {
  1998. $min_num_of_files = $qattr['min_num_of_files'];
  1999. $eqn='';
  2000. $sgqa = $qinfo['sgqa'];
  2001. switch ($type)
  2002. {
  2003. case '|': //List - dropdown
  2004. $eqn = "(" . $sgqa . "_filecount >= (" . $min_num_of_files . "))";
  2005. break;
  2006. default:
  2007. break;
  2008. }
  2009. if ($eqn != '')
  2010. {
  2011. if (!isset($validationEqn[$questionNum]))
  2012. {
  2013. $validationEqn[$questionNum] = array();
  2014. }
  2015. $validationEqn[$questionNum][] = array(
  2016. 'qtype' => $type,
  2017. 'type' => 'min_num_of_files',
  2018. 'class' => 'num_answers',
  2019. 'eqn' => $eqn,
  2020. 'qid' => $questionNum,
  2021. );
  2022. }
  2023. }
  2024. else
  2025. {
  2026. $min_num_of_files = '';
  2027. }
  2028. // max_num_of_files
  2029. // Validation:= sq_filecount <= value (which could be an expression).
  2030. if (isset($qattr['max_num_of_files']) && trim($qattr['max_num_of_files']) != '')
  2031. {
  2032. $max_num_of_files = $qattr['max_num_of_files'];
  2033. $eqn='';
  2034. $sgqa = $qinfo['sgqa'];
  2035. switch ($type)
  2036. {
  2037. case '|': //List - dropdown
  2038. $eqn = "(" . $sgqa . "_filecount <= (" . $max_num_of_files . "))";
  2039. break;
  2040. default:
  2041. break;
  2042. }
  2043. if ($eqn != '')
  2044. {
  2045. if (!isset($validationEqn[$questionNum]))
  2046. {
  2047. $validationEqn[$questionNum] = array();
  2048. }
  2049. $validationEqn[$questionNum][] = array(
  2050. 'qtype' => $type,
  2051. 'type' => 'max_num_of_files',
  2052. 'class' => 'num_answers',
  2053. 'eqn' => $eqn,
  2054. 'qid' => $questionNum,
  2055. );
  2056. }
  2057. }
  2058. else
  2059. {
  2060. $max_num_of_files = '';
  2061. }
  2062. // other_comment_mandatory
  2063. // Validation:= sqN <= value (which could be an expression).
  2064. if (isset($qattr['other_comment_mandatory']) && trim($qattr['other_comment_mandatory']) == '1')
  2065. {
  2066. $other_comment_mandatory = $qattr['other_comment_mandatory'];
  2067. $eqn='';
  2068. if ($other_comment_mandatory == '1' && $this->questionSeq2relevance[$qinfo['qseq']]['other'] == 'Y')
  2069. {
  2070. $sgqa = $qinfo['sgqa'];
  2071. switch ($type)
  2072. {
  2073. case '!': //List - dropdown
  2074. case 'L': //LIST drop-down/radio-button list
  2075. $eqn = "(" . $sgqa . ".NAOK!='-oth-' || (" . $sgqa . ".NAOK=='-oth-' && !is_empty(trim(" . $sgqa . "other.NAOK))))";
  2076. break;
  2077. case 'P': //Multiple choice with comments checkbox + text
  2078. $eqn = "(is_empty(trim(" . $sgqa . "other.NAOK)) || (!is_empty(trim(" . $sgqa . "other.NAOK)) && !is_empty(trim(" . $sgqa . "othercomment.NAOK))))";
  2079. break;
  2080. default:
  2081. break;
  2082. }
  2083. }
  2084. if ($eqn != '')
  2085. {
  2086. if (!isset($validationEqn[$questionNum]))
  2087. {
  2088. $validationEqn[$questionNum] = array();
  2089. }
  2090. $validationEqn[$questionNum][] = array(
  2091. 'qtype' => $type,
  2092. 'type' => 'other_comment_mandatory',
  2093. 'class' => 'other_comment_mandatory',
  2094. 'eqn' => $eqn,
  2095. 'qid' => $questionNum,
  2096. );
  2097. }
  2098. }
  2099. else
  2100. {
  2101. $other_comment_mandatory = '';
  2102. }
  2103. // show_totals
  2104. // TODO - create equations for these?
  2105. // assessment_value
  2106. // TODO? How does it work?
  2107. // The assessment value (referenced how?) = count(sq1,...,sqN) * assessment_value
  2108. // Since there are easy work-arounds to this, skipping it for now
  2109. // preg - a PHP Regular Expression to validate text input fields
  2110. if (isset($qinfo['preg']) && !is_null($qinfo['preg']))
  2111. {
  2112. $preg = $qinfo['preg'];
  2113. if ($hasSubqs) {
  2114. $subqs = $qinfo['subqs'];
  2115. $sq_names = array();
  2116. $subqValidEqns = array();
  2117. foreach ($subqs as $sq) {
  2118. $sq_name = NULL;
  2119. $subqValidSelector=NULL;
  2120. $sgqa = substr($sq['jsVarName'],4);
  2121. switch ($type)
  2122. {
  2123. case 'N': //NUMERICAL QUESTION TYPE
  2124. case 'K': //MULTIPLE NUMERICAL QUESTION
  2125. case 'Q': //MULTIPLE SHORT TEXT
  2126. case ';': //ARRAY (Multi Flexi) Text
  2127. case ':': //ARRAY (Multi Flexi) 1 to 10
  2128. case 'S': //SHORT FREE TEXT
  2129. case 'T': //LONG FREE TEXT
  2130. case 'U': //HUGE FREE TEXT
  2131. if ($this->sgqaNaming)
  2132. {
  2133. $sq_name = '(if(is_empty('.$sgqa.'.NAOK),0,!regexMatch("' . $preg . '", ' . $sgqa . '.NAOK)))';
  2134. }
  2135. else
  2136. {
  2137. $sq_name = '(if(is_empty('.$sq['varName'].'.NAOK),0,!regexMatch("' . $preg . '", ' . $sq['varName'] . '.NAOK)))';
  2138. }
  2139. break;
  2140. default:
  2141. break;
  2142. }
  2143. switch ($type)
  2144. {
  2145. case 'K': //MULTIPLE NUMERICAL QUESTION
  2146. case 'Q': //MULTIPLE SHORT TEXT
  2147. case ';': //ARRAY (Multi Flexi) Text
  2148. case ':': //ARRAY (Multi Flexi) 1 to 10
  2149. if ($this->sgqaNaming)
  2150. {
  2151. $subqValidEqn = '(is_empty('.$sgqa.'.NAOK) || regexMatch("' . $preg . '", ' . $sgqa . '.NAOK))';
  2152. }
  2153. else
  2154. {
  2155. $subqValidEqn = '(is_empty('.$sq['varName'].'.NAOK) || regexMatch("' . $preg . '", ' . $sq['varName'] . '.NAOK))';
  2156. }
  2157. $subqValidSelector = $sq['jsVarName_on'];
  2158. break;
  2159. default:
  2160. break;
  2161. }
  2162. if (!is_null($sq_name)) {
  2163. $sq_names[] = $sq_name;
  2164. if (isset($subqValidSelector)) {
  2165. $subqValidEqns[$subqValidSelector] = array(
  2166. 'subqValidEqn' => $subqValidEqn,
  2167. 'subqValidSelector' => $subqValidSelector,
  2168. );
  2169. }
  2170. }
  2171. }
  2172. if (count($sq_names) > 0) {
  2173. if (!isset($validationEqn[$questionNum]))
  2174. {
  2175. $validationEqn[$questionNum] = array();
  2176. }
  2177. $validationEqn[$questionNum][] = array(
  2178. 'qtype' => $type,
  2179. 'type' => 'preg',
  2180. 'class' => 'regex_validation',
  2181. 'eqn' => '(sum(' . implode(', ', $sq_names) . ') == 0)',
  2182. 'qid' => $questionNum,
  2183. 'subqValidEqns' => $subqValidEqns,
  2184. );
  2185. }
  2186. }
  2187. }
  2188. else
  2189. {
  2190. $preg='';
  2191. }
  2192. // em_validation_q_tip - a description of the EM validation equation that must be satisfied for the whole question.
  2193. if (isset($qattr['em_validation_q_tip']) && !is_null($qattr['em_validation_q_tip']) && trim($qattr['em_validation_q_tip']) != '')
  2194. {
  2195. $em_validation_q_tip = trim($qattr['em_validation_q_tip']);
  2196. }
  2197. else
  2198. {
  2199. $em_validation_q_tip = '';
  2200. }
  2201. // em_validation_q - an EM validation equation that must be satisfied for the whole question. Uses 'this' in the equation
  2202. if (isset($qattr['em_validation_q']) && !is_null($qattr['em_validation_q']) && trim($qattr['em_validation_q']) != '')
  2203. {
  2204. $em_validation_q = $qattr['em_validation_q'];
  2205. if ($hasSubqs) {
  2206. $subqs = $qinfo['subqs'];
  2207. $sq_names = array();
  2208. foreach ($subqs as $sq) {
  2209. $sq_name = NULL;
  2210. switch ($type)
  2211. {
  2212. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  2213. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  2214. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  2215. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  2216. case 'F': //ARRAY (Flexible) - Row Format
  2217. case 'K': //MULTIPLE NUMERICAL QUESTION
  2218. case 'Q': //MULTIPLE SHORT TEXT
  2219. case ';': //ARRAY (Multi Flexi) Text
  2220. case ':': //ARRAY (Multi Flexi) 1 to 10
  2221. case 'M': //Multiple choice checkbox
  2222. case 'N': //NUMERICAL QUESTION TYPE
  2223. case 'P': //Multiple choice with comments checkbox + text
  2224. case 'R': //RANKING STYLE
  2225. case 'S': //SHORT FREE TEXT
  2226. case 'T': //LONG FREE TEXT
  2227. case 'U': //HUGE FREE TEXT
  2228. if ($this->sgqaNaming)
  2229. {
  2230. $sq_name = '!(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_q) . ')';
  2231. }
  2232. else
  2233. {
  2234. $sq_name = '!(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_q) . ')';
  2235. }
  2236. break;
  2237. default:
  2238. break;
  2239. }
  2240. if (!is_null($sq_name)) {
  2241. $sq_names[] = $sq_name;
  2242. }
  2243. }
  2244. if (count($sq_names) > 0) {
  2245. if (!isset($validationEqn[$questionNum]))
  2246. {
  2247. $validationEqn[$questionNum] = array();
  2248. }
  2249. $validationEqn[$questionNum][] = array(
  2250. 'qtype' => $type,
  2251. 'type' => 'em_validation_q',
  2252. 'class' => 'q_fn_validation',
  2253. 'eqn' => '(sum(' . implode(', ', array_unique($sq_names)) . ') == 0)',
  2254. 'qid' => $questionNum,
  2255. );
  2256. }
  2257. }
  2258. }
  2259. else
  2260. {
  2261. $em_validation_q='';
  2262. }
  2263. // em_validation_sq_tip - a description of the EM validation equation that must be satisfied for each subquestion.
  2264. if (isset($qattr['em_validation_sq_tip']) && !is_null($qattr['em_validation_sq_tip']) && trim($qattr['em_validation_sq']) != '')
  2265. {
  2266. $em_validation_sq_tip = trim($qattr['em_validation_sq_tip']);
  2267. }
  2268. else
  2269. {
  2270. $em_validation_sq_tip = '';
  2271. }
  2272. // em_validation_sq - an EM validation equation that must be satisfied for each subquestion. Uses 'this' in the equation
  2273. if (isset($qattr['em_validation_sq']) && !is_null($qattr['em_validation_sq']) && trim($qattr['em_validation_sq']) != '')
  2274. {
  2275. $em_validation_sq = $qattr['em_validation_sq'];
  2276. if ($hasSubqs) {
  2277. $subqs = $qinfo['subqs'];
  2278. $sq_names = array();
  2279. $subqValidEqns = array();
  2280. foreach ($subqs as $sq) {
  2281. $sq_name = NULL;
  2282. switch ($type)
  2283. {
  2284. case 'K': //MULTIPLE NUMERICAL QUESTION
  2285. case 'Q': //MULTIPLE SHORT TEXT
  2286. case ';': //ARRAY (Multi Flexi) Text
  2287. case ':': //ARRAY (Multi Flexi) 1 to 10
  2288. case 'N': //NUMERICAL QUESTION TYPE
  2289. case 'S': //SHORT FREE TEXT
  2290. case 'T': //LONG FREE TEXT
  2291. case 'U': //HUGE FREE TEXT
  2292. if ($this->sgqaNaming)
  2293. {
  2294. $sq_name = '!(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_sq) . ')';
  2295. }
  2296. else
  2297. {
  2298. $sq_name = '!(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_sq) . ')';
  2299. }
  2300. break;
  2301. default:
  2302. break;
  2303. }
  2304. switch ($type)
  2305. {
  2306. case 'K': //MULTIPLE NUMERICAL QUESTION
  2307. case 'Q': //MULTIPLE SHORT TEXT
  2308. case ';': //ARRAY (Multi Flexi) Text
  2309. case ':': //ARRAY (Multi Flexi) 1 to 10
  2310. case 'N': //NUMERICAL QUESTION TYPE
  2311. case 'S': //SHORT FREE TEXT
  2312. case 'T': //LONG FREE TEXT
  2313. case 'U': //HUGE FREE TEXT
  2314. if ($this->sgqaNaming)
  2315. {
  2316. $subqValidEqn = '(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_sq) . ')';
  2317. }
  2318. else
  2319. {
  2320. $subqValidEqn = '(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_sq) . ')';
  2321. }
  2322. $subqValidSelector = $sq['jsVarName_on'];
  2323. break;
  2324. default:
  2325. break;
  2326. }
  2327. if (!is_null($sq_name)) {
  2328. $sq_names[] = $sq_name;
  2329. if (isset($subqValidSelector)) {
  2330. $subqValidEqns[$subqValidSelector] = array(
  2331. 'subqValidEqn' => $subqValidEqn,
  2332. 'subqValidSelector' => $subqValidSelector,
  2333. );
  2334. }
  2335. }
  2336. }
  2337. if (count($sq_names) > 0) {
  2338. if (!isset($validationEqn[$questionNum]))
  2339. {
  2340. $validationEqn[$questionNum] = array();
  2341. }
  2342. $validationEqn[$questionNum][] = array(
  2343. 'qtype' => $type,
  2344. 'type' => 'em_validation_sq',
  2345. 'class' => 'sq_fn_validation',
  2346. 'eqn' => '(sum(' . implode(', ', $sq_names) . ') == 0)',
  2347. 'qid' => $questionNum,
  2348. 'subqValidEqns' => $subqValidEqns,
  2349. );
  2350. }
  2351. }
  2352. }
  2353. else
  2354. {
  2355. $em_validation_sq='';
  2356. }
  2357. ////////////////////////////////////////////
  2358. // COMPOSE USER FRIENDLY MIN/MAX MESSAGES //
  2359. ////////////////////////////////////////////
  2360. // Put these in the order you with them to appear in messages.
  2361. $qtips=array();
  2362. // min/max answers
  2363. if ($min_answers!='' || $max_answers!='')
  2364. {
  2365. $_minA = (($min_answers == '') ? "''" : $min_answers);
  2366. $_maxA = (($max_answers == '') ? "''" : $max_answers );
  2367. $qtips['num_answers']=
  2368. "{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)!=1,sprintf('".$this->gT("Please select at least %s answers")."',fixnum($_minA)),'')}" .
  2369. "{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)==1,sprintf('".$this->gT("Please select at least one answer")."',fixnum($_minA)),'')}" .
  2370. "{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)!=1,sprintf('".$this->gT("Please select at most %s answers")."',fixnum($_maxA)),'')}" .
  2371. "{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)==1,sprintf('".$this->gT("Please select at most one answer")."',fixnum($_maxA)),'')}" .
  2372. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) == 1,'".$this->gT("Please select one answer")."','')}" .
  2373. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) != 1,sprintf('".$this->gT("Please select %s answers")."',fixnum($_minA)),'')}" .
  2374. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) != ($_maxA),sprintf('".$this->gT("Please select between %s and %s answers")."',fixnum($_minA),fixnum($_maxA)),'')}";
  2375. }
  2376. // min/max value for each numeric entry
  2377. if ($min_num_value_n!='' || $max_num_value_n!='')
  2378. {
  2379. $_minV = (($min_num_value_n == '') ? "''" : $min_num_value_n);
  2380. $_maxV = (($max_num_value_n == '') ? "''" : $max_num_value_n);
  2381. $qtips['value_range']=
  2382. "{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("Each answer must be at least %s")."',fixnum($_minV)), '')}" .
  2383. "{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("Each answer must be at most %s")."',fixnum($_maxV)), '')}" .
  2384. "{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("Each answer must be %s")."', fixnum($_minV)), '')}" .
  2385. "{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("Each answer must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
  2386. }
  2387. // min/max value for each numeric entry - for multi-flexible question type
  2388. if ($multiflexible_min!='' || $multiflexible_max!='')
  2389. {
  2390. $_minV = (($multiflexible_min == '') ? "''" : $multiflexible_min);
  2391. $_maxV = (($multiflexible_max == '') ? "''" : $multiflexible_max);
  2392. $qtips['value_range']=
  2393. "{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("Each answer must be at least %s")."',fixnum($_minV)), '')}" .
  2394. "{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("Each answer must be at most %s")."',fixnum($_maxV)), '')}" .
  2395. "{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("Each answer must be %s")."', fixnum($_minV)), '')}" .
  2396. "{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("Each answer must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
  2397. }
  2398. // min/max sum value
  2399. if ($min_num_value!='' || $max_num_value!='')
  2400. {
  2401. $_minV = (($min_num_value == '') ? "''" : $min_num_value);
  2402. $_maxV = (($max_num_value == '') ? "''" : $max_num_value);
  2403. $qtips['sum_range']=
  2404. "{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("The sum must be at least %s")."',fixnum($_minV)), '')}" .
  2405. "{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("The sum must be at most %s")."',fixnum($_maxV)), '')}" .
  2406. "{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("The sum must equal %s")."', fixnum($_minV)), '')}" .
  2407. "{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("The sum must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
  2408. }
  2409. // min/max num files
  2410. if ($min_num_of_files !='' || $max_num_of_files !='')
  2411. {
  2412. $_minA = (($min_num_of_files == '') ? "''" : $min_num_of_files);
  2413. $_maxA = (($max_num_of_files == '') ? "''" : $max_num_of_files );
  2414. // TODO - create em_num_files class so can sepately style num_files vs. num_answers
  2415. $qtips['num_answers']=
  2416. "{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)!=1,sprintf('".$this->gT("Please upload at least %s files")."',fixnum($_minA)),'')}" .
  2417. "{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)==1,sprintf('".$this->gT("Please upload at least one file")."',fixnum($_minA)),'')}" .
  2418. "{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)!=1,sprintf('".$this->gT("Please upload at most %s files")."',fixnum($_maxA)),'')}" .
  2419. "{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)==1,sprintf('".$this->gT("Please upload at most one file")."',fixnum($_maxA)),'')}" .
  2420. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) == 1,'".$this->gT("Please upload one file")."','')}" .
  2421. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) != 1,sprintf('".$this->gT("Please upload %s files")."',fixnum($_minA)),'')}" .
  2422. "{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) != ($_maxA),sprintf('".$this->gT("Please upload between %s and %s files")."',fixnum($_minA),fixnum($_maxA)),'')}";
  2423. }
  2424. // equals_num_value
  2425. if ($equals_num_value!='')
  2426. {
  2427. $qtips['sum_range']=sprintf($this->gT("The sum must equal %s."),'{fixnum('.$equals_num_value.')}');
  2428. }
  2429. // other comment mandatory
  2430. if ($other_comment_mandatory!='')
  2431. {
  2432. if (isset($qattr['other_replace_text']) && trim($qattr['other_replace_text']) != '') {
  2433. $othertext = trim($qattr['other_replace_text']);
  2434. }
  2435. else {
  2436. $othertext = $this->gT('Other:');
  2437. }
  2438. $qtips['other_comment_mandatory']=sprintf($this->gT("If you choose '%s' please also specify your choice in the accompanying text field."),$othertext);
  2439. }
  2440. // regular expression validation
  2441. if ($preg!='')
  2442. {
  2443. // do string replacement here so that curly braces within the regular expression don't trigger an EM error
  2444. // $qtips['regex_validation']=sprintf($this->gT('Each answer must conform to this regular expression: %s'), str_replace(array('{','}'),array('{ ',' }'), $preg));
  2445. $qtips['regex_validation']=$this->gT('Please check the format of your answer.');
  2446. }
  2447. if ($em_validation_sq!='')
  2448. {
  2449. if ($em_validation_sq_tip =='')
  2450. {
  2451. // $stringToParse = htmlspecialchars_decode($em_validation_sq,ENT_QUOTES);
  2452. // $gseq = $this->questionId2groupSeq[$qinfo['qid']];
  2453. // $result = $this->em->ProcessBooleanExpression($stringToParse,$gseq, $qinfo['qseq']);
  2454. // $_validation_tip = $this->em->GetPrettyPrintString();
  2455. // $qtips['sq_fn_validation']=sprintf($this->gT('Each answer must conform to this expression: %s'),$_validation_tip);
  2456. }
  2457. else
  2458. {
  2459. $qtips['sq_fn_validation']=$em_validation_sq_tip;
  2460. }
  2461. }
  2462. // em_validation_q - whole-question validation equation
  2463. if ($em_validation_q!='')
  2464. {
  2465. if ($em_validation_q_tip =='')
  2466. {
  2467. // $stringToParse = htmlspecialchars_decode($em_validation_q,ENT_QUOTES);
  2468. // $gseq = $this->questionId2groupSeq[$qinfo['qid']];
  2469. // $result = $this->em->ProcessBooleanExpression($stringToParse,$gseq, $qinfo['qseq']);
  2470. // $_validation_tip = $this->em->GetPrettyPrintString();
  2471. // $qtips['q_fn_validation']=sprintf($this->gT('The question must conform to this expression: %s'), $_validation_tip);
  2472. }
  2473. else
  2474. {
  2475. $qtips['q_fn_validation']=$em_validation_q_tip;
  2476. }
  2477. }
  2478. if (count($qtips) > 0)
  2479. {
  2480. $validationTips[$questionNum] = $qtips;
  2481. }
  2482. }
  2483. // Consolidate logic across array filters
  2484. $rowdivids = array();
  2485. $order=0;
  2486. foreach ($subQrels as $sq)
  2487. {
  2488. $oldeqn = (isset($rowdivids[$sq['rowdivid']]['eqns']) ? $rowdivids[$sq['rowdivid']]['eqns'] : array());
  2489. $oldtype = (isset($rowdivids[$sq['rowdivid']]['type']) ? $rowdivids[$sq['rowdivid']]['type'] : '');
  2490. $neweqn = (($sq['type'] == 'exclude_all_others') ? array() : array($sq['eqn']));
  2491. $oldeo = (isset($rowdivids[$sq['rowdivid']]['exclusive_options']) ? $rowdivids[$sq['rowdivid']]['exclusive_options'] : array());
  2492. $neweo = (($sq['type'] == 'exclude_all_others') ? array($sq['eqn']) : array());
  2493. $rowdivids[$sq['rowdivid']] = array(
  2494. 'order'=>$order++,
  2495. 'qid'=>$sq['qid'],
  2496. 'rowdivid'=>$sq['rowdivid'],
  2497. 'type'=>$sq['type'] . ';' . $oldtype,
  2498. 'qtype'=>$sq['qtype'],
  2499. 'sgqa'=>$sq['sgqa'],
  2500. 'eqns'=>array_merge($oldeqn, $neweqn),
  2501. 'exclusive_options'=>array_merge($oldeo, $neweo),
  2502. );
  2503. }
  2504. foreach ($rowdivids as $sq)
  2505. {
  2506. $sq['eqn'] = implode(' and ', array_unique(array_merge($sq['eqns'],$sq['exclusive_options']))); // without array_unique, get duplicate of filters for question types 1, :, and ;
  2507. $eos = array_unique($sq['exclusive_options']);
  2508. $isExclusive = '';
  2509. $irrelevantAndExclusive = '';
  2510. if (count($eos) > 0)
  2511. {
  2512. $isExclusive = '!(' . implode(' and ', $eos) . ')';
  2513. $noneos = array_unique($sq['eqns']);
  2514. if (count($noneos) > 0)
  2515. {
  2516. $irrelevantAndExclusive = '(' . implode(' and ', $noneos) . ') and ' . $isExclusive;
  2517. }
  2518. }
  2519. $this->_ProcessSubQRelevance($sq['eqn'], $sq['qid'], $sq['rowdivid'], $sq['type'], $sq['qtype'], $sq['sgqa'], $isExclusive, $irrelevantAndExclusive);
  2520. }
  2521. foreach ($validationEqn as $qid=>$eqns)
  2522. {
  2523. $parts = array();
  2524. $tips = (isset($validationTips[$qid]) ? $validationTips[$qid] : array());
  2525. $subqValidEqns = array();
  2526. $sumEqn = '';
  2527. $sumRemainingEqn = '';
  2528. foreach ($eqns as $v) {
  2529. if (!isset($parts[$v['class']]))
  2530. {
  2531. $parts[$v['class']] = array();
  2532. }
  2533. $parts[$v['class']][] = $v['eqn'];
  2534. // even if there are min/max/preg, the count or total will always be the same
  2535. $sumEqn = (isset($v['sumEqn'])) ? $v['sumEqn'] : $sumEqn;
  2536. $sumRemainingEqn = (isset($v['sumRemainingEqn'])) ? $v['sumRemainingEqn'] : $sumRemainingEqn;
  2537. if (isset($v['subqValidEqns'])) {
  2538. $subqValidEqns[] = $v['subqValidEqns'];
  2539. }
  2540. }
  2541. // combine the sub-question level validation equations into a single validation equation per sub-question
  2542. $subqValidComposite = array();
  2543. foreach ($subqValidEqns as $sqs) {
  2544. foreach ($sqs as $sq)
  2545. {
  2546. if (!isset($subqValidComposite[$sq['subqValidSelector']]))
  2547. {
  2548. $subqValidComposite[$sq['subqValidSelector']] = array(
  2549. 'subqValidSelector' => $sq['subqValidSelector'],
  2550. 'subqValidEqns' => array(),
  2551. );
  2552. }
  2553. $subqValidComposite[$sq['subqValidSelector']]['subqValidEqns'][] = $sq['subqValidEqn'];
  2554. }
  2555. }
  2556. $csubqValidEqns = array();
  2557. foreach ($subqValidComposite as $csq)
  2558. {
  2559. $csubqValidEqns[$csq['subqValidSelector']] = array(
  2560. 'subqValidSelector' => $csq['subqValidSelector'],
  2561. 'subqValidEqn' => implode(' && ', $csq['subqValidEqns']),
  2562. );
  2563. }
  2564. // now combine all classes of validation equations
  2565. $veqns = array();
  2566. foreach ($parts as $vclass=>$eqns)
  2567. {
  2568. $veqns[$vclass] = '(' . implode(' and ', $eqns) . ')';
  2569. }
  2570. $this->qid2validationEqn[$qid] = array(
  2571. 'eqn' => $veqns,
  2572. 'tips' => $tips,
  2573. 'subqValidEqns' => $csubqValidEqns,
  2574. 'sumEqn' => $sumEqn,
  2575. 'sumRemainingEqn' => $sumRemainingEqn,
  2576. );
  2577. }
  2578. // $this->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  2579. }
  2580. /**
  2581. * Recursively find all questions that logically preceded the current array_filter or array_filter_exclude request
  2582. * Note, must support:
  2583. * (a) semicolon-separated list of $qroot codes for either array_filter or array_filter_exclude
  2584. * (b) mixed history of array_filter and array_filter_exclude values
  2585. * @param type $qroot - the question root variable name
  2586. * @param type $aflist - the list of array_filter $qroot codes
  2587. * @param type $afelist - the list of array_filter_exclude $qroot codes
  2588. * @return type
  2589. */
  2590. private function _recursivelyFindAntecdentArrayFilters($qroot, $aflist, $afelist)
  2591. {
  2592. if (isset($this->qrootVarName2arrayFilter[$qroot]))
  2593. {
  2594. if (isset($this->qrootVarName2arrayFilter[$qroot]['array_filter']))
  2595. {
  2596. $_afs = explode(';',$this->qrootVarName2arrayFilter[$qroot]['array_filter']);
  2597. foreach ($_afs as $_af)
  2598. {
  2599. if (in_array($_af,$aflist))
  2600. {
  2601. continue;
  2602. }
  2603. $aflist[] = $_af;
  2604. list($aflist, $afelist) = $this->_recursivelyFindAntecdentArrayFilters($_af, $aflist, $afelist);
  2605. }
  2606. }
  2607. if (isset($this->qrootVarName2arrayFilter[$qroot]['array_filter_exclude']))
  2608. {
  2609. $_afes = explode(';',$this->qrootVarName2arrayFilter[$qroot]['array_filter_exclude']);
  2610. foreach ($_afes as $_afe)
  2611. {
  2612. if (in_array($_afe,$afelist))
  2613. {
  2614. continue;
  2615. }
  2616. $afelist[] = $_afe;
  2617. list($aflist, $afelist) = $this->_recursivelyFindAntecdentArrayFilters($_afe, $aflist, $afelist);
  2618. }
  2619. }
  2620. }
  2621. return array($aflist, $afelist);
  2622. }
  2623. /**
  2624. * Create the arrays needed by ExpressionManager to process LimeSurvey strings.
  2625. * The long part of this function should only be called once per page display (e.g. only if $fieldMap changes)
  2626. *
  2627. * @param <integer> $surveyid
  2628. * @param <Boolean> $forceRefresh
  2629. * @param <Boolean> $anonymized
  2630. * @param <Boolean> $allOnOnePage - if true (like for survey_format), uses certain optimizations
  2631. * @return boolean - true if $fieldmap had been re-created, so ExpressionManager variables need to be re-set
  2632. */
  2633. private function setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh=false,$anonymized=false,$allOnOnePage=false)
  2634. {
  2635. if (isset($_SESSION['LEMforceRefresh'])) {
  2636. unset($_SESSION['LEMforceRefresh']);
  2637. $forceRefresh=true;
  2638. }
  2639. else if (!$forceRefresh && isset($this->knownVars)) {
  2640. return false; // means that those variables have been cached and no changes needed
  2641. }
  2642. $now = microtime(true);
  2643. $this->em->SetSurveyMode($this->surveyMode);
  2644. // TODO - do I need to force refresh, or trust that createFieldMap will cache langauges properly?
  2645. $fieldmap=createFieldMap($surveyid,$style='full',$forceRefresh,false,$_SESSION['LEMlang']);
  2646. $this->sid= $surveyid;
  2647. $this->runtimeTimings[] = array(__METHOD__ . '.createFieldMap',(microtime(true) - $now));
  2648. // LimeExpressionManager::ShowStackTrace();
  2649. $now = microtime(true);
  2650. if (!isset($fieldmap)) {
  2651. return false; // implies an error occurred
  2652. }
  2653. $this->knownVars = array(); // mapping of VarName to Value
  2654. $this->qcode2sgqa = array();
  2655. $this->tempVars = array();
  2656. $this->qid2code = array(); // List of codes for each question - needed to know which to NULL if a question is irrelevant
  2657. $this->jsVar2qid = array();
  2658. $this->qcode2sgq = array();
  2659. $this->alias2varName = array();
  2660. $this->varNameAttr = array();
  2661. $this->questionId2questionSeq = array();
  2662. $this->questionId2groupSeq = array();
  2663. $this->questionSeq2relevance = array();
  2664. $this->groupId2groupSeq = array();
  2665. $this->qid2validationEqn = array();
  2666. $this->groupSeqInfo = array();
  2667. $this->gseq2relevanceStatus = array();
  2668. // Since building array of allowable answers, need to know preset values for certain question types
  2669. $presets = array();
  2670. $presets['G'] = array( //GENDER drop-down list
  2671. 'M' => $this->gT("Male"),
  2672. 'F' => $this->gT("Female"),
  2673. );
  2674. $presets['Y'] = array( //YES/NO radio-buttons
  2675. 'Y' => $this->gT("Yes"),
  2676. 'N' => $this->gT("No"),
  2677. );
  2678. $presets['C'] = array( //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  2679. 'Y' => $this->gT("Yes"),
  2680. 'N' => $this->gT("No"),
  2681. 'U' => $this->gT("Uncertain"),
  2682. );
  2683. $presets['E'] = array( //ARRAY (Increase/Same/Decrease) radio-buttons
  2684. 'I' => $this->gT("Increase"),
  2685. 'S' => $this->gT("Same"),
  2686. 'D' => $this->gT("Decrease"),
  2687. );
  2688. $this->gseq2info = $this->getGroupInfoForEM($surveyid,$_SESSION['LEMlang']);
  2689. foreach ($this->gseq2info as $aGroupInfo)
  2690. {
  2691. $this->groupId2groupSeq[$aGroupInfo['gid']] = $aGroupInfo['group_order'];
  2692. }
  2693. $qattr = $this->getQuestionAttributesForEM($surveyid,NULL,$_SESSION['LEMlang']);
  2694. $this->qattr = $qattr;
  2695. $this->runtimeTimings[] = array(__METHOD__ . ' - question_attributes_model->getQuestionAttributesForEM',(microtime(true) - $now));
  2696. $now = microtime(true);
  2697. $this->qans = $this->getAnswerSetsForEM($surveyid,NULL,$_SESSION['LEMlang']);
  2698. $this->runtimeTimings[] = array(__METHOD__ . ' - answers_model->getAnswerSetsForEM',(microtime(true) - $now));
  2699. $now = microtime(true);
  2700. $q2subqInfo = array();
  2701. $this->multiflexiAnswers=array();
  2702. foreach($fieldmap as $fielddata)
  2703. {
  2704. if (!isset($fielddata['fieldname']) || !preg_match('#^\d+X\d+X\d+#',$fielddata['fieldname']))
  2705. {
  2706. continue; // not an SGQA value
  2707. }
  2708. $sgqa = $fielddata['fieldname'];
  2709. $type = $fielddata['type'];
  2710. $mandatory = $fielddata['mandatory'];
  2711. $fieldNameParts = explode('X',$sgqa);
  2712. $groupNum = $fieldNameParts[1];
  2713. $aid = (isset($fielddata['aid']) ? $fielddata['aid'] : '');
  2714. $sqid = (isset($fielddata['sqid']) ? $fielddata['sqid'] : '');
  2715. $questionId = $fieldNameParts[2];
  2716. $questionNum = $fielddata['qid'];
  2717. $relevance = (isset($fielddata['relevance'])) ? $fielddata['relevance'] : 1;
  2718. $grelevance = (isset($fielddata['grelevance'])) ? $fielddata['grelevance'] : 1;
  2719. $hidden = (isset($qattr[$questionNum]['hidden'])) ? ($qattr[$questionNum]['hidden'] == '1') : false;
  2720. $scale_id = (isset($fielddata['scale_id'])) ? $fielddata['scale_id'] : '0';
  2721. $preg = (isset($fielddata['preg'])) ? $fielddata['preg'] : NULL; // a perl regular exrpession validation function
  2722. $defaultValue = (isset($fielddata['defaultvalue']) ? $fielddata['defaultvalue'] : NULL);
  2723. if (trim($preg) == '') {
  2724. $preg = NULL;
  2725. }
  2726. $help = (isset($fielddata['help'])) ? $fielddata['help']: '';
  2727. $other = (isset($fielddata['other'])) ? $fielddata['other'] : '';
  2728. if (isset($this->questionId2groupSeq[$questionNum])) {
  2729. $groupSeq = $this->questionId2groupSeq[$questionNum];
  2730. }
  2731. else {
  2732. $groupSeq = (isset($fielddata['groupSeq'])) ? $fielddata['groupSeq'] : -1;
  2733. $this->questionId2groupSeq[$questionNum] = $groupSeq;
  2734. }
  2735. if (isset($this->questionId2questionSeq[$questionNum])) {
  2736. $questionSeq = $this->questionId2questionSeq[$questionNum];
  2737. }
  2738. else {
  2739. $questionSeq = (isset($fielddata['questionSeq'])) ? $fielddata['questionSeq'] : -1;
  2740. $this->questionId2questionSeq[$questionNum] = $questionSeq;
  2741. }
  2742. if (!isset($this->groupSeqInfo[$groupSeq])) {
  2743. $this->groupSeqInfo[$groupSeq] = array(
  2744. 'qstart' => $questionSeq,
  2745. 'qend' => $questionSeq,
  2746. );
  2747. }
  2748. else {
  2749. $this->groupSeqInfo[$groupSeq]['qend'] = $questionSeq; // with each question, update so know ending value
  2750. }
  2751. // Create list of codes associated with each question
  2752. $codeList = (isset($this->qid2code[$questionNum]) ? $this->qid2code[$questionNum] : '');
  2753. if ($codeList == '')
  2754. {
  2755. $codeList = $sgqa;
  2756. }
  2757. else
  2758. {
  2759. $codeList .= '|' . $sgqa;
  2760. }
  2761. $this->qid2code[$questionNum] = $codeList;
  2762. $readWrite = 'Y';
  2763. // Set $ansArray
  2764. switch($type)
  2765. {
  2766. case '!': //List - dropdown
  2767. case 'L': //LIST drop-down/radio-button list
  2768. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  2769. case '1': //Array (Flexible Labels) dual scale // need scale
  2770. case 'H': //ARRAY (Flexible) - Column Format
  2771. case 'F': //ARRAY (Flexible) - Row Format
  2772. case 'R': //RANKING STYLE
  2773. $ansArray = (isset($this->qans[$questionNum]) ? $this->qans[$questionNum] : NULL);
  2774. if ($other == 'Y' && ($type == 'L' || $type == '!'))
  2775. {
  2776. if (preg_match('/other$/',$sgqa))
  2777. {
  2778. $ansArray = NULL; // since the other variable doesn't need it
  2779. }
  2780. else
  2781. {
  2782. $_qattr = isset($qattr[$questionNum]) ? $qattr[$questionNum] : array();
  2783. if (isset($_qattr['other_replace_text']) && trim($_qattr['other_replace_text']) != '') {
  2784. $othertext = trim($_qattr['other_replace_text']);
  2785. }
  2786. else {
  2787. $othertext = $this->gT('Other:');
  2788. }
  2789. $ansArray['0~-oth-'] = '0|' . $othertext;
  2790. }
  2791. }
  2792. break;
  2793. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  2794. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  2795. case ':': //ARRAY (Multi Flexi) 1 to 10
  2796. case '5': //5 POINT CHOICE radio-buttons
  2797. $ansArray=NULL;
  2798. break;
  2799. case 'N': //NUMERICAL QUESTION TYPE
  2800. case 'K': //MULTIPLE NUMERICAL QUESTION
  2801. case 'Q': //MULTIPLE SHORT TEXT
  2802. case ';': //ARRAY (Multi Flexi) Text
  2803. case 'S': //SHORT FREE TEXT
  2804. case 'T': //LONG FREE TEXT
  2805. case 'U': //HUGE FREE TEXT
  2806. case 'M': //Multiple choice checkbox
  2807. case 'P': //Multiple choice with comments checkbox + text
  2808. case 'D': //DATE
  2809. case '*': //Equation
  2810. case 'I': //Language Question
  2811. case '|': //File Upload
  2812. case 'X': //BOILERPLATE QUESTION
  2813. $ansArray = NULL;
  2814. break;
  2815. case 'G': //GENDER drop-down list
  2816. case 'Y': //YES/NO radio-buttons
  2817. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  2818. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  2819. $ansArray = $presets[$type];
  2820. break;
  2821. }
  2822. // set $subqtext text - for display of primary sub-question
  2823. $subqtext = '';
  2824. switch ($type)
  2825. {
  2826. default:
  2827. $subqtext = (isset($fielddata['subquestion']) ? $fielddata['subquestion'] : '');
  2828. break;
  2829. case ':': //ARRAY (Multi Flexi) 1 to 10
  2830. case ';': //ARRAY (Multi Flexi) Text
  2831. $subqtext = (isset($fielddata['subquestion1']) ? $fielddata['subquestion1'] : '');
  2832. $ansList = array();
  2833. if (isset($fielddata['answerList']))
  2834. {
  2835. foreach ($fielddata['answerList'] as $ans) {
  2836. $ansList['1~' . $ans['code']] = $ans['code'] . '|' . $ans['answer'];
  2837. }
  2838. $this->multiflexiAnswers[$questionNum] = $ansList;
  2839. }
  2840. break;
  2841. }
  2842. // Set $varName (question code / questions.title), $rowdivid, $csuffix, $sqsuffix, and $question
  2843. $rowdivid=NULL; // so that blank for types not needing it.
  2844. $sqsuffix='';
  2845. switch($type)
  2846. {
  2847. case '!': //List - dropdown
  2848. case '5': //5 POINT CHOICE radio-buttons
  2849. case 'D': //DATE
  2850. case 'G': //GENDER drop-down list
  2851. case 'I': //Language Question
  2852. case 'L': //LIST drop-down/radio-button list
  2853. case 'N': //NUMERICAL QUESTION TYPE
  2854. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  2855. case 'S': //SHORT FREE TEXT
  2856. case 'T': //LONG FREE TEXT
  2857. case 'U': //HUGE FREE TEXT
  2858. case 'X': //BOILERPLATE QUESTION
  2859. case 'Y': //YES/NO radio-buttons
  2860. case '|': //File Upload
  2861. case '*': //Equation
  2862. $csuffix = '';
  2863. $sqsuffix = '';
  2864. $varName = $fielddata['title'];
  2865. if ($fielddata['aid'] != '') {
  2866. $varName .= '_' . $fielddata['aid'];
  2867. }
  2868. $question = $fielddata['question'];
  2869. break;
  2870. case '1': //Array (Flexible Labels) dual scale
  2871. $csuffix = $fielddata['aid'] . '#' . $fielddata['scale_id'];
  2872. $sqsuffix = '_' . $fielddata['aid'];
  2873. $varName = $fielddata['title'] . '_' . $fielddata['aid'] . '_' . $fielddata['scale_id'];;
  2874. $question = $fielddata['subquestion'] . '[' . $fielddata['scale'] . ']';
  2875. // $question = $fielddata['question'] . ': ' . $fielddata['subquestion'] . '[' . $fielddata['scale'] . ']';
  2876. $rowdivid = substr($sgqa,0,-2);
  2877. break;
  2878. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  2879. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  2880. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  2881. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  2882. case 'F': //ARRAY (Flexible) - Row Format
  2883. case 'H': //ARRAY (Flexible) - Column Format // note does not have javatbd equivalent - so array filters don't work on it
  2884. case 'K': //MULTIPLE NUMERICAL QUESTION // note does not have javatbd equivalent - so array filters don't work on it, but need rowdivid to process validations
  2885. case 'M': //Multiple choice checkbox
  2886. case 'P': //Multiple choice with comments checkbox + text
  2887. case 'Q': //MULTIPLE SHORT TEXT // note does not have javatbd equivalent - so array filters don't work on it
  2888. case 'R': //RANKING STYLE // note does not have javatbd equivalent - so array filters don't work on it
  2889. $csuffix = $fielddata['aid'];
  2890. $varName = $fielddata['title'] . '_' . $fielddata['aid'];
  2891. $question = $fielddata['subquestion'];
  2892. // $question = $fielddata['question'] . ': ' . $fielddata['subquestion'];
  2893. if ($type != 'H') {
  2894. if ($type == 'P' && preg_match("/comment$/", $sgqa)) {
  2895. // $rowdivid = substr($sgqa,0,-7);
  2896. }
  2897. else {
  2898. $sqsuffix = '_' . $fielddata['aid'];
  2899. $rowdivid = $sgqa;
  2900. }
  2901. }
  2902. break;
  2903. case ':': //ARRAY (Multi Flexi) 1 to 10
  2904. case ';': //ARRAY (Multi Flexi) Text
  2905. $csuffix = $fielddata['aid'];
  2906. $sqsuffix = '_' . substr($fielddata['aid'],0,strpos($fielddata['aid'],'_'));
  2907. $varName = $fielddata['title'] . '_' . $fielddata['aid'];
  2908. $question = $fielddata['subquestion1'] . '[' . $fielddata['subquestion2'] . ']';
  2909. // $question = $fielddata['question'] . ': ' . $fielddata['subquestion1'] . '[' . $fielddata['subquestion2'] . ']';
  2910. $rowdivid = substr($sgqa,0,strpos($sgqa,'_'));
  2911. break;
  2912. }
  2913. // $onlynum
  2914. $onlynum=false; // the default
  2915. switch($type)
  2916. {
  2917. case 'K': //MULTIPLE NUMERICAL QUESTION
  2918. case 'N': //NUMERICAL QUESTION TYPE
  2919. case ':': //ARRAY (Multi Flexi) 1 to 10
  2920. $onlynum=true;
  2921. break;
  2922. case '*': // Equation
  2923. case ';': //ARRAY (Multi Flexi) Text
  2924. case 'Q': //MULTIPLE SHORT TEXT
  2925. case 'S': //SHORT FREE TEXT
  2926. if (isset($qattr[$questionNum]['numbers_only']) && $qattr[$questionNum]['numbers_only']=='1')
  2927. {
  2928. $onlynum=true;
  2929. }
  2930. break;
  2931. case 'L': //LIST drop-down/radio-button list
  2932. case 'M': //Multiple choice checkbox
  2933. case 'P': //Multiple choice with comments checkbox + text
  2934. if (isset($qattr[$questionNum]['other_numbers_only']) && $qattr[$questionNum]['other_numbers_only']=='1' && preg_match('/other$/',$sgqa))
  2935. {
  2936. $onlynum=true;
  2937. }
  2938. break;
  2939. default:
  2940. break;
  2941. }
  2942. // Set $jsVarName_on (for on-page variables - e.g. answerSGQA) and $jsVarName (for off-page variables; the primary name - e.g. javaSGQA)
  2943. switch($type)
  2944. {
  2945. case 'R': //RANKING STYLE
  2946. $jsVarName_on = 'answer' . $sgqa;
  2947. $jsVarName = 'java' . $sgqa;
  2948. break;
  2949. case 'D': //DATE
  2950. case 'N': //NUMERICAL QUESTION TYPE
  2951. case 'S': //SHORT FREE TEXT
  2952. case 'T': //LONG FREE TEXT
  2953. case 'U': //HUGE FREE TEXT
  2954. case 'Q': //MULTIPLE SHORT TEXT
  2955. case 'K': //MULTIPLE NUMERICAL QUESTION
  2956. case 'X': //BOILERPLATE QUESTION
  2957. $jsVarName_on = 'answer' . $sgqa;
  2958. $jsVarName = 'java' . $sgqa;
  2959. break;
  2960. case '!': //List - dropdown
  2961. if (preg_match("/other$/",$sgqa))
  2962. {
  2963. $jsVarName = 'java' . $sgqa;
  2964. $jsVarName_on = 'othertext' . substr($sgqa,0,-5);
  2965. }
  2966. else
  2967. {
  2968. $jsVarName = 'java' . $sgqa;
  2969. $jsVarName_on = $jsVarName;
  2970. }
  2971. break;
  2972. case 'L': //LIST drop-down/radio-button list
  2973. if (preg_match("/other$/",$sgqa))
  2974. {
  2975. $jsVarName = 'java' . $sgqa;
  2976. $jsVarName_on = 'answer' . $sgqa . "text";
  2977. }
  2978. else
  2979. {
  2980. $jsVarName = 'java' . $sgqa;
  2981. $jsVarName_on = $jsVarName;
  2982. }
  2983. break;
  2984. case '5': //5 POINT CHOICE radio-buttons
  2985. case 'G': //GENDER drop-down list
  2986. case 'I': //Language Question
  2987. case 'Y': //YES/NO radio-buttons
  2988. case '*': //Equation
  2989. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  2990. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  2991. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  2992. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  2993. case 'F': //ARRAY (Flexible) - Row Format
  2994. case 'H': //ARRAY (Flexible) - Column Format
  2995. case 'M': //Multiple choice checkbox
  2996. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  2997. if ($type == 'O' && preg_match('/_comment$/', $varName))
  2998. {
  2999. $jsVarName_on = 'answer' . $sgqa;
  3000. }
  3001. else
  3002. {
  3003. $jsVarName_on = 'java' . $sgqa;
  3004. }
  3005. $jsVarName = 'java' . $sgqa;
  3006. break;
  3007. case '1': //Array (Flexible Labels) dual scale
  3008. $jsVarName = 'java' . str_replace('#','_',$sgqa);
  3009. $jsVarName_on = $jsVarName;
  3010. break;
  3011. case ':': //ARRAY (Multi Flexi) 1 to 10
  3012. case ';': //ARRAY (Multi Flexi) Text
  3013. $jsVarName = 'java' . $sgqa;
  3014. $jsVarName_on = 'answer' . $sgqa;;
  3015. break;
  3016. case '|': //File Upload
  3017. $jsVarName = $sgqa;
  3018. $jsVarName_on = $jsVarName;
  3019. break;
  3020. case 'P': //Multiple choice with comments checkbox + text
  3021. if (preg_match("/(other|comment)$/",$sgqa))
  3022. {
  3023. $jsVarName_on = 'answer' . $sgqa; // is this true for survey.php and not for group.php?
  3024. $jsVarName = 'java' . $sgqa;
  3025. }
  3026. else
  3027. {
  3028. $jsVarName = 'java' . $sgqa;
  3029. $jsVarName_on = $jsVarName;
  3030. }
  3031. break;
  3032. }
  3033. if (!is_null($rowdivid) || $type == 'L' || $type == 'N' || $type == '!' || !is_null($preg)
  3034. || $type == 'S' || $type == 'T' || $type == 'U' || $type == '|') {
  3035. if (!isset($q2subqInfo[$questionNum])) {
  3036. $q2subqInfo[$questionNum] = array(
  3037. 'qid' => $questionNum,
  3038. 'qseq' => $questionSeq,
  3039. 'gseq' => $groupSeq,
  3040. 'sgqa' => $surveyid . 'X' . $groupNum . 'X' . $questionNum,
  3041. 'mandatory'=>$mandatory,
  3042. 'varName' => $varName,
  3043. 'type' => $type,
  3044. 'fieldname' => $sgqa,
  3045. 'preg' => $preg,
  3046. 'rootVarName' => $fielddata['title'],
  3047. );
  3048. }
  3049. if (!isset($q2subqInfo[$questionNum]['subqs'])) {
  3050. $q2subqInfo[$questionNum]['subqs'] = array();
  3051. }
  3052. if ($type == 'L' || $type == '!')
  3053. {
  3054. if (!is_null($ansArray))
  3055. {
  3056. foreach (array_keys($ansArray) as $key)
  3057. {
  3058. $parts = explode('~',$key);
  3059. if ($parts[1] == '-oth-') {
  3060. $parts[1] = 'other';
  3061. }
  3062. $q2subqInfo[$questionNum]['subqs'][] = array(
  3063. 'rowdivid' => $surveyid . 'X' . $groupNum . 'X' . $questionNum . $parts[1],
  3064. 'varName' => $varName,
  3065. 'sqsuffix' => '_' . $parts[1],
  3066. );
  3067. }
  3068. }
  3069. }
  3070. else if ($type == 'N'
  3071. || $type == 'S' || $type == 'T' || $type == 'U') // for $preg
  3072. {
  3073. $q2subqInfo[$questionNum]['subqs'][] = array(
  3074. 'varName' => $varName,
  3075. 'rowdivid' => $surveyid . 'X' . $groupNum . 'X' . $questionNum,
  3076. 'jsVarName' => 'java' . $surveyid . 'X' . $groupNum . 'X' . $questionNum,
  3077. 'jsVarName_on' => $jsVarName_on,
  3078. );
  3079. }
  3080. else
  3081. {
  3082. $q2subqInfo[$questionNum]['subqs'][] = array(
  3083. 'rowdivid' => $rowdivid,
  3084. 'varName' => $varName,
  3085. 'jsVarName_on' => $jsVarName_on,
  3086. 'jsVarName' => $jsVarName,
  3087. 'csuffix' => $csuffix,
  3088. 'sqsuffix' => $sqsuffix,
  3089. );
  3090. }
  3091. }
  3092. $ansList = '';
  3093. if (isset($ansArray) && !is_null($ansArray)) {
  3094. $answers = array();
  3095. foreach ($ansArray as $key => $value) {
  3096. $answers[] = "'" . $key . "':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$value),ENT_QUOTES) . "'";
  3097. }
  3098. $ansList = ",'answers':{ " . implode(",",$answers) . "}";
  3099. }
  3100. // Set mappings of variable names to needed attributes
  3101. $varInfo_Code = array(
  3102. 'jsName_on'=>$jsVarName_on,
  3103. 'jsName'=>$jsVarName,
  3104. 'readWrite'=>$readWrite,
  3105. 'hidden'=>$hidden,
  3106. 'question'=>$question,
  3107. 'qid'=>$questionNum,
  3108. 'gid'=>$groupNum,
  3109. 'grelevance'=>$grelevance,
  3110. 'relevance'=>$relevance,
  3111. 'qcode'=>$varName,
  3112. 'qseq'=>$questionSeq,
  3113. 'gseq'=>$groupSeq,
  3114. 'type'=>$type,
  3115. 'sgqa'=>$sgqa,
  3116. 'ansList'=>$ansList,
  3117. 'ansArray'=>$ansArray,
  3118. 'scale_id'=>$scale_id,
  3119. 'default'=>$defaultValue,
  3120. 'rootVarName'=>$fielddata['title'],
  3121. 'subqtext'=>$subqtext,
  3122. 'rowdivid'=>(is_null($rowdivid) ? '' : $rowdivid),
  3123. 'onlynum'=>$onlynum,
  3124. );
  3125. $this->questionSeq2relevance[$questionSeq] = array(
  3126. 'relevance'=>$relevance,
  3127. 'grelevance'=>$grelevance,
  3128. 'qid'=>$questionNum,
  3129. 'qseq'=>$questionSeq,
  3130. 'gseq'=>$groupSeq,
  3131. 'jsResultVar_on'=>$jsVarName_on,
  3132. 'jsResultVar'=>$jsVarName,
  3133. 'type'=>$type,
  3134. 'hidden'=>$hidden,
  3135. 'gid'=>$groupNum,
  3136. 'mandatory'=>$mandatory,
  3137. 'eqn'=>(($type == '*') ? $question : ''),
  3138. 'help'=>$help,
  3139. 'qtext'=>$fielddata['question'], // $question,
  3140. 'code'=>$varName,
  3141. 'other'=>$other,
  3142. 'default'=>$defaultValue,
  3143. 'rootVarName'=>$fielddata['title'],
  3144. 'rowdivid'=>(is_null($rowdivid) ? '' : $rowdivid),
  3145. 'aid'=>$aid,
  3146. 'sqid'=>$sqid,
  3147. );
  3148. $this->knownVars[$sgqa] = $varInfo_Code;
  3149. $this->qcode2sgqa[$varName]=$sgqa;
  3150. $this->jsVar2qid[$jsVarName] = $questionNum;
  3151. $this->qcode2sgq[$fielddata['title']] = $surveyid . 'X' . $groupNum . 'X' . $questionNum;
  3152. // Create JavaScript arrays
  3153. $this->alias2varName[$varName] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $varName . "':'" . $jsVarName . "'");
  3154. $this->alias2varName[$sgqa] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $sgqa . "':'" . $jsVarName . "'");
  3155. $this->varNameAttr[$jsVarName] = "'" . $jsVarName . "':{ "
  3156. . "'jsName':'" . $jsVarName
  3157. . "','jsName_on':'" . $jsVarName_on
  3158. . "','sgqa':'" . $sgqa
  3159. . "','qid':" . $questionNum
  3160. . ",'gid':" . $groupNum
  3161. // . ",'mandatory':'" . $mandatory
  3162. // . "','question':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$question),ENT_QUOTES)
  3163. . ",'type':'" . $type
  3164. // . "','relevance':'" . (($relevance != '') ? htmlspecialchars(preg_replace('/[[:space:]]/',' ',$relevance),ENT_QUOTES) : 1)
  3165. // . "','readWrite':'" . $readWrite
  3166. // . "','grelevance':'" . (($grelevance != '') ? htmlspecialchars(preg_replace('/[[:space:]]/',' ',$grelevance),ENT_QUOTES) : 1)
  3167. . "','default':'" . (is_null($defaultValue) ? '' : $defaultValue)
  3168. . "','rowdivid':'" . (is_null($rowdivid) ? '' : $rowdivid)
  3169. . "','onlynum':'" . ($onlynum ? '1' : '')
  3170. . "','gseq':" . $groupSeq
  3171. // . ",'qseq':" . $questionSeq
  3172. .$ansList;
  3173. if ($type == 'M' || $type == 'P')
  3174. {
  3175. $this->varNameAttr[$jsVarName] .= ",'question':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$question),ENT_QUOTES) . "'";
  3176. }
  3177. $this->varNameAttr[$jsVarName] .= "}";
  3178. }
  3179. $this->q2subqInfo = $q2subqInfo;
  3180. // Now set tokens
  3181. if (isset($_SESSION[$this->sessid]['token']) && $_SESSION[$this->sessid]['token'] != '')
  3182. {
  3183. //Gather survey data for tokenised surveys, for use in presenting questions
  3184. $_SESSION[$this->sessid]['thistoken']=getTokenData($surveyid, $_SESSION[$this->sessid]['token']);
  3185. $this->knownVars['TOKEN:TOKEN'] = array(
  3186. 'code'=>$_SESSION[$this->sessid]['token'],
  3187. 'jsName_on'=>'',
  3188. 'jsName'=>'',
  3189. 'readWrite'=>'N',
  3190. );
  3191. }
  3192. if (isset($_SESSION[$this->sessid]['thistoken']))
  3193. {
  3194. foreach (array_keys($_SESSION[$this->sessid]['thistoken']) as $tokenkey)
  3195. {
  3196. if ($anonymized)
  3197. {
  3198. $val = "";
  3199. }
  3200. else
  3201. {
  3202. $val = $_SESSION[$this->sessid]['thistoken'][$tokenkey];
  3203. }
  3204. $key = "TOKEN:" . strtoupper($tokenkey);
  3205. $this->knownVars[$key] = array(
  3206. 'code'=>$val,
  3207. 'jsName_on'=>'',
  3208. 'jsName'=>'',
  3209. 'readWrite'=>'N',
  3210. );
  3211. }
  3212. }
  3213. else
  3214. {
  3215. // Read list of available tokens from the tokens table so that preview and error checking works correctly
  3216. $attrs = array_keys(getTokenFieldsAndNames($surveyid));
  3217. $blankVal = array(
  3218. 'code'=>'',
  3219. 'type'=>'',
  3220. 'jsName_on'=>'',
  3221. 'jsName'=>'',
  3222. 'readWrite'=>'N',
  3223. );
  3224. foreach ($attrs as $key)
  3225. {
  3226. if (preg_match('/^(firstname|lastname|email|usesleft|token|attribute_\d+)$/',$key))
  3227. {
  3228. $this->knownVars['TOKEN:' . strtoupper($key)] = $blankVal;
  3229. }
  3230. }
  3231. }
  3232. // set default value for reserved 'this' variable
  3233. $this->knownVars['this'] = array(
  3234. 'jsName_on'=>'',
  3235. 'jsName'=>'',
  3236. 'readWrite'=>'',
  3237. 'hidden'=>'',
  3238. 'question'=>'this',
  3239. 'qid'=>'',
  3240. 'gid'=>'',
  3241. 'grelevance'=>'',
  3242. 'relevance'=>'',
  3243. 'qcode'=>'this',
  3244. 'qseq'=>'',
  3245. 'gseq'=>'',
  3246. 'type'=>'',
  3247. 'sgqa'=>'',
  3248. 'rowdivid'=>'',
  3249. 'ansList'=>'',
  3250. 'ansArray'=>array(),
  3251. 'scale_id'=>'',
  3252. 'default'=>'',
  3253. 'rootVarName'=>'this',
  3254. 'subqtext'=>'',
  3255. 'rowdivid'=>'',
  3256. );
  3257. $this->runtimeTimings[] = array(__METHOD__ . ' - process fieldMap',(microtime(true) - $now));
  3258. usort($this->questionSeq2relevance,'cmpQuestionSeq');
  3259. $this->numQuestions = count($this->questionSeq2relevance);
  3260. $this->numGroups = count($this->groupSeqInfo);
  3261. return true;
  3262. }
  3263. /**
  3264. * Return whether a sub-question is relevant
  3265. * @param <type> $sgqa
  3266. * @return <boolean>
  3267. */
  3268. static function SubQuestionIsRelevant($sgqa)
  3269. {
  3270. $LEM =& LimeExpressionManager::singleton();
  3271. if (!isset($LEM->knownVars[$sgqa]))
  3272. {
  3273. return false;
  3274. }
  3275. $var = $LEM->knownVars[$sgqa];
  3276. $sqrel=1;
  3277. if (isset($var['rowdivid']) && $var['rowdivid'] != '')
  3278. {
  3279. $sqrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$var['rowdivid']]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$var['rowdivid']] : 1);
  3280. }
  3281. $qid = $var['qid'];
  3282. $qrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
  3283. $gseq = $var['gseq'];
  3284. $grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
  3285. return ($grel && $qrel && $sqrel);
  3286. }
  3287. /**
  3288. * Return whether question $qid is relevanct
  3289. * @param <type> $qid
  3290. * @return boolean
  3291. */
  3292. static function QuestionIsRelevant($qid)
  3293. {
  3294. $LEM =& LimeExpressionManager::singleton();
  3295. $qrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
  3296. $gseq = (isset($LEM->questionId2groupSeq[$qid]) ? $LEM->questionId2groupSeq[$qid] : -1);
  3297. $grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
  3298. return ($grel && $qrel);
  3299. }
  3300. /**
  3301. * Returns true if the group is relevant and should be shown
  3302. *
  3303. * @param int $gid
  3304. * @return boolean
  3305. */
  3306. static function GroupIsRelevant($gid)
  3307. {
  3308. $LEM =& LimeExpressionManager::singleton();
  3309. $gseq = $LEM->GetGroupSeq($gid);
  3310. return !$LEM->GroupIsIrrelevantOrHidden($gseq);
  3311. }
  3312. /**
  3313. * Return whether group $gseq is relevant
  3314. * @param <type> $gseq
  3315. * @return boolean
  3316. */
  3317. static function GroupIsIrrelevantOrHidden($gseq)
  3318. {
  3319. $LEM =& LimeExpressionManager::singleton();
  3320. $grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
  3321. $gshow = (isset($LEM->indexGseq[$gseq]['show']) ? $LEM->indexGseq[$gseq]['show'] : true); // default to true?
  3322. return !($grel && $gshow);
  3323. }
  3324. /**
  3325. * Check the relevance status of all questions on or before the current group.
  3326. * This generates needed JavaScript for dynamic relevance, and sets flags about which questions and groups are relevant
  3327. */
  3328. function ProcessAllNeededRelevance($onlyThisQseq=NULL)
  3329. {
  3330. // TODO - in a running survey, only need to process the current Group. For Admin mode, do we need to process all prior questions or not?
  3331. // $now = microtime(true);
  3332. $grelComputed=array(); // so only process it once per group
  3333. foreach($this->questionSeq2relevance as $rel)
  3334. {
  3335. if (!is_null($onlyThisQseq) && $onlyThisQseq!=$rel['qseq']) {
  3336. continue;
  3337. }
  3338. $qid = $rel['qid'];
  3339. $gseq = $rel['gseq'];
  3340. if ($this->allOnOnePage) {
  3341. ; // process relevance for all questions
  3342. }
  3343. else if ($gseq != $this->currentGroupSeq) {
  3344. continue;
  3345. }
  3346. $result = $this->_ProcessRelevance(htmlspecialchars_decode($rel['relevance'],ENT_QUOTES),
  3347. $qid,
  3348. $gseq,
  3349. $rel['jsResultVar'],
  3350. $rel['type'],
  3351. $rel['hidden']
  3352. );
  3353. $_SESSION[$this->sessid]['relevanceStatus'][$qid] = $result;
  3354. if (!isset($grelComputed[$gseq])) {
  3355. $this->_ProcessGroupRelevance($gseq);
  3356. $grelComputed[$gseq]=true;
  3357. }
  3358. }
  3359. // $this->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3360. }
  3361. /**
  3362. * Translate all Expressions, Macros, registered variables, etc. in $string
  3363. * @param <type> $string - the string to be replaced
  3364. * @param <type> $questionNum - the $qid of question being replaced - needed for properly alignment of question-level relevance and tailoring
  3365. * @param <type> $replacementFields - optional replacement values
  3366. * @param <boolean> $debug - if true,write translations for this page to html-formatted log file
  3367. * @param <type> $numRecursionLevels - the number of times to recursively subtitute values in this string
  3368. * @param <type> $whichPrettyPrintIteration - if want to pretty-print the source string, which recursion level should be pretty-printed
  3369. * @param <type> $noReplacements - true if we already know that no replacements are needed (e.g. there are no curly braces)
  3370. * @return <type> - the original $string with all replacements done.
  3371. */
  3372. static function ProcessString($string, $questionNum=NULL, $replacementFields=array(), $debug=false, $numRecursionLevels=1, $whichPrettyPrintIteration=1, $noReplacements=false, $timeit=true, $staticReplacement=false)
  3373. {
  3374. $now = microtime(true);
  3375. $LEM =& LimeExpressionManager::singleton();
  3376. if ($noReplacements) {
  3377. $LEM->em->SetPrettyPrintSource($string);
  3378. return $string;
  3379. }
  3380. if (isset($replacementFields) && is_array($replacementFields) && count($replacementFields) > 0)
  3381. {
  3382. $replaceArray = array();
  3383. foreach ($replacementFields as $key => $value) {
  3384. $replaceArray[$key] = array(
  3385. 'code'=>$value,
  3386. 'jsName_on'=>'',
  3387. 'jsName'=>'',
  3388. 'readWrite'=>'N',
  3389. );
  3390. }
  3391. $LEM->tempVars = $replaceArray;
  3392. }
  3393. $questionSeq = -1;
  3394. $groupSeq = -1;
  3395. if (!is_null($questionNum)) {
  3396. $questionSeq = isset($LEM->questionId2questionSeq[$questionNum]) ? $LEM->questionId2questionSeq[$questionNum] : -1;
  3397. $groupSeq = isset($LEM->questionId2groupSeq[$questionNum]) ? $LEM->questionId2groupSeq[$questionNum] : -1;
  3398. }
  3399. $stringToParse = $string; // decode called later htmlspecialchars_decode($string,ENT_QUOTES);
  3400. $qnum = is_null($questionNum) ? 0 : $questionNum;
  3401. $result = $LEM->em->sProcessStringContainingExpressions($stringToParse,$qnum, $numRecursionLevels, $whichPrettyPrintIteration, $groupSeq, $questionSeq, $staticReplacement);
  3402. if ($timeit) {
  3403. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3404. }
  3405. return $result;
  3406. }
  3407. /**
  3408. * Compute Relevance, processing $eqn to get a boolean value. If there are syntax errors, return false.
  3409. * @param <type> $eqn - the relevance equation
  3410. * @param <type> $questionNum - needed to align question-level relevance and tailoring
  3411. * @param <type> $jsResultVar - this variable determines whether irrelevant questions are hidden
  3412. * @param <type> $type - question type
  3413. * @param <type> $hidden - whether question should always be hidden
  3414. * @return <type>
  3415. */
  3416. static function ProcessRelevance($eqn,$questionNum=NULL,$jsResultVar=NULL,$type=NULL,$hidden=0)
  3417. {
  3418. $LEM =& LimeExpressionManager::singleton();
  3419. return $LEM->_ProcessRelevance($eqn,$questionNum,NULL,$jsResultVar,$type,$hidden);
  3420. }
  3421. /**
  3422. * Compute Relevance, processing $eqn to get a boolean value. If there are syntax errors, return false.
  3423. * @param <type> $eqn - the relevance equation
  3424. * @param <type> $questionNum - needed to align question-level relevance and tailoring
  3425. * @param <type> $jsResultVar - this variable determines whether irrelevant questions are hidden
  3426. * @param <type> $type - question type
  3427. * @param <type> $hidden - whether question should always be hidden
  3428. * @return <type>
  3429. */
  3430. private function _ProcessRelevance($eqn,$questionNum=NULL,$gseq=NULL,$jsResultVar=NULL,$type=NULL,$hidden=0)
  3431. {
  3432. // These will be called in the order that questions are supposed to be asked
  3433. // TODO - cache results and generated JavaScript equations?
  3434. if (!isset($eqn) || trim($eqn=='') || trim($eqn)=='1')
  3435. {
  3436. $this->groupRelevanceInfo[] = array(
  3437. 'qid' => $questionNum,
  3438. 'gseq' => $gseq,
  3439. 'eqn' => $eqn,
  3440. 'result' => true,
  3441. 'numJsVars' => 0,
  3442. 'relevancejs' => '',
  3443. 'relevanceVars' => '',
  3444. 'jsResultVar'=> $jsResultVar,
  3445. 'type'=>$type,
  3446. 'hidden'=>$hidden,
  3447. 'hasErrors'=>false,
  3448. );
  3449. return true;
  3450. }
  3451. $questionSeq = -1;
  3452. $groupSeq = -1;
  3453. if (!is_null($questionNum)) {
  3454. $questionSeq = isset($this->questionId2questionSeq[$questionNum]) ? $this->questionId2questionSeq[$questionNum] : -1;
  3455. $groupSeq = isset($this->questionId2groupSeq[$questionNum]) ? $this->questionId2groupSeq[$questionNum] : -1;
  3456. }
  3457. $stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
  3458. $result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq, $questionSeq);
  3459. $hasErrors = $this->em->HasErrors();
  3460. if (!is_null($questionNum) && !is_null($jsResultVar)) { // so if missing either, don't generate JavaScript for this - means off-page relevance.
  3461. $jsVars = $this->em->GetJSVarsUsed();
  3462. $relevanceVars = implode('|',$this->em->GetJSVarsUsed());
  3463. $relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
  3464. $this->groupRelevanceInfo[] = array(
  3465. 'qid' => $questionNum,
  3466. 'gseq' => $gseq,
  3467. 'eqn' => $eqn,
  3468. 'result' => $result,
  3469. 'numJsVars' => count($jsVars),
  3470. 'relevancejs' => $relevanceJS,
  3471. 'relevanceVars' => $relevanceVars,
  3472. 'jsResultVar' => $jsResultVar,
  3473. 'type'=>$type,
  3474. 'hidden'=>$hidden,
  3475. 'hasErrors'=>$hasErrors,
  3476. );
  3477. }
  3478. return $result;
  3479. }
  3480. /**
  3481. * Create JavaScript needed to process sub-question-level relevance (e.g. for array_filter and _exclude)
  3482. * @param <type> $eqn - the equation to parse
  3483. * @param <type> $questionNum - the question number - needed to align relavance and tailoring blocks
  3484. * @param <type> $rowdivid - the javascript ID that needs to be shown/hidden in order to control array_filter visibility
  3485. * @param <type> $type - the type of sub-question relevance (e.g. 'array_filter', 'array_filter_exclude')
  3486. * @return <type>
  3487. */
  3488. private function _ProcessSubQRelevance($eqn,$questionNum=NULL,$rowdivid=NULL, $type=NULL, $qtype=NULL, $sgqa=NULL, $isExclusive='', $irrelevantAndExclusive='')
  3489. {
  3490. // These will be called in the order that questions are supposed to be asked
  3491. if (!isset($eqn) || trim($eqn=='') || trim($eqn)=='1')
  3492. {
  3493. return true;
  3494. }
  3495. $questionSeq = -1;
  3496. $groupSeq = -1;
  3497. if (!is_null($questionNum)) {
  3498. $questionSeq = isset($this->questionId2questionSeq[$questionNum]) ? $this->questionId2questionSeq[$questionNum] : -1;
  3499. $groupSeq = isset($this->questionId2groupSeq[$questionNum]) ? $this->questionId2groupSeq[$questionNum] : -1;
  3500. }
  3501. $stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
  3502. $result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq, $questionSeq);
  3503. $hasErrors = $this->em->HasErrors();
  3504. $prettyPrint = '';
  3505. if (($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX) {
  3506. $prettyPrint= $this->em->GetPrettyPrintString();
  3507. }
  3508. if (!is_null($questionNum)) {
  3509. $jsVars = $this->em->GetJSVarsUsed();
  3510. $relevanceVars = implode('|',$this->em->GetJSVarsUsed());
  3511. $relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
  3512. $isExclusiveJS='';
  3513. $irrelevantAndExclusiveJS='';
  3514. // Only need to extract JS, since will already have Vars and error counts from main equation
  3515. if ($isExclusive != '')
  3516. {
  3517. $this->em->ProcessBooleanExpression($isExclusive,$groupSeq, $questionSeq);
  3518. $isExclusiveJS = $this->em->GetJavaScriptEquivalentOfExpression();
  3519. }
  3520. if ($irrelevantAndExclusive != '')
  3521. {
  3522. $this->em->ProcessBooleanExpression($irrelevantAndExclusive,$groupSeq, $questionSeq);
  3523. $irrelevantAndExclusiveJS = $this->em->GetJavaScriptEquivalentOfExpression();
  3524. }
  3525. if (!isset($this->subQrelInfo[$questionNum])) {
  3526. $this->subQrelInfo[$questionNum] = array();
  3527. }
  3528. $this->subQrelInfo[$questionNum][$rowdivid] = array(
  3529. 'qid' => $questionNum,
  3530. 'eqn' => $eqn,
  3531. 'prettyPrintEqn' => $prettyPrint,
  3532. 'result' => $result,
  3533. 'numJsVars' => count($jsVars),
  3534. 'relevancejs' => $relevanceJS,
  3535. 'relevanceVars' => $relevanceVars,
  3536. 'rowdivid' => $rowdivid,
  3537. 'type'=>$type,
  3538. 'qtype'=>$qtype,
  3539. 'sgqa'=>$sgqa,
  3540. 'hasErrors'=>$hasErrors,
  3541. 'isExclusiveJS'=>$isExclusiveJS,
  3542. 'irrelevantAndExclusiveJS'=>$irrelevantAndExclusiveJS,
  3543. );
  3544. }
  3545. return $result;
  3546. }
  3547. private function _ProcessGroupRelevance($groupSeq)
  3548. {
  3549. // These will be called in the order that questions are supposed to be asked
  3550. if ($groupSeq == -1) {
  3551. return; // invalid group, so ignore
  3552. }
  3553. $eqn = (isset($this->gseq2info[$groupSeq]['grelevance']) ? $this->gseq2info[$groupSeq]['grelevance'] : 1);
  3554. if (is_null($eqn) || trim($eqn=='') || trim($eqn)=='1')
  3555. {
  3556. $this->gRelInfo[$groupSeq] = array(
  3557. 'gseq' => $groupSeq,
  3558. 'eqn' => '',
  3559. 'result' => 1,
  3560. 'numJsVars' => 0,
  3561. 'relevancejs' => '',
  3562. 'relevanceVars' => '',
  3563. 'prettyprint'=> '',
  3564. );
  3565. $_SESSION[$this->sessid]['relevanceStatus']['G' . $groupSeq] = 1;
  3566. return;
  3567. }
  3568. $stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
  3569. $result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq);
  3570. $hasErrors = $this->em->HasErrors();
  3571. $jsVars = $this->em->GetJSVarsUsed();
  3572. $relevanceVars = implode('|',$this->em->GetJSVarsUsed());
  3573. $relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
  3574. $prettyPrint = $this->em->GetPrettyPrintString();
  3575. $this->gRelInfo[$groupSeq] = array(
  3576. 'gseq' => $groupSeq,
  3577. 'eqn' => $stringToParse,
  3578. 'result' => $result,
  3579. 'numJsVars' => count($jsVars),
  3580. 'relevancejs' => $relevanceJS,
  3581. 'relevanceVars' => $relevanceVars,
  3582. 'prettyprint'=> $prettyPrint,
  3583. 'hasErrors' => $hasErrors,
  3584. );
  3585. $_SESSION[$this->sessid]['relevanceStatus']['G' . $groupSeq] = $result;
  3586. }
  3587. /**
  3588. * Used to show potential syntax errors of processing Relevance or Equations.
  3589. * @return <type>
  3590. */
  3591. static function GetLastPrettyPrintExpression()
  3592. {
  3593. $LEM =& LimeExpressionManager::singleton();
  3594. return $LEM->em->GetLastPrettyPrintExpression();
  3595. }
  3596. /**
  3597. * Expand "self.suffix" and "that.qcode.suffix" into canonical list of variable names
  3598. * @param type $qseq
  3599. * @param type $varname
  3600. */
  3601. static function GetAllVarNamesForQ($qseq,$varname)
  3602. {
  3603. $LEM =& LimeExpressionManager::singleton();
  3604. $parts = explode('.',$varname);
  3605. $qroot = '';
  3606. $suffix = '';
  3607. $sqpatts = array();
  3608. $nosqpatts = array();
  3609. $sqpatt = '';
  3610. $nosqpatt = '';
  3611. $comments = '';
  3612. if ($parts[0] == 'self')
  3613. {
  3614. $type = 'self';
  3615. }
  3616. else
  3617. {
  3618. $type = 'that';
  3619. array_shift($parts);
  3620. if (isset($parts[0]))
  3621. {
  3622. $qroot = $parts[0];
  3623. }
  3624. else
  3625. {
  3626. return $varname;
  3627. }
  3628. }
  3629. array_shift($parts);
  3630. if (count($parts) > 0)
  3631. {
  3632. if (preg_match('/^' . ExpressionManager::$RDP_regex_var_attr . '$/',$parts[count($parts)-1]))
  3633. {
  3634. $suffix = '.' . $parts[count($parts)-1];
  3635. array_pop($parts);
  3636. }
  3637. }
  3638. foreach($parts as $part)
  3639. {
  3640. if ($part == 'nocomments')
  3641. {
  3642. $comments = 'N';
  3643. }
  3644. else if ($part == 'comments')
  3645. {
  3646. $comments = 'Y';
  3647. }
  3648. else if (preg_match('/^sq_.+$/',$part))
  3649. {
  3650. $sqpatts[] = substr($part,3);
  3651. }
  3652. else if (preg_match('/^nosq_.+$/',$part))
  3653. {
  3654. $nosqpatts[] = substr($part,5);
  3655. }
  3656. else
  3657. {
  3658. return $varname; // invalid
  3659. }
  3660. }
  3661. $sqpatt = implode('|',$sqpatts);
  3662. $nosqpatt = implode('|',$nosqpatts);
  3663. $vars = array();
  3664. if(isset($LEM->knownVars))
  3665. {
  3666. foreach ($LEM->knownVars as $kv)
  3667. {
  3668. if ($type == 'self')
  3669. {
  3670. if (!isset($kv['qseq']) || $kv['qseq'] != $qseq || trim($kv['sgqa']) == '')
  3671. {
  3672. continue;
  3673. }
  3674. }
  3675. else
  3676. {
  3677. if (!isset($kv['rootVarName']) || $kv['rootVarName'] != $qroot)
  3678. {
  3679. continue;
  3680. }
  3681. }
  3682. if ($comments != '')
  3683. {
  3684. if ($comments == 'Y' && !preg_match('/comment$/',$kv['sgqa']))
  3685. {
  3686. continue;
  3687. }
  3688. if ($comments == 'N' && preg_match('/comment$/',$kv['sgqa']))
  3689. {
  3690. continue;
  3691. }
  3692. }
  3693. $sgq = $LEM->sid . 'X' . $kv['gid'] . 'X' . $kv['qid'];
  3694. $ext = substr($kv['sgqa'],strlen($sgq));
  3695. if ($sqpatt != '')
  3696. {
  3697. if (!preg_match('/'.$sqpatt.'/',$ext))
  3698. {
  3699. continue;
  3700. }
  3701. }
  3702. if ($nosqpatt != '')
  3703. {
  3704. if (preg_match('/'.$nosqpatt.'/',$ext))
  3705. {
  3706. continue;
  3707. }
  3708. }
  3709. $vars[] = $kv['sgqa'] . $suffix;
  3710. }
  3711. }
  3712. if (count($vars) > 0)
  3713. {
  3714. return implode(',',$vars);
  3715. }
  3716. return $varname; // invalid
  3717. }
  3718. /**
  3719. * Should be first function called on each page - sets/clears internally needed variables
  3720. * @param <type> $allOnOnePage - true if StartProcessingGroup will be called multiple times on this page - does some optimizatinos
  3721. * @param <type> $rooturl - if set, this tells LEM to enable hyperlinking of syntax highlighting to ease editing of questions
  3722. * @param <boolean> $initializeVars - if true, initializes the replacement variables to enable syntax highlighting on admin pages
  3723. */
  3724. static function StartProcessingPage($allOnOnePage=false,$initializeVars=false)
  3725. {
  3726. // $now = microtime(true);
  3727. $LEM =& LimeExpressionManager::singleton();
  3728. $LEM->pageRelevanceInfo=array();
  3729. $LEM->pageTailorInfo=array();
  3730. $LEM->allOnOnePage=$allOnOnePage;
  3731. $LEM->processedRelevance=false;
  3732. $LEM->surveyOptions['hyperlinkSyntaxHighlighting']=true; // this will be temporary - should be reset in running survey
  3733. $LEM->qid2exclusiveAuto=array();
  3734. $surveyinfo = (isset($LEM->sid) ? getSurveyInfo($LEM->sid) : null);
  3735. if (isset($surveyinfo['assessments']) && $surveyinfo['assessments']=='Y')
  3736. {
  3737. $LEM->surveyOptions['assessments']=true;
  3738. }
  3739. // $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3740. $LEM->initialized=true;
  3741. if ($initializeVars)
  3742. {
  3743. $LEM->em->StartProcessingGroup(
  3744. isset($_SESSION['LEMsid']) ? $_SESSION['LEMsid'] : NULL,
  3745. '',
  3746. true
  3747. );
  3748. $LEM->setVariableAndTokenMappingsForExpressionManager($_SESSION['LEMsid']);
  3749. }
  3750. }
  3751. /**
  3752. * Initialize a survey so can use EM to manage navigation
  3753. * @param <type> $surveyid
  3754. * @param <type> $surveyMode
  3755. * @param <type> $anonymized
  3756. * @param <type> $forceRefresh
  3757. */
  3758. static function StartSurvey($surveyid,$surveyMode='group',$options=NULL,$forceRefresh=false,$debugLevel=0)
  3759. {
  3760. $LEM =& LimeExpressionManager::singleton();
  3761. $LEM->sid=sanitize_int($surveyid);
  3762. $LEM->sessid = 'survey_' . $LEM->sid;
  3763. $LEM->em->StartProcessingGroup($surveyid);
  3764. if (is_null($options)) {
  3765. $options = array();
  3766. }
  3767. $LEM->surveyOptions['active'] = (isset($options['active']) ? $options['active'] : false);
  3768. $LEM->surveyOptions['allowsave'] = (isset($options['allowsave']) ? $options['allowsave'] : false);
  3769. $LEM->surveyOptions['anonymized'] = (isset($options['anonymized']) ? $options['anonymized'] : false);
  3770. $LEM->surveyOptions['assessments'] = (isset($options['assessments']) ? $options['assessments'] : false);
  3771. $LEM->surveyOptions['datestamp'] = (isset($options['datestamp']) ? $options['datestamp'] : false);
  3772. $LEM->surveyOptions['deletenonvalues'] = (isset($options['deletenonvalues']) ? ($options['deletenonvalues']=='1') : true);
  3773. $LEM->surveyOptions['hyperlinkSyntaxHighlighting'] = (isset($options['hyperlinkSyntaxHighlighting']) ? $options['hyperlinkSyntaxHighlighting'] : false);
  3774. $LEM->surveyOptions['ipaddr'] = (isset($options['ipaddr']) ? $options['ipaddr'] : false);
  3775. $LEM->surveyOptions['radix'] = (isset($options['radix']) ? $options['radix'] : '.');
  3776. $LEM->surveyOptions['refurl'] = (isset($options['refurl']) ? $options['refurl'] : NULL);
  3777. $LEM->surveyOptions['savetimings'] = (isset($options['savetimings']) ? $options['savetimings'] : '');
  3778. $LEM->sgqaNaming = (isset($options['sgqaNaming']) ? ($options['sgqaNaming']=="Y") : true); // TODO default should eventually be false
  3779. $LEM->surveyOptions['startlanguage'] = (isset($options['startlanguage']) ? $options['startlanguage'] : 'en');
  3780. $LEM->surveyOptions['surveyls_dateformat'] = (isset($options['surveyls_dateformat']) ? $options['surveyls_dateformat'] : 1);
  3781. $LEM->surveyOptions['tablename'] = (isset($options['tablename']) ? $options['tablename'] : '{{survey_' . $LEM->sid . '}}');
  3782. $LEM->surveyOptions['tablename_timings'] = ((isset($options['savetimings']) && $options['savetimings'] == 'Y') ? '{{survey_' . $LEM->sid . '_timings}}' : '');
  3783. $LEM->surveyOptions['target'] = (isset($options['target']) ? $options['target'] : '/temp/files/');
  3784. $LEM->surveyOptions['timeadjust'] = (isset($options['timeadjust']) ? $options['timeadjust'] : 0);
  3785. $LEM->surveyOptions['tempdir'] = (isset($options['tempdir']) ? $options['tempdir'] : '/temp/');
  3786. $LEM->surveyOptions['token'] = (isset($options['token']) ? $options['token'] : NULL);
  3787. $LEM->debugLevel=$debugLevel;
  3788. $_SESSION[$LEM->sessid]['LEMdebugLevel']=$debugLevel; // need acces to SESSSION to decide whether to cache serialized instance of $LEM
  3789. switch ($surveyMode) {
  3790. case 'survey':
  3791. $LEM->allOnOnePage=true;
  3792. $LEM->surveyMode = 'survey';
  3793. break;
  3794. case 'question':
  3795. $LEM->allOnOnePage=false;
  3796. $LEM->surveyMode = 'question';
  3797. break;
  3798. default:
  3799. case 'group':
  3800. $LEM->allOnOnePage=false;
  3801. $LEM->surveyMode = 'group';
  3802. break;
  3803. }
  3804. $LEM->setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh,$LEM->surveyOptions['anonymized'],$LEM->allOnOnePage);
  3805. $LEM->currentGroupSeq=-1;
  3806. $LEM->currentQuestionSeq=-1; // for question-by-question mode
  3807. $LEM->indexGseq=array();
  3808. $LEM->indexQseq=array();
  3809. $LEM->qrootVarName2arrayFilter=array();
  3810. if (isset($_SESSION[$LEM->sessid]['startingValues']) && is_array($_SESSION[$LEM->sessid]['startingValues']) && count($_SESSION[$LEM->sessid]['startingValues']) > 0)
  3811. {
  3812. $startingValues = array();
  3813. foreach ($_SESSION[$LEM->sessid]['startingValues'] as $k=>$value)
  3814. {
  3815. if (isset($LEM->knownVars[$k]))
  3816. {
  3817. $knownVar = $LEM->knownVars[$k];
  3818. }
  3819. else if (isset($LEM->qcode2sgqa[$k]))
  3820. {
  3821. $knownVar = $LEM->knownVars[$LEM->qcode2sgqa[$k]];
  3822. }
  3823. else if (isset($LEM->tempVars[$k]))
  3824. {
  3825. $knownVar = $LEM->tempVar[$k];
  3826. }
  3827. else
  3828. {
  3829. continue;
  3830. }
  3831. if (!isset($knownVar['jsName']))
  3832. {
  3833. continue;
  3834. }
  3835. switch ($knownVar['type'])
  3836. {
  3837. case 'D': //DATE
  3838. if (trim($value)=="")
  3839. {
  3840. $value = NULL;
  3841. }
  3842. else
  3843. {
  3844. $dateformatdatat=getDateFormatData($LEM->surveyOptions['surveyls_dateformat']);
  3845. $datetimeobj = new Date_Time_Converter($value, $dateformatdatat['phpdate']);
  3846. $value=$datetimeobj->convert("Y-m-d");
  3847. }
  3848. break;
  3849. case 'N': //NUMERICAL QUESTION TYPE
  3850. case 'K': //MULTIPLE NUMERICAL QUESTION
  3851. if (trim($value)=="") {
  3852. $value = NULL;
  3853. }
  3854. else {
  3855. $value = sanitize_float($value);
  3856. }
  3857. break;
  3858. case '|': //File Upload
  3859. $value=NULL; // can't upload a file via GET
  3860. break;
  3861. }
  3862. $_SESSION[$LEM->sessid][$knownVar['sgqa']] = $value;
  3863. $LEM->updatedValues[$knownVar['sgqa']]=array(
  3864. 'type'=>$knownVar['type'],
  3865. 'value'=>$value,
  3866. );
  3867. }
  3868. $LEM->_UpdateValuesInDatabase(NULL);
  3869. }
  3870. return array(
  3871. 'hasNext'=>true,
  3872. 'hasPrevious'=>false,
  3873. );
  3874. }
  3875. static function NavigateBackwards()
  3876. {
  3877. $now = microtime(true);
  3878. $LEM =& LimeExpressionManager::singleton();
  3879. $LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
  3880. $LEM->updatedValues = array();
  3881. switch ($LEM->surveyMode)
  3882. {
  3883. case 'survey':
  3884. // should never be called?
  3885. break;
  3886. case 'group':
  3887. // First validate the current group
  3888. $LEM->StartProcessingPage();
  3889. $updatedValues=$LEM->ProcessCurrentResponses();
  3890. $message = '';
  3891. while (true)
  3892. {
  3893. $LEM->currentQset = array(); // reset active list of questions
  3894. if (--$LEM->currentGroupSeq < 0)
  3895. {
  3896. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  3897. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3898. $LEM->lastMoveResult = array(
  3899. 'at_start'=>true,
  3900. 'finished'=>false,
  3901. 'message'=>$message,
  3902. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  3903. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  3904. );
  3905. return $LEM->lastMoveResult;
  3906. }
  3907. $result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
  3908. if (is_null($result)) {
  3909. continue; // this is an invalid group - skip it
  3910. }
  3911. $message .= $result['message'];
  3912. if (!$result['relevant'] || $result['hidden'])
  3913. {
  3914. // then skip this group - assume already saved?
  3915. continue;
  3916. }
  3917. else
  3918. {
  3919. // display new group
  3920. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  3921. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3922. $LEM->lastMoveResult = array(
  3923. 'at_start'=>false,
  3924. 'finished'=>false,
  3925. 'message'=>$message,
  3926. 'gseq'=>$LEM->currentGroupSeq,
  3927. 'seq'=>$LEM->currentGroupSeq,
  3928. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  3929. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  3930. 'unansweredSQs'=>$result['unansweredSQs'],
  3931. 'invalidSQs'=>$result['invalidSQs'],
  3932. );
  3933. return $LEM->lastMoveResult;
  3934. }
  3935. }
  3936. break;
  3937. case 'question':
  3938. $LEM->StartProcessingPage();
  3939. $updatedValues=$LEM->ProcessCurrentResponses();
  3940. $message = '';
  3941. while (true)
  3942. {
  3943. $LEM->currentQset = array(); // reset active list of questions
  3944. if (--$LEM->currentQuestionSeq < 0)
  3945. {
  3946. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  3947. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3948. $LEM->lastMoveResult = array(
  3949. 'at_start'=>true,
  3950. 'finished'=>false,
  3951. 'message'=>$message,
  3952. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  3953. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  3954. );
  3955. return $LEM->lastMoveResult;
  3956. }
  3957. // Set certain variables normally set by StartProcessingGroup()
  3958. $LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
  3959. $qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
  3960. $LEM->currentQID=$qInfo['qid'];
  3961. $LEM->currentGroupSeq=$qInfo['gseq'];
  3962. if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
  3963. $LEM->maxGroupSeq = $LEM->currentGroupSeq;
  3964. }
  3965. $LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
  3966. $LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
  3967. $result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
  3968. $message .= $result['message'];
  3969. $gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
  3970. $grel = $gRelInfo['result'];
  3971. if (!$grel || !$result['relevant'] || $result['hidden'])
  3972. {
  3973. // then skip this question - assume already saved?
  3974. continue;
  3975. }
  3976. else
  3977. {
  3978. // display new question
  3979. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  3980. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  3981. return array(
  3982. 'at_start'=>false,
  3983. 'finished'=>false,
  3984. 'message'=>$message,
  3985. 'gseq'=>$LEM->currentGroupSeq,
  3986. 'seq'=>$LEM->currentQuestionSeq,
  3987. 'qseq'=>$LEM->currentQuestionSeq,
  3988. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  3989. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  3990. 'unansweredSQs'=>$result['unansweredSQs'],
  3991. 'invalidSQs'=>$result['invalidSQs'],
  3992. );
  3993. }
  3994. }
  3995. break;
  3996. }
  3997. }
  3998. /**
  3999. *
  4000. * @param <type> $force - if true, continue to go forward even if there are violations to the mandatory and/or validity rules
  4001. */
  4002. static function NavigateForwards($force=false) {
  4003. $now = microtime(true);
  4004. $LEM =& LimeExpressionManager::singleton();
  4005. $LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
  4006. $LEM->updatedValues = array();
  4007. switch ($LEM->surveyMode)
  4008. {
  4009. case 'survey':
  4010. $startingGroup = $LEM->currentGroupSeq;
  4011. $LEM->StartProcessingPage(true);
  4012. $updatedValues=$LEM->ProcessCurrentResponses();
  4013. $message = '';
  4014. $LEM->currentQset = array(); // reset active list of questions
  4015. $result = $LEM->_ValidateSurvey();
  4016. $message .= $result['message'];
  4017. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4018. if (!$force && !is_null($result) && ($result['mandViolation'] || !$result['valid'] || $startingGroup == -1))
  4019. {
  4020. $finished=false;
  4021. }
  4022. else
  4023. {
  4024. $finished = true;
  4025. }
  4026. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,$finished);
  4027. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4028. $LEM->lastMoveResult = array(
  4029. 'finished'=>$finished,
  4030. 'message'=>$message,
  4031. 'gseq'=>1,
  4032. 'seq'=>1,
  4033. 'mandViolation'=>$result['mandViolation'],
  4034. 'valid'=>$result['valid'],
  4035. 'unansweredSQs'=>$result['unansweredSQs'],
  4036. 'invalidSQs'=>$result['invalidSQs'],
  4037. );
  4038. return $LEM->lastMoveResult;
  4039. break;
  4040. case 'group':
  4041. // First validate the current group
  4042. $LEM->StartProcessingPage();
  4043. $updatedValues=$LEM->ProcessCurrentResponses();
  4044. $message = '';
  4045. if (!$force && $LEM->currentGroupSeq != -1)
  4046. {
  4047. $result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
  4048. $message .= $result['message'];
  4049. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4050. if (!is_null($result) && ($result['mandViolation'] || !$result['valid']))
  4051. {
  4052. // redisplay the current group
  4053. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4054. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4055. $LEM->lastMoveResult = array(
  4056. 'finished'=>false,
  4057. 'message'=>$message,
  4058. 'gseq'=>$LEM->currentGroupSeq,
  4059. 'seq'=>$LEM->currentGroupSeq,
  4060. 'mandViolation'=>$result['mandViolation'],
  4061. 'valid'=>$result['valid'],
  4062. 'unansweredSQs'=>$result['unansweredSQs'],
  4063. 'invalidSQs'=>$result['invalidSQs'],
  4064. );
  4065. return $LEM->lastMoveResult;
  4066. }
  4067. }
  4068. while (true)
  4069. {
  4070. $LEM->currentQset = array(); // reset active list of questions
  4071. if (++$LEM->currentGroupSeq >= $LEM->numGroups)
  4072. {
  4073. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
  4074. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4075. $LEM->lastMoveResult = array(
  4076. 'finished'=>true,
  4077. 'message'=>$message,
  4078. 'gseq'=>$LEM->currentGroupSeq,
  4079. 'seq'=>$LEM->currentGroupSeq,
  4080. 'mandViolation'=>(isset($result['mandViolation']) ? $result['mandViolation'] : false),
  4081. 'valid'=>(isset($result['valid']) ? $result['valid'] : false),
  4082. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  4083. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  4084. );
  4085. return $LEM->lastMoveResult;
  4086. }
  4087. $result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
  4088. if (is_null($result)) {
  4089. continue; // this is an invalid group - skip it
  4090. }
  4091. $message .= $result['message'];
  4092. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4093. if (!$result['relevant'] || $result['hidden'])
  4094. {
  4095. // then skip this group
  4096. continue;
  4097. }
  4098. else
  4099. {
  4100. // display new group
  4101. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4102. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4103. $LEM->lastMoveResult = array(
  4104. 'finished'=>false,
  4105. 'message'=>$message,
  4106. 'gseq'=>$LEM->currentGroupSeq,
  4107. 'seq'=>$LEM->currentGroupSeq,
  4108. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4109. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4110. 'unansweredSQs'=>$result['unansweredSQs'],
  4111. 'invalidSQs'=>$result['invalidSQs'],
  4112. );
  4113. return $LEM->lastMoveResult;
  4114. }
  4115. }
  4116. break;
  4117. case 'question':
  4118. $LEM->StartProcessingPage();
  4119. $updatedValues=$LEM->ProcessCurrentResponses();
  4120. $message = '';
  4121. if (!$force && $LEM->currentQuestionSeq != -1)
  4122. {
  4123. $result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
  4124. $message .= $result['message'];
  4125. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4126. $gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
  4127. $grel = $gRelInfo['result'];
  4128. if ($grel && !is_null($result) && ($result['mandViolation'] || !$result['valid']))
  4129. {
  4130. // redisplay the current question
  4131. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4132. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4133. $LEM->lastMoveResult = array(
  4134. 'finished'=>false,
  4135. 'message'=>$message,
  4136. 'qseq'=>$LEM->currentQuestionSeq,
  4137. 'gseq'=>$LEM->currentGroupSeq,
  4138. 'seq'=>$LEM->currentQuestionSeq,
  4139. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4140. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4141. 'unansweredSQs'=>$result['unansweredSQs'],
  4142. 'invalidSQs'=>$result['invalidSQs'],
  4143. );
  4144. return $LEM->lastMoveResult;
  4145. }
  4146. }
  4147. while (true)
  4148. {
  4149. $LEM->currentQset = array(); // reset active list of questions
  4150. if (++$LEM->currentQuestionSeq >= $LEM->numQuestions)
  4151. {
  4152. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
  4153. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4154. $LEM->lastMoveResult = array(
  4155. 'finished'=>true,
  4156. 'message'=>$message,
  4157. 'qseq'=>$LEM->currentQuestionSeq,
  4158. 'gseq'=>$LEM->currentGroupSeq,
  4159. 'seq'=>$LEM->currentQuestionSeq,
  4160. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4161. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4162. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  4163. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  4164. );
  4165. return $LEM->lastMoveResult;
  4166. }
  4167. // Set certain variables normally set by StartProcessingGroup()
  4168. $LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
  4169. $qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
  4170. $LEM->currentQID=$qInfo['qid'];
  4171. $LEM->currentGroupSeq=$qInfo['gseq'];
  4172. if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
  4173. $LEM->maxGroupSeq = $LEM->currentGroupSeq;
  4174. }
  4175. $LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
  4176. $LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
  4177. $result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
  4178. $message .= $result['message'];
  4179. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4180. $gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
  4181. $grel = $gRelInfo['result'];
  4182. if (!$grel || !$result['relevant'] || $result['hidden'])
  4183. {
  4184. // then skip this question - assume already saved?
  4185. continue;
  4186. }
  4187. else
  4188. {
  4189. // display new question
  4190. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4191. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4192. $LEM->lastMoveResult = array(
  4193. 'finished'=>false,
  4194. 'message'=>$message,
  4195. 'qseq'=>$LEM->currentQuestionSeq,
  4196. 'gseq'=>$LEM->currentGroupSeq,
  4197. 'seq'=>$LEM->currentQuestionSeq,
  4198. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4199. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4200. 'unansweredSQs'=>$result['unansweredSQs'],
  4201. 'invalidSQs'=>$result['invalidSQs'],
  4202. );
  4203. return $LEM->lastMoveResult;
  4204. }
  4205. }
  4206. break;
  4207. }
  4208. }
  4209. /**
  4210. * Write values to database.
  4211. * @param <type> $updatedValues
  4212. * @param <boolean> $finished - true if the survey needs to be finalized
  4213. */
  4214. private function _UpdateValuesInDatabase($updatedValues, $finished=false)
  4215. {
  4216. // TODO - now that using $this->updatedValues, may be able to remove local copies of it (unless needed by other sub-systems)
  4217. $updatedValues = $this->updatedValues;
  4218. $message = '';
  4219. $_SESSION[$this->sessid]['datestamp']=dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']);
  4220. if ($this->surveyOptions['active'] && !isset($_SESSION[$this->sessid]['srid']))
  4221. {
  4222. // Create initial insert row for this record
  4223. $today = dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']);
  4224. $sdata = array(
  4225. "startlanguage"=>$this->surveyOptions['startlanguage'],
  4226. );
  4227. if ($this->surveyOptions['anonymized'] == false)
  4228. {
  4229. $sdata = array_merge($sdata,array("token"=>($this->surveyOptions['token'])));
  4230. }
  4231. if ($this->surveyOptions['datestamp'] == true)
  4232. {
  4233. $sdata = array_merge($sdata, array(
  4234. "datestamp"=>($this->surveyOptions['datestamp'] ? $_SESSION[$this->sessid]['datestamp'] : NULL),
  4235. "startdate"=>($this->surveyOptions['datestamp'] ? $_SESSION[$this->sessid]['datestamp'] : date("Y-m-d H:i:s",0))
  4236. ));
  4237. }
  4238. if ($this->surveyOptions['ipaddr'] == true)
  4239. {
  4240. $sdata = array_merge($sdata,array("ipaddr"=>getIPAddress()));
  4241. }
  4242. if ($this->surveyOptions['refurl'] == true)
  4243. {
  4244. $sdata = array_merge($sdata,array("refurl"=>(($this->surveyOptions['refurl']) ? getenv("HTTP_REFERER") : NULL)));
  4245. }
  4246. $sdata = array_filter($sdata);
  4247. Survey_dynamic::sid($this->sid);
  4248. $oSurvey = new Survey_dynamic;
  4249. $iNewID = $oSurvey->insertRecords($sdata);
  4250. if ($iNewID) // Checked
  4251. {
  4252. $srid = $iNewID;
  4253. $_SESSION[$this->sessid]['srid'] = $iNewID;
  4254. }
  4255. else
  4256. {
  4257. $message .= $this->gT("Unable to insert record into survey table"); // TODO - add SQL error?
  4258. }
  4259. //Insert Row for Timings, if needed
  4260. if ($this->surveyOptions['savetimings']) {
  4261. Survey_timings::sid($this->sid);
  4262. $oSurveyTimings = new Survey_timings;
  4263. $tdata = array(
  4264. 'id'=>$srid,
  4265. 'interviewtime'=>0
  4266. );
  4267. switchMSSQLIdentityInsert("survey_{$this->sid}_timings", true);
  4268. $iNewID = $oSurveyTimings->insertRecords($tdata);
  4269. switchMSSQLIdentityInsert("survey_{$this->sid}_timings", false);
  4270. }
  4271. }
  4272. if (count($updatedValues) > 0 || $finished)
  4273. {
  4274. $query = 'UPDATE ' . $this->surveyOptions['tablename'] . ' SET ';
  4275. $setter = array();
  4276. switch ($this->surveyMode)
  4277. {
  4278. case 'question':
  4279. $thisstep = $this->currentQuestionSeq;
  4280. break;
  4281. case 'group':
  4282. $thisstep = $this->currentGroupSeq;
  4283. break;
  4284. case 'survey':
  4285. $thisstep = 1;
  4286. break;
  4287. }
  4288. $setter[] = dbQuoteID('lastpage') . "=" . dbQuoteAll($thisstep);
  4289. if ($this->surveyOptions['datestamp'] && isset($_SESSION[$this->sessid]['datestamp'])) {
  4290. $setter[] = dbQuoteID('datestamp') . "=" . dbQuoteAll($_SESSION[$this->sessid]['datestamp']);
  4291. }
  4292. if ($this->surveyOptions['ipaddr']) {
  4293. $setter[] = dbQuoteID('ipaddr') . "=" . dbQuoteAll(getIPAddress());
  4294. }
  4295. foreach ($updatedValues as $key=>$value)
  4296. {
  4297. $val = (is_null($value) ? NULL : $value['value']);
  4298. $type = (is_null($value) ? NULL : $value['type']);
  4299. // Clean up the values to cope with database storage requirements
  4300. switch($type)
  4301. {
  4302. case 'D': //DATE
  4303. if (trim($val)=='') {
  4304. $val=NULL; // since some databases can't store blanks in date fields
  4305. }
  4306. // otherwise will already be in yyyy-mm-dd format after ProcessCurrentResponses()
  4307. break;
  4308. case '|': //File upload
  4309. // This block can be removed once we require 5.3 or later
  4310. if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
  4311. $val=addslashes($val);
  4312. }
  4313. break;
  4314. case 'N': //NUMERICAL QUESTION TYPE
  4315. case 'K': //MULTIPLE NUMERICAL QUESTION
  4316. if (trim($val)=='') {
  4317. $val=NULL; // since some databases can't store blanks in numerical inputs
  4318. }
  4319. break;
  4320. default:
  4321. break;
  4322. }
  4323. if (is_null($val))
  4324. {
  4325. $setter[] = dbQuoteID($key) . "=NULL";
  4326. }
  4327. else
  4328. {
  4329. $setter[] = dbQuoteID($key) . "=" . dbQuoteAll($val);
  4330. }
  4331. }
  4332. $query .= implode(', ', $setter);
  4333. $query .= " WHERE ID=";
  4334. if (isset($_SESSION[$this->sessid]['srid']) && $this->surveyOptions['active'])
  4335. {
  4336. $query .= $_SESSION[$this->sessid]['srid'];
  4337. if (!dbExecuteAssoc($query))
  4338. {
  4339. echo submitfailed(''); // TODO - report SQL error?
  4340. if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
  4341. $message .= $this->gT('Error in SQL update'); // TODO - add SQL error?
  4342. }
  4343. }
  4344. // Save Timings if needed
  4345. if ($this->surveyOptions['savetimings']) {
  4346. Yii::import("application.libraries.Save");
  4347. $cSave = new Save();
  4348. $cSave->set_answer_time();
  4349. }
  4350. if ($finished)
  4351. {
  4352. // Delete the save control record if successfully finalize the submission
  4353. $query = "DELETE FROM {{saved_control}} where srid=".$_SESSION[$this->sessid]['srid'].' and sid='.$this->sid;
  4354. Yii::app()->db->createCommand($query)->execute();
  4355. if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
  4356. $message .= ';<br />'.$query;
  4357. }
  4358. }
  4359. else if ($this->surveyOptions['allowsave'] && isset($_SESSION[$this->sessid]['scid']))
  4360. {
  4361. Saved_control::model()->updateByPk($_SESSION[$this->sessid]['scid'], array('saved_thisstep'=>$thisstep));
  4362. }
  4363. // Check Quotas
  4364. $bQuotaMatched = false;
  4365. $aQuotas = checkQuota('return', $this->sid);
  4366. if ($aQuotas !== false)
  4367. {
  4368. if ($aQuotas != false)
  4369. {
  4370. foreach ($aQuotas as $aQuota)
  4371. {
  4372. if (isset($aQuota['status']) && $aQuota['status'] == 'matched') {
  4373. $bQuotaMatched = true;
  4374. }
  4375. }
  4376. }
  4377. }
  4378. if ($bQuotaMatched)
  4379. {
  4380. checkQuota('enforce',$this->sid); // will create a page and quit.
  4381. }
  4382. else
  4383. {
  4384. if ($finished) {
  4385. $sQuery = 'UPDATE '.$this->surveyOptions['tablename'] . " SET ";
  4386. if($this->surveyOptions['datestamp'])
  4387. {
  4388. // Replace with date("Y-m-d H:i:s") ? See timeadjust
  4389. $sQuery .= dbQuoteID('submitdate') . "=" . dbQuoteAll($_SESSION[$this->sessid]['datestamp']);
  4390. }
  4391. else
  4392. {
  4393. $sQuery .= dbQuoteID('submitdate') . "=" . dbQuoteAll(date("Y-m-d H:i:s",mktime(0,0,0,1,1,1980)));
  4394. }
  4395. $sQuery .= " WHERE ID=".$_SESSION[$this->sessid]['srid'];
  4396. dbExecuteAssoc($sQuery); // Checked
  4397. }
  4398. }
  4399. }
  4400. if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
  4401. $message .= $query;
  4402. }
  4403. }
  4404. return $message;
  4405. }
  4406. /**
  4407. * Get last move information, optionally clearing the substitution cache
  4408. * @param type $clearSubstitutionInfo
  4409. * @return type
  4410. */
  4411. static function GetLastMoveResult($clearSubstitutionInfo=false)
  4412. {
  4413. $LEM =& LimeExpressionManager::singleton();
  4414. if ($clearSubstitutionInfo)
  4415. {
  4416. $LEM->em->ClearSubstitutionInfo(); // need to avoid double-generation of tailoring info
  4417. }
  4418. return (isset($LEM->lastMoveResult) ? $LEM->lastMoveResult : NULL);
  4419. }
  4420. /**
  4421. * Jump to a specific question or group sequence. If jumping forward, it re-validates everything in between
  4422. * @param <type> $seq
  4423. * @param <type> $force - if true, then skip validation of current group (e.g. will jump even if there are errors)
  4424. * @param <type> $preview - if true, then treat this group/question as relevant, even if it is not, so that it can be displayed
  4425. * @return <type>
  4426. */
  4427. static function JumpTo($seq,$preview=false,$processPOST=true,$force=false,$changeLang=false) {
  4428. $now = microtime(true);
  4429. $LEM =& LimeExpressionManager::singleton();
  4430. if ($changeLang)
  4431. {
  4432. $LEM->setVariableAndTokenMappingsForExpressionManager($LEM->sid,true,$LEM->surveyOptions['anonymized'],$LEM->allOnOnePage);
  4433. }
  4434. $LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
  4435. $LEM->updatedValues = array();
  4436. --$seq; // convert to 0-based numbering
  4437. switch ($LEM->surveyMode)
  4438. {
  4439. case 'survey':
  4440. // This only happens if saving data so far, so don't want to submit it, just validate and return
  4441. $startingGroup = $LEM->currentGroupSeq;
  4442. $LEM->StartProcessingPage(true);
  4443. if ($processPOST) {
  4444. $updatedValues=$LEM->ProcessCurrentResponses();
  4445. }
  4446. else {
  4447. $updatedValues = array();
  4448. }
  4449. $message = '';
  4450. $LEM->currentQset = array(); // reset active list of questions
  4451. $result = $LEM->_ValidateSurvey();
  4452. $message .= $result['message'];
  4453. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4454. $finished=false;
  4455. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,$finished);
  4456. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4457. $LEM->lastMoveResult = array(
  4458. 'finished'=>$finished,
  4459. 'message'=>$message,
  4460. 'gseq'=>1,
  4461. 'seq'=>1,
  4462. 'mandViolation'=>$result['mandViolation'],
  4463. 'valid'=>$result['valid'],
  4464. 'unansweredSQs'=>$result['unansweredSQs'],
  4465. 'invalidSQs'=>$result['invalidSQs'],
  4466. );
  4467. return $LEM->lastMoveResult;
  4468. break;
  4469. case 'group':
  4470. // First validate the current group
  4471. $LEM->StartProcessingPage();
  4472. if ($processPOST) {
  4473. $updatedValues=$LEM->ProcessCurrentResponses();
  4474. }
  4475. else {
  4476. $updatedValues = array();
  4477. }
  4478. $message = '';
  4479. if (!$force && $LEM->currentGroupSeq != -1 && $seq > $LEM->currentGroupSeq) // only re-validate if jumping forward
  4480. {
  4481. $result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
  4482. $message .= $result['message'];
  4483. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4484. if (!is_null($result) && ($result['mandViolation'] || !$result['valid']))
  4485. {
  4486. // redisplay the current group
  4487. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4488. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4489. $LEM->lastMoveResult = array(
  4490. 'finished'=>false,
  4491. 'message'=>$message,
  4492. 'gseq'=>$LEM->currentGroupSeq,
  4493. 'seq'=>$LEM->currentGroupSeq,
  4494. 'mandViolation'=>$result['mandViolation'],
  4495. 'valid'=>$result['valid'],
  4496. 'unansweredSQs'=>$result['unansweredSQs'],
  4497. 'invalidSQs'=>$result['invalidSQs'],
  4498. );
  4499. return $LEM->lastMoveResult;
  4500. }
  4501. }
  4502. if ($seq <= $LEM->currentGroupSeq || $preview) {
  4503. $LEM->currentGroupSeq = $seq-1; // Try to jump to the requested group, but navigate to next if needed
  4504. }
  4505. while (true)
  4506. {
  4507. $LEM->currentQset = array(); // reset active list of questions
  4508. if (++$LEM->currentGroupSeq >= $LEM->numGroups)
  4509. {
  4510. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
  4511. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4512. $LEM->lastMoveResult = array(
  4513. 'finished'=>true,
  4514. 'message'=>$message,
  4515. 'gseq'=>$LEM->currentGroupSeq,
  4516. 'seq'=>$LEM->currentGroupSeq,
  4517. 'mandViolation'=>(isset($result['mandViolation']) ? $result['mandViolation'] : false),
  4518. 'valid'=>(isset($result['valid']) ? $result['valid'] : false),
  4519. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  4520. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  4521. );
  4522. return $LEM->lastMoveResult;
  4523. }
  4524. $result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
  4525. if (is_null($result)) {
  4526. return NULL; // invalid group - either bad number, or no questions within it
  4527. }
  4528. $message .= $result['message'];
  4529. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4530. if (!$preview && (!$result['relevant'] || $result['hidden']))
  4531. {
  4532. // then skip this group - assume already saved?
  4533. continue;
  4534. }
  4535. elseif (!($result['mandViolation'] || !$result['valid']) && $LEM->currentGroupSeq < $seq) {
  4536. // if there is a violation while moving forward, need to stop and ask that set of questions
  4537. // if there are no violations, can skip this group as long as changed values are saved.
  4538. continue;
  4539. }
  4540. else
  4541. {
  4542. // display new group
  4543. if(!$preview){ // Save only if not in preview mode
  4544. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4545. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4546. }
  4547. $LEM->lastMoveResult = array(
  4548. 'finished'=>false,
  4549. 'message'=>$message,
  4550. 'gseq'=>$LEM->currentGroupSeq,
  4551. 'seq'=>$LEM->currentGroupSeq,
  4552. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4553. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4554. 'unansweredSQs'=>$result['unansweredSQs'],
  4555. 'invalidSQs'=>$result['invalidSQs'],
  4556. );
  4557. return $LEM->lastMoveResult;
  4558. }
  4559. }
  4560. break;
  4561. case 'question':
  4562. $LEM->StartProcessingPage();
  4563. if ($processPOST) {
  4564. $updatedValues=$LEM->ProcessCurrentResponses();
  4565. }
  4566. else {
  4567. $updatedValues = array();
  4568. }
  4569. $message = '';
  4570. if (!$force && $LEM->currentQuestionSeq != -1 && $seq > $LEM->currentQuestionSeq)
  4571. {
  4572. $result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
  4573. $message .= $result['message'];
  4574. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4575. $gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
  4576. $grel = $gRelInfo['result'];
  4577. if ($grel && ($result['mandViolation'] || !$result['valid']))
  4578. {
  4579. // redisplay the current question
  4580. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4581. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4582. $LEM->lastMoveResult = array(
  4583. 'finished'=>false,
  4584. 'message'=>$message,
  4585. 'qseq'=>$LEM->currentQuestionSeq,
  4586. 'gseq'=>$LEM->currentGroupSeq,
  4587. 'seq'=>$LEM->currentQuestionSeq,
  4588. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4589. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4590. 'unansweredSQs'=>$result['unansweredSQs'],
  4591. 'invalidSQs'=>$result['invalidSQs'],
  4592. );
  4593. return $LEM->lastMoveResult;
  4594. }
  4595. }
  4596. if ($seq <= $LEM->currentQuestionSeq || $preview) {
  4597. $LEM->currentQuestionSeq = $seq-1; // Try to jump to the requested group, but navigate to next if needed
  4598. }
  4599. while (true)
  4600. {
  4601. $LEM->currentQset = array(); // reset active list of questions
  4602. if (++$LEM->currentQuestionSeq >= $LEM->numQuestions)
  4603. {
  4604. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
  4605. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4606. $LEM->lastMoveResult = array(
  4607. 'finished'=>true,
  4608. 'message'=>$message,
  4609. 'qseq'=>$LEM->currentQuestionSeq,
  4610. 'gseq'=>$LEM->currentGroupSeq,
  4611. 'seq'=>$LEM->currentQuestionSeq,
  4612. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4613. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4614. 'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
  4615. 'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
  4616. );
  4617. return $LEM->lastMoveResult;
  4618. }
  4619. // Set certain variables normally set by StartProcessingGroup()
  4620. $LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
  4621. if (!isset($LEM->questionSeq2relevance[$LEM->currentQuestionSeq])) {
  4622. return NULL; // means an invalid question - probably no sub-quetions
  4623. }
  4624. $qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
  4625. $LEM->currentQID=$qInfo['qid'];
  4626. $LEM->currentGroupSeq=$qInfo['gseq'];
  4627. if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
  4628. $LEM->maxGroupSeq = $LEM->currentGroupSeq;
  4629. }
  4630. $LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
  4631. $LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
  4632. $result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
  4633. $message .= $result['message'];
  4634. $updatedValues = array_merge($updatedValues,$result['updatedValues']);
  4635. $gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
  4636. $grel = $gRelInfo['result'];
  4637. if (!$preview && (!$grel || !$result['relevant'] || $result['hidden']))
  4638. {
  4639. // then skip this question
  4640. continue;
  4641. }
  4642. else if (!$preview && !$grel)
  4643. {
  4644. continue;
  4645. }
  4646. else if (!$preview && !($result['mandViolation'] || !$result['valid']) && $LEM->currentQuestionSeq < $seq)
  4647. {
  4648. // if there is a violation while moving forward, need to stop and ask that set of questions
  4649. // if there are no violations, can skip this group as long as changed values are saved.
  4650. continue;
  4651. }
  4652. else
  4653. {
  4654. // display new question
  4655. $message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
  4656. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  4657. $LEM->lastMoveResult = array(
  4658. 'finished'=>false,
  4659. 'message'=>$message,
  4660. 'qseq'=>$LEM->currentQuestionSeq,
  4661. 'gseq'=>$LEM->currentGroupSeq,
  4662. 'seq'=>$LEM->currentQuestionSeq,
  4663. 'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
  4664. 'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
  4665. 'unansweredSQs'=>$result['unansweredSQs'],
  4666. 'invalidSQs'=>$result['invalidSQs'],
  4667. );
  4668. return $LEM->lastMoveResult;
  4669. }
  4670. }
  4671. break;
  4672. }
  4673. }
  4674. /**
  4675. * Check the entire survey
  4676. * @return <type>
  4677. */
  4678. private function _ValidateSurvey()
  4679. {
  4680. $LEM =& $this;
  4681. $message = '';
  4682. $srel=false;
  4683. $shidden=true;
  4684. $smandViolation=false;
  4685. $svalid=true;
  4686. $unansweredSQs = array();
  4687. $invalidSQs = array();
  4688. $updatedValues = array();
  4689. $sanyUnanswered = false;
  4690. ///////////////////////////////////////////////////////
  4691. // CHECK EACH GROUP, AND SET SURVEY-LEVEL PROPERTIES //
  4692. ///////////////////////////////////////////////////////
  4693. for ($i=0;$i<$LEM->numGroups;++$i) {
  4694. $LEM->currentGroupSeq=$i;
  4695. $gStatus = $LEM->_ValidateGroup($i);
  4696. if (is_null($gStatus)) {
  4697. continue; // invalid group, so skip it
  4698. }
  4699. $message .= $gStatus['message'];
  4700. if ($gStatus['relevant']) {
  4701. $srel = true;
  4702. }
  4703. if ($gStatus['relevant'] && !$gStatus['hidden']) {
  4704. $shidden=false;
  4705. }
  4706. if ($gStatus['relevant'] && !$gStatus['hidden'] && $gStatus['mandViolation']) {
  4707. $smandViolation = true;
  4708. }
  4709. if ($gStatus['relevant'] && !$gStatus['hidden'] && !$gStatus['valid']) {
  4710. $svalid=false;
  4711. }
  4712. if ($gStatus['anyUnanswered']) {
  4713. $sanyUnanswered = true;
  4714. }
  4715. if (strlen($gStatus['unansweredSQs']) > 0) {
  4716. $unansweredSQs = array_merge($unansweredSQs, explode('|',$gStatus['unansweredSQs']));
  4717. }
  4718. if (strlen($gStatus['invalidSQs']) > 0) {
  4719. $invalidSQs = array_merge($invalidSQs, explode('|',$gStatus['invalidSQs']));
  4720. }
  4721. $updatedValues = array_merge($updatedValues, $gStatus['updatedValues']);
  4722. // array_merge destroys the key, so do it manually
  4723. foreach ($gStatus['qset'] as $key=>$value) {
  4724. $LEM->currentQset[$key] = $value;
  4725. }
  4726. $LEM->FinishProcessingGroup();
  4727. }
  4728. return array(
  4729. 'relevant' => $srel,
  4730. 'hidden' => $shidden,
  4731. 'mandViolation' => $smandViolation,
  4732. 'valid' => $svalid,
  4733. 'anyUnanswered' => $sanyUnanswered,
  4734. 'message' => $message,
  4735. 'unansweredSQs' => implode('|',$unansweredSQs),
  4736. 'invalidSQs' => implode('|',$invalidSQs),
  4737. 'updatedValues' => $updatedValues,
  4738. 'seq'=>1,
  4739. );
  4740. }
  4741. /**
  4742. * Check a group and all of the questions it contains
  4743. * @param <type> $groupSeq - the index-0 sequence number for this group
  4744. * @return <array> - detailed information about this group
  4745. */
  4746. function _ValidateGroup($groupSeq)
  4747. {
  4748. $LEM =& $this;
  4749. if ($groupSeq < 0 || $groupSeq >= $LEM->numGroups) {
  4750. return NULL; // TODO - what is desired behavior?
  4751. }
  4752. $groupSeqInfo = (isset($LEM->groupSeqInfo[$groupSeq]) ? $LEM->groupSeqInfo[$groupSeq] : NULL);
  4753. if (is_null($groupSeqInfo)) {
  4754. // then there are no questions in this group
  4755. return NULL;
  4756. }
  4757. $qInfo = $LEM->questionSeq2relevance[$groupSeqInfo['qstart']];
  4758. $gseq = $qInfo['gseq'];
  4759. $gid = $qInfo['gid'];
  4760. $LEM->StartProcessingGroup($gseq, $LEM->surveyOptions['anonymized'], $LEM->sid); // analyze the data we have about this group
  4761. $grel=false; // assume irrelevant until find a relevant question
  4762. $ghidden=true; // assume hidden until find a non-hidden question. If there are no relevant questions on this page, $ghidden will stay true
  4763. $gmandViolation=false; // assume that the group contains no manditory questions that have not been fully answered
  4764. $gvalid=true; // assume valid until discover otherwise
  4765. $debug_message = '';
  4766. $messages = array();
  4767. $currentQset = array();
  4768. $unansweredSQs = array();
  4769. $invalidSQs = array();
  4770. $updatedValues = array();
  4771. $ganyUnanswered = false;
  4772. $gRelInfo = $LEM->gRelInfo[$groupSeq];
  4773. /////////////////////////////////////////////////////////
  4774. // CHECK EACH QUESTION, AND SET GROUP-LEVEL PROPERTIES //
  4775. /////////////////////////////////////////////////////////
  4776. for ($i=$groupSeqInfo['qstart'];$i<=$groupSeqInfo['qend']; ++$i)
  4777. {
  4778. $qStatus = $LEM->_ValidateQuestion($i);
  4779. $updatedValues = array_merge($updatedValues,$qStatus['updatedValues']);
  4780. if ($gRelInfo['result']==true && $qStatus['relevant']==true) {
  4781. $grel = $gRelInfo['result']; // true; // at least one question relevant
  4782. }
  4783. if ($qStatus['hidden']==false && $qStatus['relevant'] == true) {
  4784. $ghidden=false; // at least one question is visible
  4785. }
  4786. if ($qStatus['relevant']==true && $qStatus['hidden']==false && $qStatus['mandViolation']==true) {
  4787. $gmandViolation=true; // at least one relevant question fails mandatory test
  4788. }
  4789. if ($qStatus['anyUnanswered']==true) {
  4790. $ganyUnanswered=true;
  4791. }
  4792. if ($qStatus['relevant']==true && $qStatus['hidden']==false && $qStatus['valid']==false) {
  4793. $gvalid=false; // at least one question fails validity constraints
  4794. }
  4795. $currentQset[$qStatus['info']['qid']] = $qStatus;
  4796. $messages[] = $qStatus['message'];
  4797. if (strlen($qStatus['unansweredSQs']) > 0) {
  4798. $unansweredSQs[] = $qStatus['unansweredSQs'];
  4799. }
  4800. if (strlen($qStatus['invalidSQs']) > 0) {
  4801. $invalidSQs[] = $qStatus['invalidSQs'];
  4802. }
  4803. }
  4804. $unansweredSQList = implode('|',$unansweredSQs);
  4805. $invalidSQList = implode('|',$invalidSQs);
  4806. /////////////////////////////////////////////////////////
  4807. // OPTIONALLY DISPLAY (DETAILED) DEBUGGING INFORMATION //
  4808. /////////////////////////////////////////////////////////
  4809. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY)
  4810. {
  4811. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid);
  4812. $debug_message .= '<br />[G#' . $LEM->currentGroupSeq . ']'
  4813. . '[' . $groupSeqInfo['qstart'] . '-' . $groupSeqInfo['qend'] . ']'
  4814. . "[<a href='$editlink'>"
  4815. . 'GID:' . $gid . "</a>]: "
  4816. . ($grel ? 'relevant ' : " <span style='color:red'>irrelevant</span> ")
  4817. . (($gRelInfo['eqn'] != '') ? $gRelInfo['prettyprint'] : '')
  4818. . (($ghidden && $grel) ? " <span style='color:red'>always-hidden</span> " : ' ')
  4819. . ($gmandViolation ? " <span style='color:red'>(missing a relevant mandatory)</span> " : ' ')
  4820. . ($gvalid ? '' : " <span style='color:red'>(fails at least one validation rule)</span> ")
  4821. . "<br />\n"
  4822. . implode('', $messages);
  4823. if ($grel == true)
  4824. {
  4825. if (!$gvalid)
  4826. {
  4827. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  4828. {
  4829. $debug_message .= "**At least one relevant question was invalid, so re-show this group<br />\n";
  4830. $debug_message .= "**Validity Violators: " . implode(', ', explode('|',$invalidSQList)) . "<br />\n";
  4831. }
  4832. }
  4833. if ($gmandViolation)
  4834. {
  4835. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  4836. {
  4837. $debug_message .= "**At least one relevant question was mandatory but unanswered, so re-show this group<br />\n";
  4838. $debug_message .= '**Mandatory Violators: ' . implode(', ', explode('|',$unansweredSQList)). "<br />\n";
  4839. }
  4840. }
  4841. if ($ghidden == true)
  4842. {
  4843. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  4844. {
  4845. $debug_message .= '** Page is relevant but hidden, so NULL irrelevant values and save relevant Equation results:</br>';
  4846. }
  4847. }
  4848. }
  4849. else
  4850. {
  4851. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  4852. {
  4853. $debug_message .= '** Page is irrelevant, so NULL all questions in this group<br />';
  4854. }
  4855. }
  4856. }
  4857. //////////////////////////////////////////////////////////////////////////
  4858. // STORE METADATA NEEDED FOR SUBSEQUENT PROCESSING AND DISPLAY PURPOSES //
  4859. //////////////////////////////////////////////////////////////////////////
  4860. $currentGroupInfo = array(
  4861. 'gseq' => $groupSeq,
  4862. 'message' => $debug_message,
  4863. 'relevant' => $grel,
  4864. 'hidden' => $ghidden,
  4865. 'mandViolation' => $gmandViolation,
  4866. 'valid' => $gvalid,
  4867. 'qset' => $currentQset,
  4868. 'unansweredSQs' => $unansweredSQList,
  4869. 'anyUnanswered' => $ganyUnanswered,
  4870. 'invalidSQs' => $invalidSQList,
  4871. 'updatedValues' => $updatedValues,
  4872. );
  4873. ////////////////////////////////////////////////////////
  4874. // STORE METADATA NEEDED TO GENERATE NAVIGATION INDEX //
  4875. ////////////////////////////////////////////////////////
  4876. $LEM->indexGseq[$groupSeq] = array(
  4877. 'gtext' => $LEM->gseq2info[$groupSeq]['description'],
  4878. 'gname' => $LEM->gseq2info[$groupSeq]['group_name'],
  4879. 'gid' => $LEM->gseq2info[$groupSeq]['gid'], // TODO how used if random?
  4880. 'anyUnanswered' => $ganyUnanswered,
  4881. 'anyErrors' => (($gmandViolation || !$gvalid) ? true : false),
  4882. 'valid' => $gvalid,
  4883. 'mandViolation' => $gmandViolation,
  4884. 'show' => (($grel && !$ghidden) ? true : false),
  4885. );
  4886. $LEM->gseq2relevanceStatus[$gseq] = $grel;
  4887. return $currentGroupInfo;
  4888. }
  4889. /**
  4890. * For the current set of questions (whether in survey, gtoup, or question-by-question mode), assesses the following:
  4891. * (a) mandatory - if so, then all relevant sub-questions must be answered (e.g. pay attention to array_filter and array_filter_exclude)
  4892. * (b) always-hidden
  4893. * (c) relevance status - including sub-question-level relevance
  4894. * (d) answered - if $_SESSION[$LEM->sessid][sgqa]=='' or NULL, then it is not answered
  4895. * (e) validity - whether relevant questions pass their validity tests
  4896. * @param <type> $questionSeq - the 0-index sequence number for this question
  4897. * @return <array> of information about this question and its sub-questions
  4898. */
  4899. function _ValidateQuestion($questionSeq)
  4900. {
  4901. $LEM =& $this;
  4902. $qInfo = $LEM->questionSeq2relevance[$questionSeq]; // this array is by group and question sequence
  4903. $qrel=true; // assume relevant unless discover otherwise
  4904. $prettyPrintRelEqn=''; // assume no relevance eqn by default
  4905. $qid=$qInfo['qid'];
  4906. $gid=$qInfo['gid'];
  4907. $qhidden = $qInfo['hidden'];
  4908. $debug_qmessage='';
  4909. $gRelInfo = $LEM->gRelInfo[$qInfo['gseq']];
  4910. $grel = $gRelInfo['result'];
  4911. ///////////////////////////
  4912. // IS QUESTION RELEVANT? //
  4913. ///////////////////////////
  4914. if (!isset($qInfo['relevance']) || $qInfo['relevance'] == '' )
  4915. {
  4916. $relevanceEqn = 1;
  4917. }
  4918. else
  4919. {
  4920. $relevanceEqn = $qInfo['relevance'];
  4921. }
  4922. // cache results
  4923. $relevanceEqn = htmlspecialchars_decode($relevanceEqn,ENT_QUOTES); // TODO is this needed?
  4924. if (isset($LEM->ParseResultCache[$relevanceEqn]))
  4925. {
  4926. $qrel = $LEM->ParseResultCache[$relevanceEqn]['result'];
  4927. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  4928. {
  4929. $prettyPrintRelEqn = $LEM->ParseResultCache[$relevanceEqn]['prettyprint'];
  4930. }
  4931. }
  4932. else
  4933. {
  4934. $qrel = $LEM->em->ProcessBooleanExpression($relevanceEqn,$qInfo['gseq'], $qInfo['qseq']); // assumes safer to re-process relevance and not trust POST values
  4935. $hasErrors = $LEM->em->HasErrors();
  4936. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  4937. {
  4938. $prettyPrintRelEqn = $LEM->em->GetPrettyPrintString();
  4939. }
  4940. $LEM->ParseResultCache[$relevanceEqn] = array(
  4941. 'result'=>$qrel,
  4942. 'prettyprint'=>$prettyPrintRelEqn,
  4943. 'hasErrors'=>$hasErrors,
  4944. );
  4945. }
  4946. //////////////////////////////////////
  4947. // ARE ANY SUB-QUESTION IRRELEVANT? //
  4948. //////////////////////////////////////
  4949. // identify the relevant subquestions (array_filter and array_filter_exclude may make some irrelevant)
  4950. $relevantSQs=array();
  4951. $irrelevantSQs=array();
  4952. $prettyPrintSQRelEqns=array();
  4953. $prettyPrintSQRelEqn='';
  4954. $prettyPrintValidTip='';
  4955. $anyUnanswered = false;
  4956. if (!$qrel)
  4957. {
  4958. // All sub-questions are irrelevant
  4959. $irrelevantSQs = explode('|', $LEM->qid2code[$qid]);
  4960. }
  4961. else
  4962. {
  4963. // Check filter status to determine which subquestions are relevant
  4964. if ($qInfo['type'] == 'X') {
  4965. $sgqas = array(); // Boilerplate questions can be ignored
  4966. }
  4967. else {
  4968. $sgqas = explode('|',$LEM->qid2code[$qid]);
  4969. }
  4970. foreach ($sgqas as $sgqa)
  4971. {
  4972. // for each subq, see if it is part of an array_filter or array_filter_exclude
  4973. if (!isset($LEM->subQrelInfo[$qid]))
  4974. {
  4975. $relevantSQs[] = $sgqa;
  4976. continue;
  4977. }
  4978. $foundSQrelevance=false;
  4979. foreach ($LEM->subQrelInfo[$qid] as $sq)
  4980. {
  4981. switch ($sq['qtype'])
  4982. {
  4983. case '1': //Array (Flexible Labels) dual scale
  4984. if ($sgqa == ($sq['rowdivid'] . '#0') || $sgqa == ($sq['rowdivid'] . '#1')) {
  4985. $foundSQrelevance=true;
  4986. if (isset($LEM->ParseResultCache[$sq['eqn']]))
  4987. {
  4988. $sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
  4989. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  4990. {
  4991. $prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
  4992. }
  4993. }
  4994. else
  4995. {
  4996. $stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
  4997. $sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
  4998. $hasErrors = $LEM->em->HasErrors();
  4999. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5000. {
  5001. $prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
  5002. $prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
  5003. }
  5004. $LEM->ParseResultCache[$sq['eqn']] = array(
  5005. 'result'=>$sqrel,
  5006. 'prettyprint'=>$prettyPrintSQRelEqn,
  5007. 'hasErrors'=>$hasErrors,
  5008. );
  5009. }
  5010. if ($sqrel)
  5011. {
  5012. $relevantSQs[] = $sgqa;
  5013. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
  5014. }
  5015. else
  5016. {
  5017. $irrelevantSQs[] = $sgqa;
  5018. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
  5019. }
  5020. }
  5021. break;
  5022. case ':': //ARRAY (Multi Flexi) 1 to 10
  5023. case ';': //ARRAY (Multi Flexi) Text
  5024. if (preg_match('/^' . $sq['rowdivid'] . '_/', $sgqa))
  5025. {
  5026. $foundSQrelevance=true;
  5027. if (isset($LEM->ParseResultCache[$sq['eqn']]))
  5028. {
  5029. $sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
  5030. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5031. {
  5032. $prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
  5033. }
  5034. }
  5035. else
  5036. {
  5037. $stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
  5038. $sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
  5039. $hasErrors = $LEM->em->HasErrors();
  5040. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5041. {
  5042. $prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
  5043. $prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
  5044. }
  5045. $LEM->ParseResultCache[$sq['eqn']] = array(
  5046. 'result'=>$sqrel,
  5047. 'prettyprint'=>$prettyPrintSQRelEqn,
  5048. 'hasErrors'=>$hasErrors,
  5049. );
  5050. }
  5051. if ($sqrel)
  5052. {
  5053. $relevantSQs[] = $sgqa;
  5054. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
  5055. }
  5056. else
  5057. {
  5058. $irrelevantSQs[] = $sgqa;
  5059. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
  5060. }
  5061. }
  5062. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  5063. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  5064. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  5065. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  5066. case 'F': //ARRAY (Flexible) - Row Format
  5067. case 'M': //Multiple choice checkbox
  5068. case 'P': //Multiple choice with comments checkbox + text
  5069. // Note, for M and P, Mandatory should mean that at least one answer was picked - not that all were checked
  5070. case 'K': //MULTIPLE NUMERICAL QUESTION
  5071. case 'Q': //MULTIPLE SHORT TEXT
  5072. if ($sgqa == $sq['rowdivid'] || $sgqa == ($sq['rowdivid'] . 'comment')) // to catch case 'P'
  5073. {
  5074. $foundSQrelevance=true;
  5075. if (isset($LEM->ParseResultCache[$sq['eqn']]))
  5076. {
  5077. $sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
  5078. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5079. {
  5080. $prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
  5081. }
  5082. }
  5083. else
  5084. {
  5085. $stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
  5086. $sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
  5087. $hasErrors = $LEM->em->HasErrors();
  5088. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5089. {
  5090. $prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
  5091. $prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
  5092. }
  5093. $LEM->ParseResultCache[$sq['eqn']] = array(
  5094. 'result'=>$sqrel,
  5095. 'prettyprint'=>$prettyPrintSQRelEqn,
  5096. 'hasErrors'=>$hasErrors,
  5097. );
  5098. }
  5099. if ($sqrel)
  5100. {
  5101. $relevantSQs[] = $sgqa;
  5102. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
  5103. }
  5104. else
  5105. {
  5106. $irrelevantSQs[] = $sgqa;
  5107. $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
  5108. }
  5109. }
  5110. break;
  5111. case 'L': //LIST drop-down/radio-button list
  5112. if ($sgqa == ($sq['sgqa'] . 'other') && $sgqa == $sq['rowdivid']) // don't do sub-q level validition to main question, just to other option
  5113. {
  5114. $foundSQrelevance=true;
  5115. if (isset($LEM->ParseResultCache[$sq['eqn']]))
  5116. {
  5117. $sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
  5118. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5119. {
  5120. $prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
  5121. }
  5122. }
  5123. else
  5124. {
  5125. $stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
  5126. $sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
  5127. $hasErrors = $LEM->em->HasErrors();
  5128. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5129. {
  5130. $prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
  5131. $prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
  5132. }
  5133. $LEM->ParseResultCache[$sq['eqn']] = array(
  5134. 'result'=>$sqrel,
  5135. 'prettyprint'=>$prettyPrintSQRelEqn,
  5136. 'hasErrors'=>$hasErrors,
  5137. );
  5138. }
  5139. if ($sqrel)
  5140. {
  5141. $relevantSQs[] = $sgqa;
  5142. }
  5143. else
  5144. {
  5145. $irrelevantSQs[] = $sgqa;
  5146. }
  5147. }
  5148. break;
  5149. default:
  5150. break;
  5151. }
  5152. } // end foreach($LEM->subQrelInfo) [checking array-filters]
  5153. if (!$foundSQrelevance)
  5154. {
  5155. // then this question is relevant
  5156. $relevantSQs[] = $sgqa; // TODO - check this
  5157. }
  5158. }
  5159. } // end of processing relevant question for sub-questions
  5160. if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
  5161. {
  5162. // TODO - why is array_unique needed here?
  5163. // $prettyPrintSQRelEqns = array_unique($prettyPrintSQRelEqns);
  5164. }
  5165. // These array_unique only apply to array_filter of type L (list)
  5166. $relevantSQs = array_unique($relevantSQs);
  5167. $irrelevantSQs = array_unique($irrelevantSQs);
  5168. ////////////////////////////////////////////////////////////////////
  5169. // WHICH RELEVANT, VISIBLE (SUB)-QUESTIONS HAVEN'T BEEN ANSWERED? //
  5170. ////////////////////////////////////////////////////////////////////
  5171. // check that all mandatories have been fully answered (but don't require answers for sub-questions that are irrelevant
  5172. $unansweredSQs = array(); // list of sub-questions that weren't answered
  5173. foreach ($relevantSQs as $sgqa)
  5174. {
  5175. if (($qInfo['type'] != '*') && (!isset($_SESSION[$LEM->sessid][$sgqa]) || ($_SESSION[$LEM->sessid][$sgqa] === '' || is_null($_SESSION[$LEM->sessid][$sgqa]))))
  5176. {
  5177. // then a relevant, visible, mandatory question hasn't been answered
  5178. // Equations are ignored, since set automatically
  5179. $unansweredSQs[] = $sgqa;
  5180. }
  5181. }
  5182. //////////////////////////////////////////////
  5183. // DETECT ANY VIOLATIONS OF MANDATORY RULES //
  5184. //////////////////////////////////////////////
  5185. $qmandViolation = false; // assume there is no mandatory violation until discover otherwise
  5186. $mandatoryTip = '';
  5187. if ($qrel && !$qhidden && ($qInfo['mandatory'] == 'Y'))
  5188. {
  5189. $mandatoryTip = "<strong><br /><span class='errormandatory'>".$LEM->gT('This question is mandatory').'. ';
  5190. switch ($qInfo['type'])
  5191. {
  5192. case 'M':
  5193. case 'P':
  5194. case '!': //List - dropdown
  5195. case 'L': //LIST drop-down/radio-button list
  5196. // If at least one checkbox is checked, we're OK
  5197. if (count($relevantSQs) > 0 && (count($relevantSQs) == count($unansweredSQs)))
  5198. {
  5199. $qmandViolation = true;
  5200. }
  5201. if (!($qInfo['type'] == '!' || $qInfo['type'] == 'L'))
  5202. {
  5203. $mandatoryTip .= $LEM->gT('Please check at least one item.');
  5204. }
  5205. if ($qInfo['other']=='Y')
  5206. {
  5207. $qattr = isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array();
  5208. if (isset($qattr['other_replace_text']) && trim($qattr['other_replace_text']) != '') {
  5209. $othertext = trim($qattr['other_replace_text']);
  5210. }
  5211. else {
  5212. $othertext = $LEM->gT('Other:');
  5213. }
  5214. $mandatoryTip .= "<br />\n".sprintf($this->gT("If you choose '%s' please also specify your choice in the accompanying text field."),$othertext);
  5215. }
  5216. break;
  5217. case 'X': // Boilerplate can never be mandatory
  5218. case '*': // Equation is auto-computed, so can't violate mandatory rules
  5219. break;
  5220. case 'A':
  5221. case 'B':
  5222. case 'C':
  5223. case 'Q':
  5224. case 'K':
  5225. case 'E':
  5226. case 'F':
  5227. case 'J':
  5228. case 'H':
  5229. case ';':
  5230. case '1':
  5231. // In general, if any relevant questions aren't answered, then it violates the mandatory rule
  5232. if (count($unansweredSQs) > 0)
  5233. {
  5234. $qmandViolation = true; // TODO - what about 'other'?
  5235. }
  5236. $mandatoryTip .= $LEM->gT('Please complete all parts').'.';
  5237. break;
  5238. case ':':
  5239. $qattr = isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array();
  5240. if (isset($qattr['multiflexible_checkbox']) && $qattr['multiflexible_checkbox'] == 1)
  5241. {
  5242. // Need to check whether there is at least one checked box per row
  5243. foreach ($LEM->q2subqInfo[$qid]['subqs'] as $sq)
  5244. {
  5245. if (!isset($_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]) || $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']])
  5246. {
  5247. $rowCount=0;
  5248. $numUnanswered=0;
  5249. foreach ($sgqas as $s)
  5250. {
  5251. if (strpos($s, $sq['rowdivid']) !== false)
  5252. {
  5253. ++$rowCount;
  5254. if (array_search($s,$unansweredSQs) !== false) {
  5255. ++$numUnanswered;
  5256. }
  5257. }
  5258. }
  5259. if ($rowCount > 0 && $rowCount == $numUnanswered)
  5260. {
  5261. $qmandViolation = true;
  5262. }
  5263. }
  5264. }
  5265. $mandatoryTip .= $LEM->gT('Please check at least one box per row').'.';
  5266. }
  5267. else
  5268. {
  5269. if (count($unansweredSQs) > 0)
  5270. {
  5271. $qmandViolation = true; // TODO - what about 'other'?
  5272. }
  5273. $mandatoryTip .= $LEM->gT('Please complete all parts').'.';
  5274. }
  5275. break;
  5276. case 'R':
  5277. if (count($unansweredSQs) > 0)
  5278. {
  5279. $qmandViolation = true; // TODO - what about 'other'?
  5280. }
  5281. $mandatoryTip .= $LEM->gT('Please rank all items').'.';
  5282. break;
  5283. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  5284. $_count=0;
  5285. for ($i=0;$i<count($unansweredSQs);++$i)
  5286. {
  5287. if (preg_match("/comment$/",$unansweredSQs[$i])) {
  5288. continue;
  5289. }
  5290. ++$_count;
  5291. }
  5292. if ($_count > 0)
  5293. {
  5294. $qmandViolation = true;
  5295. }
  5296. break;
  5297. default:
  5298. if (count($unansweredSQs) > 0)
  5299. {
  5300. $qmandViolation = true;
  5301. }
  5302. break;
  5303. }
  5304. $mandatoryTip .= "</span></strong>\n";
  5305. }
  5306. /////////////////////////////////////////////////////////////
  5307. // DETECT WHETHER QUESTION SHOULD BE FLAGGED AS UNANSWERED //
  5308. /////////////////////////////////////////////////////////////
  5309. if ($qrel && !$qhidden)
  5310. {
  5311. switch ($qInfo['type'])
  5312. {
  5313. case 'M':
  5314. case 'P':
  5315. case '!': //List - dropdown
  5316. case 'L': //LIST drop-down/radio-button list
  5317. // If at least one checkbox is checked, we're OK
  5318. if (count($relevantSQs) > 0 && (count($relevantSQs) == count($unansweredSQs)))
  5319. {
  5320. $anyUnanswered = true;
  5321. }
  5322. // what about optional vs. mandatory comment and 'other' fields?
  5323. break;
  5324. default:
  5325. $anyUnanswered = (count($unansweredSQs) > 0);
  5326. break;
  5327. }
  5328. }
  5329. ///////////////////////////////////////////////
  5330. // DETECT ANY VIOLATIONS OF VALIDATION RULES //
  5331. ///////////////////////////////////////////////
  5332. $qvalid=true; // assume valid unless discover otherwise
  5333. $hasValidationEqn=false;
  5334. $prettyPrintValidEqn=''; // assume no validation eqn by default
  5335. $validationEqn='';
  5336. $validationJS=''; // assume can't generate JavaScript to validate equation
  5337. $validTip=''; // default is none
  5338. if (isset($LEM->qid2validationEqn[$qid]))
  5339. {
  5340. $hasValidationEqn=true;
  5341. if (!$qhidden) // do this even is starts irrelevant, else will never show this information.
  5342. {
  5343. $validationEqns = $LEM->qid2validationEqn[$qid]['eqn'];
  5344. $validationEqn = implode(' and ', $validationEqns);
  5345. $qvalid = $LEM->em->ProcessBooleanExpression($validationEqn,$qInfo['gseq'], $qInfo['qseq']);
  5346. $hasErrors = $LEM->em->HasErrors();
  5347. if (!$hasErrors)
  5348. {
  5349. $validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
  5350. }
  5351. $prettyPrintValidEqn = $validationEqn;
  5352. if ((($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX))
  5353. {
  5354. $prettyPrintValidEqn = $LEM->em->GetPrettyPrintString();
  5355. }
  5356. $stringToParse = '';
  5357. foreach ($LEM->qid2validationEqn[$qid]['tips'] as $vclass=>$vtip)
  5358. {
  5359. $stringToParse .= "<div id='vmsg_" . $qid . '_' . $vclass . "' class='em_" . $vclass . "'>" . $vtip . "</div>\n";
  5360. }
  5361. $prettyPrintValidTip = $stringToParse;
  5362. $validTip = $LEM->ProcessString($stringToParse, $qid,NULL,false,1,1,false,false);
  5363. // TODO check for errors?
  5364. if ((($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX))
  5365. {
  5366. $prettyPrintValidTip = $LEM->GetLastPrettyPrintExpression();
  5367. }
  5368. $sumEqn = $LEM->qid2validationEqn[$qid]['sumEqn'];
  5369. $sumRemainingEqn = $LEM->qid2validationEqn[$qid]['sumRemainingEqn'];
  5370. // $countEqn = $LEM->qid2validationEqn[$qid]['countEqn'];
  5371. // $countRemainingEqn = $LEM->qid2validationEqn[$qid]['countRemainingEqn'];
  5372. }
  5373. else
  5374. {
  5375. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  5376. {
  5377. $prettyPrintValidEqn = 'Question is Irrelevant, so no need to further validate it';
  5378. }
  5379. }
  5380. }
  5381. if (!$qvalid)
  5382. {
  5383. $invalidSQs = $LEM->qid2code[$qid]; // TODO - currently invalidates all - should only invalidate those that truly fail validation rules.
  5384. }
  5385. /////////////////////////////////////////////////////////
  5386. // OPTIONALLY DISPLAY (DETAILED) DEBUGGING INFORMATION //
  5387. /////////////////////////////////////////////////////////
  5388. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY)
  5389. {
  5390. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid . '/qid/' . $qid);
  5391. $debug_qmessage .= '--[Q#' . $qInfo['qseq'] . ']'
  5392. . "[<a href='$editlink'>"
  5393. . 'QID:'. $qid . '</a>][' . $qInfo['type'] . ']: '
  5394. . ($qrel ? 'relevant' : " <span style='color:red'>irrelevant</span> ")
  5395. . ($qhidden ? " <span style='color:red'>always-hidden</span> " : ' ')
  5396. . (($qInfo['mandatory'] == 'Y')? ' mandatory' : ' ')
  5397. . (($hasValidationEqn) ? (!$qvalid ? " <span style='color:red'>(fails validation rule)</span> " : ' valid') : '')
  5398. . ($qmandViolation ? " <span style='color:red'>(missing a relevant mandatory)</span> " : ' ')
  5399. . $prettyPrintRelEqn
  5400. . "<br />\n";
  5401. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  5402. {
  5403. if ($mandatoryTip != '')
  5404. {
  5405. $debug_qmessage .= '----Mandatory Tip: ' . flattenText($mandatoryTip) . "<br />\n";
  5406. }
  5407. if ($prettyPrintValidTip != '')
  5408. {
  5409. $debug_qmessage .= '----Pretty Validation Tip: <br />' . $prettyPrintValidTip . "<br />\n";
  5410. }
  5411. if ($validTip != '')
  5412. {
  5413. $debug_qmessage .= '----Validation Tip: <br />' . $validTip . "<br />\n";
  5414. }
  5415. if ($prettyPrintValidEqn != '')
  5416. {
  5417. $debug_qmessage .= '----Validation Eqn: ' . $prettyPrintValidEqn . "<br />\n";
  5418. }
  5419. if ($validationJS != '')
  5420. {
  5421. $debug_qmessage .= '----Validation JavaScript: ' . $validationJS . "<br />\n";
  5422. }
  5423. // what are the database question codes for this question?
  5424. $subQList = '{' . implode('}, {', explode('|',$LEM->qid2code[$qid])) . '}';
  5425. // pretty-print them
  5426. $LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
  5427. $prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
  5428. $debug_qmessage .= '----SubQs=> ' . $prettyPrintSubQList . "<br />\n";
  5429. if (count($prettyPrintSQRelEqns) > 0)
  5430. {
  5431. $debug_qmessage .= "----Array Filters Applied:<br />\n";
  5432. foreach ($prettyPrintSQRelEqns as $key => $value)
  5433. {
  5434. $debug_qmessage .= '------' . $key . ': ' . $value . "<br />\n";
  5435. }
  5436. $debug_qmessage .= "<br />\n";
  5437. }
  5438. if (count($relevantSQs) > 0)
  5439. {
  5440. $subQList = '{' . implode('}, {', $relevantSQs) . '}';
  5441. // pretty-print them
  5442. $LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
  5443. $prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
  5444. $debug_qmessage .= '----Relevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
  5445. }
  5446. if (count($irrelevantSQs) > 0)
  5447. {
  5448. $subQList = '{' . implode('}, {', $irrelevantSQs) . '}';
  5449. // pretty-print them
  5450. $LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
  5451. $prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
  5452. $debug_qmessage .= '----Irrelevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
  5453. }
  5454. // show which relevant subQs were not answered
  5455. if (count($unansweredSQs) > 0)
  5456. {
  5457. $subQList = '{' . implode('}, {', $unansweredSQs) . '}';
  5458. // pretty-print them
  5459. $LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
  5460. $prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
  5461. $debug_qmessage .= '----Unanswered Relevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
  5462. }
  5463. }
  5464. }
  5465. /////////////////////////////////////////////////////////////
  5466. // CREATE ARRAY OF VALUES THAT NEED TO BE SILENTLY UPDATED //
  5467. /////////////////////////////////////////////////////////////
  5468. $updatedValues=array();
  5469. if ((!$qrel || !$grel) && $LEM->surveyOptions['deletenonvalues'])
  5470. {
  5471. // If not relevant, then always NULL it in the database
  5472. $sgqas = explode('|',$LEM->qid2code[$qid]);
  5473. foreach ($sgqas as $sgqa)
  5474. {
  5475. $_SESSION[$LEM->sessid][$sgqa] = NULL;
  5476. $updatedValues[$sgqa] = NULL;
  5477. $LEM->updatedValues[$sgqa] = NULL;
  5478. }
  5479. }
  5480. else if ($qInfo['type'] == '*')
  5481. {
  5482. // Process relevant equations, even if hidden, and write the result to the database
  5483. $result = flattenText($LEM->ProcessString($qInfo['eqn'], $qInfo['qid'],NULL,false,1,1,false,false));
  5484. $sgqa = $LEM->qid2code[$qid]; // there will be only one, since Equation
  5485. // Store the result of the Equation in the SESSION
  5486. $_SESSION[$LEM->sessid][$sgqa] = $result;
  5487. $_update = array(
  5488. 'type'=>'*',
  5489. 'value'=>$result,
  5490. );
  5491. $updatedValues[$sgqa] = $_update;
  5492. $LEM->updatedValues[$sgqa] = $_update;
  5493. if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
  5494. {
  5495. $prettyPrintEqn = $LEM->em->GetPrettyPrintString();
  5496. $debug_qmessage .= '** Process Hidden but Relevant Equation [' . $sgqa . '](' . $prettyPrintEqn . ') => ' . $result . "<br />\n";
  5497. }
  5498. }
  5499. if ( $LEM->surveyOptions['deletenonvalues'] )
  5500. {
  5501. foreach ($irrelevantSQs as $sq)
  5502. {
  5503. // NULL irrelevant sub-questions
  5504. $_SESSION[$LEM->sessid][$sq] = NULL;
  5505. $updatedValues[$sq] = NULL;
  5506. $LEM->updatedValues[$sq]= NULL;
  5507. }
  5508. }
  5509. // Regardless of whether relevant or hidden, if there is a default value and $_SESSION[$LEM->sessid][$sgqa] is NULL, then use the default value in $_SESSION, but don't write to database
  5510. // Also, set this AFTER testing relevance
  5511. $sgqas = explode('|',$LEM->qid2code[$qid]);
  5512. foreach ($sgqas as $sgqa)
  5513. {
  5514. if (!is_null($LEM->knownVars[$sgqa]['default']) && !isset($_SESSION[$LEM->sessid][$sgqa])) {
  5515. // add support for replacements
  5516. $defaultVal = $LEM->ProcessString($LEM->knownVars[$sgqa]['default'], NULL, NULL, false, 1, 1, false, false, true);
  5517. $_SESSION[$LEM->sessid][$sgqa] = $defaultVal;
  5518. }
  5519. }
  5520. //////////////////////////////////////////////////////////////////////////
  5521. // STORE METADATA NEEDED FOR SUBSEQUENT PROCESSING AND DISPLAY PURPOSES //
  5522. //////////////////////////////////////////////////////////////////////////
  5523. $qStatus = array(
  5524. 'info' => $qInfo, // collect all questions within the group - includes mandatory and always-hiddden status
  5525. 'relevant' => $qrel,
  5526. 'hidden' => $qInfo['hidden'],
  5527. 'relEqn' => $prettyPrintRelEqn,
  5528. 'sgqa' => $LEM->qid2code[$qid],
  5529. 'unansweredSQs' => implode('|',$unansweredSQs),
  5530. 'valid' => $qvalid,
  5531. 'validEqn' => $validationEqn,
  5532. 'prettyValidEqn' => $prettyPrintValidEqn,
  5533. 'validTip' => $validTip,
  5534. 'prettyValidTip' => $prettyPrintValidTip,
  5535. 'validJS' => $validationJS,
  5536. 'invalidSQs' => (isset($invalidSQs) ? $invalidSQs : ''),
  5537. 'relevantSQs' => implode('|',$relevantSQs),
  5538. 'irrelevantSQs' => implode('|',$irrelevantSQs),
  5539. 'subQrelEqn' => implode('<br />',$prettyPrintSQRelEqns),
  5540. 'mandViolation' => $qmandViolation,
  5541. 'anyUnanswered' => $anyUnanswered,
  5542. 'mandTip' => $mandatoryTip,
  5543. 'message' => $debug_qmessage,
  5544. 'updatedValues' => $updatedValues,
  5545. 'sumEqn' => (isset($sumEqn) ? $sumEqn : ''),
  5546. 'sumRemainingEqn' => (isset($sumRemainingEqn) ? $sumRemainingEqn : ''),
  5547. // 'countEqn' => (isset($countEqn) ? $countEqn : ''),
  5548. // 'countRemainingEqn' => (isset($countRemainingEqn) ? $countRemainingEqn : ''),
  5549. );
  5550. $LEM->currentQset[$qid] = $qStatus;
  5551. ////////////////////////////////////////////////////////
  5552. // STORE METADATA NEEDED TO GENERATE NAVIGATION INDEX //
  5553. ////////////////////////////////////////////////////////
  5554. $groupSeq = $qInfo['gseq'];
  5555. $LEM->indexQseq[$questionSeq] = array(
  5556. 'qid' => $qInfo['qid'],
  5557. 'qtext' => $qInfo['qtext'],
  5558. 'qcode' => $qInfo['code'],
  5559. 'qhelp' => $qInfo['help'],
  5560. 'anyUnanswered' => $anyUnanswered,
  5561. 'anyErrors' => (($qmandViolation || !$qvalid) ? true : false),
  5562. 'show' => (($qrel && !$qInfo['hidden']) ? true : false),
  5563. 'gseq' => $groupSeq,
  5564. 'gtext' => $LEM->gseq2info[$groupSeq]['description'],
  5565. 'gname' => $LEM->gseq2info[$groupSeq]['group_name'],
  5566. 'gid' => $LEM->gseq2info[$groupSeq]['gid'],
  5567. 'mandViolation' => $qmandViolation,
  5568. 'valid' => $qvalid,
  5569. );
  5570. $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] = $qrel;
  5571. return $qStatus;
  5572. }
  5573. static function GetQuestionStatus($qid)
  5574. {
  5575. $LEM =& LimeExpressionManager::singleton();
  5576. if (isset($LEM->currentQset[$qid]))
  5577. {
  5578. return $LEM->currentQset[$qid];
  5579. }
  5580. return NULL;
  5581. }
  5582. /**
  5583. * Get array of info needed to display the Group Index
  5584. * @return <type>
  5585. */
  5586. static function GetGroupIndexInfo($gseq=NULL)
  5587. {
  5588. $LEM =& LimeExpressionManager::singleton();
  5589. if (is_null($gseq)) {
  5590. return $LEM->indexGseq;
  5591. }
  5592. else {
  5593. return $LEM->indexGseq[$gseq];
  5594. }
  5595. }
  5596. /**
  5597. * Translate GID to 0-index Group Sequence number
  5598. * @param <type> $gid
  5599. * @return <type>
  5600. */
  5601. static function GetGroupSeq($gid)
  5602. {
  5603. $LEM =& LimeExpressionManager::singleton();
  5604. return (isset($LEM->groupId2groupSeq[$gid]) ? $LEM->groupId2groupSeq[$gid] : -1);
  5605. }
  5606. /**
  5607. * Get question sequence number from QID
  5608. * @param <type> $qid
  5609. * @return <type>
  5610. */
  5611. static function GetQuestionSeq($qid)
  5612. {
  5613. $LEM =& LimeExpressionManager::singleton();
  5614. return (isset($LEM->questionId2questionSeq[$qid]) ? $LEM->questionId2questionSeq[$qid] : -1);
  5615. }
  5616. /**
  5617. * Get array of info needed to display the Question Index
  5618. * @return <type>
  5619. */
  5620. static function GetQuestionIndexInfo()
  5621. {
  5622. $LEM =& LimeExpressionManager::singleton();
  5623. return $LEM->indexQseq;
  5624. }
  5625. /**
  5626. * Return entries needed to build the navigation index
  5627. * @param <type> $step - if specified, return a single value, otherwise return entire array
  5628. * @return <type> - will be either question or group-level, depending upon $surveyMode
  5629. */
  5630. static function GetStepIndexInfo($step=NULL)
  5631. {
  5632. $LEM =& LimeExpressionManager::singleton();
  5633. switch ($LEM->surveyMode)
  5634. {
  5635. case 'survey':
  5636. return $LEM->lastMoveResult;
  5637. break;
  5638. case 'group':
  5639. if (is_null($step)) {
  5640. return $LEM->indexGseq;
  5641. }
  5642. return $LEM->indexGseq[$step];
  5643. break;
  5644. case 'question':
  5645. if (is_null($step)) {
  5646. return $LEM->indexQseq;
  5647. }
  5648. return $LEM->indexQseq[$step];
  5649. break;
  5650. }
  5651. }
  5652. /**
  5653. * This should be called each time a new group is started, whether on same or different pages. Sets/Clears needed internal parameters.
  5654. * @param <type> $gseq - the group sequence
  5655. * @param <type> $anonymized - whether anonymized
  5656. * @param <type> $surveyid - the surveyId
  5657. * @param <type> $forceRefresh - whether to force refresh of setting variable and token mappings (should be done rarely)
  5658. */
  5659. static function StartProcessingGroup($gseq=NULL,$anonymized=false,$surveyid=NULL,$forceRefresh=false)
  5660. {
  5661. $LEM =& LimeExpressionManager::singleton();
  5662. $LEM->em->StartProcessingGroup(
  5663. isset($surveyid) ? $surveyid : NULL,
  5664. '',
  5665. isset($LEM->surveyOptions['hyperlinkSyntaxHighlighting']) ? $LEM->surveyOptions['hyperlinkSyntaxHighlighting'] : false
  5666. );
  5667. $LEM->groupRelevanceInfo = array();
  5668. if (!is_null($gseq))
  5669. {
  5670. $LEM->currentGroupSeq = $gseq;
  5671. if (!is_null($surveyid))
  5672. {
  5673. $LEM->setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh,$anonymized,$LEM->allOnOnePage);
  5674. if ($gseq > $LEM->maxGroupSeq) {
  5675. $LEM->maxGroupSeq = $gseq;
  5676. }
  5677. if (!$LEM->allOnOnePage || ($LEM->allOnOnePage && !$LEM->processedRelevance)) {
  5678. $LEM->ProcessAllNeededRelevance(); // TODO - what if this is called using Survey or Data Entry format?
  5679. $LEM->_CreateSubQLevelRelevanceAndValidationEqns();
  5680. $LEM->processedRelevance=true;
  5681. }
  5682. }
  5683. }
  5684. }
  5685. /**
  5686. * Should be called after each group finishes
  5687. */
  5688. static function FinishProcessingGroup($skipReprocessing=false)
  5689. {
  5690. // $now = microtime(true);
  5691. $LEM =& LimeExpressionManager::singleton();
  5692. if ($skipReprocessing && $LEM->surveyMode != 'survey')
  5693. {
  5694. $LEM->pageTailorInfo=array();
  5695. $LEM->pageRelevanceInfo=array();
  5696. }
  5697. $LEM->pageTailorInfo[] = $LEM->em->GetCurrentSubstitutionInfo();
  5698. $LEM->pageRelevanceInfo[] = $LEM->groupRelevanceInfo;
  5699. // $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  5700. }
  5701. /**
  5702. * Returns an array of string parts, splitting out expressions
  5703. * @param type $src
  5704. * @return type
  5705. */
  5706. static function SplitStringOnExpressions($src)
  5707. {
  5708. $LEM =& LimeExpressionManager::singleton();
  5709. return $LEM->em->asSplitStringOnExpressions($src);
  5710. }
  5711. /**
  5712. * Return a formatted table showing how much time each part of EM consumed
  5713. * @return <type>
  5714. */
  5715. static function GetDebugTimingMessage()
  5716. {
  5717. $LEM =& LimeExpressionManager::singleton();
  5718. return $LEM->debugTimingMsg;
  5719. }
  5720. /**
  5721. * Should be called at end of each page
  5722. */
  5723. static function FinishProcessingPage()
  5724. {
  5725. $LEM =& LimeExpressionManager::singleton();
  5726. $totalTime = 0.;
  5727. if ((($LEM->debugLevel & LEM_DEBUG_TIMING) == LEM_DEBUG_TIMING) && count($LEM->runtimeTimings)>0) {
  5728. $LEM->debugTimingMsg='';
  5729. foreach($LEM->runtimeTimings as $unit) {
  5730. $totalTime += $unit[1];
  5731. }
  5732. $LEM->debugTimingMsg .= "<table border='1'><tr><td colspan=2><b>Total time attributable to EM = " . $totalTime . "</b></td></tr>\n";
  5733. foreach ($LEM->runtimeTimings as $t)
  5734. {
  5735. $LEM->debugTimingMsg .= "<tr><td>" . $t[0] . "</td><td>" . $t[1] . "</td></tr>\n";
  5736. }
  5737. $LEM->debugTimingMsg .= "</table>\n";
  5738. }
  5739. $LEM->runtimeTimings = array(); // reset them
  5740. $LEM->initialized=false; // so detect calls after done
  5741. $LEM->ParseResultCache=array(); // don't need to persist it in session
  5742. $_SESSION['LEMsingleton']=serialize($LEM);
  5743. }
  5744. /*
  5745. * Generate JavaScript needed to do dynamic relevance and tailoring
  5746. * Also create list of variables that need to be declared
  5747. */
  5748. static function GetRelevanceAndTailoringJavaScript()
  5749. {
  5750. $now = microtime(true);
  5751. $LEM =& LimeExpressionManager::singleton();
  5752. $jsParts=array();
  5753. $allJsVarsUsed=array();
  5754. $rowdividList=array(); // list of subquestions needing relevance entries
  5755. $jsParts[] = '<script type="text/javascript" src="'.Yii::app()->getConfig('generalscripts').'expressions/em_javascript.js"></script>';
  5756. $jsParts[] = "\n<script type='text/javascript'>\n<!--\n";
  5757. $jsParts[] = "var LEMmode='" . $LEM->surveyMode . "';\n";
  5758. if ($LEM->surveyMode == 'group')
  5759. {
  5760. $jsParts[] = "var LEMgseq=" . $LEM->currentGroupSeq . ";\n";
  5761. }
  5762. if ($LEM->surveyMode == 'question' && isset($LEM->currentQID))
  5763. {
  5764. $jsParts[] = "var LEMqid=" . $LEM->currentQID . ";\n"; // current group num so can compute isOnCurrentPage
  5765. }
  5766. $jsParts[] = "function ExprMgr_process_relevance_and_tailoring(evt_type,sgqa,type){\n";
  5767. $jsParts[] = "if (typeof LEM_initialized == 'undefined') {\nLEM_initialized=true;\nLEMsetTabIndexes();\n}\n";
  5768. $jsParts[] = "if (evt_type == 'onchange' && (typeof last_sgqa !== 'undefined' && sgqa==last_sgqa) && (typeof last_evt_type !== 'undefined' && last_evt_type == 'TAB' && type != 'checkbox')) {\n";
  5769. $jsParts[] = " last_evt_type='onchange';\n";
  5770. $jsParts[] = " last_sgqa=sgqa;\n";
  5771. $jsParts[] = " return;\n";
  5772. $jsParts[] = "}\n";
  5773. $jsParts[] = "last_evt_type = evt_type;\n";
  5774. $jsParts[] = "last_sgqa=sgqa;\n";
  5775. // flatten relevance array, keeping proper order
  5776. $pageRelevanceInfo=array();
  5777. $qidList = array(); // list of questions used in relevance and tailoring
  5778. $gseqList = array(); // list of gseqs on this page
  5779. $gseq_qidList = array(); // list of qids using relevance/tailoring within each group
  5780. if (is_array($LEM->pageRelevanceInfo))
  5781. {
  5782. foreach($LEM->pageRelevanceInfo as $prel)
  5783. {
  5784. foreach($prel as $rel)
  5785. {
  5786. $pageRelevanceInfo[] = $rel;
  5787. }
  5788. }
  5789. }
  5790. $valEqns = array();
  5791. $relEqns = array();
  5792. $relChangeVars = array();
  5793. $dynamicQinG = array(); // array of questions, per group, that might affect group-level visibility in all-in-one mode
  5794. $GalwaysRelevant = array(); // checks whether a group is always relevant (e.g. has at least one question that is always shown)
  5795. if (is_array($pageRelevanceInfo))
  5796. {
  5797. foreach ($pageRelevanceInfo as $arg)
  5798. {
  5799. if (!$LEM->allOnOnePage && $LEM->currentGroupSeq != $arg['gseq']) {
  5800. continue;
  5801. }
  5802. $gseqList[$arg['gseq']] = $arg['gseq']; // so keep them in order
  5803. // First check if there is any tailoring and construct the tailoring JavaScript if needed
  5804. $tailorParts = array();
  5805. $relParts = array(); // relevance equation
  5806. $valParts = array(); // validation
  5807. $relJsVarsUsed = array(); // vars used in relevance and tailoring
  5808. $valJsVarsUsed = array(); // vars used in validations
  5809. foreach ($LEM->pageTailorInfo as $tailor)
  5810. {
  5811. if (is_array($tailor))
  5812. {
  5813. foreach ($tailor as $sub)
  5814. {
  5815. if ($sub['questionNum'] == $arg['qid'])
  5816. {
  5817. $tailorParts[] = $sub['js'];
  5818. $vars = explode('|',$sub['vars']);
  5819. if (is_array($vars))
  5820. {
  5821. $allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
  5822. $relJsVarsUsed = array_merge($relJsVarsUsed,$vars);
  5823. }
  5824. }
  5825. }
  5826. }
  5827. }
  5828. // Now check whether there is sub-question relevance to perform for this question
  5829. $subqParts = array();
  5830. if (isset($LEM->subQrelInfo[$arg['qid']]))
  5831. {
  5832. foreach ($LEM->subQrelInfo[$arg['qid']] as $subq)
  5833. {
  5834. $subqParts[$subq['rowdivid']] = $subq;
  5835. }
  5836. }
  5837. $qidList[$arg['qid']] = $arg['qid'];
  5838. if (!isset($gseq_qidList[$arg['gseq']]))
  5839. {
  5840. $gseq_qidList[$arg['gseq']] = array();
  5841. }
  5842. $gseq_qidList[$arg['gseq']][$arg['qid']] = '0'; // means the qid is within this gseq, but may not have a relevance equation
  5843. // Now check whether any sub-question validation needs to be performed
  5844. $subqValidations = array();
  5845. $validationEqns = array();
  5846. if (isset($LEM->qid2validationEqn[$arg['qid']]))
  5847. {
  5848. if (isset($LEM->qid2validationEqn[$arg['qid']]['subqValidEqns']))
  5849. {
  5850. $_veqs = $LEM->qid2validationEqn[$arg['qid']]['subqValidEqns'];
  5851. foreach($_veqs as $_veq)
  5852. {
  5853. // generate JavaScript for each - tests whether invalid.
  5854. if (strlen(trim($_veq['subqValidEqn'])) == 0) {
  5855. continue;
  5856. }
  5857. $subqValidations[] = array(
  5858. 'subqValidEqn' => $_veq['subqValidEqn'],
  5859. 'subqValidSelector' => $_veq['subqValidSelector'],
  5860. );
  5861. }
  5862. }
  5863. $validationEqns = $LEM->qid2validationEqn[$arg['qid']]['eqn'];
  5864. }
  5865. // Process relevance for question $arg['qid'];
  5866. $relevance = $arg['relevancejs'];
  5867. $relChangeVars[] = " relChange" . $arg['qid'] . "=false;\n"; // detect change in relevance status
  5868. if (($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)) && count($tailorParts) == 0 && count($subqParts) == 0 && count($subqValidations) == 0 && count($validationEqns) == 0)
  5869. {
  5870. // Only show constitutively true relevances if there is tailoring that should be done.
  5871. $relParts[] = "$('#relevance" . $arg['qid'] . "').val('1'); // always true\n";
  5872. $GalwaysRelevant[$arg['gseq']] = true;
  5873. continue;
  5874. }
  5875. $relevance = ($relevance == '' || ($arg['result'] == true && $arg['numJsVars']==0)) ? '1' : $relevance;
  5876. $relParts[] = "\nif (" . $relevance . ")\n{\n";
  5877. ////////////////////////////////////////////////////////////////////////
  5878. // DO ALL ARRAY FILTERING FIRST - MAY AFFECT VALIDATION AND TAILORING //
  5879. ////////////////////////////////////////////////////////////////////////
  5880. // Do all sub-question filtering (e..g array_filter)
  5881. /**
  5882. * $afHide - if true, then use jQuery.show(). If false, then disable/enable the row
  5883. */
  5884. $afHide = (isset($LEM->qattr[$arg['qid']]['array_filter_style']) ? ($LEM->qattr[$arg['qid']]['array_filter_style'] == '0') : true);
  5885. $inputSelector = (($arg['type'] == 'R') ? '' : ' :input:not(:hidden)');
  5886. foreach ($subqParts as $sq)
  5887. {
  5888. $rowdividList[$sq['rowdivid']] = $sq['result'];
  5889. $relParts[] = " if ( " . $sq['relevancejs'] . " ) {\n";
  5890. if ($afHide)
  5891. {
  5892. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').show();\n";
  5893. }
  5894. else
  5895. {
  5896. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
  5897. }
  5898. if ($sq['isExclusiveJS'] != '')
  5899. {
  5900. $relParts[] = " if ( " . $sq['isExclusiveJS'] . " ) {\n";
  5901. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
  5902. $relParts[] = " }\n";
  5903. $relParts[] = " else {\n";
  5904. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
  5905. $relParts[] = " }\n";
  5906. }
  5907. $relParts[] = " relChange" . $arg['qid'] . "=true;\n";
  5908. if($arg['type']!='R') // Ranking: rowdivid are subquestion, but array filter apply to answers and not SQ.
  5909. $relParts[] = " $('#relevance" . $sq['rowdivid'] . "').val('1');\n";
  5910. $relParts[] = " }\n else {\n";
  5911. if ($sq['isExclusiveJS'] != '')
  5912. {
  5913. if ($sq['irrelevantAndExclusiveJS'] != '')
  5914. {
  5915. $relParts[] = " if ( " . $sq['irrelevantAndExclusiveJS'] . " ) {\n";
  5916. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
  5917. $relParts[] = " }\n";
  5918. $relParts[] = " else {\n";
  5919. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
  5920. if ($afHide)
  5921. {
  5922. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').hide();\n";
  5923. }
  5924. else
  5925. {
  5926. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
  5927. }
  5928. $relParts[] = " }\n";
  5929. }
  5930. else
  5931. {
  5932. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
  5933. }
  5934. }
  5935. else
  5936. {
  5937. if ($afHide)
  5938. {
  5939. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').hide();\n";
  5940. }
  5941. else
  5942. {
  5943. $relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
  5944. }
  5945. }
  5946. $relParts[] = " relChange" . $arg['qid'] . "=true;\n";
  5947. if($arg['type']!='R') // Ranking: rowdivid are subquestion, but array filter apply to answers and not SQ.
  5948. $relParts[] = " $('#relevance" . $sq['rowdivid'] . "').val('');\n";
  5949. switch ($sq['qtype'])
  5950. {
  5951. case 'L': //LIST drop-down/radio-button list
  5952. $listItem = substr($sq['rowdivid'],strlen($sq['sgqa'])); // gets the part of the rowdiv id past the end of the sgqa code.
  5953. $relParts[] = " if (($('#java" . $sq['sgqa'] ."').val() == '" . $listItem . "')";
  5954. if ($listItem == 'other') {
  5955. $relParts[] = " || ($('#java" . $sq['sgqa'] ."').val() == '-oth-')";
  5956. }
  5957. $relParts[] = "){\n";
  5958. $relParts[] = " $('#java" . $sq['sgqa'] . "').val('');\n";
  5959. $relParts[] = " $('#answer" . $sq['sgqa'] . "NANS').attr('checked',true);\n";
  5960. $relParts[] = " }\n";
  5961. break;
  5962. default:
  5963. break;
  5964. }
  5965. $relParts[] = " }\n";
  5966. $sqvars = explode('|',$sq['relevanceVars']);
  5967. if (is_array($sqvars))
  5968. {
  5969. $allJsVarsUsed = array_merge($allJsVarsUsed,$sqvars);
  5970. $relJsVarsUsed = array_merge($relJsVarsUsed,$sqvars);
  5971. }
  5972. }
  5973. // Do all tailoring
  5974. $relParts[] = implode("\n",$tailorParts);
  5975. // Do custom validation
  5976. foreach ($subqValidations as $_veq)
  5977. {
  5978. if ($_veq['subqValidSelector'] == '') {
  5979. continue;
  5980. }
  5981. $isValid = $LEM->em->ProcessBooleanExpression($_veq['subqValidEqn'],$arg['gseq'],$LEM->questionId2questionSeq[$arg['qid']]);
  5982. $_sqValidVars = $LEM->em->GetJSVarsUsed();
  5983. $allJsVarsUsed = array_merge($allJsVarsUsed,$_sqValidVars);
  5984. $valJsVarsUsed = array_merge($valJsVarsUsed,$_sqValidVars);
  5985. $validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
  5986. $valParts[] = "\n if(" . $validationJS . "){\n";
  5987. $valParts[] = " $('#" . $_veq['subqValidSelector'] . "').addClass('em_sq_validation').removeClass('error').addClass('good');;\n";
  5988. $valParts[] = " }\n else {\n";
  5989. $valParts[] = " $('#" . $_veq['subqValidSelector'] . "').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
  5990. $valParts[] = " }\n";
  5991. }
  5992. // Set color-coding for validation equations
  5993. if (count($validationEqns) > 0) {
  5994. $_hasSumRange=false;
  5995. $_hasOtherValidation=false;
  5996. $_hasOther2Validation=false;
  5997. $valParts[] = " isValidSum" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
  5998. $valParts[] = " isValidOther" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
  5999. $valParts[] = " isValidOtherComment" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
  6000. foreach ($validationEqns as $vclass=>$validationEqn)
  6001. {
  6002. if ($validationEqn == '') {
  6003. continue;
  6004. }
  6005. if ($vclass == 'sum_range')
  6006. {
  6007. $_hasSumRange = true;
  6008. }
  6009. else if ($vclass == 'other_comment_mandatory')
  6010. {
  6011. $_hasOther2Validation = true;
  6012. }
  6013. else
  6014. {
  6015. $_hasOtherValidation = true;
  6016. }
  6017. $_isValid = $LEM->em->ProcessBooleanExpression($validationEqn,$arg['gseq'],$LEM->questionId2questionSeq[$arg['qid']]);
  6018. $_vars = $LEM->em->GetJSVarsUsed();
  6019. $allJsVarsUsed = array_merge($allJsVarsUsed,$_vars);
  6020. $valJsVarsUsed = array_merge($valJsVarsUsed,$_vars);
  6021. $_validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
  6022. $valParts[] = "\n if(" . $_validationJS . "){\n";
  6023. $valParts[] = " $('#vmsg_" . $arg['qid'] . '_' . $vclass . "').removeClass('error').addClass('good');\n";
  6024. $valParts[] = " }\n else {\n";
  6025. $valParts[] = " $('#vmsg_" . $arg['qid'] . '_' . $vclass ."').removeClass('good').addClass('error');\n";
  6026. switch ($vclass)
  6027. {
  6028. case 'sum_range':
  6029. $valParts[] = " isValidSum" . $arg['qid'] . "=false;\n";
  6030. break;
  6031. case 'other_comment_mandatory':
  6032. $valParts[] = " isValidOtherComment" . $arg['qid'] . "=false;\n";
  6033. break;
  6034. // case 'num_answers':
  6035. // case 'value_range':
  6036. // case 'sq_fn_validation':
  6037. // case 'q_fn_validation':
  6038. // case 'regex_validation':
  6039. default:
  6040. $valParts[] = " isValidOther" . $arg['qid'] . "=false;\n";
  6041. break;
  6042. }
  6043. $valParts[] = " }\n";
  6044. }
  6045. $valParts[] = "\n if(isValidSum" . $arg['qid'] . "){\n";
  6046. $valParts[] = " $('#totalvalue_" . $arg['qid'] . "').removeClass('error').addClass('good');\n";
  6047. $valParts[] = " }\n else {\n";
  6048. $valParts[] = " $('#totalvalue_" . $arg['qid'] . "').removeClass('good').addClass('error');\n";
  6049. $valParts[] = " }\n";
  6050. // color-code single-entry fields as needed
  6051. switch ($arg['type'])
  6052. {
  6053. case 'N':
  6054. case 'S':
  6055. case 'T':
  6056. case 'U':
  6057. $valParts[] = "\n if(isValidOther" . $arg['qid'] . "){\n";
  6058. $valParts[] = " $('#question" . $arg['qid'] . " :input').addClass('em_sq_validation').removeClass('error').addClass('good');\n";
  6059. $valParts[] = " }\n else {\n";
  6060. $valParts[] = " $('#question" . $arg['qid'] . " :input').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
  6061. $valParts[] = " }\n";
  6062. break;
  6063. default:
  6064. break;
  6065. }
  6066. // color-code mandatory other comment fields
  6067. switch ($arg['type'])
  6068. {
  6069. case '!':
  6070. case 'L':
  6071. case 'P':
  6072. switch ($arg['type'])
  6073. {
  6074. case '!':
  6075. $othervar = 'othertext' . substr($arg['jsResultVar'],4,-5);
  6076. break;
  6077. case 'L':
  6078. $othervar = 'answer' . substr($arg['jsResultVar'],4) . 'text';
  6079. break;
  6080. case 'P':
  6081. $othervar = 'answer' . substr($arg['jsResultVar'],4);
  6082. break;
  6083. }
  6084. $valParts[] = "\n if(isValidOtherComment" . $arg['qid'] . "){\n";
  6085. $valParts[] = " $('#" . $othervar . "').addClass('em_sq_validation').removeClass('error').addClass('good');\n";
  6086. $valParts[] = " }\n else {\n";
  6087. $valParts[] = " $('#" . $othervar . "').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
  6088. $valParts[] = " }\n";
  6089. break;
  6090. default:
  6091. break;
  6092. }
  6093. }
  6094. if (count($valParts) > 0)
  6095. {
  6096. $valJsVarsUsed = array_unique($valJsVarsUsed);
  6097. $qvalJS = "function LEMval" . $arg['qid'] . "(sgqa){\n";
  6098. // $qvalJS .= " var UsesVars = ' " . implode(' ', $valJsVarsUsed) . " ';\n";
  6099. // $qvalJS .= " if (typeof sgqa !== 'undefined' && !LEMregexMatch('/ java' + sgqa + ' /', UsesVars)) {\n return;\n }\n";
  6100. $qvalJS .= implode("",$valParts);
  6101. $qvalJS .= "}\n";
  6102. $valEqns[] = $qvalJS;
  6103. $relParts[] = " LEMval" . $arg['qid'] . "(sgqa);\n";
  6104. }
  6105. if ($arg['hidden']) {
  6106. $relParts[] = " // This question should always be hidden\n";
  6107. $relParts[] = " $('#question" . $arg['qid'] . "').hide();\n";
  6108. }
  6109. else {
  6110. if (!($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)))
  6111. {
  6112. // In such cases, PHP will make the question visible by default. By not forcing a re-show(), template.js can hide questions with impunity
  6113. $relParts[] = " $('#question" . $arg['qid'] . "').show();\n";
  6114. if ($arg['type'] == 'S')
  6115. {
  6116. $relParts[] = " if($('#question" . $arg['qid'] . " div[id^=\"gmap_canvas\"]').length > 0)\n";
  6117. $relParts[] = " {\n";
  6118. $relParts[] = " resetMap(" . $arg['qid'] . ");\n";
  6119. $relParts[] = " }\n";
  6120. }
  6121. }
  6122. }
  6123. // If it is an equation, and relevance is true, then write the value from the question to the answer field storing the result
  6124. if ($arg['type'] == '*')
  6125. {
  6126. $relParts[] = " // Write value from the question into the answer field\n";
  6127. $jsResultVar = $LEM->em->GetJsVarFor($arg['jsResultVar']);
  6128. // Note, this will destroy embedded HTML in the equation (e.g. if it is a report)
  6129. // Should be possible to use jQuery to remove just the LEMtailoring span, but not easy since done (the following doesn't work)
  6130. // _tmpval = $('#question801 .em_equation').clone()
  6131. // $(_tmpval).find('[id^=LEMtailor]').each(function(){ $(this).replaceWith(function(){ $(this).contents; }); })
  6132. $relParts[] = " $('#" . substr($jsResultVar,1,-1) . "').val($.trim(LEMstrip_tags($('#question" . $arg['qid'] . " .em_equation').html())));\n";
  6133. }
  6134. $relParts[] = " relChange" . $arg['qid'] . "=true;\n"; // any change to this value should trigger a propagation of changess
  6135. $relParts[] = " $('#relevance" . $arg['qid'] . "').val('1');\n";
  6136. $relParts[] = "}\n";
  6137. if (!($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)))
  6138. {
  6139. if (!isset($dynamicQinG[$arg['gseq']]))
  6140. {
  6141. $dynamicQinG[$arg['gseq']] = array();
  6142. }
  6143. $dynamicQinG[$arg['gseq']][$arg['qid']]=true;
  6144. $relParts[] = "else {\n";
  6145. $relParts[] = " $('#question" . $arg['qid'] . "').hide();\n";
  6146. $relParts[] = " if ($('#relevance" . $arg['qid'] . "').val()=='1') { relChange" . $arg['qid'] . "=true; }\n"; // only propagate changes if changing from relevant to irrelevant
  6147. $relParts[] = " $('#relevance" . $arg['qid'] . "').val('0');\n";
  6148. $relParts[] = "}\n";
  6149. }
  6150. $vars = explode('|',$arg['relevanceVars']);
  6151. if (is_array($vars))
  6152. {
  6153. $allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
  6154. $relJsVarsUsed = array_merge($relJsVarsUsed,$vars);
  6155. }
  6156. $relJsVarsUsed = array_merge($relJsVarsUsed,$valJsVarsUsed);
  6157. $relJsVarsUsed = array_unique($relJsVarsUsed);
  6158. $qrelQIDs = array();
  6159. $qrelgseqs = array(); // so that any group-level change is also propagated
  6160. foreach ($relJsVarsUsed as $jsVar)
  6161. {
  6162. if ($jsVar != '' && isset($LEM->knownVars[substr($jsVar,4)]['qid']))
  6163. {
  6164. $knownVar = $LEM->knownVars[substr($jsVar,4)];
  6165. if ($LEM->surveyMode=='group' && $knownVar['gseq'] != $LEM->currentGroupSeq) {
  6166. continue; // don't make dependent upon off-page variables
  6167. }
  6168. $_qid = $knownVar['qid'];
  6169. if ($_qid == $arg['qid']) {
  6170. continue; // don't make dependent upon itself
  6171. }
  6172. $qrelQIDs[] = 'relChange' . $_qid;
  6173. $qrelgseqs[] = 'relChangeG' . $knownVar['gseq'];
  6174. }
  6175. }
  6176. $qrelgseqs[] = 'relChangeG' . $arg['gseq']; // so if current group changes visibility, re-tailor it.
  6177. $qrelQIDs = array_unique($qrelQIDs);
  6178. $qrelgseqs = array_unique($qrelgseqs);
  6179. if ($LEM->surveyMode=='question')
  6180. {
  6181. $qrelQIDs=array(); // in question-by-questin mode, should never test for dependencies on self or other questions.
  6182. $qrelgseqs=array();
  6183. }
  6184. $qrelJS = "function LEMrel" . $arg['qid'] . "(sgqa){\n";
  6185. $qrelJS .= " var UsesVars = ' " . implode(' ', $relJsVarsUsed) . " ';\n";
  6186. if (count($qrelQIDs) > 0)
  6187. {
  6188. $qrelJS .= " if(" . implode(' || ', $qrelQIDs) . "){\n ;\n }\n else";
  6189. }
  6190. if (count($qrelgseqs) > 0)
  6191. {
  6192. $qrelJS .= " if(" . implode(' || ', $qrelgseqs) . "){\n ;\n }\n else";
  6193. }
  6194. $qrelJS .= " if (typeof sgqa !== 'undefined' && !LEMregexMatch('/ java' + sgqa + ' /', UsesVars)) {\n";
  6195. $qrelJS .= " return;\n }\n";
  6196. $qrelJS .= implode("",$relParts);
  6197. $qrelJS .= "}\n";
  6198. $relEqns[] = $qrelJS;
  6199. $gseq_qidList[$arg['gseq']][$arg['qid']] = '1'; // means has an explicit LEMrel() function
  6200. }
  6201. }
  6202. foreach(array_keys($gseq_qidList) as $_gseq)
  6203. {
  6204. $relChangeVars[] = " relChangeG" . $_gseq . "=false;\n";
  6205. }
  6206. $jsParts[] = implode("",$relChangeVars);
  6207. // Process relevance for each group; and if group is relevant, process each contained question in order
  6208. foreach ($LEM->gRelInfo as $gr)
  6209. {
  6210. if (!array_key_exists($gr['gseq'],$gseqList)) {
  6211. continue;
  6212. }
  6213. if ($gr['relevancejs'] != '')
  6214. {
  6215. // $jsParts[] = "\n// Process Relevance for Group " . $gr['gid'];
  6216. // $jsParts[] = ": { " . $gr['eqn'] . " }";
  6217. $jsParts[] = "\nif (" . $gr['relevancejs'] . ") {\n";
  6218. $jsParts[] = " $('#group-" . $gr['gseq'] . "').show();\n";
  6219. $jsParts[] = " relChangeG" . $gr['gseq'] . "=true;\n";
  6220. $jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(1);\n";
  6221. $qids = $gseq_qidList[$gr['gseq']];
  6222. foreach ($qids as $_qid=>$_val)
  6223. {
  6224. $qid2exclusiveAuto = (isset($LEM->qid2exclusiveAuto[$_qid]) ? $LEM->qid2exclusiveAuto[$_qid] : array());
  6225. if ($_val==1)
  6226. {
  6227. $jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
  6228. if (isset($LEM->qattr[$_qid]['exclude_all_others_auto']) && $LEM->qattr[$_qid]['exclude_all_others_auto'] == '1'
  6229. && isset($qid2exclusiveAuto['js']) && strlen($qid2exclusiveAuto['js']) > 0)
  6230. {
  6231. $jsParts[] = $qid2exclusiveAuto['js'];
  6232. $vars = explode('|',$qid2exclusiveAuto['relevanceVars']);
  6233. if (is_array($vars))
  6234. {
  6235. $allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
  6236. }
  6237. if (!isset($rowdividList[$qid2exclusiveAuto['rowdivid']]))
  6238. {
  6239. $rowdividList[$qid2exclusiveAuto['rowdivid']] = true;
  6240. }
  6241. }
  6242. if (isset($LEM->qattr[$_qid]['exclude_all_others']))
  6243. {
  6244. foreach (explode(';',trim($LEM->qattr[$_qid]['exclude_all_others'])) as $eo)
  6245. {
  6246. // then need to call the function twice so that cascading of array filter onto an excluded option works
  6247. $jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
  6248. }
  6249. }
  6250. }
  6251. }
  6252. $jsParts[] = "}\nelse {\n";
  6253. $jsParts[] = " $('#group-" . $gr['gseq'] . "').hide();\n";
  6254. $jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='1') { relChangeG" . $gr['gseq'] . "=true; }\n";
  6255. $jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(0);\n";
  6256. $jsParts[] = "}\n";
  6257. }
  6258. else
  6259. {
  6260. $qids = $gseq_qidList[$gr['gseq']];
  6261. foreach ($qids as $_qid=>$_val)
  6262. {
  6263. $qid2exclusiveAuto = (isset($LEM->qid2exclusiveAuto[$_qid]) ? $LEM->qid2exclusiveAuto[$_qid] : array());
  6264. if ($_val == 1)
  6265. {
  6266. $jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
  6267. if (isset($LEM->qattr[$_qid]['exclude_all_others_auto']) && $LEM->qattr[$_qid]['exclude_all_others_auto'] == '1'
  6268. && isset($qid2exclusiveAuto['js']) && strlen($qid2exclusiveAuto['js']) > 0)
  6269. {
  6270. $jsParts[] = $qid2exclusiveAuto['js'];
  6271. $vars = explode('|',$qid2exclusiveAuto['relevanceVars']);
  6272. if (is_array($vars))
  6273. {
  6274. $allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
  6275. }
  6276. if (!isset($rowdividList[$qid2exclusiveAuto['rowdivid']]))
  6277. {
  6278. $rowdividList[$qid2exclusiveAuto['rowdivid']] = true;
  6279. }
  6280. }
  6281. if (isset($LEM->qattr[$_qid]['exclude_all_others']))
  6282. {
  6283. foreach (explode(';',trim($LEM->qattr[$_qid]['exclude_all_others'])) as $eo)
  6284. {
  6285. // then need to call the function twice so that cascading of array filter onto an excluded option works
  6286. $jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
  6287. }
  6288. }
  6289. }
  6290. }
  6291. }
  6292. // Add logic for all-in-one mode to show/hide groups as long as at there is at least one relevant question within the group
  6293. // Only do this if there is no explicit group-level relevance equation, else may override group-level relevance
  6294. $dynamicQidsInG = (isset($dynamicQinG[$gr['gseq']]) ? $dynamicQinG[$gr['gseq']] : array());
  6295. $GalwaysVisible = (isset($GalwaysRelevant[$gr['gseq']]) ? $GalwaysRelevant[$gr['gseq']] : false);
  6296. if ($LEM->surveyMode == 'survey' && !$GalwaysVisible && count($dynamicQidsInG) > 0 && strlen(trim($gr['relevancejs']))== 0)
  6297. {
  6298. // check whether any dependent questions have changed
  6299. $relStatusTest = "($('#relevance" . implode("').val()=='1' || $('#relevance", array_keys($dynamicQidsInG)) . "').val()=='1')";
  6300. $jsParts[] = "\nif (" . $relStatusTest . ") {\n";
  6301. $jsParts[] = " $('#group-" . $gr['gseq'] . "').show();\n";
  6302. $jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='0') { relChangeG" . $gr['gseq'] . "=true; }\n";
  6303. $jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(1);\n";
  6304. $jsParts[] = "}\nelse {\n";
  6305. $jsParts[] = " $('#group-" . $gr['gseq'] . "').hide();\n";
  6306. $jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='1') { relChangeG" . $gr['gseq'] . "=true; }\n";
  6307. $jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(0);\n";
  6308. $jsParts[] = "}\n";
  6309. }
  6310. // now make sure any needed variables are accessible
  6311. $vars = explode('|',$gr['relevanceVars']);
  6312. if (is_array($vars))
  6313. {
  6314. $allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
  6315. }
  6316. }
  6317. $jsParts[] = "\n}\n";
  6318. $jsParts[] = implode("\n",$relEqns);
  6319. $jsParts[] = implode("\n",$valEqns);
  6320. $allJsVarsUsed = array_unique($allJsVarsUsed);
  6321. // Add JavaScript Mapping Arrays
  6322. if (isset($LEM->alias2varName) && count($LEM->alias2varName) > 0)
  6323. {
  6324. $neededAliases=array();
  6325. $neededCanonical=array();
  6326. $neededCanonicalAttr=array();
  6327. foreach ($allJsVarsUsed as $jsVar)
  6328. {
  6329. if ($jsVar == '') {
  6330. continue;
  6331. }
  6332. if (preg_match("/^.*\.NAOK$/", $jsVar)) {
  6333. $jsVar = preg_replace("/\.NAOK$/","",$jsVar);
  6334. }
  6335. $neededCanonical[] = $jsVar;
  6336. foreach ($LEM->alias2varName as $key=>$value)
  6337. {
  6338. if ($jsVar == $value['jsName'])
  6339. {
  6340. $neededAliases[] = $value['jsPart'];
  6341. }
  6342. }
  6343. }
  6344. $neededCanonical = array_unique($neededCanonical);
  6345. foreach ($neededCanonical as $nc)
  6346. {
  6347. $neededCanonicalAttr[] = $LEM->varNameAttr[$nc];
  6348. }
  6349. $neededAliases = array_unique($neededAliases);
  6350. if (count($neededAliases) > 0)
  6351. {
  6352. $jsParts[] = "var LEMalias2varName = {\n";
  6353. $jsParts[] = implode(",\n",$neededAliases);
  6354. $jsParts[] = "};\n";
  6355. }
  6356. if (count($neededCanonicalAttr) > 0)
  6357. {
  6358. $jsParts[] = "var LEMvarNameAttr = {\n";
  6359. $jsParts[] = implode(",\n",$neededCanonicalAttr);
  6360. $jsParts[] = "};\n";
  6361. }
  6362. }
  6363. $jsParts[] = "//-->\n</script>\n";
  6364. // Now figure out which variables have not been declared (those not on the current page)
  6365. $undeclaredJsVars = array();
  6366. $undeclaredVal = array();
  6367. if (!$LEM->allOnOnePage)
  6368. {
  6369. foreach ($LEM->knownVars as $key=>$knownVar)
  6370. {
  6371. if (!is_numeric($key[0])) {
  6372. continue;
  6373. }
  6374. if ($knownVar['jsName'] == '') {
  6375. continue;
  6376. }
  6377. foreach ($allJsVarsUsed as $jsVar)
  6378. {
  6379. if ($jsVar == $knownVar['jsName'])
  6380. {
  6381. if ($LEM->surveyMode=='group' && $knownVar['gseq'] == $LEM->currentGroupSeq) {
  6382. if ($knownVar['hidden'] && $knownVar['type'] != '*') {
  6383. ; // need to declare a hidden variable for non-equation hidden variables so can do dynamic lookup.
  6384. }
  6385. else {
  6386. continue;
  6387. }
  6388. }
  6389. if ($LEM->surveyMode=='question' && $knownVar['qid'] == $LEM->currentQID) {
  6390. continue;
  6391. }
  6392. $undeclaredJsVars[] = $jsVar;
  6393. $sgqa = $knownVar['sgqa'];
  6394. $codeValue = (isset($_SESSION[$LEM->sessid][$sgqa])) ? $_SESSION[$LEM->sessid][$sgqa] : '';
  6395. $undeclaredVal[$jsVar] = $codeValue;
  6396. if (isset($LEM->jsVar2qid[$jsVar])) {
  6397. $qidList[$LEM->jsVar2qid[$jsVar]] = $LEM->jsVar2qid[$jsVar];
  6398. }
  6399. }
  6400. }
  6401. }
  6402. $undeclaredJsVars = array_unique($undeclaredJsVars);
  6403. foreach ($undeclaredJsVars as $jsVar)
  6404. {
  6405. // TODO - is different type needed for text? Or process value to striphtml?
  6406. if ($jsVar == '') continue;
  6407. $jsParts[] = "<input type='hidden' id='" . $jsVar . "' name='" . substr($jsVar,4) . "' value='" . htmlspecialchars($undeclaredVal[$jsVar],ENT_QUOTES) . "'/>\n";
  6408. }
  6409. }
  6410. else
  6411. {
  6412. // For all-in-one mode, declare the always-hidden variables, since qanda will not be called for them.
  6413. foreach ($LEM->knownVars as $key=>$knownVar)
  6414. {
  6415. if (!is_numeric($key[0])) {
  6416. continue;
  6417. }
  6418. if ($knownVar['jsName'] == '') {
  6419. continue;
  6420. }
  6421. if ($knownVar['hidden'])
  6422. {
  6423. $jsVar = $knownVar['jsName'];
  6424. $undeclaredJsVars[] = $jsVar;
  6425. $sgqa = $knownVar['sgqa'];
  6426. $codeValue = (isset($_SESSION[$LEM->sessid][$sgqa])) ? $_SESSION[$LEM->sessid][$sgqa] : '';
  6427. $undeclaredVal[$jsVar] = $codeValue;
  6428. }
  6429. }
  6430. $undeclaredJsVars = array_unique($undeclaredJsVars);
  6431. foreach ($undeclaredJsVars as $jsVar)
  6432. {
  6433. if ($jsVar == '') continue;
  6434. $jsParts[] = "<input type='hidden' id='" . $jsVar . "' name='" . $jsVar . "' value='" . htmlspecialchars($undeclaredVal[$jsVar],ENT_QUOTES) . "'/>\n";
  6435. }
  6436. }
  6437. foreach ($qidList as $qid)
  6438. {
  6439. if (isset($_SESSION[$LEM->sessid]['relevanceStatus'])) {
  6440. $relStatus = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
  6441. }
  6442. else {
  6443. $relStatus = 1;
  6444. }
  6445. $jsParts[] = "<input type='hidden' id='relevance" . $qid . "' name='relevance" . $qid . "' value='" . $relStatus . "'/>\n";
  6446. }
  6447. foreach ($gseqList as $gseq)
  6448. {
  6449. if (isset($_SESSION['relevanceStatus'])) {
  6450. $relStatus = (isset($_SESSION['relevanceStatus']['G' . $gseq]) ? $_SESSION['relevanceStatus']['G' . $gseq] : 1);
  6451. }
  6452. else {
  6453. $relStatus = 1;
  6454. }
  6455. $jsParts[] = "<input type='hidden' id='relevanceG" . $gseq . "' name='relevanceG" . $gseq . "' value='" . $relStatus . "'/>\n";
  6456. }
  6457. foreach ($rowdividList as $key=>$val)
  6458. {
  6459. $jsParts[] = "<input type='hidden' id='relevance" . $key . "' name='relevance" . $key . "' value='" . $val . "'/>\n";
  6460. }
  6461. $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
  6462. return implode('',$jsParts);
  6463. }
  6464. static function setTempVars($vars)
  6465. {
  6466. $LEM =& LimeExpressionManager::singleton();
  6467. $LEM->tempVars = $vars;
  6468. }
  6469. /**
  6470. * Unit test strings containing expressions
  6471. */
  6472. static function UnitTestProcessStringContainingExpressions()
  6473. {
  6474. $vars = array(
  6475. 'name' => array('sgqa'=>'name', 'code'=>'Peter', 'jsName'=>'java61764X1X1', 'readWrite'=>'N', 'type'=>'X', 'question'=>'What is your first/given name?', 'qseq'=>10, 'gseq'=>1),
  6476. 'surname' => array('sgqa'=>'surname', 'code'=>'Smith', 'jsName'=>'java61764X1X1', 'readWrite'=>'Y', 'type'=>'X', 'question'=>'What is your last/surname?', 'qseq'=>20, 'gseq'=>1),
  6477. 'age' => array('sgqa'=>'age', 'code'=>45, 'jsName'=>'java61764X1X2', 'readWrite'=>'Y', 'type'=>'X', 'question'=>'How old are you?', 'qseq'=>30, 'gseq'=>2),
  6478. 'numKids' => array('sgqa'=>'numKids', 'code'=>2, 'jsName'=>'java61764X1X3', 'readWrite'=>'Y', 'type'=>'X', 'question'=>'How many kids do you have?', 'relevance'=>'1', 'qid'=>'40','qseq'=>40, 'gseq'=>2),
  6479. 'numPets' => array('sgqa'=>'numPets', 'code'=>1, 'jsName'=>'java61764X1X4', 'readWrite'=>'Y', 'type'=>'X','question'=>'How many pets do you have?', 'qseq'=>50, 'gseq'=>2),
  6480. 'gender' => array('sgqa'=>'gender', 'code'=>'M', 'jsName'=>'java61764X1X5', 'readWrite'=>'Y', 'type'=>'X', 'shown'=>'Male','question'=>'What is your gender (male/female)?', 'qseq'=>110, 'gseq'=>2),
  6481. 'notSetYet' => array('sgqa'=>'notSetYet', 'code'=>'?', 'jsName'=>'java61764X3X6', 'readWrite'=>'Y', 'type'=>'X', 'shown'=>'Unknown','question'=>'Who will win the next election?', 'qseq'=>200, 'gseq'=>3),
  6482. // Constants
  6483. '61764X1X1' => array('sgqa'=>'61764X1X1', 'code'=> '<Sergei>', 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>70, 'gseq'=>2),
  6484. '61764X1X2' => array('sgqa'=>'61764X1X2', 'code'=> 45, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>80, 'gseq'=>2),
  6485. '61764X1X3' => array('sgqa'=>'61764X1X3', 'code'=> 2, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>15, 'gseq'=>1),
  6486. '61764X1X4' => array('sgqa'=>'61764X1X4', 'code'=> 1, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>100, 'gseq'=>2),
  6487. 'TOKEN:ATTRIBUTE_1' => array('code'=> 'worker', 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X'),
  6488. );
  6489. $tests = <<<EOD
  6490. This example shows escaping of the curly braces: \{\{test\}\} {if(1==1,'{{test}}', '1 is not 1?')} should not throw any errors.
  6491. <b>Here is an example of OK syntax with tooltips</b><br />Hello {if(gender=='M','Mr.','Mrs.')} {surname}, it is now {date('g:i a',time())}. Do you know where your {sum(numPets,numKids)} chidren and pets are?
  6492. <b>Here are common errors so you can see the tooltips</b><br />Variables used before they are declared: {notSetYet}<br />Unknown Function: {iff(numPets>numKids,1,2)}<br />Unknown Variable: {sum(age,num_pets,numKids)}<br />Wrong # parameters: {sprintf()},{if(1,2)},{date()}<br />Assign read-only-vars:{TOKEN:ATTRIBUTE_1+=10},{name='Sally'}<br />Unbalanced parentheses: {pow(3,4},{(pow(3,4)},{pow(3,4))}
  6493. <b>Here is some of the unsupported syntax</b><br />No support for '++', '--', '%',';': {min(++age, --age,age % 2);}<br />Nor '|', '&', '^': {(sum(2 | 3,3 & 4,5 ^ 6)}}<br />Nor arrays: {name[2], name['mine']}
  6494. <b>Inline JavaScipt that forgot to add spaces after curly brace</b><br />[script type="text/javascript" language="Javascript"] var job='{TOKEN:ATTRIBUTE_1}'; if (job=='worker') {document.write('BOSSES');}[/script]
  6495. <b>Unknown/Misspelled Variables, Functions, and Operators</b><br />{if(sex=='M','Mr.','Mrs.')} {surname}, next year you will be {age++} years old.
  6496. <b>Warns if use = instead of == or perform value assignments</b><br>Hello, {if(gender='M','Mr.','Mrs.')} {surname}, next year you will be {age+=1} years old.
  6497. <b>Wrong number of arguments for functions:</b><br />{if(gender=='M','Mr.','Mrs.','Other')} {surname}, sum(age,numKids,numPets)={sum(age,numKids,numPets,)}
  6498. <b>Mismatched parentheses</b><br />pow(3,4)={pow(3,4)}<br />but these are wrong: {pow(3,4}, {(((pow(3,4)}, {pow(3,4))}
  6499. <b>Unsupported syntax</b><br />No support for '++', '--', '%',';': {min(++age, --age, age % 2);}<br />Nor '|', '&', '^': {(sum(2 | 3, 3 & 4, 5 ^ 6)}}<br />Nor arrays: {name[2], name['mine']}
  6500. <b>Invalid assignments</b><br />Assign values to equations or strings: {(3 + 4)=5}, {'hi'='there'}<br />Assign read-only vars: {TOKEN:ATTRIBUTE_1='boss'}, {name='Sally'}
  6501. <b>Values:</b><br />name={name}; surname={surname}<br />gender={gender}; age={age}; numPets={numPets}<br />numKids=INSERTANS:61764X1X3={numKids}={INSERTANS:61764X1X3}<br />TOKEN:ATTRIBUTE_1={TOKEN:ATTRIBUTE_1}
  6502. <b>Question attributes:</b><br />numKids.question={numKids.question}; Question#={numKids.qid}; .relevance={numKids.relevance}
  6503. <b>Math:</b><br/>5+7={5+7}; 2*pi={2*pi()}; sin(pi/2)={sin(pi()/2)}; max(age,numKids,numPets)={max(age,numKids,numPets)}
  6504. <b>Text Processing:</b><br />{str_replace('like','love','I like LimeSurvey')}<br />{ucwords('hi there')}, {name}<br />{implode('--',name,'this is','a convenient way','way to','concatenate strings')}
  6505. <b>Dates:</b><br />{name}, the current date/time is: {date('F j, Y, g:i a',time())}
  6506. <b>Conditional:</b><br />Hello, {if(gender=='M','Mr.','Mrs.')} {surname}, may I call you {name}?
  6507. <b>Tailored Paragraph:</b><br />{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.<p>Since you have more {if((numKids > numPets),'children','pets')} than you do {if((numKids > numPets),'pets','children')}, do you feel that the {if((numKids > numPets),'pets','children')} are at a disadvantage?</p>
  6508. <b>EM processes within strings:</b><br />Here is your picture [img src='images/users_{name}_{surname}.jpg' alt='{if(gender=='M','Mr.','Mrs.')} {name} {surname}'/];
  6509. <b>EM doesn't process curly braces like these:</b><br />{name}, { this is not an expression}<br />{nor is this }, { nor this }<br />\{nor this\},{this\},\{or this }
  6510. {INSERTANS:61764X1X1}, you said that you are {INSERTANS:61764X1X2} years old, and that you have {INSERTANS:61764X1X3} {if((INSERTANS:61764X1X3==1),'child','children')} and {INSERTANS:61764X1X4} {if((INSERTANS:61764X1X4==1),'pet','pets')} running around the house. So, you have {INSERTANS:61764X1X3 + INSERTANS:61764X1X4} wild {if((INSERTANS:61764X1X3 + INSERTANS:61764X1X4 ==1),'beast','beasts')} to chase around every day.
  6511. Since you have more {if((INSERTANS:61764X1X3 > INSERTANS:61764X1X4),'children','pets')} than you do {if((INSERTANS:61764X1X3 > INSERTANS:61764X1X4),'pets','children')}, do you feel that the {if((INSERTANS:61764X1X3 > INSERTANS:61764X1X4),'pets','children')} are at a disadvantage?
  6512. {INSERTANS:61764X1X1}, you said that you are {INSERTANS:61764X1X2} years old, and that you have {INSERTANS:61764X1X3} {if((INSERTANS:61764X1X3==1),'child','children','kiddies')} and {INSERTANS:61764X1X4} {if((INSERTANS:61764X1X4==1),'pet','pets')} running around the house. So, you have {INSERTANS:61764X1X3 + INSERTANS:61764X1X4} wild {if((INSERTANS:61764X1X3 + INSERTANS:61764X1X4 ==1),'beast','beasts')} to chase around every day.
  6513. This line should throw errors since the curly-brace enclosed functions do not have linefeeds after them (and before the closing curly brace): var job='{TOKEN:ATTRIBUTE_1}'; if (job=='worker') { document.write('BOSSES') } else { document.write('WORKERS') }
  6514. This line has a script section, but if you look at the source, you will see that it has errors: <script type="text/javascript" language="Javascript">var job='{TOKEN:ATTRIBUTE_1}'; if (job=='worker') {document.write('BOSSES')} else {document.write('WORKERS')} </script>.
  6515. Substitions that begin or end with a space should be ignored: { name} {age }
  6516. EOD;
  6517. $alltests = explode("\n",$tests);
  6518. $javascript1 = <<<EOST
  6519. var job='{TOKEN:ATTRIBUTE_1}';
  6520. if (job=='worker') {
  6521. document.write('BOSSES')
  6522. } else {
  6523. document.write('WORKERS')
  6524. }
  6525. EOST;
  6526. $javascript2 = <<<EOST
  6527. var job='{TOKEN:ATTRIBUTE_1}';
  6528. if (job=='worker') {
  6529. document.write('BOSSES')
  6530. } else { document.write('WORKERS') }
  6531. EOST;
  6532. $alltests[] = 'This line should have no errors - the Javascript has curly braces followed by line feeds:' . $javascript1;
  6533. $alltests[] = 'This line should also be OK: ' . $javascript2;
  6534. $alltests[] = 'This line has a hidden script: <script type="text/javascript" language="Javascript">' . $javascript1 . '</script>';
  6535. $alltests[] = 'This line has a hidden script: <script type="text/javascript" language="Javascript">' . $javascript2 . '</script>';
  6536. LimeExpressionManager::StartProcessingPage();
  6537. LimeExpressionManager::StartProcessingGroup(1);
  6538. $LEM =& LimeExpressionManager::singleton();
  6539. $LEM->tempVars = $vars;
  6540. $LEM->questionId2questionSeq = array();
  6541. $LEM->questionId2groupSeq = array();
  6542. $_SESSION[$LEM->sessid]['relevanceStatus'] = array();
  6543. foreach ($vars as $var) {
  6544. if (isset($var['qseq'])) {
  6545. $LEM->questionId2questionSeq[$var['qseq']] = $var['qseq'];
  6546. $LEM->questionId2groupSeq[$var['qseq']] = $var['gseq'];
  6547. $_SESSION[$LEM->sessid]['relevanceStatus'][$var['qseq']] = 1;
  6548. }
  6549. }
  6550. print "<h3>Note, if the <i>Vars Used</i> column is red, then at least one error was found in the <b>Source</b>. In such cases, the <i>Vars Used</i> list may be missing names of variables from sub-expressions containing errors</h3>";
  6551. print '<table border="1"><tr><th>Source</th><th>Pretty Print</th><th>Result</th><th>Vars Used</th></tr>';
  6552. for ($i=0;$i<count($alltests);++$i)
  6553. {
  6554. $test = $alltests[$i];
  6555. $result = LimeExpressionManager::ProcessString($test, 40, NULL, false, 1, 1);
  6556. $prettyPrint = LimeExpressionManager::GetLastPrettyPrintExpression();
  6557. $varsUsed = $LEM->em->GetAllVarsUsed();
  6558. if (count($varsUsed) > 0) {
  6559. sort($varsUsed);
  6560. $varList = implode(',<br />', $varsUsed);
  6561. }
  6562. else {
  6563. $varList = '&nbsp;';
  6564. }
  6565. print "<tr><td>" . htmlspecialchars($test,ENT_QUOTES) . "</td>\n";
  6566. print "<td>" . $prettyPrint . "</td>\n";
  6567. print "<td>" . $result . "</td>\n";
  6568. if ($LEM->em->HasErrors()) {
  6569. print "<td style='background-color: red'>";
  6570. }
  6571. else {
  6572. print "<td>";
  6573. }
  6574. print $varList . "</td>\n";
  6575. print "</tr>\n";
  6576. }
  6577. print '</table>';
  6578. LimeExpressionManager::FinishProcessingGroup();
  6579. LimeExpressionManager::FinishProcessingPage();
  6580. }
  6581. /**
  6582. * Unit test Relevance using a simplified syntax to represent questions.
  6583. */
  6584. static function UnitTestRelevance()
  6585. {
  6586. // Tests: varName~relevance~inputType~message
  6587. $tests = <<<EOT
  6588. name~1~text~What is your name?
  6589. age~1~text~How old are you (must be 16-80)?
  6590. badage~1~expr~{badage=((age<16) || (age>80))}
  6591. agestop~!is_empty(age) && ((age<16) || (age>80))~message~Sorry, {name}, you are too {if((age<16),'young',if((age>80),'old','middle-aged'))} for this test.
  6592. kids~!((age<16) || (age>80))~yesno~Do you have children (Y/N)?
  6593. kidsO~!is_empty(kids) && !(kids=='Y' or kids=='N')~message~Please answer the question about whether you have children with 'Y' or 'N'.
  6594. wantsKids~kids=='N'~yesno~Do you hope to have kids some day (Y/N)?
  6595. wantsKidsY~wantsKids=='Y'~message~{name}, I hope you are able to have children some day!
  6596. wantsKidsN~wantsKids=='N'~message~{name}, I hope you have a wonderfully fulfilling life!
  6597. wantsKidsO~!is_empty(wantsKids) && !(wantsKids=='Y' or wantsKids=='N')~message~Please answer the question about whether you want children with 'Y' or 'N'.
  6598. parents~1~expr~{parents = (!badage && kids=='Y')}
  6599. numKids~kids=='Y'~text~How many children do you have?
  6600. numKidsValidation~parents and strlen(numKids) > 0 and numKids <= 0~message~{name}, please check your entries. You said you do have children, {numKids} of them, which makes no sense.
  6601. kid1~numKids >= 1~text~How old is your first child?
  6602. kid2~numKids >= 2~text~How old is your second child?
  6603. kid3~numKids >= 3~text~How old is your third child?
  6604. kid4~numKids >= 4~text~How old is your fourth child?
  6605. kid5~numKids >= 5~text~How old is your fifth child?
  6606. sumage~1~expr~{sumage=sum(kid1.NAOK,kid2.NAOK,kid3.NAOK,kid4.NAOK,kid5.NAOK)}
  6607. report~numKids > 0~message~{name}, you said you are {age} and that you have {numKids} kids. The sum of ages of your first {min(numKids,5)} kids is {sumage}.
  6608. EOT;
  6609. $vars = array();
  6610. $varsNAOK = array();
  6611. $varSeq = array();
  6612. $testArgs = array();
  6613. $argInfo = array();
  6614. LimeExpressionManager::SetDirtyFlag();
  6615. $LEM =& LimeExpressionManager::singleton();
  6616. LimeExpressionManager::StartProcessingPage(true);
  6617. LimeExpressionManager::StartProcessingGroup(1); // pretending this is group 1
  6618. // collect variables
  6619. $i=0;
  6620. foreach(explode("\n",$tests) as $test)
  6621. {
  6622. $args = explode("~",$test);
  6623. $type = (($args[1]=='expr') ? '*' : ($args[1]=='message') ? 'X' : 'S');
  6624. $vars[$args[0]] = array('sgqa'=>$args[0], 'code'=>'', 'jsName'=>'java' . $args[0], 'jsName_on'=>'java' . $args[0], 'readWrite'=>'Y', 'type'=>$type, 'relevanceStatus'=>'1', 'gid'=>1, 'gseq'=>1, 'qseq'=>$i, 'qid'=>$i);
  6625. $varSeq[] = $args[0];
  6626. $testArgs[] = $args;
  6627. $LEM->questionId2questionSeq[$i] = $i;
  6628. $LEM->questionId2groupSeq[$i] = 1;
  6629. $LEM->questionSeq2relevance[$i] = array(
  6630. 'relevance'=>htmlspecialchars(preg_replace('/[[:space:]]/',' ',$args[1]),ENT_QUOTES),
  6631. 'qid'=>$i,
  6632. 'qseq'=>$i,
  6633. 'gseq'=>1,
  6634. 'jsResultVar'=>'java' . $args[0],
  6635. 'type'=>$type,
  6636. 'hidden'=>false,
  6637. 'gid'=>1, // ($i % 3),
  6638. );
  6639. ++$i;
  6640. }
  6641. $LEM->knownVars = $vars;
  6642. $LEM->gRelInfo[1] = array(
  6643. 'gid' => 1,
  6644. 'gseq' => 1,
  6645. 'eqn' => '',
  6646. 'result' => 1,
  6647. 'numJsVars' => 0,
  6648. 'relevancejs' => '',
  6649. 'relevanceVars' => '',
  6650. 'prettyPrint'=> '',
  6651. );
  6652. $LEM->ProcessAllNeededRelevance();
  6653. // collect relevance
  6654. $alias2varName = array();
  6655. $varNameAttr = array();
  6656. for ($i=0;$i<count($testArgs);++$i)
  6657. {
  6658. $testArg = $testArgs[$i];
  6659. $var = $testArg[0];
  6660. $rel = LimeExpressionManager::QuestionIsRelevant($i);
  6661. $question = LimeExpressionManager::ProcessString($testArg[3], $i, NULL, true, 1, 1);
  6662. $jsVarName='java' . str_replace('#','_',$testArg[0]);
  6663. $argInfo[] = array(
  6664. 'num' => $i,
  6665. 'name' => $jsVarName,
  6666. 'sgqa' => $testArg[0],
  6667. 'type' => $testArg[2],
  6668. 'question' => $question,
  6669. 'relevance' => $testArg[1],
  6670. 'relevanceStatus' => $rel
  6671. );
  6672. $alias2varName[$var] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $var . "':'" . $jsVarName . "'");
  6673. $alias2varName[$jsVarName] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $jsVarName . "':'" . $jsVarName . "'");
  6674. $varNameAttr[$jsVarName] = "'" . $jsVarName . "':{"
  6675. . "'jsName':'" . $jsVarName
  6676. . "','jsName_on':'" . $jsVarName
  6677. . "','sgqa':'" . substr($jsVarName,4)
  6678. . "','qid':" . $i
  6679. . ",'gid':". 1 // ($i % 3) // so have 3 possible group numbers
  6680. . "}";
  6681. }
  6682. $LEM->alias2varName = $alias2varName;
  6683. $LEM->varNameAttr = $varNameAttr;
  6684. LimeExpressionManager::FinishProcessingGroup();
  6685. LimeExpressionManager::FinishProcessingPage();
  6686. print <<< EOD
  6687. <script type='text/javascript'>
  6688. <!--
  6689. var LEMradix='.';
  6690. function checkconditions(value, name, type, evt_type)
  6691. {
  6692. if (typeof evt_type === 'undefined')
  6693. {
  6694. evt_type = 'onchange';
  6695. }
  6696. ExprMgr_process_relevance_and_tailoring(evt_type,name,type);
  6697. }
  6698. // -->
  6699. </script>
  6700. EOD;
  6701. print LimeExpressionManager::GetRelevanceAndTailoringJavaScript();
  6702. // Print Table of questions
  6703. print "<h3>This is a test of dynamic relevance.</h3>";
  6704. print "Enter your name and age, and try all the permutations of answers to whether you have or want children.<br />\n";
  6705. print "Note how the text and sum of ages changes dynamically; that prior answers are remembered; and that irrelevant values are not included in the sum of ages.<br />";
  6706. print "<table border='1'><tr><td>";
  6707. foreach ($argInfo as $arg)
  6708. {
  6709. $rel = LimeExpressionManager::QuestionIsRelevant($arg['num']);
  6710. print "<div id='question" . $arg['num'] . (($rel) ? "'" : "' style='display: none'") . ">\n";
  6711. print "<input type='hidden' id='display" . $arg['num'] . "' name='" . $arg['num'] . "' value='" . (($rel) ? 'on' : '') . "'/>\n";
  6712. if ($arg['type'] == 'expr')
  6713. {
  6714. // Hack for testing purposes - rather than using LimeSurvey internals to store the results of equations, process them via a hidden <div>
  6715. print "<div style='display: none' id='hack_" . $arg['name'] . "'>" . $arg['question'];
  6716. print "<input type='hidden' id='" . $arg['name'] . "' name='" . $arg['name'] . "' value=''/></div>\n";
  6717. }
  6718. else {
  6719. print "<table border='1' width='100%'>\n<tr>\n<td>[Q" . $arg['num'] . "] " . $arg['question'] . "</td>\n";
  6720. switch($arg['type'])
  6721. {
  6722. case 'yesno':
  6723. case 'text':
  6724. print "<td><input type='text' id='" . $arg['name'] . "' name='" . $arg['sgqa'] . "' value='' onchange='checkconditions(this.value, this.name, this.type)'/></td>\n";
  6725. break;
  6726. case 'message':
  6727. print "<input type='hidden' id='" . $arg['name'] . "' name='" . $arg['sgqa'] . "' value=''/>\n";
  6728. break;
  6729. }
  6730. print "</tr>\n</table>\n";
  6731. }
  6732. print "</div>\n";
  6733. }
  6734. print "</table>";
  6735. LimeExpressionManager::SetDirtyFlag(); // so subsequent tests don't try to access these variables
  6736. }
  6737. /**
  6738. * Set the 'this' variable as an alias for SGQA within the code.
  6739. * @param <type> $sgqa
  6740. */
  6741. public static function SetThisAsAliasForSGQA($sgqa)
  6742. {
  6743. $LEM =& LimeExpressionManager::singleton();
  6744. if (isset($LEM->knownVars[$sgqa]))
  6745. {
  6746. $LEM->qcode2sgqa['this']=$sgqa;
  6747. }
  6748. }
  6749. public static function ShowStackTrace($msg=NULL,&$args=NULL)
  6750. {
  6751. $LEM =& LimeExpressionManager::singleton();
  6752. $msg = array("**Stack Trace**" . (is_null($msg) ? '' : ' - ' . $msg));
  6753. $count = 0;
  6754. foreach (debug_backtrace(false) as $log)
  6755. {
  6756. if ($count++ == 0){
  6757. continue; // skip this call
  6758. }
  6759. $LEM->debugStack = array();
  6760. $subargs=array();
  6761. if (!is_null($args) && $log['function'] == 'templatereplace') {
  6762. foreach ($args as $arg)
  6763. {
  6764. if (isset($log['args'][2][$arg])) {
  6765. $subargs[$arg] = $log['args'][2][$arg];
  6766. }
  6767. }
  6768. if (count($subargs) > 0) {
  6769. $arglist = print_r($subargs,true);
  6770. }
  6771. else {
  6772. $arglist = '';
  6773. }
  6774. }
  6775. else {
  6776. $arglist = '';
  6777. }
  6778. $msg[] = ' '
  6779. . (isset($log['file']) ? '[' . basename($log['file']) . ']': '')
  6780. . (isset($log['class']) ? $log['class'] : '')
  6781. . (isset($log['type']) ? $log['type'] : '')
  6782. . (isset($log['function']) ? $log['function'] : '')
  6783. . (isset($log['line']) ? '[' . $log['line'] . ']' : '')
  6784. . $arglist;
  6785. }
  6786. }
  6787. private function gT($string, $escapemode = 'html')
  6788. {
  6789. // eventually replace this with i8n
  6790. if (isset(Yii::app()->lang))
  6791. {
  6792. return Yii::app()->lang->gT($string, $escapemode);
  6793. }
  6794. else
  6795. {
  6796. return $string;
  6797. }
  6798. }
  6799. private function ngT($single, $plural, $number, $escapemode = 'html')
  6800. {
  6801. // eventually replace this with i8n
  6802. if (isset(Yii::app()->lang))
  6803. {
  6804. return Yii::app()->lang->ngT($single, $plural, $number, $escapemode);
  6805. }
  6806. else
  6807. {
  6808. return $string;
  6809. }
  6810. }
  6811. /**
  6812. * Returns true if the survey is using comma as the radix
  6813. * @return type
  6814. */
  6815. public static function usingCommaAsRadix()
  6816. {
  6817. $LEM =& LimeExpressionManager::singleton();
  6818. $usingCommaAsRadix = (($LEM->surveyOptions['radix']==',') ? true : false);
  6819. return $usingCommaAsRadix;
  6820. }
  6821. private static function getConditionsForEM($surveyid=NULL, $qid=NULL)
  6822. {
  6823. if (!is_null($qid)) {
  6824. $where = " c.qid = ".$qid." and ";
  6825. }
  6826. else if (!is_null($surveyid)) {
  6827. $where = " c.qid in (select qid from {{questions}} where sid = ".$surveyid.") and ";
  6828. }
  6829. else {
  6830. $where = "";
  6831. }
  6832. $query = "select distinct c.*"
  6833. .", q.sid, q.type"
  6834. ." from {{conditions}} as c"
  6835. .", {{questions}} as q"
  6836. ." where " . $where
  6837. ." c.cqid=q.qid"
  6838. ." union "
  6839. ." select c.*, q.sid, '' as type"
  6840. ." from {{conditions}} as c"
  6841. .", {{questions}} as q"
  6842. ." where ". $where
  6843. ." c.cqid = 0 and c.qid = q.qid";
  6844. $databasetype = Yii::app()->db->getDriverName();
  6845. if ($databasetype=='mssql')
  6846. {
  6847. $query .= " order by sid, c.qid, scenario, cqid, cfieldname, value";
  6848. }
  6849. else
  6850. {
  6851. $query .= " order by sid, qid, scenario, cqid, cfieldname, value";
  6852. }
  6853. $data = dbExecuteAssoc($query);
  6854. return $data;
  6855. }
  6856. /**
  6857. * Deprecate obsolete question attributes.
  6858. * @param boolean $changedb - if true, updates parameters and deletes old ones
  6859. * @param type $surveyid - if set, then only for that survey
  6860. * @param type $onlythisqid - if set, then only for this question ID
  6861. */
  6862. public static function UpgradeQuestionAttributes($changeDB=false,$surveyid=NULL,$onlythisqid=NULL)
  6863. {
  6864. $LEM =& LimeExpressionManager::singleton();
  6865. $qattrs = $LEM->getQuestionAttributesForEM($surveyid,$onlythisqid);
  6866. $qupdates = array();
  6867. $attibutemap = array(
  6868. 'max_num_value_sgqa' => 'max_num_value',
  6869. 'min_num_value_sgqa' => 'min_num_value',
  6870. 'num_value_equals_sgqa' => 'equals_num_value',
  6871. );
  6872. $reverseAttributeMap = array_flip($attibutemap);
  6873. foreach ($qattrs as $qid => $qattr)
  6874. {
  6875. $updates = array();
  6876. foreach ($attibutemap as $src=>$target)
  6877. {
  6878. if (isset($qattr[$src]) && trim($qattr[$src]) != '')
  6879. {
  6880. $updates[$target] = $qattr[$src];
  6881. }
  6882. }
  6883. if (count($updates) > 0)
  6884. {
  6885. $qupdates[$qid] = $updates;
  6886. }
  6887. }
  6888. if ($changeDB)
  6889. {
  6890. $queries = array();
  6891. foreach ($qupdates as $qid=>$updates)
  6892. {
  6893. foreach ($updates as $key=>$value)
  6894. {
  6895. $query = "UPDATE {{question_attributes}} SET value=".Yii::app()->db->quoteValue($value)." WHERE qid=".$qid." and attribute=".Yii::app()->db->quoteValue($key);
  6896. $queries[] = $query;
  6897. $query = "DELETE FROM {{question_attributes}} WHERE qid=".$qid." and attribute=".Yii::app()->db->quoteValue($reverseAttributeMap[$key]);
  6898. $queries[] = $query;
  6899. }
  6900. }
  6901. // now update the datbase
  6902. foreach ($queries as $query)
  6903. {
  6904. dbExecuteAssoc($query);
  6905. }
  6906. return $queries;
  6907. }
  6908. else
  6909. {
  6910. return $qupdates;
  6911. }
  6912. }
  6913. private function getQuestionAttributesForEM($surveyid=NULL,$qid=NULL, $lang=NULL)
  6914. {
  6915. if (!is_null($qid)) {
  6916. $where = " a.qid = ".$qid." and a.qid=b.qid";
  6917. }
  6918. else if (!is_null($surveyid)) {
  6919. $where = " a.qid=b.qid and b.sid=".$surveyid;
  6920. }
  6921. else {
  6922. $where = " a.qid=b.qid";
  6923. }
  6924. if (!is_null($lang)) {
  6925. $lang = " and a.language='".$lang."' and b.language='".$lang."'";
  6926. }
  6927. $databasetype = Yii::app()->db->getDriverName();
  6928. if ($databasetype=='mssql' || $databasetype=="sqlsrv")
  6929. {
  6930. $query = "select distinct a.qid, a.attribute, CAST(a.value as varchar(max)) as value";
  6931. }
  6932. else
  6933. {
  6934. $query = "select distinct a.qid, a.attribute, a.value";
  6935. }
  6936. $query .= " from {{question_attributes}} as a, {{questions}} as b"
  6937. ." where " . $where
  6938. .$lang
  6939. ." order by a.qid, a.attribute";
  6940. $data = dbExecuteAssoc($query);
  6941. $qattr = array();
  6942. foreach($data->readAll() as $row) {
  6943. $qattr[$row['qid']][$row['attribute']] = $row['value'];
  6944. }
  6945. if (!is_null($lang))
  6946. {
  6947. // Then get non-language specific first, and overwrite with language-specific
  6948. $qattr2 = $qattr;
  6949. $qattr = $this->getQuestionAttributesForEM($surveyid,$qid);
  6950. foreach ($qattr2 as $q => $qattrs) {
  6951. if (isset($qattrs) && is_array($qattrs)) {
  6952. foreach ($qattrs as $attr=>$value) {
  6953. $qattr[$q][$attr] = $value;
  6954. }
  6955. }
  6956. }
  6957. }
  6958. return $qattr;
  6959. }
  6960. /**
  6961. * Return array of language-specific answer codes
  6962. * @param <type> $surveyid
  6963. * @param <type> $qid
  6964. * @return <type>
  6965. */
  6966. function getAnswerSetsForEM($surveyid=NULL,$qid=NULL,$lang=NULL)
  6967. {
  6968. if (!is_null($qid)) {
  6969. $where = "a.qid = ".$qid;
  6970. }
  6971. else if (!is_null($surveyid)) {
  6972. $where = "a.qid = q.qid and q.sid = ".$surveyid;
  6973. }
  6974. else {
  6975. $where = "1";
  6976. }
  6977. if (!is_null($lang)) {
  6978. $lang = " and a.language='".$lang."' and q.language='".$lang."'";
  6979. }
  6980. $query = "SELECT a.qid, a.code, a.answer, a.scale_id, a.assessment_value"
  6981. ." FROM {{answers}} AS a, {{questions}} as q"
  6982. ." WHERE ".$where
  6983. .$lang
  6984. ." ORDER BY a.qid, a.scale_id, a.sortorder";
  6985. $data = dbExecuteAssoc($query);
  6986. $qans = array();
  6987. $useAssessments = ((isset($this->surveyOptions['assessments'])) ? $this->surveyOptions['assessments'] : false);
  6988. foreach($data->readAll() as $row) {
  6989. if (!isset($qans[$row['qid']])) {
  6990. $qans[$row['qid']] = array();
  6991. }
  6992. $qans[$row['qid']][$row['scale_id'].'~'.$row['code']] = ($useAssessments ? $row['assessment_value'] : $row['code']) . '|' . $row['answer'];
  6993. }
  6994. return $qans;
  6995. }
  6996. /**
  6997. * Returns group info needed for indexes
  6998. * @param <type> $surveyid
  6999. * @param string $lang
  7000. * @return <type>
  7001. */
  7002. function getGroupInfoForEM($surveyid,$lang=NULL)
  7003. {
  7004. if (!is_null($lang)) {
  7005. $lang = " and a.language='".$lang."'";
  7006. }
  7007. $query = "SELECT a.group_name, a.description, a.gid, a.group_order, a.grelevance"
  7008. ." FROM {{groups}} AS a"
  7009. ." WHERE a.sid=".$surveyid
  7010. .$lang
  7011. ." ORDER BY group_order";
  7012. $data = dbExecuteAssoc($query);
  7013. $qinfo = array();
  7014. $_order=0;
  7015. foreach ($data as $d)
  7016. {
  7017. $gid[$d['gid']] = array(
  7018. 'group_order' => $_order,
  7019. 'gid' => $d['gid'],
  7020. 'group_name' => $d['group_name'],
  7021. 'description' => $d['description'],
  7022. 'grelevance' => $d['grelevance'],
  7023. );
  7024. $qinfo[$_order] = $gid[$d['gid']];
  7025. ++$_order;
  7026. }
  7027. if (isset($_SESSION['survey_'.$surveyid]) && isset($_SESSION['survey_'.$surveyid]['grouplist'])) {
  7028. $_order=0;
  7029. $qinfo = array();
  7030. foreach ($_SESSION['survey_'.$surveyid]['grouplist'] as $info)
  7031. {
  7032. $gid[$info['gid']]['group_order'] = $_order;
  7033. $qinfo[$_order] = $gid[$info['gid']];
  7034. ++$_order;
  7035. }
  7036. }
  7037. return $qinfo;
  7038. }
  7039. /**
  7040. * Cleanse the $_POSTed data and update $_SESSION variables accordingly
  7041. */
  7042. static function ProcessCurrentResponses()
  7043. {
  7044. $LEM =& LimeExpressionManager::singleton();
  7045. if (!isset($LEM->currentQset)) {
  7046. return array();
  7047. }
  7048. $updatedValues=array();
  7049. $radixchange = (($LEM->surveyOptions['radix']==',') ? true : false);
  7050. foreach ($LEM->currentQset as $qinfo)
  7051. {
  7052. $relevant=false;
  7053. $qid = $qinfo['info']['qid'];
  7054. $gseq = $qinfo['info']['gseq'];
  7055. $relevant = (isset($_POST['relevance' . $qid]) ? ($_POST['relevance' . $qid] == 1) : false);
  7056. $grelevant = (isset($_POST['relevanceG' . $gseq]) ? ($_POST['relevanceG' . $gseq] == 1) : false);
  7057. $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] = $relevant;
  7058. $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] = $grelevant;
  7059. foreach (explode('|',$qinfo['sgqa']) as $sq)
  7060. {
  7061. $sqrelevant=true;
  7062. if (isset($LEM->subQrelInfo[$qid][$sq]['rowdivid']))
  7063. {
  7064. $rowdivid = $LEM->subQrelInfo[$qid][$sq]['rowdivid'];
  7065. if ($rowdivid!='' && isset($_POST['relevance' . $rowdivid]))
  7066. {
  7067. $sqrelevant = ($_POST['relevance' . $rowdivid] == 1);
  7068. $_SESSION[$LEM->sessid]['relevanceStatus'][$rowdivid] = $sqrelevant;
  7069. }
  7070. }
  7071. $type = $qinfo['info']['type'];
  7072. if (($relevant && $grelevant && $sqrelevant) || !$LEM->surveyOptions['deletenonvalues'])
  7073. {
  7074. if ($qinfo['info']['hidden'] && !isset($_POST[$sq]))
  7075. {
  7076. $value = (isset($_SESSION[$LEM->sessid][$sq]) ? $_SESSION[$LEM->sessid][$sq] : ''); // if always hidden, use the default value, if any
  7077. }
  7078. else
  7079. {
  7080. $value = (isset($_POST[$sq]) ? $_POST[$sq] : '');
  7081. }
  7082. if ($radixchange && isset($LEM->knownVars[$sq]['onlynum']) && $LEM->knownVars[$sq]['onlynum']=='1')
  7083. {
  7084. // convert from comma back to decimal
  7085. $value = implode('.',explode(',',$value));
  7086. }
  7087. switch($type)
  7088. {
  7089. case 'D': //DATE
  7090. if (trim($value)=="")
  7091. {
  7092. $value = "";
  7093. }
  7094. else
  7095. {
  7096. $aAttributes=$LEM->getQuestionAttributesForEM($LEM->sid, $qid,$_SESSION['LEMlang']);
  7097. if (!isset($aAttributes[$qid])) {
  7098. $aAttributes[$qid]=array();
  7099. }
  7100. $aDateFormatData=getDateFormatDataForQID($aAttributes[$qid],$LEM->surveyOptions);
  7101. $oDateTimeConverter = new Date_Time_Converter($value, $aDateFormatData['phpdate']);
  7102. $value=$oDateTimeConverter->convert("Y-m-d H:i");
  7103. }
  7104. break;
  7105. case 'N': //NUMERICAL QUESTION TYPE
  7106. case 'K': //MULTIPLE NUMERICAL QUESTION
  7107. if (trim($value)=="") {
  7108. $value = "";
  7109. }
  7110. else {
  7111. $value = sanitize_float($value);
  7112. }
  7113. break;
  7114. case '|': //File Upload
  7115. if (!preg_match('/_filecount$/', $sq))
  7116. {
  7117. $json = $value;
  7118. $phparray = json_decode(stripslashes($json));
  7119. // if the files have not been saved already,
  7120. // move the files from tmp to the files folder
  7121. $tmp = $LEM->surveyOptions['tempdir'] . 'upload'. DIRECTORY_SEPARATOR;
  7122. if (!is_null($phparray) && count($phparray) > 0)
  7123. {
  7124. // Move the (unmoved, temp) files from temp to files directory.
  7125. // Check all possible file uploads
  7126. for ($i = 0; $i < count($phparray); $i++)
  7127. {
  7128. if (file_exists($tmp . $phparray[$i]->filename))
  7129. {
  7130. $sDestinationFileName = 'fu_' . randomChars(15);
  7131. if (!is_dir($LEM->surveyOptions['target']))
  7132. {
  7133. mkdir($LEM->surveyOptions['target'], 0777, true);
  7134. }
  7135. if (!rename($tmp . $phparray[$i]->filename, $LEM->surveyOptions['target'] . $sDestinationFileName))
  7136. {
  7137. echo "Error moving file to target destination";
  7138. }
  7139. $phparray[$i]->filename = $sDestinationFileName;
  7140. }
  7141. }
  7142. $value = ls_json_encode($phparray); // so that EM doesn't try to parse it.
  7143. }
  7144. }
  7145. break;
  7146. }
  7147. $_SESSION[$LEM->sessid][$sq] = $value;
  7148. $_update = array (
  7149. 'type'=>$type,
  7150. 'value'=>$value,
  7151. );
  7152. $updatedValues[$sq] = $_update;
  7153. $LEM->updatedValues[$sq] = $_update;
  7154. }
  7155. else { // irrelevant, so database will be NULLed separately
  7156. // Must unset the value, rather than setting to '', so that EM can re-use the default value as needed.
  7157. unset($_SESSION[$LEM->sessid][$sq]);
  7158. $_update = array (
  7159. 'type'=>$type,
  7160. 'value'=>NULL,
  7161. );
  7162. $updatedValues[$sq] = $_update;
  7163. $LEM->updatedValues[$sq] = $_update;
  7164. }
  7165. }
  7166. }
  7167. if (isset($_POST['timerquestion']))
  7168. {
  7169. $_SESSION[$LEM->sessid][$_POST['timerquestion']]=sanitize_float($_POST[$_POST['timerquestion']]);
  7170. }
  7171. return $updatedValues;
  7172. }
  7173. static public function isValidVariable($varName)
  7174. {
  7175. $LEM =& LimeExpressionManager::singleton();
  7176. if (isset($LEM->knownVars[$varName]))
  7177. {
  7178. return true;
  7179. }
  7180. else if (isset($LEM->qcode2sgqa[$varName]))
  7181. {
  7182. return true;
  7183. }
  7184. else if (isset($LEM->tempVars[$varName]))
  7185. {
  7186. return true;
  7187. }
  7188. return false;
  7189. }
  7190. static public function GetVarAttribute($name,$attr,$default,$gseq,$qseq)
  7191. {
  7192. $LEM =& LimeExpressionManager::singleton();
  7193. return $LEM->_GetVarAttribute($name,$attr,$default,$gseq,$qseq);
  7194. }
  7195. private function _GetVarAttribute($name,$attr,$default,$gseq,$qseq)
  7196. {
  7197. $args = explode(".", $name);
  7198. $varName = $args[0];
  7199. $varName = preg_replace("/^(?:INSERTANS:)?(.*?)$/", "$1", $varName);
  7200. if (isset($this->knownVars[$varName]))
  7201. {
  7202. $var = $this->knownVars[$varName];
  7203. }
  7204. else if (isset($this->qcode2sgqa[$varName]))
  7205. {
  7206. $var = $this->knownVars[$this->qcode2sgqa[$varName]];
  7207. }
  7208. else if (isset($this->tempVars[$varName]))
  7209. {
  7210. $var = $this->tempVars[$varName];
  7211. }
  7212. else
  7213. {
  7214. return '{' . $name . '}';
  7215. }
  7216. $sgqa = isset($var['sgqa']) ? $var['sgqa'] : NULL;
  7217. if (is_null($attr))
  7218. {
  7219. // then use the requested attribute, if any
  7220. $_attr = 'code';
  7221. if (preg_match("/INSERTANS:/",$args[0]))
  7222. {
  7223. $_attr = 'shown';
  7224. }
  7225. $attr = (count($args)==2) ? $args[1] : $_attr;
  7226. }
  7227. // Like JavaScript, if an answer is irrelevant, always return ''
  7228. if (preg_match('/^code|NAOK|shown|valueNAOK|value$/',$attr) && isset($var['qid']) && $var['qid']!='')
  7229. {
  7230. if (!$this->_GetVarAttribute($varName,'relevanceStatus',false,$gseq,$qseq))
  7231. {
  7232. return '';
  7233. }
  7234. }
  7235. switch ($attr)
  7236. {
  7237. case 'varName':
  7238. return $name;
  7239. break;
  7240. case 'code':
  7241. case 'NAOK':
  7242. if (isset($var['code'])) {
  7243. return $var['code']; // for static values like TOKEN
  7244. }
  7245. else {
  7246. if (isset($_SESSION[$this->sessid][$sgqa])) {
  7247. $type = $var['type'];
  7248. switch($type)
  7249. {
  7250. case 'Q': //MULTIPLE SHORT TEXT
  7251. case ';': //ARRAY (Multi Flexi) Text
  7252. case 'S': //SHORT FREE TEXT
  7253. case 'T': //LONG FREE TEXT
  7254. case 'U': //HUGE FREE TEXT
  7255. return htmlspecialchars($_SESSION[$this->sessid][$sgqa],ENT_NOQUOTES);// Minimum sanitizing the string entered by user
  7256. case '!': //List - dropdown
  7257. case 'L': //LIST drop-down/radio-button list
  7258. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  7259. case 'M': //Multiple choice checkbox
  7260. case 'P': //Multiple choice with comments checkbox + text
  7261. if (preg_match('/comment$/',$sgqa) || preg_match('/other$/',$sgqa) || preg_match('/_other$/',$name))
  7262. {
  7263. return htmlspecialchars($_SESSION[$this->sessid][$sgqa],ENT_NOQUOTES);// Minimum sanitizing the string entered by user
  7264. }
  7265. else
  7266. {
  7267. return $_SESSION[$this->sessid][$sgqa];
  7268. }
  7269. default:
  7270. return $_SESSION[$this->sessid][$sgqa];
  7271. }
  7272. }
  7273. elseif (isset($var['default']) && !is_null($var['default'])) {
  7274. return $var['default'];
  7275. }
  7276. return $default;
  7277. }
  7278. break;
  7279. case 'value':
  7280. case 'valueNAOK':
  7281. {
  7282. $type = $var['type'];
  7283. $code = $this->_GetVarAttribute($name,'code',$default,$gseq,$qseq);
  7284. switch($type)
  7285. {
  7286. case '!': //List - dropdown
  7287. case 'L': //LIST drop-down/radio-button list
  7288. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  7289. case '1': //Array (Flexible Labels) dual scale // need scale
  7290. case 'H': //ARRAY (Flexible) - Column Format
  7291. case 'F': //ARRAY (Flexible) - Row Format
  7292. case 'R': //RANKING STYLE
  7293. if ($type == 'O' && preg_match('/comment\.value/',$name))
  7294. {
  7295. $value = $code;
  7296. }
  7297. else if (($type == 'L' || $type == '!') && preg_match('/_other\.value/',$name))
  7298. {
  7299. $value = $code;
  7300. }
  7301. else
  7302. {
  7303. $scale_id = $this->_GetVarAttribute($name,'scale_id','0',$gseq,$qseq);
  7304. $which_ans = $scale_id . '~' . $code;
  7305. $ansArray = $var['ansArray'];
  7306. if (is_null($ansArray))
  7307. {
  7308. $value = $default;
  7309. }
  7310. else
  7311. {
  7312. if (isset($ansArray[$which_ans])) {
  7313. $answerInfo = explode('|',$ansArray[$which_ans]);
  7314. $answer = $answerInfo[0];
  7315. }
  7316. else {
  7317. $answer = $default;
  7318. }
  7319. $value = $answer;
  7320. }
  7321. }
  7322. break;
  7323. default:
  7324. $value = $code;
  7325. break;
  7326. }
  7327. return $value;
  7328. }
  7329. break;
  7330. case 'jsName':
  7331. if ($this->surveyMode=='survey'
  7332. || ($this->surveyMode=='group' && $gseq != -1 && isset($var['gseq']) && $gseq == $var['gseq'])
  7333. || ($this->surveyMode=='question' && $qseq != -1 && isset($var['qseq']) && $qseq == $var['qseq']))
  7334. {
  7335. return (isset($var['jsName_on']) ? $var['jsName_on'] : (isset($var['jsName'])) ? $var['jsName'] : $default);
  7336. }
  7337. else {
  7338. return (isset($var['jsName']) ? $var['jsName'] : $default);
  7339. }
  7340. break;
  7341. case 'sgqa':
  7342. case 'mandatory':
  7343. case 'qid':
  7344. case 'gid':
  7345. case 'grelevance':
  7346. case 'question':
  7347. case 'readWrite':
  7348. case 'relevance':
  7349. case 'rowdivid':
  7350. case 'type':
  7351. case 'qcode':
  7352. case 'gseq':
  7353. case 'qseq':
  7354. case 'ansList':
  7355. case 'scale_id':
  7356. return (isset($var[$attr])) ? $var[$attr] : $default;
  7357. case 'shown':
  7358. if (isset($var['shown']))
  7359. {
  7360. return $var['shown']; // for static values like TOKEN
  7361. }
  7362. else
  7363. {
  7364. $type = $var['type'];
  7365. $code = $this->_GetVarAttribute($name,'code',$default,$gseq,$qseq);
  7366. switch($type)
  7367. {
  7368. case '!': //List - dropdown
  7369. case 'L': //LIST drop-down/radio-button list
  7370. case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
  7371. case '1': //Array (Flexible Labels) dual scale // need scale
  7372. case 'H': //ARRAY (Flexible) - Column Format
  7373. case 'F': //ARRAY (Flexible) - Row Format
  7374. case 'R': //RANKING STYLE
  7375. if ($type == 'O' && preg_match('/comment$/',$name))
  7376. {
  7377. $shown = $code;
  7378. }
  7379. else if (($type == 'L' || $type == '!') && preg_match('/_other$/',$name))
  7380. {
  7381. $shown = $code;
  7382. }
  7383. else
  7384. {
  7385. $scale_id = $this->_GetVarAttribute($name,'scale_id','0',$gseq,$qseq);
  7386. $which_ans = $scale_id . '~' . $code;
  7387. $ansArray = $var['ansArray'];
  7388. if (is_null($ansArray))
  7389. {
  7390. $shown=$code;
  7391. }
  7392. else
  7393. {
  7394. if (isset($ansArray[$which_ans])) {
  7395. $answerInfo = explode('|',$ansArray[$which_ans]);
  7396. array_shift($answerInfo);
  7397. $answer = join('|',$answerInfo);
  7398. }
  7399. else {
  7400. $answer = $code;
  7401. }
  7402. $shown = $answer;
  7403. }
  7404. }
  7405. break;
  7406. case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
  7407. case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
  7408. case ':': //ARRAY (Multi Flexi) 1 to 10
  7409. case '5': //5 POINT CHOICE radio-buttons
  7410. $shown = $code;
  7411. break;
  7412. case 'N': //NUMERICAL QUESTION TYPE
  7413. case 'K': //MULTIPLE NUMERICAL QUESTION
  7414. case 'Q': //MULTIPLE SHORT TEXT
  7415. case ';': //ARRAY (Multi Flexi) Text
  7416. case 'S': //SHORT FREE TEXT
  7417. case 'T': //LONG FREE TEXT
  7418. case 'U': //HUGE FREE TEXT
  7419. case 'D': //DATE
  7420. case '*': //Equation
  7421. case 'I': //Language Question
  7422. case '|': //File Upload
  7423. case 'X': //BOILERPLATE QUESTION
  7424. $shown = $code;
  7425. break;
  7426. case 'M': //Multiple choice checkbox
  7427. case 'P': //Multiple choice with comments checkbox + text
  7428. if ($code == 'Y' && isset($var['question']) && !preg_match('/comment$/',$sgqa))
  7429. {
  7430. $shown = $var['question'];
  7431. }
  7432. elseif (preg_match('/comment$/',$sgqa)) {
  7433. $shown=$code; // This one return sgqa.code
  7434. }
  7435. else
  7436. {
  7437. $shown = $default;
  7438. }
  7439. break;
  7440. case 'G': //GENDER drop-down list
  7441. case 'Y': //YES/NO radio-buttons
  7442. case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
  7443. case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
  7444. $ansArray = $var['ansArray'];
  7445. if (is_null($ansArray))
  7446. {
  7447. $shown=$default;
  7448. }
  7449. else
  7450. {
  7451. if (isset($ansArray[$code])) {
  7452. $answer = $ansArray[$code];
  7453. }
  7454. else {
  7455. $answer = $default;
  7456. }
  7457. $shown = $answer;
  7458. }
  7459. break;
  7460. }
  7461. return $shown;
  7462. }
  7463. case 'relevanceStatus':
  7464. $gseq = (isset($var['gseq'])) ? $var['gseq'] : -1;
  7465. $qid = (isset($var['qid'])) ? $var['qid'] : -1;
  7466. $rowdivid = (isset($var['rowdivid']) && $var['rowdivid']!='') ? $var['rowdivid'] : -1;
  7467. if ($qid == -1 || $gseq == -1) {
  7468. return 1;
  7469. }
  7470. if (isset($args[1]) && $args[1]=='NAOK') {
  7471. return 1;
  7472. }
  7473. $grel = (isset($_SESSION[$this->sessid]['relevanceStatus']['G'.$gseq]) ? $_SESSION[$this->sessid]['relevanceStatus']['G'.$gseq] : 1); // true by default
  7474. $qrel = (isset($_SESSION[$this->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$this->sessid]['relevanceStatus'][$qid] : 0);
  7475. $sqrel = (isset($_SESSION[$this->sessid]['relevanceStatus'][$rowdivid]) ? $_SESSION[$this->sessid]['relevanceStatus'][$rowdivid] : 1); // true by default - only want false if a subquestion is irrelevant
  7476. return ($grel && $qrel && $sqrel);
  7477. default:
  7478. print 'UNDEFINED ATTRIBUTE: ' . $attr . "<br />\n";
  7479. return $default;
  7480. }
  7481. return $default; // and throw and error?
  7482. }
  7483. public static function SetVariableValue($op,$name,$value)
  7484. {
  7485. $LEM =& LimeExpressionManager::singleton();
  7486. if (isset($LEM->tempVars[$name]))
  7487. {
  7488. switch($op)
  7489. {
  7490. case '=':
  7491. $LEM->tempVars[$name]['code'] = $value;
  7492. break;
  7493. case '*=':
  7494. $LEM->tempVars[$name]['code'] *= $value;
  7495. break;
  7496. case '/=':
  7497. $LEM->tempVars[$name]['code'] /= $value;
  7498. break;
  7499. case '+=':
  7500. $LEM->tempVars[$name]['code'] += $value;
  7501. break;
  7502. case '-=':
  7503. $LEM->tempVars[$name]['code'] -= $value;
  7504. break;
  7505. }
  7506. $_result = $LEM->tempVars[$name]['code'];
  7507. $_SESSION[$LEM->sessid][$name] = $_result;
  7508. $LEM->updatedValues[$name] = array(
  7509. 'type'=>'*',
  7510. 'value'=>$_result,
  7511. );
  7512. return $_result;
  7513. }
  7514. else
  7515. {
  7516. if (!isset($LEM->knownVars[$name]))
  7517. {
  7518. if (isset($LEM->qcode2sgqa[$name]))
  7519. {
  7520. $name = $LEM->qcode2sgqa[$name];
  7521. }
  7522. else
  7523. {
  7524. return ''; // shouldn't happen
  7525. }
  7526. }
  7527. if (isset($_SESSION[$LEM->sessid][$name]))
  7528. {
  7529. $_result = $_SESSION[$LEM->sessid][$name];
  7530. }
  7531. else
  7532. {
  7533. $_result = (isset($LEM->knownVars[$name]['default']) ? $LEM->knownVars[$name]['default'] : 0);
  7534. }
  7535. switch($op)
  7536. {
  7537. case '=':
  7538. $_result = $value;
  7539. break;
  7540. case '*=':
  7541. $_result *= $value;
  7542. break;
  7543. case '/=':
  7544. $_result /= $value;
  7545. break;
  7546. case '+=':
  7547. $_result += $value;
  7548. break;
  7549. case '-=':
  7550. $_result -= $value;
  7551. break;
  7552. }
  7553. $_SESSION[$LEM->sessid][$name] = $_result;
  7554. $_type = $LEM->knownVars[$name]['type'];
  7555. $LEM->updatedValues[$name] = array(
  7556. 'type'=>$_type,
  7557. 'value'=>$_result,
  7558. );
  7559. return $_result;
  7560. }
  7561. }
  7562. /**
  7563. * Create HTML view of the survey, showing everything that uses EM
  7564. * @param <type> $sid
  7565. * @param <type> $gid
  7566. * @param <type> $qid
  7567. */
  7568. static public function ShowSurveyLogicFile($sid, $gid=NULL, $qid=NULL,$LEMdebugLevel=0,$assessments=false)
  7569. {
  7570. // Title
  7571. // Welcome
  7572. // G1, name, relevance, text
  7573. // *Q1, name [type], relevance [validation], text, help, default, help_msg
  7574. // SQ1, name [scale], relevance [validation], text
  7575. // A1, code, assessment_value, text
  7576. // End Message
  7577. $LEM =& LimeExpressionManager::singleton();
  7578. $aSurveyInfo=getSurveyInfo($sid);
  7579. $allErrors = array();
  7580. $warnings = 0;
  7581. $surveyOptions = array(
  7582. 'assessments'=>($aSurveyInfo['assessments']=='Y'),
  7583. 'hyperlinkSyntaxHighlighting'=>true,
  7584. );
  7585. $varNamesUsed = array(); // keeps track of whether variables have been declared
  7586. if (!is_null($qid))
  7587. {
  7588. $surveyMode='question';
  7589. LimeExpressionManager::StartSurvey($sid, 'question', $surveyOptions, false,$LEMdebugLevel);
  7590. $qseq = LimeExpressionManager::GetQuestionSeq($qid);
  7591. $moveResult = LimeExpressionManager::JumpTo($qseq+1,true,false,true);
  7592. }
  7593. else if (!is_null($gid))
  7594. {
  7595. $surveyMode='group';
  7596. LimeExpressionManager::StartSurvey($sid, 'group', $surveyOptions, false,$LEMdebugLevel);
  7597. $gseq = LimeExpressionManager::GetGroupSeq($gid);
  7598. $moveResult = LimeExpressionManager::JumpTo($gseq+1,true,false,true);
  7599. }
  7600. else
  7601. {
  7602. $surveyMode='survey';
  7603. LimeExpressionManager::StartSurvey($sid, 'survey', $surveyOptions, false,$LEMdebugLevel);
  7604. $moveResult = LimeExpressionManager::NavigateForwards();
  7605. }
  7606. $qtypes=getQuestionTypeList('','array');
  7607. if (is_null($moveResult) || is_null($LEM->currentQset) || count($LEM->currentQset) == 0) {
  7608. return array(
  7609. 'errors'=>1,
  7610. 'html'=>sprintf($LEM->gT('Invalid question - probably missing sub-questions or language-specific settings for language %s'),$_SESSION['LEMlang'])
  7611. );
  7612. }
  7613. $surveyname = templatereplace('{SURVEYNAME}',array('SURVEYNAME'=>$aSurveyInfo['surveyls_title']));
  7614. $out = '<div id="showlogicfilediv" ><H3>' . $LEM->gT('Logic File for Survey # ') . '[' . $LEM->sid . "]: $surveyname</H3>\n";
  7615. $out .= "<table id='logicfiletable'>";
  7616. if (is_null($gid) && is_null($qid))
  7617. {
  7618. $description = templatereplace('{SURVEYDESCRIPTION}', array('SURVEYDESCRIPTION'=>$aSurveyInfo['surveyls_description']));
  7619. $errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
  7620. if ($description != '')
  7621. {
  7622. $out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("Description:") . "</td><td colspan=2>" . $description . "</td></tr>";
  7623. }
  7624. $welcome = templatereplace('{WELCOME}', array('WELCOME'=>$aSurveyInfo['surveyls_welcometext']));
  7625. $errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
  7626. if ($welcome != '')
  7627. {
  7628. $out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("Welcome:") . "</td><td colspan=2>" . $welcome . "</td></tr>";
  7629. }
  7630. $endmsg = templatereplace('{ENDTEXT}', array('ENDTEXT'=>$aSurveyInfo['surveyls_endtext']));
  7631. $errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
  7632. if ($endmsg != '')
  7633. {
  7634. $out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("End message:") . "</td><td colspan=2>" . $endmsg . "</td></tr>";
  7635. }
  7636. $_linkreplace = templatereplace('{URL}', array('URL'=>$aSurveyInfo['surveyls_url']));
  7637. $errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
  7638. if ($_linkreplace != '')
  7639. {
  7640. $out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("End URL") . ":</td><td colspan=2>" . $_linkreplace . "</td></tr>";
  7641. }
  7642. }
  7643. $out .= "<tr><th>#</th><th>".$LEM->gT('Name [ID]')."</th><th>".$LEM->gT('Relevance [Validation] (Default)')."</th><th>".$LEM->gT('Text [Help] (Tip)')."</th></tr>\n";
  7644. $_gseq=-1;
  7645. foreach ($LEM->currentQset as $q) {
  7646. $gseq = $q['info']['gseq'];
  7647. $gid = $q['info']['gid'];
  7648. $qid = $q['info']['qid'];
  7649. $qseq = $q['info']['qseq'];
  7650. $errorCount=0;
  7651. //////
  7652. // SHOW GROUP-LEVEL INFO
  7653. //////
  7654. if ($gseq != $_gseq) {
  7655. $LEM->ParseResultCache=array(); // reset for each group so get proper color coding?
  7656. $_gseq = $gseq;
  7657. $ginfo = $LEM->gseq2info[$gseq];
  7658. $grelevance = '{' . (($ginfo['grelevance']=='') ? 1 : $ginfo['grelevance']) . '}';
  7659. $gtext = ((trim($ginfo['description']) == '') ? '&nbsp;' : $ginfo['description']);
  7660. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid);
  7661. $groupRow = "<tr class='LEMgroup'>"
  7662. . "<td>G-$gseq</td>"
  7663. . "<td><b>".$ginfo['group_name']."</b><br />[<a target='_blank' href='$editlink'>GID ".$gid."</a>]</td>"
  7664. . "<td>".$grelevance."</td>"
  7665. . "<td>".$gtext."</td>"
  7666. . "</tr>\n";
  7667. $LEM->ProcessString($groupRow, $qid,NULL,false,1,1,false,false);
  7668. $out .= $LEM->GetLastPrettyPrintExpression();
  7669. if ($LEM->em->HasErrors()) {
  7670. ++$errorCount;
  7671. }
  7672. }
  7673. //////
  7674. // SHOW QUESTION-LEVEL INFO
  7675. //////
  7676. $mandatory = (($q['info']['mandatory']=='Y') ? "<span class='mandatory'>*</span>" : '');
  7677. $type = $q['info']['type'];
  7678. $typedesc = $qtypes[$type]['description'];
  7679. $sgqas = explode('|',$q['sgqa']);
  7680. if (count($sgqas) == 1 && !is_null($q['info']['default']))
  7681. {
  7682. $LEM->ProcessString($q['info']['default'], $qid,NULL,false,1,1,false,false);
  7683. $_default = $LEM->GetLastPrettyPrintExpression();
  7684. if ($LEM->em->HasErrors()) {
  7685. ++$errorCount;
  7686. }
  7687. $default = '<br />(' . $LEM->gT('Default:') . ' ' . $_default . ')';
  7688. }
  7689. else
  7690. {
  7691. $default = '';
  7692. }
  7693. $qtext = (($q['info']['qtext'] != '') ? $q['info']['qtext'] : '&nbsp');
  7694. $help = (($q['info']['help'] != '') ? '<hr/>[' . $LEM->gT("Help:") . ' ' . $q['info']['help'] . ']': '');
  7695. $prettyValidTip = (($q['prettyValidTip'] == '') ? '' : '<hr/>(' . $LEM->gT("Tip:") . ' ' . $q['prettyValidTip'] . ')');
  7696. //////
  7697. // SHOW QUESTION ATTRIBUTES THAT ARE PROCESSED BY EM
  7698. //////
  7699. $attrTable = '';
  7700. $attrs = (isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array());
  7701. if (isset($LEM->q2subqInfo[$qid]['preg']))
  7702. {
  7703. $attrs['regex_validation'] = $LEM->q2subqInfo[$qid]['preg'];
  7704. }
  7705. if (isset($LEM->questionSeq2relevance[$qseq]['other']))
  7706. {
  7707. $attrs['other'] = $LEM->questionSeq2relevance[$qseq]['other'];
  7708. }
  7709. if (count($attrs) > 0) {
  7710. $attrTable = "<table id='logicfileattributetable'><tr><th>" . $LEM->gT("Question attribute") . "</th><th>" . $LEM->gT("Value"). "</th></tr>\n";
  7711. $count=0;
  7712. foreach ($attrs as $key=>$value) {
  7713. if (is_null($value) || trim($value) == '') {
  7714. continue;
  7715. }
  7716. switch ($key)
  7717. {
  7718. // @todo: Rather compares the current attribute value to the defaults in the question attributes array to decide which ones should show (only the ones that are non-standard)
  7719. default:
  7720. case 'exclude_all_others':
  7721. case 'exclude_all_others_auto':
  7722. case 'hidden':
  7723. if ($value == false || $value == '0') {
  7724. $value = NULL; // so can skip this one - just using continue here doesn't work.
  7725. }
  7726. break;
  7727. case 'time_limit_action':
  7728. if ( $value == '1') {
  7729. $value = NULL; // so can skip this one - just using continue here doesn't work.
  7730. }
  7731. case 'relevance':
  7732. $value = NULL; // means an outdate database structure
  7733. break;
  7734. case 'array_filter':
  7735. case 'array_filter_exclude':
  7736. case 'code_filter':
  7737. case 'em_validation_q_tip':
  7738. case 'em_validation_sq_tip':
  7739. break;
  7740. case 'equals_num_value':
  7741. case 'em_validation_q':
  7742. case 'em_validation_sq':
  7743. case 'max_answers':
  7744. case 'max_num_value':
  7745. case 'max_num_value_n':
  7746. case 'min_answers':
  7747. case 'min_num_value':
  7748. case 'min_num_value_n':
  7749. case 'min_num_of_files':
  7750. case 'max_num_of_files':
  7751. case 'multiflexible_max':
  7752. case 'multiflexible_min':
  7753. $value = '{' . $value . '}';
  7754. break;
  7755. case 'other_replace_text':
  7756. case 'show_totals':
  7757. case 'regex_validation':
  7758. break;
  7759. case 'other':
  7760. if ($value == 'N') {
  7761. $value = NULL; // so can skip this one
  7762. }
  7763. break;
  7764. }
  7765. if (is_null($value)) {
  7766. continue; // since continuing from within a switch statement doesn't work
  7767. }
  7768. ++$count;
  7769. $attrTable .= "<tr><td>$key</td><td>$value</td></tr>\n";
  7770. }
  7771. $attrTable .= "</table>\n";
  7772. if ($count == 0) {
  7773. $attrTable = '';
  7774. }
  7775. }
  7776. $LEM->ProcessString($qtext . $help . $prettyValidTip . $attrTable, $qid,NULL,false,1,1,false,false);
  7777. $qdetails = $LEM->GetLastPrettyPrintExpression();
  7778. if ($LEM->em->HasErrors()) {
  7779. ++$errorCount;
  7780. }
  7781. //////
  7782. // SHOW RELEVANCE
  7783. //////
  7784. // Must parse Relevance this way, otherwise if try to first split expressions, regex equations won't work
  7785. $relevanceEqn = (($q['info']['relevance'] == '') ? 1 : $q['info']['relevance']);
  7786. if (!isset($LEM->ParseResultCache[$relevanceEqn]))
  7787. {
  7788. $result = $LEM->em->ProcessBooleanExpression($relevanceEqn, $gseq, $qseq);
  7789. $prettyPrint = $LEM->em->GetPrettyPrintString();
  7790. $hasErrors = $LEM->em->HasErrors();
  7791. $LEM->ParseResultCache[$relevanceEqn] = array(
  7792. 'result' => $result,
  7793. 'prettyprint' => $prettyPrint,
  7794. 'hasErrors' => $hasErrors,
  7795. );
  7796. }
  7797. $relevance = $LEM->ParseResultCache[$relevanceEqn]['prettyprint'];
  7798. if ($LEM->ParseResultCache[$relevanceEqn]['hasErrors']) {
  7799. ++$errorCount;
  7800. }
  7801. //////
  7802. // SHOW VALIDATION EQUATION
  7803. //////
  7804. // Must parse Validation this way so that regex (preg) works
  7805. $prettyValidEqn = '';
  7806. if ($q['prettyValidEqn'] != '') {
  7807. $validationEqn = $q['validEqn'];
  7808. if (!isset($LEM->ParseResultCache[$validationEqn]))
  7809. {
  7810. $result = $LEM->em->ProcessBooleanExpression($validationEqn, $gseq, $qseq);
  7811. $prettyPrint = $LEM->em->GetPrettyPrintString();
  7812. $hasErrors = $LEM->em->HasErrors();
  7813. $LEM->ParseResultCache[$validationEqn] = array(
  7814. 'result' => $result,
  7815. 'prettyprint' => $prettyPrint,
  7816. 'hasErrors' => $hasErrors,
  7817. );
  7818. }
  7819. $prettyValidEqn = '<hr/>(VALIDATION: ' . $LEM->ParseResultCache[$validationEqn]['prettyprint'] . ')';
  7820. if ($LEM->ParseResultCache[$validationEqn]['hasErrors']) {
  7821. ++$errorCount;
  7822. }
  7823. }
  7824. //////
  7825. // TEST VALIDITY OF ROOT VARIABLE NAME AND WHETHER HAS BEEN USED
  7826. //////
  7827. $rootVarName = $q['info']['rootVarName'];
  7828. $varNameErrorMsg = '';
  7829. $varNameError = NULL;
  7830. if (isset($varNamesUsed[$rootVarName]))
  7831. {
  7832. $varNameErrorMsg .= $LEM->gT('This variable name has already been used.');
  7833. }
  7834. else
  7835. {
  7836. $varNamesUsed[$rootVarName] = array(
  7837. 'gseq'=>$gseq,
  7838. 'qid'=>$qid
  7839. );
  7840. }
  7841. if (!preg_match('/^[_a-zA-Z][_0-9a-zA-Z]*$/', $rootVarName))
  7842. {
  7843. $varNameErrorMsg .= $LEM->gT('Starting in 1.92, variable names should only contain letters, numbers, and underscores; and may not start with a number. This variable name is deprecated.');
  7844. }
  7845. if ($varNameErrorMsg != '')
  7846. {
  7847. $varNameError = array (
  7848. 'message' => $varNameErrorMsg,
  7849. 'gseq' => $varNamesUsed[$rootVarName]['gseq'],
  7850. 'qid' => $varNamesUsed[$rootVarName]['qid'],
  7851. 'gid' => $gid,
  7852. );
  7853. if (!$LEM->sgqaNaming)
  7854. {
  7855. ++$errorCount;
  7856. }
  7857. else
  7858. {
  7859. ++$warnings;
  7860. }
  7861. }
  7862. //////
  7863. // SHOW ALL SUB-QUESTIONS
  7864. //////
  7865. $sqRows='';
  7866. $i=0;
  7867. $sawThis = array(); // array of rowdivids already seen so only show them once
  7868. foreach ($sgqas as $sgqa)
  7869. {
  7870. if ($LEM->knownVars[$sgqa]['qcode'] == $rootVarName) {
  7871. continue; // so don't show the main question as a sub-question too
  7872. }
  7873. $rowdivid=$sgqa;
  7874. $varName=$LEM->knownVars[$sgqa]['qcode'];
  7875. switch ($q['info']['type'])
  7876. {
  7877. case '1':
  7878. if (preg_match('/#1$/',$sgqa)) {
  7879. $rowdivid = NULL; // so that doesn't show same message for second scale
  7880. }
  7881. else {
  7882. $rowdivid = substr($sgqa,0,-2); // strip suffix
  7883. $varName = substr($LEM->knownVars[$sgqa]['qcode'],0,-2);
  7884. }
  7885. break;
  7886. case 'P':
  7887. if (preg_match('/comment$/',$sgqa)) {
  7888. $rowdivid = NULL;
  7889. }
  7890. break;
  7891. case ':':
  7892. case ';':
  7893. $_rowdivid = $LEM->knownVars[$sgqa]['rowdivid'];
  7894. if (isset($sawThis[$qid . '~' . $_rowdivid])) {
  7895. $rowdivid = NULL; // so don't show again
  7896. }
  7897. else {
  7898. $sawThis[$qid . '~' . $_rowdivid] = true;
  7899. $rowdivid = $_rowdivid;
  7900. $sgqa_len = strlen($sid . 'X'. $gid . 'X' . $qid);
  7901. $varName = $rootVarName . '_' . substr($_rowdivid,$sgqa_len);
  7902. }
  7903. }
  7904. if (is_null($rowdivid)) {
  7905. continue;
  7906. }
  7907. ++$i;
  7908. $subQeqn = '&nbsp;';
  7909. if (isset($LEM->subQrelInfo[$qid][$rowdivid]))
  7910. {
  7911. $sq = $LEM->subQrelInfo[$qid][$rowdivid];
  7912. $subQeqn = $sq['prettyPrintEqn']; // {' . $sq['eqn'] . '}'; // $sq['prettyPrintEqn'];
  7913. if ($sq['hasErrors']) {
  7914. ++$errorCount;
  7915. }
  7916. }
  7917. $sgqaInfo = $LEM->knownVars[$sgqa];
  7918. $subqText = $sgqaInfo['subqtext'];
  7919. if (isset($sgqaInfo['default']) && $sgqaInfo['default'] !== '')
  7920. {
  7921. $LEM->ProcessString($sgqaInfo['default'], $qid,NULL,false,1,1,false,false);
  7922. $_default = $LEM->GetLastPrettyPrintExpression();
  7923. if ($LEM->em->HasErrors()) {
  7924. ++$errorCount;
  7925. }
  7926. $subQeqn .= '<br />(' . $LEM->gT('Default:') . ' ' . $_default . ')';
  7927. }
  7928. $sqRows .= "<tr class='LEMsubq'>"
  7929. . "<td>SQ-$i</td>"
  7930. . "<td><b>" . $varName . "</b></td>"
  7931. . "<td>$subQeqn</td>"
  7932. . "<td>" .$subqText . "</td>"
  7933. . "</tr>";
  7934. }
  7935. $LEM->ProcessString($sqRows, $qid,NULL,false,1,1,false,false);
  7936. $sqRows = $LEM->GetLastPrettyPrintExpression();
  7937. if ($LEM->em->HasErrors()) {
  7938. ++$errorCount;
  7939. }
  7940. //////
  7941. // SHOW ANSWER OPTIONS FOR ENUMERATED LISTS, AND FOR MULTIFLEXI
  7942. //////
  7943. $answerRows='';
  7944. if (isset($LEM->qans[$qid]) || isset($LEM->multiflexiAnswers[$qid]))
  7945. {
  7946. $_scale=-1;
  7947. if (isset($LEM->multiflexiAnswers[$qid])) {
  7948. $ansList = $LEM->multiflexiAnswers[$qid];
  7949. }
  7950. else {
  7951. $ansList = $LEM->qans[$qid];
  7952. }
  7953. foreach ($ansList as $ans=>$value)
  7954. {
  7955. $ansInfo = explode('~',$ans);
  7956. $valParts = explode('|',$value);
  7957. $valInfo[0] = array_shift($valParts);
  7958. $valInfo[1] = implode('|',$valParts);
  7959. if ($_scale != $ansInfo[0]) {
  7960. $i=1;
  7961. $_scale = $ansInfo[0];
  7962. }
  7963. $subQeqn = '';
  7964. $rowdivid = $sgqas[0] . $ansInfo[1];
  7965. if ($q['info']['type'] == 'R')
  7966. {
  7967. $rowdivid = $LEM->sid . 'X' . $gid . 'X' . $qid . $ansInfo[1];
  7968. }
  7969. if (isset($LEM->subQrelInfo[$qid][$rowdivid]))
  7970. {
  7971. $sq = $LEM->subQrelInfo[$qid][$rowdivid];
  7972. $subQeqn = ' ' . $sq['prettyPrintEqn'];
  7973. if ($sq['hasErrors']) {
  7974. ++$errorCount;
  7975. }
  7976. }
  7977. $answerRows .= "<tr class='LEManswer'>"
  7978. . "<td>A[" . $ansInfo[0] . "]-" . $i++ . "</td>"
  7979. . "<td><b>" . $ansInfo[1]. "</b></td>"
  7980. . "<td>[VALUE: " . $valInfo[0] . "]".$subQeqn."</td>"
  7981. . "<td>" . $valInfo[1] . "</td>"
  7982. . "</tr>\n";
  7983. }
  7984. $LEM->ProcessString($answerRows, $qid,NULL,false,1,1,false,false);
  7985. $answerRows = $LEM->GetLastPrettyPrintExpression();
  7986. if ($LEM->em->HasErrors()) {
  7987. ++$errorCount;
  7988. }
  7989. }
  7990. //////
  7991. // FINALLY, SHOW THE QUESTION ROW(S), COLOR-CODING QUESTIONS THAT CONTAIN ERRORS
  7992. //////
  7993. $errclass = ($errorCount > 0) ? "class='LEMerror' title='" . sprintf($LEM->ngT("This question has at least %s error.","This question has at least %s errors.",$errorCount), $errorCount) . "'" : '';
  7994. $questionRow = "<tr class='LEMquestion'>"
  7995. . "<td $errclass>Q-" . $q['info']['qseq'] . "</td>"
  7996. . "<td><b>" . $mandatory;
  7997. if ($varNameErrorMsg == '')
  7998. {
  7999. $questionRow .= $rootVarName;
  8000. }
  8001. else
  8002. {
  8003. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $varNameError['gid'] . '/qid/' . $varNameError['qid']);
  8004. $questionRow .= "<span class='highlighterror' title='" . $varNameError['message'] . "' "
  8005. . "onclick='window.open(\"$editlink\",\"_blank\")'>"
  8006. . $rootVarName . "</span>";
  8007. }
  8008. $editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $sid . '/gid/' . $gid . '/qid/' . $qid);
  8009. $questionRow .= "</b><br />[<a target='_blank' href='$editlink'>QID $qid</a>]<br/>$typedesc [$type]</td>"
  8010. . "<td>" . $relevance . $prettyValidEqn . $default . "</td>"
  8011. . "<td>" . $qdetails . "</td>"
  8012. . "</tr>\n";
  8013. $out .= $questionRow;
  8014. $out .= $sqRows;
  8015. $out .= $answerRows;
  8016. if ($errorCount > 0) {
  8017. $allErrors[$gid . '~' . $qid] = $errorCount;
  8018. }
  8019. }
  8020. $out .= "</table>";
  8021. LimeExpressionManager::FinishProcessingPage();
  8022. if (($LEMdebugLevel & LEM_DEBUG_TIMING) == LEM_DEBUG_TIMING) {
  8023. $out .= LimeExpressionManager::GetDebugTimingMessage();
  8024. }
  8025. if (count($allErrors) > 0) {
  8026. $out = "<p class='LEMerror'>". sprintf($LEM->ngT("%s question contains errors that need to be corrected","%s questions contain errors that need to be corrected",count($allErrors)), count($allErrors)) . "</p>\n" . $out;
  8027. }
  8028. else {
  8029. switch ($surveyMode)
  8030. {
  8031. case 'survey':
  8032. $message = $LEM->gT('No syntax errors detected in this survey');
  8033. break;
  8034. case 'group':
  8035. $message = $LEM->gT('This group, by itself, does not contain any syntax errors');
  8036. break;
  8037. case 'question':
  8038. $message = $LEM->gT('This question, by itself, does not contain any syntax errors');
  8039. break;
  8040. }
  8041. $out = "<p class='LEMheading'>$message</p>\n" . $out."</div>";
  8042. }
  8043. return array(
  8044. 'errors'=>$allErrors,
  8045. 'html'=>$out
  8046. );
  8047. }
  8048. /**
  8049. * TSV survey definition in format readable by TSVSurveyImport
  8050. * one line each per group, question, sub-question, and answer
  8051. * does not use SGQA naming at all.
  8052. * @param type $sid
  8053. * @return type
  8054. */
  8055. static public function &TSVSurveyExport($sid)
  8056. {
  8057. $fields = array(
  8058. 'class',
  8059. 'type/scale',
  8060. 'name',
  8061. 'relevance',
  8062. 'text',
  8063. 'help',
  8064. 'language',
  8065. 'validation',
  8066. 'mandatory',
  8067. 'other',
  8068. 'default',
  8069. 'same_default',
  8070. // Advanced question attributes
  8071. 'allowed_filetypes',
  8072. 'alphasort',
  8073. 'answer_width',
  8074. 'array_filter',
  8075. 'array_filter_exclude',
  8076. 'assessment_value',
  8077. 'category_separator',
  8078. 'display_columns',
  8079. 'display_rows',
  8080. 'dropdown_dates_year_min',
  8081. 'dropdown_dates',
  8082. 'dropdown_dates_year_max',
  8083. 'dropdown_prefix',
  8084. 'dropdown_prepostfix',
  8085. 'dropdown_separators',
  8086. 'dropdown_size',
  8087. 'dualscale_headerA',
  8088. 'dualscale_headerB',
  8089. 'em_validation_q_tip',
  8090. 'em_validation_q',
  8091. 'em_validation_sq',
  8092. 'em_validation_sq_tip',
  8093. 'equals_num_value',
  8094. 'exclude_all_others',
  8095. 'exclude_all_others_auto',
  8096. 'hidden',
  8097. 'hide_tip',
  8098. 'input_boxes',
  8099. 'location_city',
  8100. 'location_country',
  8101. 'location_defaultcoordinates',
  8102. 'location_mapheight',
  8103. 'location_mapservice',
  8104. 'location_mapwidth',
  8105. 'location_mapzoom',
  8106. 'location_nodefaultfromip',
  8107. 'location_postal',
  8108. 'location_state',
  8109. 'max_answers',
  8110. 'max_filesize',
  8111. 'max_num_of_files',
  8112. 'max_num_value',
  8113. 'max_num_value_n',
  8114. 'maximum_chars',
  8115. 'min_answers',
  8116. 'min_num_of_files',
  8117. 'min_num_value',
  8118. 'min_num_value_n',
  8119. 'multiflexible_checkbox',
  8120. 'multiflexible_max',
  8121. 'multiflexible_min',
  8122. 'multiflexible_step',
  8123. 'num_value_int_only',
  8124. 'numbers_only',
  8125. 'other_comment_mandatory',
  8126. 'other_numbers_only',
  8127. 'other_replace_text',
  8128. 'page_break',
  8129. 'prefix',
  8130. 'public_statistics',
  8131. 'random_group',
  8132. 'random_order',
  8133. 'reverse',
  8134. 'scale_export',
  8135. 'show_comment',
  8136. 'show_grand_total',
  8137. 'show_title',
  8138. 'show_totals',
  8139. 'slider_accuracy',
  8140. 'slider_default',
  8141. 'slider_layout',
  8142. 'slider_max',
  8143. 'slider_middlestart',
  8144. 'slider_min',
  8145. 'slider_rating',
  8146. 'slider_separator',
  8147. 'slider_showminmax',
  8148. 'suffix',
  8149. 'text_input_width',
  8150. 'time_limit_action',
  8151. 'time_limit_countdown_message',
  8152. 'time_limit_disable_next',
  8153. 'time_limit_disable_prev',
  8154. 'time_limit_message',
  8155. 'time_limit_message_delay',
  8156. 'time_limit_message_style',
  8157. 'time_limit_timer_style',
  8158. 'time_limit_warning_2_display_time',
  8159. 'time_limit_warning_2_message',
  8160. 'time_limit_warning_2_style',
  8161. 'time_limit_warning_2',
  8162. 'time_limit_warning',
  8163. 'time_limit',
  8164. 'time_limit_warning_display_time',
  8165. 'time_limit_warning_message',
  8166. 'time_limit_warning_style',
  8167. 'use_dropdown',
  8168. );
  8169. $rows = array();
  8170. $primarylang='en';
  8171. $otherlangs='';
  8172. $langs = array();
  8173. // Export survey-level information
  8174. $query = "select * from {{surveys}} where sid = " . $sid;
  8175. $data = dbExecuteAssoc($query);
  8176. foreach ($data->readAll() as $r)
  8177. {
  8178. foreach ($r as $key=>$value)
  8179. {
  8180. if ($value != '')
  8181. {
  8182. $row['class'] = 'S';
  8183. $row['name'] = $key;
  8184. $row['text'] = $value;
  8185. $rows[] = $row;
  8186. }
  8187. if ($key=='language')
  8188. {
  8189. $primarylang = $value;
  8190. }
  8191. if ($key=='additional_languages')
  8192. {
  8193. $otherlangs = $value;
  8194. }
  8195. }
  8196. }
  8197. $langs = explode(' ',$primarylang . ' ' . $otherlangs);
  8198. $langs = array_unique($langs);
  8199. // Export survey language settings
  8200. $query = "select * from {{surveys_languagesettings}} where surveyls_survey_id = " . $sid;
  8201. $data = dbExecuteAssoc($query);
  8202. foreach ($data->readAll() as $r)
  8203. {
  8204. $_lang = $r['surveyls_language'];
  8205. foreach ($r as $key=>$value)
  8206. {
  8207. if ($value != '' && $key != 'surveyls_language' && $key != 'surveyls_survey_id')
  8208. {
  8209. $row['class'] = 'SL';
  8210. $row['name'] = $key;
  8211. $row['text'] = $value;
  8212. $row['language'] = $_lang;
  8213. $rows[] = $row;
  8214. }
  8215. }
  8216. }
  8217. $surveyinfo = getSurveyInfo($sid);
  8218. $assessments = false;
  8219. if (isset($surveyinfo['assessments']) && $surveyinfo['assessments']=='Y')
  8220. {
  8221. $assessments = true;
  8222. }
  8223. foreach($langs as $lang)
  8224. {
  8225. if (trim($lang) == '')
  8226. {
  8227. continue;
  8228. }
  8229. SetSurveyLanguage($sid,$lang);
  8230. LimeExpressionManager::StartSurvey($sid, 'survey', array('sgqaNaming'=>'N','assessments'=>$assessments), true);
  8231. $moveResult = LimeExpressionManager::NavigateForwards();
  8232. $LEM =& LimeExpressionManager::singleton();
  8233. if (is_null($moveResult) || is_null($LEM->currentQset) || count($LEM->currentQset) == 0) {
  8234. continue;
  8235. }
  8236. $_gseq=-1;
  8237. foreach ($LEM->currentQset as $q) {
  8238. $gseq = $q['info']['gseq'];
  8239. $gid = $q['info']['gid'];
  8240. $qid = $q['info']['qid'];
  8241. //////
  8242. // SHOW GROUP-LEVEL INFO
  8243. //////
  8244. if ($gseq != $_gseq) {
  8245. $_gseq = $gseq;
  8246. $ginfo = $LEM->gseq2info[$gseq];
  8247. // if relevance equation is using SGQA coding, convert to qcoding
  8248. $grelevance = (($ginfo['grelevance']=='') ? 1 : $ginfo['grelevance']);
  8249. $LEM->em->ProcessBooleanExpression($grelevance, $gseq, 0); // $qseq
  8250. $grelevance = trim(strip_tags($LEM->em->GetPrettyPrintString()));
  8251. $gtext = ((trim($ginfo['description']) == '') ? '' : $ginfo['description']);
  8252. $row = array();
  8253. $row['class'] = 'G';
  8254. $row['name'] = $ginfo['group_name'];
  8255. $row['relevance'] = $grelevance;
  8256. $row['text'] = $gtext;
  8257. $row['language'] = $lang;
  8258. $rows[] = $row;
  8259. }
  8260. //////
  8261. // SHOW QUESTION-LEVEL INFO
  8262. //////
  8263. $row = array();
  8264. $mandatory = (($q['info']['mandatory']=='Y') ? 'Y' : '');
  8265. $type = $q['info']['type'];
  8266. $sgqas = explode('|',$q['sgqa']);
  8267. if (count($sgqas) == 1 && !is_null($q['info']['default']))
  8268. {
  8269. $default = $q['info']['default'];
  8270. }
  8271. else
  8272. {
  8273. $default = '';
  8274. }
  8275. $qtext = (($q['info']['qtext'] != '') ? $q['info']['qtext'] : '');
  8276. $help = (($q['info']['help'] != '') ? $q['info']['help']: '');
  8277. //////
  8278. // SHOW QUESTION ATTRIBUTES THAT ARE PROCESSED BY EM
  8279. //////
  8280. if (isset($LEM->qattr[$qid]) && count($LEM->qattr[$qid]) > 0) {
  8281. foreach ($LEM->qattr[$qid] as $key=>$value) {
  8282. if (is_null($value) || trim($value) == '') {
  8283. continue;
  8284. }
  8285. switch ($key)
  8286. {
  8287. default:
  8288. case 'exclude_all_others':
  8289. case 'exclude_all_others_auto':
  8290. case 'hidden':
  8291. if ($value == false || $value == '0') {
  8292. $value = NULL; // so can skip this one - just using continue here doesn't work.
  8293. }
  8294. break;
  8295. case 'relevance':
  8296. $value = NULL; // means an outdate database structure
  8297. break;
  8298. }
  8299. if (is_null($value) || trim($value) == '') {
  8300. continue; // since continuing from within a switch statement doesn't work
  8301. }
  8302. $row[$key] = $value;
  8303. }
  8304. }
  8305. // if relevance equation is using SGQA coding, convert to qcoding
  8306. $relevanceEqn = (($q['info']['relevance'] == '') ? 1 : $q['info']['relevance']);
  8307. $LEM->em->ProcessBooleanExpression($relevanceEqn, $gseq, $q['info']['qseq']); // $qseq
  8308. $relevanceEqn = trim(strip_tags($LEM->em->GetPrettyPrintString()));
  8309. $rootVarName = $q['info']['rootVarName'];
  8310. $preg = '';
  8311. if (isset($LEM->q2subqInfo[$q['info']['qid']]['preg']))
  8312. {
  8313. $preg = $LEM->q2subqInfo[$q['info']['qid']]['preg'];
  8314. if (is_null($preg))
  8315. {
  8316. $preg = '';
  8317. }
  8318. }
  8319. $row['class'] = 'Q';
  8320. $row['type/scale'] = $type;
  8321. $row['name'] = $rootVarName;
  8322. $row['relevance'] = $relevanceEqn;
  8323. $row['text'] = $qtext;
  8324. $row['help'] = $help;
  8325. $row['language'] = $lang;
  8326. $row['validation'] = $preg;
  8327. $row['mandatory'] = $mandatory;
  8328. $row['other'] = $q['info']['other'];
  8329. $row['default'] = $default;
  8330. $row['same_default'] = 1; // TODO - need this: $q['info']['same_default'];
  8331. $rows[] = $row;
  8332. //////
  8333. // SHOW ALL SUB-QUESTIONS
  8334. //////
  8335. $sawThis = array(); // array of rowdivids already seen so only show them once
  8336. foreach ($sgqas as $sgqa)
  8337. {
  8338. if ($LEM->knownVars[$sgqa]['qcode'] == $rootVarName) {
  8339. continue; // so don't show the main question as a sub-question too
  8340. }
  8341. $rowdivid=$sgqa;
  8342. $varName=$LEM->knownVars[$sgqa]['qcode'];
  8343. switch ($q['info']['type'])
  8344. {
  8345. case '1':
  8346. if (preg_match('/#1$/',$sgqa)) {
  8347. $rowdivid = NULL; // so that doesn't show same message for second scale
  8348. }
  8349. else {
  8350. $rowdivid = substr($sgqa,0,-2); // strip suffix
  8351. $varName = substr($LEM->knownVars[$sgqa]['qcode'],0,-2);
  8352. }
  8353. break;
  8354. case 'P':
  8355. if (preg_match('/comment$/',$sgqa)) {
  8356. $rowdivid = NULL;
  8357. }
  8358. break;
  8359. case ':':
  8360. case ';':
  8361. $_rowdivid = $LEM->knownVars[$sgqa]['rowdivid'];
  8362. if (isset($sawThis[$qid . '~' . $_rowdivid])) {
  8363. $rowdivid = NULL; // so don't show again
  8364. }
  8365. else {
  8366. $sawThis[$qid . '~' . $_rowdivid] = true;
  8367. $rowdivid = $_rowdivid;
  8368. $sgqa_len = strlen($sid . 'X'. $gid . 'X' . $qid);
  8369. $varName = $rootVarName . '_' . substr($_rowdivid,$sgqa_len);
  8370. }
  8371. break;
  8372. }
  8373. if (is_null($rowdivid)) {
  8374. continue;
  8375. }
  8376. $sgqaInfo = $LEM->knownVars[$sgqa];
  8377. $subqText = $sgqaInfo['subqtext'];
  8378. if (isset($sgqaInfo['default']))
  8379. {
  8380. $default = $sgqaInfo['default'];
  8381. }
  8382. else
  8383. {
  8384. $default = '';
  8385. }
  8386. $row = array();
  8387. $row['class'] = 'SQ';
  8388. $row['type/scale'] = 0;
  8389. $row['name'] = substr($varName,strlen($rootVarName)+1);
  8390. $row['text'] = $subqText;
  8391. $row['language'] = $lang;
  8392. $row['default'] = $default;
  8393. $rows[] = $row;
  8394. }
  8395. //////
  8396. // SHOW ANSWER OPTIONS FOR ENUMERATED LISTS, AND FOR MULTIFLEXI
  8397. //////
  8398. if (isset($LEM->qans[$qid]) || isset($LEM->multiflexiAnswers[$qid]))
  8399. {
  8400. $_scale=-1;
  8401. if (isset($LEM->multiflexiAnswers[$qid])) {
  8402. $ansList = $LEM->multiflexiAnswers[$qid];
  8403. }
  8404. else {
  8405. $ansList = $LEM->qans[$qid];
  8406. }
  8407. foreach ($ansList as $ans=>$value)
  8408. {
  8409. $ansInfo = explode('~',$ans);
  8410. $valParts = explode('|',$value);
  8411. $valInfo[0] = array_shift($valParts);
  8412. $valInfo[1] = implode('|',$valParts);
  8413. if ($_scale != $ansInfo[0]) {
  8414. $_scale = $ansInfo[0];
  8415. }
  8416. $row = array();
  8417. if ($type == ':' || $type == ';')
  8418. {
  8419. $row['class'] = 'SQ';
  8420. }
  8421. else
  8422. {
  8423. $row['class'] = 'A';
  8424. }
  8425. $row['type/scale'] = $_scale;
  8426. $row['name'] = $ansInfo[1];
  8427. $row['relevance'] = $valInfo[0]; // TODO - true? - what if it isn't an assessment value?
  8428. $row['text'] = $valInfo[1];
  8429. $row['language'] = $lang;
  8430. $rows[] = $row;
  8431. }
  8432. }
  8433. }
  8434. }
  8435. // Now generate the array out output data
  8436. $out = array();
  8437. $out[] = $fields;
  8438. foreach ($rows as $row)
  8439. {
  8440. $tsv = array();
  8441. foreach ($fields as $field)
  8442. {
  8443. $val = (isset($row[$field]) ? $row[$field] : '');
  8444. $tsv[] = $val;
  8445. }
  8446. $out[] = $tsv;
  8447. }
  8448. return $out;
  8449. }
  8450. /**
  8451. * Returns the survey ID of the EM singleton
  8452. */
  8453. public static function getLEMsurveyId() {
  8454. $LEM =& LimeExpressionManager::singleton();
  8455. return $LEM->sid;
  8456. }
  8457. }
  8458. /**
  8459. * Used by usort() to order $this->questionSeq2relevance in proper order
  8460. * @param <type> $a
  8461. * @param <type> $b
  8462. * @return <type>
  8463. */
  8464. function cmpQuestionSeq($a, $b)
  8465. {
  8466. if (is_null($a['qseq'])) {
  8467. if (is_null($b['qseq'])) {
  8468. return 0;
  8469. }
  8470. return 1;
  8471. }
  8472. if (is_null($b['qseq'])) {
  8473. return -1;
  8474. }
  8475. if ($a['qseq'] == $b['qseq']) {
  8476. return 0;
  8477. }
  8478. return ($a['qseq'] < $b['qseq']) ? -1 : 1;
  8479. }
  8480. ?>