mirror of
https://github.com/YunoHost-Apps/limesurvey_ynh.git
synced 2024-09-03 19:36:32 +02:00
9980 lines
490 KiB
PHP
9980 lines
490 KiB
PHP
<?php
|
|
/**
|
|
* LimeSurvey
|
|
* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
|
|
* All rights reserved.
|
|
* License: GNU/GPL License v2 or later, see LICENSE.php
|
|
* LimeSurvey is free software. This version may have been modified pursuant
|
|
* to the GNU General Public License, and as distributed it includes or
|
|
* is derivative of works licensed under the GNU General Public License or
|
|
* other free or open source software licenses.
|
|
* See COPYRIGHT.php for copyright notices and details.
|
|
*
|
|
*/
|
|
/**
|
|
* Description of LimeExpressionManager
|
|
* This is a wrapper class around ExpressionManager that implements a Singleton and eases
|
|
* passing of LimeSurvey variable values into ExpressionManager
|
|
*
|
|
* @author LimeSurvey Team (limesurvey.org)
|
|
* @author Thomas M. White (TMSWhite)
|
|
*/
|
|
include_once('em_core_helper.php');
|
|
Yii::app()->loadHelper('database');
|
|
Yii::app()->loadHelper('frontend');
|
|
Yii::app()->loadHelper('surveytranslator');
|
|
Yii::import("application.libraries.Date_Time_Converter");
|
|
define('LEM_DEBUG_TIMING',1);
|
|
define('LEM_DEBUG_VALIDATION_SUMMARY',2); // also includes SQL error messages
|
|
define('LEM_DEBUG_VALIDATION_DETAIL',4);
|
|
define('LEM_PRETTY_PRINT_ALL_SYNTAX',32);
|
|
|
|
define('LEM_DEFAULT_PRECISION',12);
|
|
|
|
class LimeExpressionManager {
|
|
/**
|
|
* LimeExpressionManager is a singleton. $instance is its storage location.
|
|
* @var LimeExpressionManager
|
|
*/
|
|
private static $instance;
|
|
/**
|
|
* Implements the recursive descent parser that processes expressions
|
|
* @var ExpressionManager
|
|
*/
|
|
private $em;
|
|
/**
|
|
*
|
|
* @var type
|
|
*/
|
|
private $groupRelevanceInfo;
|
|
/**
|
|
* The survey ID
|
|
* @var integer
|
|
*/
|
|
private $sid;
|
|
/**
|
|
* sum of LEM_DEBUG constants - use bitwise AND comparisons to identify which parts to use
|
|
* @var type
|
|
*/
|
|
private $debugLevel=0;
|
|
/**
|
|
* sPreviewMode used for relevance equation force to 1 in preview mode
|
|
* Maybe we can set it public
|
|
* @var string
|
|
*/
|
|
private $sPreviewMode=false;
|
|
/**
|
|
* Collection of variable attributes, indexed by SGQA code
|
|
*
|
|
* Actual variables are stored in this structure:
|
|
* $knownVars[$sgqa] = array(
|
|
* 'jsName_on' => // the name of the javascript variable if it is defined on the current page - often 'answerSGQA'
|
|
* 'jsName' => // the name of the javascript variable when referenced on different pages - usually 'javaSGQA'
|
|
* 'readWrite' => // 'Y' for yes, 'N' for no - currently not used
|
|
* 'hidden' => // 1 if the question attribute 'hidden' is true, otherwise 0
|
|
* 'question' => // the text of the question (or sub-question)
|
|
* 'qid' => // the numeric question id - e.g. the Q part of the SGQA name
|
|
* 'gid' => // the numeric group id - e.g. the G part of the SGQA name
|
|
* 'grelevance' => // the group level relevance string
|
|
* 'relevance' => // the question level relevance string
|
|
* 'qcode' => // the qcode-style variable name for this question (or sub-question)
|
|
* 'qseq' => // the 0-based index of the question within the survey
|
|
* 'gseq' => // the 0-based index of the group within the survey
|
|
* 'type' => // the single character type code for the question
|
|
* 'sgqa' => // the SGQA name for the variable
|
|
* 'ansList' => // ansArray converted to a JavaScript fragment - e.g. ",'answers':{ 'M':'Male','F':'Female'}"
|
|
* 'ansArray' => // PHP array of answer strings, keyed on the answer code = e.g. array['M']='Male';
|
|
* 'scale_id' => // '0' for most answers. '1' for second scale within dual-scale questions
|
|
* '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
|
|
* 'subqtext' => // the sub-question text
|
|
* 'rowdivid' => // the JavaScript ID of the row identifier for a question. This is used to show/hide entire question rows
|
|
* '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
|
|
* );
|
|
*
|
|
* Reserved variables (e.g. TOKEN:xxxx) are stored with this structure:
|
|
* $knownVars[$token] = array(
|
|
* 'code' => // the static value for the variable
|
|
* 'type' => // ''
|
|
* 'jsName_on' => // ''
|
|
* 'jsName' => // ''
|
|
* 'readWrite' => // 'N' - since these are always read-only variables
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $knownVars;
|
|
/**
|
|
* maps qcode varname to SGQA code
|
|
*
|
|
* @example ['gender'] = '38612X10X145'
|
|
* @var type
|
|
*/
|
|
private $qcode2sgqa;
|
|
/**
|
|
* variables temporarily set for substitution purposes
|
|
*
|
|
* These are typically the LimeReplacement Fields passed in via templatereplace()
|
|
* Each has the following structure: array(
|
|
* 'code' => // the static value of the variable
|
|
* 'jsName_on' => // ''
|
|
* 'jsName' => // ''
|
|
* 'readWrite' => // 'N'
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $tempVars;
|
|
/**
|
|
* Array of relevance information for each page (gseq), indexed by gseq.
|
|
* Within a page, it contains a sequential list of the results of each relevance equation processed
|
|
* array(
|
|
* 'qid' => // question id -- e.g. 154
|
|
* 'gseq' => // 0-based group sequence -- e.g. 2
|
|
* 'eqn' => // the raw relevance equation parsed -- e.g. "!is_empty(p2_sex)"
|
|
* 'result' => // the Boolean result of parsing that equation in the current context -- e.g. 0
|
|
* 'numJsVars' => // the number of dynamic JavaScript variables used in that equation -- e.g. 1
|
|
* 'relevancejs' => // the actual JavaScript to insert for that relevance equation -- e.g. "LEMif(LEManyNA('p2_sex'),'',( ! LEMempty(LEMval('p2_sex') )))"
|
|
* 'relevanceVars' => // a pipe-delimited list of JavaScript variables upon which that equation depends -- e.g. "java38612X12X153"
|
|
* 'jsResultVar' => // the JavaScript variable in which that result will be stored -- e.g. "java38612X12X154"
|
|
* 'type' => // the single character type of the question -- e.g. 'S'
|
|
* 'hidden' => // 1 if the question should always be hidden
|
|
* 'hasErrors' => // 1 if there were parsing errors processing that relevance equation
|
|
* @var type
|
|
*/
|
|
private $pageRelevanceInfo;
|
|
/**
|
|
*
|
|
* @var type
|
|
*/
|
|
private $pageTailorInfo;
|
|
/**
|
|
* internally set to true (1) for survey.php so get group-specific logging but keep javascript variable namings consistent on the page.
|
|
* @var type
|
|
*/
|
|
private $allOnOnePage=false;
|
|
/**
|
|
* survey mode. One of 'survey', 'group', or 'question'
|
|
* @var string
|
|
*/
|
|
private $surveyMode='group';
|
|
/**
|
|
* a set of global survey options passed from LimeSurvey
|
|
*
|
|
* For example, array(
|
|
* 'rooturl' => // URL prefix needed to be able to click on a syntax-highlighted variable name and have it open the needed editting window
|
|
* 'hyperlinkSyntaxHighlighting' => // true if should be able to click on variables to edit them
|
|
* 'active' => // 0 for inactive, 1 for active survey
|
|
* 'allowsave' => // 0 for do not allow save; 1 for allow save
|
|
* 'anonymized' => // 1 for anonymous
|
|
* 'assessments' => // 1 for use assessments
|
|
* 'datestamp' => // 1 for use date stamps
|
|
* 'ipaddr' => // 1 for capture IP address
|
|
* 'radix' => // '.' for use period as decimal separator; ',' for use comma as decimal separator
|
|
* 'savetimings' => // "Y" if should save survey timings
|
|
* 'startlanguage' => // the starting language -- e.g. 'en'
|
|
* 'surveyls_dateformat' => // the index of the language specific date format -- e.g. 1
|
|
* 'tablename' => // the name of the table storing the survey data, if active -- e.g. lime_survey_38612
|
|
* 'target' => // the path for uploading files -- e.g. '/temp/files/'
|
|
* 'timeadjust' => // the time offset -- e.g. 0
|
|
* 'tempdir' => // the temporary directory for uploading files -- e.g. '/temp/'
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $surveyOptions=array();
|
|
/**
|
|
* array of mappings of Question # (qid) to pipe-delimited list of SGQA codes used within it
|
|
*
|
|
* @example [150] = "38612X11X150|38612X11X150other"
|
|
* @var type
|
|
*/
|
|
private $qid2code;
|
|
/**
|
|
* array of mappings of JavaScript Variable names to Question number (qid)
|
|
*
|
|
* @example ['java38612X13X161other'] = '161'
|
|
* @var type
|
|
*/
|
|
private $jsVar2qid;
|
|
/**
|
|
* maps name of the variable to the SGQ name (without the A suffix)
|
|
*
|
|
* @example ['p1_sex'] = "38612X10X147"
|
|
* @example ['afDS_sq1_1'] = "26626X37X705sq1#1"
|
|
* @var type
|
|
*/
|
|
private $qcode2sgq;
|
|
/**
|
|
* array of mappings of knownVar aliases to the JavaScript variable names.
|
|
* This maps both the SGQA and qcode alias names to the same 2 dimensional array
|
|
*
|
|
* @example ['p1_sex'] = array(
|
|
* 'jsName' => // the JavaScript variable name used by EM -- e.g. "java38612X11X147"
|
|
* 'jsPart' => // the JavaScript fragment used in EM's ____ array -- e.g. "'p1_sex':'java38612X11X147'"
|
|
* );
|
|
* @example ['afDS_sq1_1] = array(
|
|
* 'jsName' => "java26626X37X705sq1#1"
|
|
* 'jsPart' => "'afDS_sq1_1':'java26626X37X705sq1#1'"
|
|
* );
|
|
* @var type
|
|
*/
|
|
private $alias2varName;
|
|
/**
|
|
* JavaScript array of mappings of canonical JavaScript variable name to key attributes.
|
|
* These fragments are used to create the JavaScript varNameAttr array.
|
|
*
|
|
* @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'}}"
|
|
* @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'}}"
|
|
*
|
|
* @var type
|
|
*/
|
|
private $varNameAttr;
|
|
|
|
/**
|
|
* array of enumerated answer lists indexed by qid
|
|
* These use a tilde syntax to indicate which scale the answer is part of.
|
|
*
|
|
* @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'
|
|
* @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'
|
|
* @example // TODO - add example from survey using assessments
|
|
*
|
|
* @var type
|
|
*/
|
|
private $qans;
|
|
/**
|
|
* map of gid to 0-based sequence number of groups
|
|
*
|
|
* @example [10] = 0 // means that the first group (gseq=0) has gid=10
|
|
*
|
|
* @var type
|
|
*/
|
|
private $groupId2groupSeq;
|
|
/**
|
|
* map question # to an incremental count of question order across the whole survey
|
|
*
|
|
* @example [157] = 13 // means that that 14th question in the survey has qid=157
|
|
*
|
|
* @var type
|
|
*/
|
|
private $questionId2questionSeq;
|
|
/**
|
|
* map question # to the group it is within, using an incremental count of group order
|
|
*
|
|
* @example [157] = 2 // means that qid 157 is in the 3rd page of questions (gseq = 2)
|
|
*
|
|
* @var type
|
|
*/
|
|
private $questionId2groupSeq;
|
|
/**
|
|
* array of info about each Group, indexed by GroupSeq
|
|
*
|
|
* @example [2] = array(
|
|
* 'qstart' => 9 // the first qseq within that group
|
|
* 'qend' => 13 //the last qseq within that group
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $groupSeqInfo;
|
|
|
|
/**
|
|
* tracks which groups have at least one relevant, non-hidden question
|
|
*
|
|
* @example [2] = 0 // means that the third group (gseq==2) is currently irrelevant
|
|
*
|
|
* @var type
|
|
*/
|
|
private $gseq2relevanceStatus;
|
|
/**
|
|
* maps question # to the validation equation(s) for that question.
|
|
* These are grouped by qid then validation type, such as 'value_range', and 'num_answers'
|
|
*
|
|
* @example [703] = array(
|
|
* 'eqn' => array(
|
|
* 'value_range' = "((is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK >= (0)) and (is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK <= (5)))"
|
|
* ),
|
|
* 'tips' => array(
|
|
* 'value_range' = "Each answer must be between {fixnum(0)} and {fixnum(5)}"
|
|
* ),
|
|
* 'subqValidEqns' = array(
|
|
* [] = array(
|
|
* 'subqValidSelector' => '' //
|
|
* 'subqValidEqn' => "(is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK >= (0)) && (is_empty(26626X34X703.NAOK) || 26626X34X703.NAOK <= (5))"
|
|
* ),
|
|
* 'sumEqn' => '' // the equation to compute the current sum of the responses
|
|
* '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)
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $qid2validationEqn;
|
|
|
|
/**
|
|
* keeps relevance in proper sequence so can minimize relevance processing to see what should be see on page and in indexes
|
|
* Array is indexed on qseq
|
|
*
|
|
* @example [3] = array(
|
|
* 'relevance' => "!is_empty(num)" // the question-level relevance equation
|
|
* 'grelevance' => "" // the group-level relevance equation
|
|
* 'qid' => "699" // the question id
|
|
* 'qseq' => 3 // the 0-index question sequence
|
|
* 'gseq' => 0 // the 0-index group sequence
|
|
* 'jsResultVar_on' => 'answer26626X34X699' // the javascript variable holding the input value
|
|
* 'jsResultVar' => 'java26226X34X699' // the javascript variable (often hidden) holding the value to be submitted
|
|
* 'type' => 'N' // the one character question type
|
|
* 'hidden' => 0 // 1 if it should be always_hidden
|
|
* 'gid' => "34" // group id
|
|
* 'mandatory' => 'N' // 'Y' if mandatory
|
|
* 'eqn' => "" // TODO ??
|
|
* 'help' => "" // the help text
|
|
* 'qtext' => "Enter a larger number than {num}" // the question text
|
|
* 'code' => 'afDS_sq5_1' // the full variable name
|
|
* 'other' => 'N' // whether the question supports the 'other' option - 'Y' if true
|
|
* 'rowdivid' => '2626X37X705sq5' // the javascript id for the row - in this case, the 5th sub-question
|
|
* 'aid' => 'sq5' // the answer id
|
|
* 'sqid' => '791' // the sub-question's qid (only populated for some question types)
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $questionSeq2relevance;
|
|
/**
|
|
* current Group sequence (0-based index)
|
|
* @example 1
|
|
* @var integer
|
|
*/
|
|
private $currentGroupSeq;
|
|
/**
|
|
* for Question-by-Question mode, the 0-based index
|
|
* @example 3
|
|
* @var integer
|
|
*/
|
|
private $currentQuestionSeq;
|
|
/**
|
|
* used in Question-by-Question mode
|
|
* @var integer
|
|
*/
|
|
private $currentQID;
|
|
/**
|
|
* set of the current set of questions to be displayed, indexed by QID - at least one must be relevant
|
|
*
|
|
* The array has N entries, where N is the number if qids in the Qset. Each has the following contents:
|
|
* @example [705] = array(
|
|
* 'info' => array() // this is an exact copy of $questionSeq2relevance[$qseq] -- TODO - remove redundancy
|
|
* 'relevant' => 1 // 1 if the question is currently relevant
|
|
* 'hidden' => 0 // 1 if the question is always hidden
|
|
* 'relEqn' => '' // the relevance equation -- TODO - how different from ['info']['relevance']?
|
|
* '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"
|
|
* '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"
|
|
* 'valid' => 0 // 1 if the current answers pass all of the validation criteria for the question
|
|
* '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)))"
|
|
* 'prettyValidEqn' => // syntax-highlighted version of validEqn, only showing syntax errors
|
|
* '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>"
|
|
* '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>"
|
|
* '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') )))))"
|
|
* '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"
|
|
* '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"
|
|
* 'irrelevantSQs' => // current list of subquestions that are irrelevant -- e.g. "26626X37X705sq2#0|26626X37X705sq2#1|26626X37X705sq4#0|26626X37X705sq4#1"
|
|
* 'subQrelEqn' => // TODO - ??
|
|
* 'mandViolation' => 0 // 1 if the question is mandatory and fails the mandatory criteria
|
|
* 'anyUnanswered' => 1 // 1 if any parts of the question are unanswered
|
|
* 'mandTip' => '' // message to display if the question fails mandatory criteria
|
|
* 'message' => '' // TODO ??
|
|
* 'updatedValues' => // array of values that should be updated for this question, as [$sgqa] = $value
|
|
* 'sumEqn' => '' //
|
|
* 'sumRemainingEqn' => '' //
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $currentQset=NULL;
|
|
/**
|
|
* last result of NavigateForwards, NavigateBackwards, or JumpTo
|
|
* Array of status information about last movement, whether at question, group, or survey level
|
|
*
|
|
* @example = array(
|
|
* 'finished' => 0 // 1 if the survey has been completed and needs to be finalized
|
|
* 'message' => '' // any error message that needs to be displayed
|
|
* 'seq' => 1 // the sequence count, using gseq, or qseq units if in 'group' or 'question' mode, respectively
|
|
* 'mandViolation' => 0 // whether there was any violation of mandatory constraints in the last movement
|
|
* 'valid' => 0 // 1 if the last movement passed all validation constraints. 0 if there were any validation errors
|
|
* 'unansweredSQs' => // pipe-separated list of any sub-questions that were not answered
|
|
* 'invalidSQs' => // pipe-separated list of any sub-questions that failed validation constraints
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $lastMoveResult=NULL;
|
|
/**
|
|
* array of information needed to generate navigation index in question-by-question mode
|
|
* One entry for each question, indexed by qseq
|
|
*
|
|
* @example [4] = array(
|
|
* 'qid' => "700" // the question id
|
|
* 'qtext' => 'How old are you?' // the question text
|
|
* 'qcode' => 'age' // the variable name
|
|
* 'qhelp' => '' // the help text
|
|
* 'anyUnanswered' => 0 // 1 if there are any sub-questions answered. Used for index display
|
|
* 'anyErrors' => 0 // 1 if there are any errors among the sub-questions. Could be used for index display
|
|
* 'show' => 1 // 1 if there are any relevant, non-hidden sub-questions. Only if so, then display the index entry
|
|
* 'gseq' => 0 // the group sequence
|
|
* 'gtext' => // text description for the group
|
|
* 'gname' => 'G1' // the group title
|
|
* 'gid' => "34" // the group id
|
|
* 'mandViolation' => 0 // 1 if the question as a whole fails the mandatory criteria
|
|
* 'valid' => 1 // 0 if any part of the question fails validation criteria.
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $indexQseq;
|
|
/**
|
|
* array of information needed to generate navigation index in group-by-group mode
|
|
* One entry for each group, indexed by gseq
|
|
*
|
|
* @example [0] = array(
|
|
* 'gtext' => // the description for the group
|
|
* 'gname' => 'G1' // the group title
|
|
* 'gid' => '34' // the group id
|
|
* 'anyUnanswered' => 0 // 1 if any questions within the group are unanswered
|
|
* 'anyErrors' => 0 // 1 if any of the questions within the group fail either validity or mandatory constraints
|
|
* 'valid' => 1 // 1 if at least question in the group is relevant and non-hidden
|
|
* 'mandViolation' => 0 // 1 if at least one relevant, non-hidden question in the group fails mandatory constraints
|
|
* 'show' => 1 // 1 if there is at least one relevant, non-hidden question within the group
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $indexGseq;
|
|
/**
|
|
* array of group sequence number to static info
|
|
* One entry per group, indexed on gseq
|
|
*
|
|
* @example [0] = array(
|
|
* 'group_order' => 0 // gseq
|
|
* 'gid' => "34" // group id
|
|
* 'group_name' => 'G2' // the group title
|
|
* 'description' => // the description of the group (e.g. gtitle)
|
|
* 'grelevance' => '' // the group-level relevance
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $gseq2info;
|
|
|
|
/**
|
|
* the maximum groupSeq reached - this is needed for Index
|
|
* @var type
|
|
*/
|
|
private $maxGroupSeq;
|
|
/**
|
|
* mapping of questions to information about their subquestions.
|
|
* One entry per question, indexed on qid
|
|
*
|
|
* @example [702] = array(
|
|
* 'qid' => 702 // the question id
|
|
* 'qseq' => 6 // the question sequence
|
|
* 'gseq' => 0 // the group sequence
|
|
* 'sgqa' => '26626X34X702' // the root of the SGQA code (reallly just the SGQ)
|
|
* 'varName' => 'afSrcFilter_sq1' // the full qcode variable name - note, if there are sub-questions, don't use this one.
|
|
* 'type' => 'M' // the one-letter question type
|
|
* 'fieldname' => '26626X34X702sq1' // the fieldname (used as JavaScript variable name, and also as database column name
|
|
* 'rootVarName' => 'afDS' // the root variable name
|
|
* 'preg' => '/[A-Z]+/' // regular expression validation equation, if any
|
|
* 'subqs' => array() of sub-questions, where each contains:
|
|
* 'rowdivid' => '26626X34X702sq1' // the javascript id identifying the question row (so array_filter can hide rows)
|
|
* 'varName' => 'afSrcFilter_sq1' // the full variable name for the sub-question
|
|
* 'jsVarName_on' => 'java26626X34X702sq1' // the JavaScript variable name if the variable is defined on the current page
|
|
* 'jsVarName' => 'java26626X34X702sq1' // the JavaScript variable name to use if the variable is defined on a different page
|
|
* 'csuffix' => 'sq1' // the SGQ suffix to use for a fieldname
|
|
* 'sqsuffix' => '_sq1' // the suffix to use for a qcode variable name
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $q2subqInfo;
|
|
/**
|
|
* array of advanced question attributes for each question
|
|
* Indexed by qid; available for all quetions
|
|
*
|
|
* @example [784] = array(
|
|
* 'array_filter_exclude' => 'afSrcFilter'
|
|
* 'exclude_all_others' => 'sq5'
|
|
* 'max_answers' => '3'
|
|
* 'min_answers' => '1'
|
|
* 'other_replace_text' => '{afSrcFilter_other}'
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $qattr;
|
|
/**
|
|
* list of needed sub-question relevance (e.g. array_filter)
|
|
* Indexed by qid then sgqa; only generated for current group of questions
|
|
*
|
|
* @example [708][26626X37X708sq2] = array(
|
|
* 'qid' => '708' // the question id
|
|
* 'eqn' => "((26626X34X702sq2 != ''))" // the auto-generated sub-question-level relevance equation
|
|
* 'prettyPrintEqn' => '' // only generated if there errors - shows syntax highlighting of them
|
|
* 'result' => 0 // result of processing the sub-question-level relevance equation in the current context
|
|
* 'numJsVars' => 1 // the number of on-page javascript variables in 'eqn'
|
|
* 'relevancejs' => // the generated javascript from 'eqn' -- e.g. "LEMif(LEManyNA('26626X34X702sq2'),'',(((LEMval('26626X34X702sq2') != ""))))"
|
|
* 'relevanceVars' => "java26626X34X702sq2" // the pipe-separated list of on-page javascript variables in 'eqn'
|
|
* 'rowdivid' => "26626X37X708sq2" // the javascript id of the question row (so can apply array_filter)
|
|
* 'type' => 'array_filter' // semicolon delimited list of types of subquestion relevance filters applied
|
|
* 'qtype' => 'A' // the single character question type
|
|
* 'sgqa' => "26626X37X708" // the SGQ portion of the fieldname
|
|
* 'hasErrors' => 0 // 1 if there are any parse errors in the sub-question validation equations
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $subQrelInfo=array();
|
|
/**
|
|
* array of Group-level relevance status
|
|
* Indexed by gseq; only shows groups that have been visited
|
|
*
|
|
* @example [1] = array(
|
|
* 'gseq' => 1 // group sequence
|
|
* 'eqn' => '' // the group-level relevance
|
|
* 'result' => 1 // result of processing the group-level relevance
|
|
* 'numJsVars' => 0 // the number of on-page javascript variables in the group-level relevance equation
|
|
* 'relevancejs' => '' // the javascript version of the relevance equation
|
|
* 'relevanceVars' => '' // the pipe-delimited list of on-page javascript variable names used within the group-level relevance equation
|
|
* 'prettyPrint' => '' // a pretty-print version of the group-level relevance equation, only if there are errors
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $gRelInfo=array();
|
|
|
|
/**
|
|
* Array of timing information to debug how long it takes for portions of LEM to run.
|
|
* Array of timing information (in seconds) for EM to help with debugging
|
|
*
|
|
* @example [1] = array(
|
|
* [0]="LimeExpressionManager::NavigateForwards"
|
|
* [1]=1.7079849243164
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $runtimeTimings=array();
|
|
/**
|
|
* True (1) if calling LimeExpressionManager functions between StartSurvey and FinishProcessingPage
|
|
* Used (mostly deprecated) to detect calls to LEM which happen outside of the normal processing scope
|
|
* @var Boolean
|
|
*/
|
|
private $initialized=false;
|
|
/**
|
|
* True (1) if have already processed the relevance equations (so don't need to do it again)
|
|
*
|
|
* @var Boolean
|
|
*/
|
|
private $processedRelevance=false;
|
|
/**
|
|
* Message generated to show debug timing values, if debugLevel includes LEM_DEBUG_TIMING
|
|
* @var type
|
|
*/
|
|
private $debugTimingMsg='';
|
|
/**
|
|
* temporary variable to reduce need to parse same equation multiple times. Used for relevance and validation
|
|
* Array, indexed on equation, providing the following information:
|
|
*
|
|
* @example ['!is_empty(num)'] = array(
|
|
* 'result' => 1 // result of processing the equation in the current scope
|
|
* 'prettyPrint' => '' // syntax-highlighted version of equation if there are any errors
|
|
* 'hasErrors' => 0 // 1 if there are any syntax errors
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $ParseResultCache;
|
|
/**
|
|
* array of 2nd scale answer lists for types ':' and ';' -- needed for convenient print of logic file
|
|
* Indexed on qid; available for all questions
|
|
*
|
|
* @example [706] = array(
|
|
* '1~1' => '1|Never',
|
|
* '1~2' => '2|Sometimes',
|
|
* '1~3' => '3|Always'
|
|
* );
|
|
*
|
|
* @var type
|
|
*/
|
|
private $multiflexiAnswers;
|
|
|
|
/**
|
|
* used to specify whether to generate equations using SGQA codes or qcodes
|
|
* Default is to convert all qcode naming to sgqa naming when generating javascript, as that provides the greatest backwards compatibility
|
|
* TSV export of survey structure sets this to false so as to force use of qcode naming
|
|
*
|
|
* @var Boolean
|
|
*/
|
|
private $sgqaNaming = true;
|
|
/**
|
|
* Number of groups in survey (number of possible pages to display)
|
|
* @var integer
|
|
*/
|
|
private $numGroups=0;
|
|
/**
|
|
* Numer of questions in survey (counting display-only ones?)
|
|
* @var integer
|
|
*/
|
|
private $numQuestions=0;
|
|
/**
|
|
* String identifier for the active session
|
|
* @var type
|
|
*/
|
|
private $sessid;
|
|
/**
|
|
* Linked list of array filters
|
|
* @var array
|
|
*/
|
|
private $qrootVarName2arrayFilter = array();
|
|
/**
|
|
* Array, keyed on qid, to JavaScript and list of variables needed to implement exclude_all_others_auto
|
|
* @var type
|
|
*/
|
|
private $qid2exclusiveAuto = array();
|
|
/**
|
|
* Array of values to be updated
|
|
* @var type
|
|
*/
|
|
private $updatedValues = array();
|
|
|
|
/**
|
|
* A private constructor; prevents direct creation of object
|
|
*/
|
|
private function __construct()
|
|
{
|
|
self::$instance =& $this;
|
|
$this->em = new ExpressionManager();
|
|
if (!isset($_SESSION['LEMlang'])) {
|
|
$_SESSION['LEMlang'] = 'en'; // so that there is a default
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures there is only one instances of LEM. Note, if switch between surveys, have to clear this cache
|
|
* @return LimeExpressionManager
|
|
*/
|
|
public static function &singleton()
|
|
{
|
|
$now = microtime(true);
|
|
if (isset($_SESSION['LEMdirtyFlag'])) {
|
|
$c = __CLASS__;
|
|
self::$instance = new $c;
|
|
unset($_SESSION['LEMdirtyFlag']);
|
|
}
|
|
else if (!isset(self::$instance)) {
|
|
if (isset($_SESSION['LEMsingleton'])) {
|
|
self::$instance = unserialize($_SESSION['LEMsingleton']);
|
|
}
|
|
else {
|
|
$c = __CLASS__;
|
|
self::$instance = new $c;
|
|
}
|
|
}
|
|
else {
|
|
// does exist, and OK to cache
|
|
return self::$instance;
|
|
}
|
|
// only record duration if have to create new (or unserialize) an instance
|
|
self::$instance->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Prevent users to clone the instance
|
|
*/
|
|
public function __clone()
|
|
{
|
|
trigger_error('Clone is not allowed.', E_USER_ERROR);
|
|
}
|
|
|
|
/**
|
|
* Set the previewmode
|
|
*/
|
|
public static function SetPreviewMode($previewmode=false)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->sPreviewMode=$previewmode;
|
|
//$_SESSION[$LEM->sessid]['previewmode']=$previewmode;
|
|
}
|
|
|
|
/**
|
|
* Tells Expression Manager that something has changed enough that needs to eliminate internal caching
|
|
*/
|
|
public static function SetDirtyFlag()
|
|
{
|
|
$_SESSION['LEMdirtyFlag'] = true;
|
|
$_SESSION['LEMforceRefresh'] = true;
|
|
}
|
|
|
|
/**
|
|
* Set the SurveyId - really checks whether the survey you're about to work with is new, and if so, clears the LEM cache
|
|
* @param <integer> $sid
|
|
*/
|
|
public static function SetSurveyId($sid=NULL)
|
|
{
|
|
if (!is_null($sid)) {
|
|
if (isset($_SESSION['LEMsid']) && $sid != $_SESSION['LEMsid']) {
|
|
// then trying to use a new survey - so clear the LEM cache
|
|
self::SetDirtyFlag();
|
|
}
|
|
$_SESSION['LEMsid'] = $sid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the language for Expression Manager. If the language has changed, then EM cache must be invalidated and refreshed
|
|
* @param <string> $lang
|
|
*/
|
|
public static function SetEMLanguage($lang=NULL)
|
|
{
|
|
if (is_null($lang)) {
|
|
return; // should never happen
|
|
}
|
|
if (!isset($_SESSION['LEMlang'])) {
|
|
$_SESSION['LEMlang'] = $lang;
|
|
}
|
|
if ($_SESSION['LEMlang'] != $lang) {
|
|
// then changing languages, so clear cache
|
|
self::SetDirtyFlag();
|
|
}
|
|
$_SESSION['LEMlang'] = $lang;
|
|
}
|
|
|
|
/**
|
|
* Do bulk-update/save of Condition to Relevance
|
|
* @param <integer> $surveyId - if NULL, processes the entire database, otherwise just the specified survey
|
|
* @param <integer> $qid - if specified, just updates that one question
|
|
* @return array of query strings
|
|
*/
|
|
public static function UpgradeConditionsToRelevance($surveyId=NULL, $qid=NULL)
|
|
{
|
|
LimeExpressionManager::SetDirtyFlag(); // set dirty flag even if not conditions, since must have had a DB change
|
|
// Cheat and upgrade question attributes here too.
|
|
self::UpgradeQuestionAttributes(true,$surveyId,$qid);
|
|
|
|
$releqns = self::ConvertConditionsToRelevance($surveyId,$qid);
|
|
$num = count($releqns);
|
|
if ($num == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
$queries = array();
|
|
foreach ($releqns as $key=>$value) {
|
|
$query = "UPDATE {{questions}} SET relevance=".Yii::app()->db->quoteValue($value)." WHERE qid=".$key;
|
|
dbExecuteAssoc($query);
|
|
$queries[] = $query;
|
|
}
|
|
LimeExpressionManager::SetDirtyFlag();
|
|
return $queries;
|
|
}
|
|
|
|
/**
|
|
* This reverses UpgradeConditionsToRelevance(). It removes Relevance for questions that have Condition
|
|
* @param <integer> $surveyId
|
|
* @param <integer> $qid
|
|
*/
|
|
public static function RevertUpgradeConditionsToRelevance($surveyId=NULL, $qid=NULL)
|
|
{
|
|
LimeExpressionManager::SetDirtyFlag(); // set dirty flag even if not conditions, since must have had a DB change
|
|
$releqns = self::ConvertConditionsToRelevance($surveyId,$qid);
|
|
$num = count($releqns);
|
|
if ($num == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
foreach ($releqns as $key=>$value) {
|
|
$query = "UPDATE {{questions}} SET relevance=1 WHERE qid=".$key;
|
|
dbExecuteAssoc($query);
|
|
}
|
|
return count($releqns);
|
|
}
|
|
|
|
/**
|
|
* Return array database name as key, LEM name as value
|
|
* @example (['gender'] => '38612X10X145')
|
|
* @param <integer> $surveyId
|
|
**/
|
|
public static function getLEMqcode2sgqa($iSurveyId){
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$LEM->SetEMLanguage(Survey::model()->findByPk($iSurveyId)->language);
|
|
$LEM->SetSurveyId($iSurveyId);
|
|
$LEM->StartProcessingPage(true,true);
|
|
return $LEM->qcode2sgqa;
|
|
}
|
|
|
|
/**
|
|
* If $qid is set, returns the relevance equation generated from conditions (or NULL if there are no conditions for that $qid)
|
|
* If $qid is NULL, returns an array of relevance equations generated from Condition, keyed on the question ID
|
|
* @param <integer> $surveyId
|
|
* @param <integer> $qid - if passed, only generates relevance equation for that question - otherwise genereates for all questions with conditions
|
|
* @return array of generated relevance strings, indexed by $qid
|
|
*/
|
|
public static function ConvertConditionsToRelevance($surveyId=NULL, $qid=NULL)
|
|
{
|
|
$query = LimeExpressionManager::getConditionsForEM($surveyId,$qid);
|
|
|
|
$_qid = -1;
|
|
$relevanceEqns = array();
|
|
$scenarios = array();
|
|
$relAndList = array();
|
|
$relOrList = array();
|
|
foreach($query->readAll() as $row)
|
|
{
|
|
$row['method']=trim($row['method']); //For Postgres
|
|
if ($row['qid'] != $_qid)
|
|
{
|
|
// output the values for prior question is there was one
|
|
if ($_qid != -1)
|
|
{
|
|
if (count($relOrList) > 0)
|
|
{
|
|
$relAndList[] = '(' . implode(' or ', $relOrList) . ')';
|
|
}
|
|
if (count($relAndList) > 0)
|
|
{
|
|
$scenarios[] = '(' . implode(' and ', $relAndList) . ')';
|
|
}
|
|
$relevanceEqn = implode(' or ', $scenarios);
|
|
$relevanceEqns[$_qid] = $relevanceEqn;
|
|
}
|
|
|
|
// clear for next question
|
|
$_qid = $row['qid'];
|
|
$_scenario = $row['scenario'];
|
|
$_cqid = $row['cqid'];
|
|
$_subqid = -1;
|
|
$relAndList = array();
|
|
$relOrList = array();
|
|
$scenarios = array();
|
|
$releqn = '';
|
|
}
|
|
if ($row['scenario'] != $_scenario)
|
|
{
|
|
if (count($relOrList) > 0)
|
|
{
|
|
$relAndList[] = '(' . implode(' or ', $relOrList) . ')';
|
|
}
|
|
$scenarios[] = '(' . implode(' and ', $relAndList) . ')';
|
|
$relAndList = array();
|
|
$relOrList = array();
|
|
$_scenario = $row['scenario'];
|
|
$_cqid = $row['cqid'];
|
|
$_subqid = -1;
|
|
}
|
|
if ($row['cqid'] != $_cqid)
|
|
{
|
|
$relAndList[] = '(' . implode(' or ', $relOrList) . ')';
|
|
$relOrList = array();
|
|
$_cqid = $row['cqid'];
|
|
$_subqid = -1;
|
|
}
|
|
|
|
// fix fieldnames
|
|
if ($row['type'] == '' && preg_match('/^{.+}$/',$row['cfieldname'])) {
|
|
$fieldname = substr($row['cfieldname'],1,-1); // {TOKEN:xxxx}
|
|
$subqid = $fieldname;
|
|
$value = $row['value'];
|
|
}
|
|
else if ($row['type'] == 'M' || $row['type'] == 'P') {
|
|
if (substr($row['cfieldname'],0,1) == '+') {
|
|
// if prefixed with +, then a fully resolved name
|
|
$fieldname = substr($row['cfieldname'],1) . '.NAOK';
|
|
$subqid = $fieldname;
|
|
$value = $row['value'];
|
|
}
|
|
else {
|
|
// else create name by concatenating two parts together
|
|
$fieldname = $row['cfieldname'] . $row['value'] . '.NAOK';
|
|
$subqid = $row['cfieldname'];
|
|
$value = 'Y';
|
|
}
|
|
}
|
|
else {
|
|
$fieldname = $row['cfieldname'] . '.NAOK';
|
|
$subqid = $fieldname;
|
|
$value = $row['value'];
|
|
}
|
|
if ($_subqid != -1 && $_subqid != $subqid)
|
|
{
|
|
$relAndList[] = '(' . implode(' or ', $relOrList) . ')';
|
|
$relOrList = array();
|
|
}
|
|
$_subqid = $subqid;
|
|
|
|
// fix values
|
|
if (preg_match('/^@\d+X\d+X\d+.*@$/',$value)) {
|
|
$value = substr($value,1,-1);
|
|
}
|
|
else if (preg_match('/^{.+}$/',$value)) {
|
|
$value = substr($value,1,-1);
|
|
}
|
|
else if ($row['method'] == 'RX') {
|
|
if (!preg_match('#^/.*/$#',$value))
|
|
{
|
|
$value = '"/' . $value . '/"'; // if not surrounded by slashes, add them.
|
|
}
|
|
}
|
|
else {
|
|
$value = '"' . $value . '"';
|
|
}
|
|
|
|
// add equation
|
|
if ($row['method'] == 'RX')
|
|
{
|
|
$relOrList[] = "regexMatch(" . $value . "," . $fieldname . ")";
|
|
}
|
|
else
|
|
{
|
|
// Condition uses ' ' to mean not answered, but internally it is really stored as ''. Fix this
|
|
if ($value === '" "' || $value == '""') {
|
|
if ($row['method'] == '==')
|
|
{
|
|
$relOrList[] = "is_empty(" . $fieldname . ")";
|
|
}
|
|
else if ($row['method'] == '!=')
|
|
{
|
|
$relOrList[] = "!is_empty(" . $fieldname . ")";
|
|
}
|
|
else
|
|
{
|
|
$relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($value == '"0"' || !preg_match('/^".+"$/',$value))
|
|
{
|
|
switch ($row['method'])
|
|
{
|
|
case '==':
|
|
case '<':
|
|
case '<=':
|
|
case '>=':
|
|
$relOrList[] = '(!is_empty(' . $fieldname . ') && (' . $fieldname . " " . $row['method'] . " " . $value . '))';
|
|
break;
|
|
case '!=':
|
|
$relOrList[] = '(is_empty(' . $fieldname . ') || (' . $fieldname . " != " . $value . '))';
|
|
break;
|
|
default:
|
|
$relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ($row['method'])
|
|
{
|
|
case '<':
|
|
case '<=':
|
|
$relOrList[] = '(!is_empty(' . $fieldname . ') && (' . $fieldname . " " . $row['method'] . " " . $value . '))';
|
|
break;
|
|
default:
|
|
$relOrList[] = $fieldname . " " . $row['method'] . " " . $value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($row['cqid'] == 0 || substr($row['cfieldname'],0,1) == '+') {
|
|
$_cqid = -1; // forces this statement to be ANDed instead of being part of a cqid OR group
|
|
}
|
|
}
|
|
// output last one
|
|
if ($_qid != -1)
|
|
{
|
|
if (count($relOrList) > 0)
|
|
{
|
|
$relAndList[] = '(' . implode(' or ', $relOrList) . ')';
|
|
}
|
|
if (count($relAndList) > 0)
|
|
{
|
|
$scenarios[] = '(' . implode(' and ', $relAndList) . ')';
|
|
}
|
|
$relevanceEqn = implode(' or ', $scenarios);
|
|
$relevanceEqns[$_qid] = $relevanceEqn;
|
|
}
|
|
if (is_null($qid)) {
|
|
return $relevanceEqns;
|
|
}
|
|
else {
|
|
if (isset($relevanceEqns[$qid]))
|
|
{
|
|
$result = array();
|
|
$result[$qid] = $relevanceEqns[$qid];
|
|
return $result;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return list of relevance equations generated from conditions
|
|
* @param <integer> $surveyId
|
|
* @param <integer> $qid
|
|
* @return array of relevance equations, indexed by $qid
|
|
*/
|
|
public static function UnitTestConvertConditionsToRelevance($surveyId=NULL, $qid=NULL)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->ConvertConditionsToRelevance($surveyId, $qid);
|
|
}
|
|
|
|
/**
|
|
* Process all question attributes that apply to EM
|
|
* (1) Sub-question-level relevance: e.g. array_filter, array_filter_exclude
|
|
* (2) Validations: e.g. min/max number of answers; min/max/eq sum of answers
|
|
* @param <integer> $onlyThisQseq - only process these attributes for the specified question
|
|
*/
|
|
public function _CreateSubQLevelRelevanceAndValidationEqns($onlyThisQseq=NULL)
|
|
{
|
|
// $now = microtime(true);
|
|
$this->subQrelInfo=array(); // reset it each time this is called
|
|
$subQrels = array(); // array of sub-question-level relevance equations
|
|
$validationEqn = array();
|
|
$validationTips = array(); // array of visible tips for validation criteria, indexed by $qid
|
|
|
|
// Associate these with $qid so that can be nested under appropriate question-level relevance
|
|
foreach ($this->q2subqInfo as $qinfo)
|
|
{
|
|
if (!is_null($onlyThisQseq) && $onlyThisQseq != $qinfo['qseq']) {
|
|
continue;
|
|
}
|
|
else if (!$this->allOnOnePage && $this->currentGroupSeq != $qinfo['gseq']) {
|
|
continue; // only need subq relevance for current page.
|
|
}
|
|
$questionNum = $qinfo['qid'];
|
|
$type = $qinfo['type'];
|
|
$hasSubqs = (isset($qinfo['subqs']) && count($qinfo['subqs'] > 0));
|
|
$qattr = isset($this->qattr[$questionNum]) ? $this->qattr[$questionNum] : array();
|
|
if (isset($qattr['input_boxes']) && $qattr['input_boxes'] == '1')
|
|
{
|
|
$input_boxes='1';
|
|
}
|
|
else
|
|
{
|
|
$input_boxes='';
|
|
}
|
|
|
|
if (isset($qattr['value_range_allows_missing']) && $qattr['value_range_allows_missing'] == '1')
|
|
{
|
|
$value_range_allows_missing = true;
|
|
}
|
|
else
|
|
{
|
|
$value_range_allows_missing = false;
|
|
}
|
|
|
|
// array_filter
|
|
// If want to filter question Q2 on Q1, where each have subquestions SQ1-SQ3, this is equivalent to relevance equations of:
|
|
// relevance for Q2_SQ1 is Q1_SQ1!=''
|
|
$array_filter = NULL;
|
|
if (isset($qattr['array_filter']) && trim($qattr['array_filter']) != '')
|
|
{
|
|
$array_filter = $qattr['array_filter'];
|
|
$this->qrootVarName2arrayFilter[$qinfo['rootVarName']]['array_filter'] = $array_filter;
|
|
}
|
|
|
|
// array_filter_exclude
|
|
// If want to filter question Q2 on Q1, where each have subquestions SQ1-SQ3, this is equivalent to relevance equations of:
|
|
// relevance for Q2_SQ1 is Q1_SQ1==''
|
|
$array_filter_exclude = NULL;
|
|
if (isset($qattr['array_filter_exclude']) && trim($qattr['array_filter_exclude']) != '')
|
|
{
|
|
$array_filter_exclude = $qattr['array_filter_exclude'];
|
|
$this->qrootVarName2arrayFilter[$qinfo['rootVarName']]['array_filter_exclude'] = $array_filter_exclude;
|
|
}
|
|
|
|
// array_filter and array_filter_exclude get processed together
|
|
if (!is_null($array_filter) || !is_null($array_filter_exclude))
|
|
{
|
|
if ($hasSubqs) {
|
|
$cascadedAF = array();
|
|
$cascadedAFE = array();
|
|
|
|
list($cascadedAF, $cascadedAFE) = $this->_recursivelyFindAntecdentArrayFilters($qinfo['rootVarName'],array(),array());
|
|
|
|
$cascadedAF = array_reverse($cascadedAF);
|
|
$cascadedAFE = array_reverse($cascadedAFE);
|
|
|
|
$subqs = $qinfo['subqs'];
|
|
if ($type == 'R') {
|
|
$subqs = array();
|
|
foreach ($this->qans[$qinfo['qid']] as $k=>$v)
|
|
{
|
|
$_code = explode('~',$k);
|
|
$subqs[] = array(
|
|
'rowdivid'=>$qinfo['sgqa'] . $_code[1],
|
|
'sqsuffix'=>'_' . $_code[1],
|
|
);
|
|
}
|
|
}
|
|
$last_rowdivid = '--';
|
|
foreach ($subqs as $sq) {
|
|
if ($sq['rowdivid'] == $last_rowdivid)
|
|
{
|
|
continue;
|
|
}
|
|
$last_rowdivid = $sq['rowdivid'];
|
|
$af_names = array();
|
|
$afe_names = array();
|
|
switch ($type)
|
|
{
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case 'R': //Ranking
|
|
// if ($this->sgqaNaming)
|
|
// {
|
|
foreach ($cascadedAF as $_caf)
|
|
{
|
|
$sgq = ((isset($this->qcode2sgq[$_caf])) ? $this->qcode2sgq[$_caf] : $_caf);
|
|
$fqid = explode('X',$sgq);
|
|
if (!isset($fqid[2]))
|
|
{
|
|
continue;
|
|
}
|
|
$fqid = $fqid[2];
|
|
if ($this->q2subqInfo[$fqid]['type'] == 'R')
|
|
{
|
|
$rankables = array();
|
|
foreach ($this->qans[$fqid] as $k=>$v)
|
|
{
|
|
$rankable = explode('~',$k);
|
|
$rankables[] = '_' . $rankable[1];
|
|
}
|
|
if (array_search($sq['sqsuffix'],$rankables) === false)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
$fsqs = array();
|
|
foreach ($this->q2subqInfo[$fqid]['subqs'] as $fsq)
|
|
{
|
|
if (!isset($fsq['csuffix'])) $fsq['csuffix']='';
|
|
if ($this->q2subqInfo[$fqid]['type'] == 'R')
|
|
{
|
|
// we know the suffix exists
|
|
$fsqs[] = '(' . $sgq . $fsq['csuffix'] . ".NAOK == '" . substr($sq['sqsuffix'],1) . "')";
|
|
}
|
|
else if ($this->q2subqInfo[$fqid]['type'] == ':' && isset($this->qattr[$fqid]['multiflexible_checkbox']) && $this->qattr[$fqid]['multiflexible_checkbox']=='1')
|
|
{
|
|
if ($fsq['sqsuffix'] == $sq['sqsuffix'])
|
|
{
|
|
$fsqs[] = $sgq . $fsq['csuffix'] . '.NAOK=="1"';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($fsq['sqsuffix'] == $sq['sqsuffix'])
|
|
{
|
|
$fsqs[] = '!is_empty(' . $sgq . $fsq['csuffix'] . '.NAOK)';
|
|
}
|
|
}
|
|
}
|
|
if (count($fsqs) > 0)
|
|
{
|
|
$af_names[] = '(' . implode(' or ', $fsqs) . ')';
|
|
}
|
|
}
|
|
foreach ($cascadedAFE as $_cafe)
|
|
{
|
|
$sgq = ((isset($this->qcode2sgq[$_cafe])) ? $this->qcode2sgq[$_cafe] : $_cafe);
|
|
$fqid = explode('X',$sgq);
|
|
if (!isset($fqid[2]))
|
|
{
|
|
continue;
|
|
}
|
|
$fqid = $fqid[2];
|
|
if ($this->q2subqInfo[$fqid]['type'] == 'R')
|
|
{
|
|
$rankables = array();
|
|
foreach ($this->qans[$fqid] as $k=>$v)
|
|
{
|
|
$rankable = explode('~',$k);
|
|
$rankables[] = '_' . $rankable[1];
|
|
}
|
|
if (array_search($sq['sqsuffix'],$rankables) === false)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
$fsqs = array();
|
|
foreach ($this->q2subqInfo[$fqid]['subqs'] as $fsq)
|
|
{
|
|
if ($this->q2subqInfo[$fqid]['type'] == 'R')
|
|
{
|
|
// we know the suffix exists
|
|
$fsqs[] = '(' . $sgq . $fsq['csuffix'] . ".NAOK != '" . substr($sq['sqsuffix'],1) . "')";
|
|
}
|
|
else if ($this->q2subqInfo[$fqid]['type'] == ':' && isset($this->qattr[$fqid]['multiflexible_checkbox']) && $this->qattr[$fqid]['multiflexible_checkbox']=='1')
|
|
{
|
|
if ($fsq['sqsuffix'] == $sq['sqsuffix'])
|
|
{
|
|
$fsqs[] = $sgq . $fsq['csuffix'] . '.NAOK!="1"';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($fsq['sqsuffix'] == $sq['sqsuffix'])
|
|
{
|
|
$fsqs[] = 'is_empty(' . $sgq . $fsq['csuffix'] . '.NAOK)';
|
|
}
|
|
}
|
|
}
|
|
if (count($fsqs) > 0)
|
|
{
|
|
$afe_names[] = '(' . implode(' and ', $fsqs) . ')';
|
|
}
|
|
}
|
|
// }
|
|
// else // TODO - implement qcode naming for this
|
|
// {
|
|
// foreach ($cascadedAF as $_caf)
|
|
// {
|
|
// $sgq = $_caf . $sq['sqsuffix'];
|
|
// if (isset($this->knownVars[$sgq]))
|
|
// {
|
|
// $af_names[] = $sgq . '.NAOK';
|
|
// }
|
|
// }
|
|
// foreach ($cascadedAFE as $_cafe)
|
|
// {
|
|
// $sgq = $_cafe . $sq['sqsuffix'];
|
|
// if (isset($this->knownVars[$sgq]))
|
|
// {
|
|
// $afe_names[] = $sgq . '.NAOK';
|
|
// }
|
|
// }
|
|
// }
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
$af_names = array_unique($af_names);
|
|
$afe_names= array_unique($afe_names);
|
|
|
|
if (count($af_names) > 0 || count($afe_names) > 0) {
|
|
$afs_eqn = '';
|
|
if (count($af_names) > 0)
|
|
{
|
|
$afs_eqn .= implode(' && ', $af_names);
|
|
}
|
|
if (count($afe_names) > 0)
|
|
{
|
|
if ($afs_eqn != '')
|
|
{
|
|
$afs_eqn .= ' && ';
|
|
}
|
|
$afs_eqn .= implode(' && ', $afe_names);
|
|
}
|
|
|
|
$subQrels[] = array(
|
|
'qtype' => $type,
|
|
'type' => 'array_filter',
|
|
'rowdivid' => $sq['rowdivid'],
|
|
'eqn' => '(' . $afs_eqn . ')',
|
|
'qid' => $questionNum,
|
|
'sgqa' => $qinfo['sgqa'],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// code_filter: WZ
|
|
// This can be skipped, since question types 'W' (list-dropdown-flexible) and 'Z'(list-radio-flexible) are no longer supported
|
|
|
|
// Default validation for question type
|
|
switch ($type)
|
|
{
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_equs[] = 'is_numeric('.$sq_name.')';
|
|
}else{
|
|
$sq_equs[] = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';
|
|
}
|
|
if($type=="K")
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
else
|
|
$subqValidSelector = "";
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'default',
|
|
'class' => 'default',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
break;
|
|
case 'K': //MULTI NUMERICAL QUESTION TYPE
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
$subqValidEqns = array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
$sq_equ = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';// Leave mandatory to mandatory attribute (see #08665)
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
if (!is_null($sq_name)) {
|
|
$sq_equs[] = $sq_equ;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_equ,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'default',
|
|
'class' => 'default',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
break;
|
|
case 'R':
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names=array();
|
|
foreach($subqs as $subq)
|
|
{
|
|
$sq_names[] = $subq['varName'].".NAOK";
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'default',
|
|
'class' => 'default',
|
|
'eqn' => 'unique(' . implode(',',$sq_names) . ')',
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
break;
|
|
case 'D': // dropdown box: validate that a complete date is entered
|
|
// TODO: generic validation as to dateformat[SGQA].value
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_equs[] = '('.$sq_name.'!="INVALID")';
|
|
}else{
|
|
$sq_equs[] = '('.$sq_name.'!="INVALID")';
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'default',
|
|
'class' => 'default',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// commented_checkbox : only for checkbox with comment ("P")
|
|
$commented_checkbox='';
|
|
if (isset($qattr['commented_checkbox']) && trim($qattr['commented_checkbox']) != '')
|
|
{
|
|
switch ($type)
|
|
{
|
|
case 'P':
|
|
if ($hasSubqs) {
|
|
$commented_checkbox=$qattr['commented_checkbox'];
|
|
$subqs = $qinfo['subqs'];
|
|
switch ($commented_checkbox)
|
|
{
|
|
case 'checked':
|
|
$sq_eqn_commented_checkbox=array();
|
|
foreach($subqs as $subq)
|
|
{
|
|
$sq_eqn_commented_checkbox[] = "(is_empty({$subq['varName']}.NAOK) and !is_empty({$subq['varName']}comment.NAOK))";
|
|
}
|
|
$eqn="sum(".implode(",",$sq_eqn_commented_checkbox).")==0";
|
|
break;
|
|
case 'unchecked':
|
|
$sq_eqn_commented_checkbox=array();
|
|
foreach($subqs as $subq)
|
|
{
|
|
$sq_eqn_commented_checkbox[] = "(!is_empty({$subq['varName']}.NAOK) and !is_empty({$subq['varName']}comment.NAOK))";
|
|
}
|
|
$eqn="sum(".implode(",",$sq_eqn_commented_checkbox).")==0";
|
|
break;
|
|
case 'allways':
|
|
default:
|
|
break;
|
|
}
|
|
if($commented_checkbox!="allways")
|
|
{
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'commented_checkbox',
|
|
'class' => 'commented_checkbox',
|
|
'eqn' => $eqn,
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// date_min
|
|
// Maximum date allowed in date question
|
|
if (isset($qattr['date_min']) && trim($qattr['date_min']) != '')
|
|
{
|
|
$date_min = $qattr['date_min'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'D': //DATE QUESTION TYPE
|
|
// date_min: Determine whether we have an expression, a full date (YYYY-MM-DD) or only a year(YYYY)
|
|
if (trim($qattr['date_min'])!='')
|
|
{
|
|
$mindate=$qattr['date_min'];
|
|
if ((strlen($mindate)==4) && ($mindate>=1900) && ($mindate<=2099))
|
|
{
|
|
// backward compatibility: if only a year is given, add month and day
|
|
$date_min='\''.$mindate.'-01-01'.' 00:00\'';
|
|
}
|
|
elseif (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/",$mindate))
|
|
{
|
|
$date_min='\''.$mindate.' 00:00\'';
|
|
}
|
|
elseif (array_key_exists($date_min, $this->qcode2sgqa)) // refers to another question
|
|
{
|
|
$date_min=$date_min.'.NAOK';
|
|
}
|
|
}
|
|
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '('. $sq_name . ' >= ' . $date_min . ')';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq_name . ') || ('. $sq_name . ' >= ' . $date_min . '))';
|
|
}
|
|
$subqValidSelector = '';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'date_min',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$date_min='';
|
|
}
|
|
|
|
// date_max
|
|
// Maximum date allowed in date question
|
|
if (isset($qattr['date_max']) && trim($qattr['date_max']) != '')
|
|
{
|
|
$date_max = $qattr['date_max'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'D': //DATE QUESTION TYPE
|
|
// date_max: Determine whether we have an expression, a full date (YYYY-MM-DD) or only a year(YYYY)
|
|
if (trim($qattr['date_max'])!='')
|
|
{
|
|
$maxdate=$qattr['date_max'];
|
|
if ((strlen($maxdate)==4) && ($maxdate>=1900) && ($maxdate<=2099))
|
|
{
|
|
// backward compatibility: if only a year is given, add month and day
|
|
$date_max='\''.$maxdate.'-12-31 23:59'.'\'';
|
|
}
|
|
elseif (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/",$maxdate))
|
|
{
|
|
$date_max='\''.$maxdate.' 23:59\'';
|
|
}
|
|
elseif (array_key_exists($date_max, $this->qcode2sgqa)) // refers to another question
|
|
{
|
|
$date_max=$date_max.'.NAOK';
|
|
}
|
|
}
|
|
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '(is_empty(' . $date_max . ') || ('. $sq_name . ' <= ' . $date_max . '))';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq_name . ') || is_empty(' . $date_max . ') || ('. $sq_name . ' <= ' . $date_max . '))';
|
|
}
|
|
$subqValidSelector = '';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'date_max',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$date_max='';
|
|
}
|
|
|
|
// equals_num_value
|
|
// Validation:= sum(sq1,...,sqN) == value (which could be an expression).
|
|
if (isset($qattr['equals_num_value']) && trim($qattr['equals_num_value']) != '')
|
|
{
|
|
$equals_num_value = $qattr['equals_num_value'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
// sumEqn and sumRemainingEqn may need to be rounded if using sliders
|
|
$precision=LEM_DEFAULT_PRECISION; // default is not to round
|
|
if (isset($qattr['slider_layout']) && $qattr['slider_layout']=='1')
|
|
{
|
|
$precision=0; // default is to round to whole numbers
|
|
if (isset($qattr['slider_accuracy']) && trim($qattr['slider_accuracy'])!='')
|
|
{
|
|
$slider_accuracy = $qattr['slider_accuracy'];
|
|
$_parts = explode('.',$slider_accuracy);
|
|
if (isset($_parts[1]))
|
|
{
|
|
$precision = strlen($_parts[1]); // number of digits after mantissa
|
|
}
|
|
}
|
|
}
|
|
$sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
|
|
$sumRemainingEqn = '(' . $equals_num_value . ' - sum(' . implode(', ', $sq_names) . '))';
|
|
$mainEqn = 'sum(' . implode(', ', $sq_names) . ')';
|
|
|
|
if (!is_null($precision))
|
|
{
|
|
$sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
|
|
$sumRemainingEqn = 'round(' . $sumRemainingEqn . ', ' . $precision . ')';
|
|
$mainEqn = 'round(' . $mainEqn . ', ' . $precision . ')';
|
|
}
|
|
|
|
$noanswer_option = '';
|
|
if ($value_range_allows_missing)
|
|
{
|
|
$noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
|
|
}
|
|
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'equals_num_value',
|
|
'class' => 'sum_range',
|
|
'eqn' => ($qinfo['mandatory']=='Y')?'(' . $mainEqn . ' == (' . $equals_num_value . '))':'(' . $mainEqn . ' == (' . $equals_num_value . ')' . $noanswer_option . ')',
|
|
'qid' => $questionNum,
|
|
'sumEqn' => $sumEqn,
|
|
'sumRemainingEqn' => $sumRemainingEqn,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$equals_num_value='';
|
|
}
|
|
|
|
// exclude_all_others
|
|
// If any excluded options are true (and relevant), then disable all other input elements for that question
|
|
if (isset($qattr['exclude_all_others']) && trim($qattr['exclude_all_others']) != '')
|
|
{
|
|
$exclusive_options = explode(';',$qattr['exclude_all_others']);
|
|
if ($hasSubqs) {
|
|
foreach ($exclusive_options as $exclusive_option)
|
|
{
|
|
$exclusive_option = trim($exclusive_option);
|
|
if ($exclusive_option == '') {
|
|
continue;
|
|
}
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
if ($sq['csuffix'] == $exclusive_option)
|
|
{
|
|
continue; // so don't make the excluded option irrelevant
|
|
}
|
|
switch ($type)
|
|
{
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $qinfo['sgqa'] . trim($exclusive_option) . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $qinfo['sgqa'] . trim($exclusive_option) . '.NAOK';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$subQrels[] = array(
|
|
'qtype' => $type,
|
|
'type' => 'exclude_all_others',
|
|
'rowdivid' => $sq['rowdivid'],
|
|
'eqn' => 'is_empty(' . $sq_name . ')',
|
|
'qid' => $questionNum,
|
|
'sgqa' => $qinfo['sgqa'],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$exclusive_option = '';
|
|
}
|
|
|
|
// exclude_all_others_auto
|
|
// if (count(this.relevanceStatus) == count(this)) { set exclusive option value to "Y" and call checkconditions() }
|
|
// 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
|
|
if (isset($qattr['exclude_all_others_auto']) && trim($qattr['exclude_all_others_auto']) == '1'
|
|
&& isset($qattr['exclude_all_others']) && trim($qattr['exclude_all_others']) != '' && count(explode(';',trim($qattr['exclude_all_others']))) == 1)
|
|
{
|
|
$exclusive_option = trim($qattr['exclude_all_others']);
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = substr($sq['jsVarName'],4);
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'];
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name))
|
|
{
|
|
if ($sq['csuffix'] == $exclusive_option)
|
|
{
|
|
$eoVarName = substr($sq['jsVarName'],4);
|
|
}
|
|
else
|
|
{
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
$relpart = "sum(" . implode(".relevanceStatus, ", $sq_names) . ".relevanceStatus)";
|
|
$checkedpart = "count(" . implode(".NAOK, ", $sq_names) . ".NAOK)";
|
|
$eoRelevantAndUnchecked = "(" . $eoVarName . ".relevanceStatus && is_empty(" . $eoVarName . "))";
|
|
$eoEqn = "(" . $eoRelevantAndUnchecked . " && (" . $relpart . " == " . $checkedpart . "))";
|
|
|
|
$this->em->ProcessBooleanExpression($eoEqn, $qinfo['gseq'], $qinfo['qseq']);
|
|
|
|
$relevanceVars = implode('|',$this->em->GetJSVarsUsed());
|
|
$relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
|
|
// Unset all checkboxes and hidden values for this question (irregardless of whether they are array filtered)
|
|
$eosaJS = "if (" . $relevanceJS . ") {\n";
|
|
$eosaJS .=" $('#question" . $questionNum . " [type=checkbox]').attr('checked',false);\n";
|
|
$eosaJS .=" $('#java" . $qinfo['sgqa'] . "other').val('');\n";
|
|
$eosaJS .=" $('#answer" . $qinfo['sgqa'] . "other').val('');\n";
|
|
$eosaJS .=" $('[id^=java" . $qinfo['sgqa'] . "]').val('');\n";
|
|
$eosaJS .=" $('#answer" . $eoVarName . "').attr('checked',true);\n";
|
|
$eosaJS .=" $('#java" . $eoVarName . "').val('Y');\n";
|
|
$eosaJS .=" LEMrel" . $questionNum . "();\n";
|
|
$eosaJS .=" relChange" . $questionNum ."=true;\n";
|
|
$eosaJS .="}\n";
|
|
|
|
$this->qid2exclusiveAuto[$questionNum] = array(
|
|
'js'=>$eosaJS,
|
|
'relevanceVars'=>$relevanceVars, // so that EM knows which variables to declare
|
|
'rowdivid'=>$eoVarName, // to ensure that EM creates a hidden relevanceSGQA input for the exclusive option
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// input_boxes
|
|
if (isset($qattr['input_boxes']) && $qattr['input_boxes'] == 1) {
|
|
$input_boxes=1;
|
|
switch($type)
|
|
{
|
|
case ':': //Array Numbers
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
$subqValidEqns = array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?substr($sq['jsVarName'],4).".NAOK":$sq['varName'].".NAOK";
|
|
$sq_equ = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';// Leave mandatory to mandatory attribute (see #08665)
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
if (!is_null($sq_name)) {
|
|
$sq_equs[] = $sq_equ;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_equ,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'input_boxes',
|
|
'class' => 'input_boxes',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}else{
|
|
$input_boxes="";
|
|
}
|
|
|
|
// min_answers
|
|
// Validation:= count(sq1,...,sqN) >= value (which could be an expression).
|
|
if (isset($qattr['min_answers']) && trim($qattr['min_answers']) != '' && trim($qattr['min_answers']) != '0')
|
|
{
|
|
$min_answers = $qattr['min_answers'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
if (substr($sq['varName'],-1,1) == '0')
|
|
{
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$base = $sq['rowdivid']."#";
|
|
$sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
|
|
}
|
|
else
|
|
{
|
|
$base = substr($sq['varName'],0,-1);
|
|
$sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
|
|
}
|
|
}
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case 'M': //Multiple choice checkbox
|
|
case 'R': //RANKING STYLE
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = substr($sq['jsVarName'],4) . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
break;
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if (!preg_match('/comment$/',$sq['varName'])) {
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'min_answers',
|
|
'class' => 'num_answers',
|
|
'eqn' => 'if(is_empty('.$min_answers.'),1,(count(' . implode(', ', $sq_names) . ') >= (' . $min_answers . ')))',
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$min_answers='';
|
|
}
|
|
|
|
// max_answers
|
|
// Validation:= count(sq1,...,sqN) <= value (which could be an expression).
|
|
if (isset($qattr['max_answers']) && trim($qattr['max_answers']) != '')
|
|
{
|
|
$max_answers = $qattr['max_answers'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
if (substr($sq['varName'],-1,1) == '0')
|
|
{
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$base = $sq['rowdivid']."#";
|
|
$sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
|
|
}
|
|
else
|
|
{
|
|
$base = substr($sq['varName'],0,-1);
|
|
$sq_name = "if(count(" . $base . "0.NAOK," . $base . "1.NAOK)==2,1,'')";
|
|
}
|
|
}
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case 'M': //Multiple choice checkbox
|
|
case 'R': //RANKING STYLE
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = substr($sq['jsVarName'],4) . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
break;
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if (!preg_match('/comment$/',$sq['varName'])) {
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'max_answers',
|
|
'class' => 'num_answers',
|
|
'eqn' => '(if(is_empty('.$max_answers.'),1,count(' . implode(', ', $sq_names) . ') <= (' . $max_answers . ')))',
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$max_answers='';
|
|
}
|
|
|
|
// Fix min_num_value_n and max_num_value_n for multinumeric with slider: see bug #7798
|
|
if($type=="K" && isset($qattr['slider_min']) && ( !isset($qattr['min_num_value_n']) || trim($qattr['min_num_value_n'])==''))
|
|
$qattr['min_num_value_n']=$qattr['slider_min'];
|
|
// min_num_value_n
|
|
// Validation:= N >= value (which could be an expression).
|
|
if (isset($qattr['min_num_value_n']) && trim($qattr['min_num_value_n']) != '')
|
|
{
|
|
$min_num_value_n = $qattr['min_num_value_n'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '('. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '('. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
if ($this->sgqaNaming)
|
|
{
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '('. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_name = '('. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}else{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK >= (' . $min_num_value_n . '))';
|
|
}
|
|
}
|
|
$subqValidSelector = '';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'min_num_value_n',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$min_num_value_n='';
|
|
}
|
|
|
|
// Fix min_num_value_n and max_num_value_n for multinumeric with slider: see bug #7798
|
|
if($type=="K" && isset($qattr['slider_max']) && ( !isset($qattr['max_num_value_n']) || trim($qattr['max_num_value_n'])==''))
|
|
$qattr['max_num_value_n']=$qattr['slider_max'];
|
|
// max_num_value_n
|
|
// Validation:= N <= value (which could be an expression).
|
|
if (isset($qattr['max_num_value_n']) && trim($qattr['max_num_value_n']) != '')
|
|
{
|
|
$max_num_value_n = $qattr['max_num_value_n'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK <= (' . $max_num_value_n . '))';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK <= (' . $max_num_value_n . '))';
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['rowdivid'] . '.NAOK) || '. $sq['rowdivid'] . '.NAOK <= (' . $max_num_value_n . '))';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || '. $sq['varName'] . '.NAOK <= (' . $max_num_value_n . '))';
|
|
}
|
|
$subqValidSelector = '';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'max_num_value_n',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$max_num_value_n='';
|
|
}
|
|
|
|
// min_num_value
|
|
// Validation:= sum(sq1,...,sqN) >= value (which could be an expression).
|
|
if (isset($qattr['min_num_value']) && trim($qattr['min_num_value']) != '')
|
|
{
|
|
$min_num_value = $qattr['min_num_value'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
|
|
$sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
|
|
$precision = LEM_DEFAULT_PRECISION;
|
|
if (!is_null($precision))
|
|
{
|
|
$sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
|
|
}
|
|
|
|
$noanswer_option = '';
|
|
if ($value_range_allows_missing)
|
|
{
|
|
$noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
|
|
}
|
|
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'min_num_value',
|
|
'class' => 'sum_range',
|
|
'eqn' => '(sum(' . implode(', ', $sq_names) . ') >= (' . $min_num_value . ')' . $noanswer_option . ')',
|
|
'qid' => $questionNum,
|
|
'sumEqn' => $sumEqn,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$min_num_value='';
|
|
}
|
|
|
|
// max_num_value
|
|
// Validation:= sum(sq1,...,sqN) <= value (which could be an expression).
|
|
if (isset($qattr['max_num_value']) && trim($qattr['max_num_value']) != '')
|
|
{
|
|
$max_num_value = $qattr['max_num_value'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = $sq['rowdivid'] . '.NAOK';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = $sq['varName'] . '.NAOK';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
|
|
$sumEqn = 'sum(' . implode(', ', $sq_names) . ')';
|
|
$precision = LEM_DEFAULT_PRECISION;
|
|
if (!is_null($precision))
|
|
{
|
|
$sumEqn = 'round(' . $sumEqn . ', ' . $precision . ')';
|
|
}
|
|
|
|
$noanswer_option = '';
|
|
if ($value_range_allows_missing)
|
|
{
|
|
$noanswer_option = ' || count(' . implode(', ', $sq_names) . ') == 0';
|
|
}
|
|
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'max_num_value',
|
|
'class' => 'sum_range',
|
|
'eqn' => '(sum(' . implode(', ', $sq_names) . ') <= (' . $max_num_value . ')' . $noanswer_option . ')',
|
|
'qid' => $questionNum,
|
|
'sumEqn' => $sumEqn,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$max_num_value='';
|
|
}
|
|
|
|
// multiflexible_min
|
|
// Validation:= sqN >= value (which could be an expression).
|
|
if (isset($qattr['multiflexible_min']) && trim($qattr['multiflexible_min']) != '' && $input_boxes=='1')
|
|
{
|
|
$multiflexible_min = $qattr['multiflexible_min'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case ':': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sgqa = substr($sq['jsVarName'],4);
|
|
$sq_name = '(is_empty(' . $sgqa . '.NAOK) || ' . $sgqa . '.NAOK >= (' . $multiflexible_min . '))';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || ' . $sq['varName'] . '.NAOK >= (' . $multiflexible_min . '))';
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'multiflexible_min',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$multiflexible_min='';
|
|
}
|
|
|
|
// multiflexible_max
|
|
// Validation:= sqN <= value (which could be an expression).
|
|
if (isset($qattr['multiflexible_max']) && trim($qattr['multiflexible_max']) != '' && $input_boxes=='1')
|
|
{
|
|
$multiflexible_max = $qattr['multiflexible_max'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case ':': //MULTIPLE NUMERICAL QUESTION
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sgqa = substr($sq['jsVarName'],4);
|
|
$sq_name = '(is_empty(' . $sgqa . '.NAOK) || ' . $sgqa . '.NAOK <= (' . $multiflexible_max . '))';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '(is_empty(' . $sq['varName'] . '.NAOK) || ' . $sq['varName'] . '.NAOK <= (' . $multiflexible_max . '))';
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_name,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'multiflexible_max',
|
|
'class' => 'value_range',
|
|
'eqn' => implode(' && ', $sq_names),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$multiflexible_max='';
|
|
}
|
|
|
|
// min_num_of_files
|
|
// Validation:= sq_filecount >= value (which could be an expression).
|
|
if (isset($qattr['min_num_of_files']) && trim($qattr['min_num_of_files']) != '' && trim($qattr['min_num_of_files']) != '0')
|
|
{
|
|
$min_num_of_files = $qattr['min_num_of_files'];
|
|
|
|
$eqn='';
|
|
$sgqa = $qinfo['sgqa'];
|
|
switch ($type)
|
|
{
|
|
case '|': //List - dropdown
|
|
$eqn = "(" . $sgqa . "_filecount >= (" . $min_num_of_files . "))";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if ($eqn != '')
|
|
{
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'min_num_of_files',
|
|
'class' => 'num_answers',
|
|
'eqn' => $eqn,
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$min_num_of_files = '';
|
|
}
|
|
// max_num_of_files
|
|
// Validation:= sq_filecount <= value (which could be an expression).
|
|
if (isset($qattr['max_num_of_files']) && trim($qattr['max_num_of_files']) != '')
|
|
{
|
|
$max_num_of_files = $qattr['max_num_of_files'];
|
|
$eqn='';
|
|
$sgqa = $qinfo['sgqa'];
|
|
switch ($type)
|
|
{
|
|
case '|': //List - dropdown
|
|
$eqn = "(" . $sgqa . "_filecount <= (" . $max_num_of_files . "))";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if ($eqn != '')
|
|
{
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'max_num_of_files',
|
|
'class' => 'num_answers',
|
|
'eqn' => $eqn,
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$max_num_of_files = '';
|
|
}
|
|
|
|
// num_value_int_only
|
|
// Validation fixnum(sqN)==int(fixnum(sqN)) : fixnum or not fix num ..... 10.00 == 10
|
|
if (isset($qattr['num_value_int_only']) && trim($qattr['num_value_int_only']) == "1")
|
|
{
|
|
$num_value_int_only="1";
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_eqns = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_eqn=null;
|
|
$subqValidSelector = '';
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTI NUMERICAL QUESTION TYPE (Need a attribute, not set in 131014)
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_eqn = 'is_int('.$sq_name.')';
|
|
}else{
|
|
$sq_eqn = 'is_int('.$sq_name.') || is_empty('.$sq_name.')';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_eqn)) {
|
|
$sq_eqns[] = $sq_eqn;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_eqn,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (count($sq_eqns) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'num_value_int_only',
|
|
'class' => 'value_integer',
|
|
'eqn' => implode(' and ', $sq_eqns),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$num_value_int_only='';
|
|
}
|
|
|
|
// num_value_int_only
|
|
// Validation is_numeric(sqN)
|
|
if (isset($qattr['numbers_only']) && trim($qattr['numbers_only']) == "1")
|
|
{
|
|
$numbers_only=1;
|
|
switch ($type)
|
|
{
|
|
case 'S': // Short text
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
if(($qinfo['mandatory']=='Y')){
|
|
$sq_equs[] = 'is_numeric('.$sq_name.')';
|
|
}else{
|
|
$sq_equs[] = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'numbers_only',
|
|
'class' => 'numbers_only',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
break;
|
|
case 'Q': // multi text
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
$subqValidEqns = array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?$sq['rowdivid'].".NAOK":$sq['varName'].".NAOK";
|
|
$sq_equ = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';// Leave mandatory to mandatory attribute (see #08665)
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
if (!is_null($sq_name)) {
|
|
$sq_equs[] = $sq_equ;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_equ,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'numbers_only',
|
|
'class' => 'numbers_only',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
break;
|
|
case ';': // Array of text
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_equs=array();
|
|
$subqValidEqns = array();
|
|
foreach($subqs as $sq)
|
|
{
|
|
$sq_name = ($this->sgqaNaming)?substr($sq['jsVarName'],4).".NAOK":$sq['varName'].".NAOK";
|
|
$sq_equ = '( is_numeric('.$sq_name.') || is_empty('.$sq_name.') )';// Leave mandatory to mandatory attribute (see #08665)
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
if (!is_null($sq_name)) {
|
|
$sq_equs[] = $sq_equ;
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $sq_equ,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'numbers_only',
|
|
'class' => 'numbers_only',
|
|
'eqn' => implode(' and ',$sq_equs),
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
break;
|
|
case '*': // Don't think we need equation ?
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$numbers_only="";
|
|
}
|
|
|
|
// other_comment_mandatory
|
|
// Validation:= sqN <= value (which could be an expression).
|
|
if (isset($qattr['other_comment_mandatory']) && trim($qattr['other_comment_mandatory']) == '1')
|
|
{
|
|
$other_comment_mandatory = $qattr['other_comment_mandatory'];
|
|
$eqn='';
|
|
if ($other_comment_mandatory == '1' && $this->questionSeq2relevance[$qinfo['qseq']]['other'] == 'Y')
|
|
{
|
|
$sgqa = $qinfo['sgqa'];
|
|
switch ($type)
|
|
{
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
$eqn = "(" . $sgqa . ".NAOK!='-oth-' || (" . $sgqa . ".NAOK=='-oth-' && !is_empty(trim(" . $sgqa . "other.NAOK))))";
|
|
break;
|
|
case 'P': //Multiple choice with comments
|
|
$eqn = "(is_empty(trim(" . $sgqa . "other.NAOK)) || (!is_empty(trim(" . $sgqa . "other.NAOK)) && !is_empty(trim(" . $sgqa . "othercomment.NAOK))))";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ($eqn != '')
|
|
{
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'other_comment_mandatory',
|
|
'class' => 'other_comment_mandatory',
|
|
'eqn' => $eqn,
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$other_comment_mandatory = '';
|
|
}
|
|
|
|
// other_numbers_only
|
|
// Validation:= is_numeric(sqN).
|
|
if (isset($qattr['other_numbers_only']) && trim($qattr['other_numbers_only']) == '1')
|
|
{
|
|
$other_numbers_only = 1;
|
|
$eqn='';
|
|
if ($this->questionSeq2relevance[$qinfo['qseq']]['other'] == 'Y')
|
|
{
|
|
$sgqa = $qinfo['sgqa'];
|
|
switch ($type)
|
|
{
|
|
//case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'M': //Multiple choice
|
|
case 'P': //Multiple choice with
|
|
$eqn = "(is_empty(trim(" . $sgqa . "other.NAOK)) ||is_numeric(" . $sgqa . "other.NAOK))";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ($eqn != '')
|
|
{
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'other_numbers_only',
|
|
'class' => 'other_numbers_only',
|
|
'eqn' => $eqn,
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$other_numbers_only = '';
|
|
}
|
|
|
|
|
|
// show_totals
|
|
// TODO - create equations for these?
|
|
|
|
// assessment_value
|
|
// TODO? How does it work?
|
|
// The assessment value (referenced how?) = count(sq1,...,sqN) * assessment_value
|
|
// Since there are easy work-arounds to this, skipping it for now
|
|
|
|
// preg - a PHP Regular Expression to validate text input fields
|
|
if (isset($qinfo['preg']) && !is_null($qinfo['preg']))
|
|
{
|
|
$preg = $qinfo['preg'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
$subqValidSelector=NULL;
|
|
$sgqa = substr($sq['jsVarName'],4);
|
|
switch ($type)
|
|
{
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = '(if(is_empty('.$sgqa.'.NAOK),0,!regexMatch("' . $preg . '", ' . $sgqa . '.NAOK)))';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '(if(is_empty('.$sq['varName'].'.NAOK),0,!regexMatch("' . $preg . '", ' . $sq['varName'] . '.NAOK)))';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$subqValidEqn = '(is_empty('.$sgqa.'.NAOK) || regexMatch("' . $preg . '", ' . $sgqa . '.NAOK))';
|
|
}
|
|
else
|
|
{
|
|
$subqValidEqn = '(is_empty('.$sq['varName'].'.NAOK) || regexMatch("' . $preg . '", ' . $sq['varName'] . '.NAOK))';
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
if (isset($subqValidSelector)) {
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $subqValidEqn,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'preg',
|
|
'class' => 'regex_validation',
|
|
'eqn' => '(sum(' . implode(', ', $sq_names) . ') == 0)',
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$preg='';
|
|
}
|
|
|
|
// em_validation_q_tip - a description of the EM validation equation that must be satisfied for the whole question.
|
|
if (isset($qattr['em_validation_q_tip']) && !is_null($qattr['em_validation_q_tip']) && trim($qattr['em_validation_q_tip']) != '')
|
|
{
|
|
$em_validation_q_tip = trim($qattr['em_validation_q_tip']);
|
|
}
|
|
else
|
|
{
|
|
$em_validation_q_tip = '';
|
|
}
|
|
|
|
|
|
// em_validation_q - an EM validation equation that must be satisfied for the whole question. Uses 'this' in the equation
|
|
if (isset($qattr['em_validation_q']) && !is_null($qattr['em_validation_q']) && trim($qattr['em_validation_q']) != '')
|
|
{
|
|
$em_validation_q = $qattr['em_validation_q'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case 'M': //Multiple choice checkbox
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
case 'R': //RANKING STYLE
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
case 'D': //DATE
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = '!(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_q) . ')';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '!(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_q) . ')';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'em_validation_q',
|
|
'class' => 'q_fn_validation',
|
|
'eqn' => '(sum(' . implode(', ', array_unique($sq_names)) . ') == 0)',
|
|
'qid' => $questionNum,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$em_validation_q='';
|
|
}
|
|
|
|
// em_validation_sq_tip - a description of the EM validation equation that must be satisfied for each subquestion.
|
|
if (isset($qattr['em_validation_sq_tip']) && !is_null($qattr['em_validation_sq_tip']) && trim($qattr['em_validation_sq']) != '')
|
|
{
|
|
$em_validation_sq_tip = trim($qattr['em_validation_sq_tip']);
|
|
}
|
|
else
|
|
{
|
|
$em_validation_sq_tip = '';
|
|
}
|
|
|
|
|
|
// em_validation_sq - an EM validation equation that must be satisfied for each subquestion. Uses 'this' in the equation
|
|
if (isset($qattr['em_validation_sq']) && !is_null($qattr['em_validation_sq']) && trim($qattr['em_validation_sq']) != '')
|
|
{
|
|
$em_validation_sq = $qattr['em_validation_sq'];
|
|
if ($hasSubqs) {
|
|
$subqs = $qinfo['subqs'];
|
|
$sq_names = array();
|
|
$subqValidEqns = array();
|
|
foreach ($subqs as $sq) {
|
|
$sq_name = NULL;
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$sq_name = '!(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_sq) . ')';
|
|
}
|
|
else
|
|
{
|
|
$sq_name = '!(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_sq) . ')';
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
switch ($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
if ($this->sgqaNaming)
|
|
{
|
|
$subqValidEqn = '(' . preg_replace('/\bthis\b/',substr($sq['jsVarName'],4), $em_validation_sq) . ')';
|
|
}
|
|
else
|
|
{
|
|
$subqValidEqn = '(' . preg_replace('/\bthis\b/',$sq['varName'], $em_validation_sq) . ')';
|
|
}
|
|
$subqValidSelector = $sq['jsVarName_on'];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!is_null($sq_name)) {
|
|
$sq_names[] = $sq_name;
|
|
if (isset($subqValidSelector)) {
|
|
$subqValidEqns[$subqValidSelector] = array(
|
|
'subqValidEqn' => $subqValidEqn,
|
|
'subqValidSelector' => $subqValidSelector,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if (count($sq_names) > 0) {
|
|
if (!isset($validationEqn[$questionNum]))
|
|
{
|
|
$validationEqn[$questionNum] = array();
|
|
}
|
|
$validationEqn[$questionNum][] = array(
|
|
'qtype' => $type,
|
|
'type' => 'em_validation_sq',
|
|
'class' => 'sq_fn_validation',
|
|
'eqn' => '(sum(' . implode(', ', $sq_names) . ') == 0)',
|
|
'qid' => $questionNum,
|
|
'subqValidEqns' => $subqValidEqns,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$em_validation_sq='';
|
|
}
|
|
|
|
////////////////////////////////////////////
|
|
// COMPOSE USER FRIENDLY MIN/MAX MESSAGES //
|
|
////////////////////////////////////////////
|
|
|
|
// Put these in the order you with them to appear in messages.
|
|
$qtips=array();
|
|
|
|
// Default validation qtip without attribute
|
|
switch ($type)
|
|
{
|
|
case 'N':
|
|
$qtips['default']=$this->gT("Only numbers may be entered in this field.");
|
|
break;
|
|
case 'K':
|
|
$qtips['default']=$this->gT("Only numbers may be entered in these fields.");
|
|
break;
|
|
case 'R':
|
|
$qtips['default']=$this->gT("All your answers must be different.");
|
|
break;
|
|
// Helptext is added in qanda_help.php
|
|
/* case 'D':
|
|
$qtips['default']=$this->gT("Please complete all parts of the date.");
|
|
break;
|
|
*/
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if($commented_checkbox)
|
|
{
|
|
switch ($commented_checkbox)
|
|
{
|
|
case 'checked':
|
|
$qtips['commented_checkbox']=$this->gT("Comment only when you choose an answer.");
|
|
break;
|
|
case 'unchecked':
|
|
$qtips['commented_checkbox']=$this->gT("Comment only when you don't choose an answer.");
|
|
break;
|
|
case 'allways':
|
|
default:
|
|
$qtips['commented_checkbox']=$this->gT("Comment your answers.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// equals_num_value
|
|
if ($equals_num_value!='')
|
|
{
|
|
$qtips['sum_range']=sprintf($this->gT("The sum must equal %s."),'{fixnum('.$equals_num_value.')}');
|
|
}
|
|
|
|
if($input_boxes)
|
|
{
|
|
switch ($type)
|
|
{
|
|
case ':':
|
|
$qtips['input_boxes']=$this->gT("Only numbers may be entered in these fields.");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// min/max answers
|
|
if ($min_answers!='' || $max_answers!='')
|
|
{
|
|
$_minA = (($min_answers == '') ? "''" : $min_answers);
|
|
$_maxA = (($max_answers == '') ? "''" : $max_answers );
|
|
/* different messages for text and checkbox questions */
|
|
if($type == 'Q' || $type == 'K' || $type == ';' || $type == ':')
|
|
{
|
|
$_msgs = array(
|
|
'atleast_m' => $this->gT("Please fill in at least %s answers"),
|
|
'atleast_1' => $this->gT("Please fill in at least one answer"),
|
|
'atmost_m' => $this->gT("Please fill in at most %s answers"),
|
|
'atmost_1' => $this->gT("Please fill in at most one answer"),
|
|
'1' => $this->gT("Please fill in at most one answer"),
|
|
'n' => $this->gT("Please fill in %s answers"),
|
|
'between' => $this->gT("Please fill in between %s and %s answers")
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$_msgs = array(
|
|
'atleast_m' => $this->gT("Please select at least %s answers"),
|
|
'atleast_1' => $this->gT("Please select at least one answer"),
|
|
'atmost_m' => $this->gT("Please select at most %s answers"),
|
|
'atmost_1' => $this->gT("Please select at most one answer"),
|
|
'1' => $this->gT("Please select at most one answer"),
|
|
'n' => $this->gT("Please select %s answers"),
|
|
'between' => $this->gT("Please select between %s and %s answers")
|
|
);
|
|
}
|
|
$qtips['num_answers']=
|
|
"{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)!=1,sprintf('".$_msgs['atleast_m']."',fixnum($_minA)),'')}" .
|
|
"{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)==1,sprintf('".$_msgs['atleast_1']."',fixnum($_minA)),'')}" .
|
|
"{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)!=1,sprintf('".$_msgs['atmost_m']."',fixnum($_maxA)),'')}" .
|
|
"{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)==1,sprintf('".$_msgs['atmost_1']."',fixnum($_maxA)),'')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) == 1,'".$_msgs['1']."','')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) != 1,sprintf('".$_msgs['n']."',fixnum($_minA)),'')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) != ($_maxA),sprintf('".$_msgs['between']."',fixnum($_minA),fixnum($_maxA)),'')}";
|
|
}
|
|
|
|
// min/max value for each numeric entry
|
|
if ($min_num_value_n!='' || $max_num_value_n!='')
|
|
{
|
|
$_minV = (($min_num_value_n == '') ? "''" : $min_num_value_n);
|
|
$_maxV = (($max_num_value_n == '') ? "''" : $max_num_value_n);
|
|
$qtips['value_range']=
|
|
"{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("Each answer must be at least %s")."',fixnum($_minV)), '')}" .
|
|
"{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("Each answer must be at most %s")."',fixnum($_maxV)), '')}" .
|
|
"{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("Each answer must be %s")."', fixnum($_minV)), '')}" .
|
|
"{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("Each answer must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
|
|
}
|
|
|
|
// min/max value for dates
|
|
if ($date_min!='' || $date_max!='')
|
|
{
|
|
//Get date format of current question and convert date in help text accordingly
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$aAttributes=$LEM->getQuestionAttributesForEM($LEM->sid, $questionNum,$_SESSION['LEMlang']);
|
|
$aDateFormatData=getDateFormatDataForQID($aAttributes[$questionNum],$LEM->surveyOptions);
|
|
$_minV = (($date_min == '') ? "''" : "if((strtotime(".$date_min.")), date('".$aDateFormatData['phpdate']."', strtotime(".$date_min.")),'')");
|
|
$_maxV = (($date_max == '') ? "''" : "if((strtotime(".$date_max.")), date('".$aDateFormatData['phpdate']."', strtotime(".$date_max.")),'')");
|
|
$qtips['value_range']=
|
|
"{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("Answer must be greater or equal to %s")."',$_minV), '')}" .
|
|
"{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("Answer must be less or equal to %s")."',$_maxV), '')}" .
|
|
"{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("Answer must be %s")."', $_minV), '')}" .
|
|
"{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("Answer must be between %s and %s")."', ($_minV), ($_maxV)), '')}";
|
|
}
|
|
|
|
// min/max value for each numeric entry - for multi-flexible question type
|
|
if ($multiflexible_min!='' || $multiflexible_max!='')
|
|
{
|
|
$_minV = (($multiflexible_min == '') ? "''" : $multiflexible_min);
|
|
$_maxV = (($multiflexible_max == '') ? "''" : $multiflexible_max);
|
|
$qtips['value_range']=
|
|
"{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("Each answer must be at least %s")."',fixnum($_minV)), '')}" .
|
|
"{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("Each answer must be at most %s")."',fixnum($_maxV)), '')}" .
|
|
"{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("Each answer must be %s")."', fixnum($_minV)), '')}" .
|
|
"{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("Each answer must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
|
|
}
|
|
|
|
// min/max sum value
|
|
if ($min_num_value!='' || $max_num_value!='')
|
|
{
|
|
$_minV = (($min_num_value == '') ? "''" : $min_num_value);
|
|
$_maxV = (($max_num_value == '') ? "''" : $max_num_value);
|
|
$qtips['sum_range']=
|
|
"{if(!is_empty($_minV) && is_empty($_maxV), sprintf('".$this->gT("The sum must be at least %s")."',fixnum($_minV)), '')}" .
|
|
"{if(is_empty($_minV) && !is_empty($_maxV), sprintf('".$this->gT("The sum must be at most %s")."',fixnum($_maxV)), '')}" .
|
|
"{if(!is_empty($_minV) && ($_minV) == ($_maxV),sprintf('".$this->gT("The sum must equal %s")."', fixnum($_minV)), '')}" .
|
|
"{if(!is_empty($_minV) && !is_empty($_maxV) && ($_minV) != ($_maxV), sprintf('".$this->gT("The sum must be between %s and %s")."', fixnum($_minV), fixnum($_maxV)), '')}";
|
|
}
|
|
|
|
// min/max num files
|
|
if ($min_num_of_files !='' || $max_num_of_files !='')
|
|
{
|
|
$_minA = (($min_num_of_files == '') ? "''" : $min_num_of_files);
|
|
$_maxA = (($max_num_of_files == '') ? "''" : $max_num_of_files);
|
|
// TODO - create em_num_files class so can sepately style num_files vs. num_answers
|
|
$qtips['num_answers']=
|
|
"{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)!=1,sprintf('".$this->gT("Please upload at least %s files")."',fixnum($_minA)),'')}" .
|
|
"{if(!is_empty($_minA) && is_empty($_maxA) && ($_minA)==1,sprintf('".$this->gT("Please upload at least one file")."',fixnum($_minA)),'')}" .
|
|
"{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)!=1,sprintf('".$this->gT("Please upload at most %s files")."',fixnum($_maxA)),'')}" .
|
|
"{if(is_empty($_minA) && !is_empty($_maxA) && ($_maxA)==1,sprintf('".$this->gT("Please upload at most one file")."',fixnum($_maxA)),'')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) == 1,'".$this->gT("Please upload one file")."','')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) == ($_maxA) && ($_minA) != 1,sprintf('".$this->gT("Please upload %s files")."',fixnum($_minA)),'')}" .
|
|
"{if(!is_empty($_minA) && !is_empty($_maxA) && ($_minA) != ($_maxA),sprintf('".$this->gT("Please upload between %s and %s files")."',fixnum($_minA),fixnum($_maxA)),'')}";
|
|
}
|
|
|
|
|
|
// integer for numeric
|
|
if ($num_value_int_only!='')
|
|
{
|
|
switch ($type)
|
|
{
|
|
case 'N':
|
|
$qtips['default']='';
|
|
$qtips['value_integer']=$this->gT("Only an integer value may be entered in this field.");
|
|
break;
|
|
case 'K':
|
|
$qtips['default']='';
|
|
$qtips['value_integer']=$this->gT("Only integer values may be entered in these fields.");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// numbers only
|
|
if($numbers_only)
|
|
{
|
|
switch ($type)
|
|
{
|
|
case 'S':
|
|
$qtips['numbers_only']=$this->gT("Only numbers may be entered in this field.");
|
|
break;
|
|
case 'Q':
|
|
case ';':
|
|
$qtips['numbers_only']=$this->gT("Only numbers may be entered in these fields.");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// other comment mandatory
|
|
if ($other_comment_mandatory!='')
|
|
{
|
|
if (isset($qattr['other_replace_text']) && trim($qattr['other_replace_text']) != '') {
|
|
$othertext = trim($qattr['other_replace_text']);
|
|
}
|
|
else {
|
|
$othertext = $this->gT('Other:');
|
|
}
|
|
$qtips['other_comment_mandatory']=sprintf($this->gT("If you choose '%s' please also specify your choice in the accompanying text field."),$othertext);
|
|
}
|
|
|
|
// other comment mandatory
|
|
if ($other_numbers_only!='')
|
|
{
|
|
if (isset($qattr['other_replace_text']) && trim($qattr['other_replace_text']) != '') {
|
|
$othertext = trim($qattr['other_replace_text']);
|
|
}
|
|
else {
|
|
$othertext = $this->gT('Other:');
|
|
}
|
|
$qtips['other_numbers_only']=sprintf($this->gT("Only numbers may be entered in '%s' accompanying text field."),$othertext);
|
|
}
|
|
|
|
// regular expression validation
|
|
if ($preg!='')
|
|
{
|
|
// do string replacement here so that curly braces within the regular expression don't trigger an EM error
|
|
// $qtips['regex_validation']=sprintf($this->gT('Each answer must conform to this regular expression: %s'), str_replace(array('{','}'),array('{ ',' }'), $preg));
|
|
$qtips['regex_validation']=$this->gT('Please check the format of your answer.');
|
|
}
|
|
|
|
if ($em_validation_sq!='')
|
|
{
|
|
if ($em_validation_sq_tip =='')
|
|
{
|
|
// $stringToParse = htmlspecialchars_decode($em_validation_sq,ENT_QUOTES);
|
|
// $gseq = $this->questionId2groupSeq[$qinfo['qid']];
|
|
// $result = $this->em->ProcessBooleanExpression($stringToParse,$gseq, $qinfo['qseq']);
|
|
// $_validation_tip = $this->em->GetPrettyPrintString();
|
|
// $qtips['sq_fn_validation']=sprintf($this->gT('Each answer must conform to this expression: %s'),$_validation_tip);
|
|
}
|
|
else
|
|
{
|
|
$qtips['sq_fn_validation']=$em_validation_sq_tip;
|
|
}
|
|
|
|
}
|
|
|
|
// em_validation_q - whole-question validation equation
|
|
if ($em_validation_q!='')
|
|
{
|
|
if ($em_validation_q_tip =='')
|
|
{
|
|
// $stringToParse = htmlspecialchars_decode($em_validation_q,ENT_QUOTES);
|
|
// $gseq = $this->questionId2groupSeq[$qinfo['qid']];
|
|
// $result = $this->em->ProcessBooleanExpression($stringToParse,$gseq, $qinfo['qseq']);
|
|
// $_validation_tip = $this->em->GetPrettyPrintString();
|
|
// $qtips['q_fn_validation']=sprintf($this->gT('The question must conform to this expression: %s'), $_validation_tip);
|
|
}
|
|
else
|
|
{
|
|
$qtips['q_fn_validation']=$em_validation_q_tip;
|
|
}
|
|
}
|
|
|
|
if (count($qtips) > 0)
|
|
{
|
|
$validationTips[$questionNum] = $qtips;
|
|
}
|
|
}
|
|
|
|
// Consolidate logic across array filters
|
|
$rowdivids = array();
|
|
$order=0;
|
|
foreach ($subQrels as $sq)
|
|
{
|
|
$oldeqn = (isset($rowdivids[$sq['rowdivid']]['eqns']) ? $rowdivids[$sq['rowdivid']]['eqns'] : array());
|
|
$oldtype = (isset($rowdivids[$sq['rowdivid']]['type']) ? $rowdivids[$sq['rowdivid']]['type'] : '');
|
|
$neweqn = (($sq['type'] == 'exclude_all_others') ? array() : array($sq['eqn']));
|
|
$oldeo = (isset($rowdivids[$sq['rowdivid']]['exclusive_options']) ? $rowdivids[$sq['rowdivid']]['exclusive_options'] : array());
|
|
$neweo = (($sq['type'] == 'exclude_all_others') ? array($sq['eqn']) : array());
|
|
$rowdivids[$sq['rowdivid']] = array(
|
|
'order'=>$order++,
|
|
'qid'=>$sq['qid'],
|
|
'rowdivid'=>$sq['rowdivid'],
|
|
'type'=>$sq['type'] . ';' . $oldtype,
|
|
'qtype'=>$sq['qtype'],
|
|
'sgqa'=>$sq['sgqa'],
|
|
'eqns'=>array_merge($oldeqn, $neweqn),
|
|
'exclusive_options'=>array_merge($oldeo, $neweo),
|
|
);
|
|
}
|
|
|
|
foreach ($rowdivids as $sq)
|
|
{
|
|
$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 ;
|
|
$eos = array_unique($sq['exclusive_options']);
|
|
$isExclusive = '';
|
|
$irrelevantAndExclusive = '';
|
|
if (count($eos) > 0)
|
|
{
|
|
$isExclusive = '!(' . implode(' and ', $eos) . ')';
|
|
$noneos = array_unique($sq['eqns']);
|
|
if (count($noneos) > 0)
|
|
{
|
|
$irrelevantAndExclusive = '(' . implode(' and ', $noneos) . ') and ' . $isExclusive;
|
|
}
|
|
}
|
|
$this->_ProcessSubQRelevance($sq['eqn'], $sq['qid'], $sq['rowdivid'], $sq['type'], $sq['qtype'], $sq['sgqa'], $isExclusive, $irrelevantAndExclusive);
|
|
}
|
|
|
|
foreach ($validationEqn as $qid=>$eqns)
|
|
{
|
|
$parts = array();
|
|
$tips = (isset($validationTips[$qid]) ? $validationTips[$qid] : array());
|
|
$subqValidEqns = array();
|
|
$sumEqn = '';
|
|
$sumRemainingEqn = '';
|
|
foreach ($eqns as $v) {
|
|
if (!isset($parts[$v['class']]))
|
|
{
|
|
$parts[$v['class']] = array();
|
|
}
|
|
$parts[$v['class']][] = $v['eqn'];
|
|
// even if there are min/max/preg, the count or total will always be the same
|
|
$sumEqn = (isset($v['sumEqn'])) ? $v['sumEqn'] : $sumEqn;
|
|
$sumRemainingEqn = (isset($v['sumRemainingEqn'])) ? $v['sumRemainingEqn'] : $sumRemainingEqn;
|
|
if (isset($v['subqValidEqns'])) {
|
|
$subqValidEqns[] = $v['subqValidEqns'];
|
|
}
|
|
}
|
|
// combine the sub-question level validation equations into a single validation equation per sub-question
|
|
$subqValidComposite = array();
|
|
foreach ($subqValidEqns as $sqs) {
|
|
foreach ($sqs as $sq)
|
|
{
|
|
if (!isset($subqValidComposite[$sq['subqValidSelector']]))
|
|
{
|
|
$subqValidComposite[$sq['subqValidSelector']] = array(
|
|
'subqValidSelector' => $sq['subqValidSelector'],
|
|
'subqValidEqns' => array(),
|
|
);
|
|
}
|
|
$subqValidComposite[$sq['subqValidSelector']]['subqValidEqns'][] = $sq['subqValidEqn'];
|
|
}
|
|
}
|
|
$csubqValidEqns = array();
|
|
foreach ($subqValidComposite as $csq)
|
|
{
|
|
$csubqValidEqns[$csq['subqValidSelector']] = array(
|
|
'subqValidSelector' => $csq['subqValidSelector'],
|
|
'subqValidEqn' => implode(' && ', $csq['subqValidEqns']),
|
|
);
|
|
}
|
|
// now combine all classes of validation equations
|
|
$veqns = array();
|
|
foreach ($parts as $vclass=>$eqns)
|
|
{
|
|
$veqns[$vclass] = '(' . implode(' and ', $eqns) . ')';
|
|
}
|
|
$this->qid2validationEqn[$qid] = array(
|
|
'eqn' => $veqns,
|
|
'tips' => $tips,
|
|
'subqValidEqns' => $csubqValidEqns,
|
|
'sumEqn' => $sumEqn,
|
|
'sumRemainingEqn' => $sumRemainingEqn,
|
|
);
|
|
}
|
|
|
|
// $this->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
}
|
|
|
|
/**
|
|
* Recursively find all questions that logically preceded the current array_filter or array_filter_exclude request
|
|
* Note, must support:
|
|
* (a) semicolon-separated list of $qroot codes for either array_filter or array_filter_exclude
|
|
* (b) mixed history of array_filter and array_filter_exclude values
|
|
* @param type $qroot - the question root variable name
|
|
* @param type $aflist - the list of array_filter $qroot codes
|
|
* @param type $afelist - the list of array_filter_exclude $qroot codes
|
|
* @return type
|
|
*/
|
|
private function _recursivelyFindAntecdentArrayFilters($qroot, $aflist, $afelist)
|
|
{
|
|
if (isset($this->qrootVarName2arrayFilter[$qroot]))
|
|
{
|
|
if (isset($this->qrootVarName2arrayFilter[$qroot]['array_filter']))
|
|
{
|
|
$_afs = explode(';',$this->qrootVarName2arrayFilter[$qroot]['array_filter']);
|
|
foreach ($_afs as $_af)
|
|
{
|
|
if (in_array($_af,$aflist))
|
|
{
|
|
continue;
|
|
}
|
|
$aflist[] = $_af;
|
|
list($aflist, $afelist) = $this->_recursivelyFindAntecdentArrayFilters($_af, $aflist, $afelist);
|
|
}
|
|
}
|
|
if (isset($this->qrootVarName2arrayFilter[$qroot]['array_filter_exclude']))
|
|
{
|
|
$_afes = explode(';',$this->qrootVarName2arrayFilter[$qroot]['array_filter_exclude']);
|
|
foreach ($_afes as $_afe)
|
|
{
|
|
if (in_array($_afe,$afelist))
|
|
{
|
|
continue;
|
|
}
|
|
$afelist[] = $_afe;
|
|
list($aflist, $afelist) = $this->_recursivelyFindAntecdentArrayFilters($_afe, $aflist, $afelist);
|
|
}
|
|
}
|
|
}
|
|
return array($aflist, $afelist);
|
|
}
|
|
|
|
/**
|
|
* Create the arrays needed by ExpressionManager to process LimeSurvey strings.
|
|
* The long part of this function should only be called once per page display (e.g. only if $fieldMap changes)
|
|
*
|
|
* @param <integer> $surveyid
|
|
* @param <Boolean> $forceRefresh
|
|
* @param <Boolean> $anonymized
|
|
* @param <Boolean> $allOnOnePage - if true (like for survey_format), uses certain optimizations
|
|
* @return boolean - true if $fieldmap had been re-created, so ExpressionManager variables need to be re-set
|
|
*/
|
|
|
|
private function setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh=false,$anonymized=false,$allOnOnePage=false)
|
|
{
|
|
if (isset($_SESSION['LEMforceRefresh'])) {
|
|
unset($_SESSION['LEMforceRefresh']);
|
|
$forceRefresh=true;
|
|
}
|
|
else if (!$forceRefresh && isset($this->knownVars) && !$this->sPreviewMode ) {
|
|
return false; // means that those variables have been cached and no changes needed
|
|
}
|
|
$now = microtime(true);
|
|
$this->em->SetSurveyMode($this->surveyMode);
|
|
|
|
// TODO - do I need to force refresh, or trust that createFieldMap will cache langauges properly?
|
|
$fieldmap=createFieldMap($surveyid,$style='full',$forceRefresh,false,$_SESSION['LEMlang']);
|
|
$this->sid= $surveyid;
|
|
|
|
$this->runtimeTimings[] = array(__METHOD__ . '.createFieldMap',(microtime(true) - $now));
|
|
// LimeExpressionManager::ShowStackTrace();
|
|
|
|
$now = microtime(true);
|
|
|
|
if (!isset($fieldmap)) {
|
|
return false; // implies an error occurred
|
|
}
|
|
$this->knownVars = array(); // mapping of VarName to Value
|
|
$this->qcode2sgqa = array();
|
|
$this->tempVars = array();
|
|
$this->qid2code = array(); // List of codes for each question - needed to know which to NULL if a question is irrelevant
|
|
$this->jsVar2qid = array();
|
|
$this->qcode2sgq = array();
|
|
$this->alias2varName = array();
|
|
$this->varNameAttr = array();
|
|
$this->questionId2questionSeq = array();
|
|
$this->questionId2groupSeq = array();
|
|
$this->questionSeq2relevance = array();
|
|
$this->groupId2groupSeq = array();
|
|
$this->qid2validationEqn = array();
|
|
$this->groupSeqInfo = array();
|
|
$this->gseq2relevanceStatus = array();
|
|
|
|
// Since building array of allowable answers, need to know preset values for certain question types
|
|
$presets = array();
|
|
$presets['G'] = array( //GENDER drop-down list
|
|
'M' => $this->gT("Male"),
|
|
'F' => $this->gT("Female"),
|
|
);
|
|
$presets['Y'] = array( //YES/NO radio-buttons
|
|
'Y' => $this->gT("Yes"),
|
|
'N' => $this->gT("No"),
|
|
);
|
|
$presets['C'] = array( //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
'Y' => $this->gT("Yes"),
|
|
'N' => $this->gT("No"),
|
|
'U' => $this->gT("Uncertain"),
|
|
);
|
|
$presets['E'] = array( //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
'I' => $this->gT("Increase"),
|
|
'S' => $this->gT("Same"),
|
|
'D' => $this->gT("Decrease"),
|
|
);
|
|
|
|
$this->gseq2info = $this->getGroupInfoForEM($surveyid,$_SESSION['LEMlang']);
|
|
foreach ($this->gseq2info as $aGroupInfo)
|
|
{
|
|
$this->groupId2groupSeq[$aGroupInfo['gid']] = $aGroupInfo['group_order'];
|
|
}
|
|
|
|
$qattr = $this->getQuestionAttributesForEM($surveyid,0,$_SESSION['LEMlang']);
|
|
|
|
$this->qattr = $qattr;
|
|
|
|
$this->runtimeTimings[] = array(__METHOD__ . ' - question_attributes_model->getQuestionAttributesForEM',(microtime(true) - $now));
|
|
$now = microtime(true);
|
|
|
|
$this->qans = $this->getAnswerSetsForEM($surveyid,NULL,$_SESSION['LEMlang']);
|
|
|
|
$this->runtimeTimings[] = array(__METHOD__ . ' - answers_model->getAnswerSetsForEM',(microtime(true) - $now));
|
|
$now = microtime(true);
|
|
|
|
$q2subqInfo = array();
|
|
|
|
$this->multiflexiAnswers=array();
|
|
foreach($fieldmap as $fielddata)
|
|
{
|
|
if (!isset($fielddata['fieldname']) || !preg_match('#^\d+X\d+X\d+#',$fielddata['fieldname']))
|
|
{
|
|
continue; // not an SGQA value
|
|
}
|
|
$sgqa = $fielddata['fieldname'];
|
|
$type = $fielddata['type'];
|
|
$mandatory = $fielddata['mandatory'];
|
|
$fieldNameParts = explode('X',$sgqa);
|
|
$groupNum = $fieldNameParts[1];
|
|
$aid = (isset($fielddata['aid']) ? $fielddata['aid'] : '');
|
|
$sqid = (isset($fielddata['sqid']) ? $fielddata['sqid'] : '');
|
|
if($this->sPreviewMode=='question') $fielddata['relevance']=1;
|
|
if($this->sPreviewMode=='group') $fielddata['grelevance']=1;
|
|
|
|
$questionId = $fieldNameParts[2];
|
|
$questionNum = $fielddata['qid'];
|
|
$relevance = (isset($fielddata['relevance'])) ? $fielddata['relevance'] : 1;
|
|
$grelevance = (isset($fielddata['grelevance'])) ? $fielddata['grelevance'] : 1;
|
|
$hidden = (isset($qattr[$questionNum]['hidden'])) ? ($qattr[$questionNum]['hidden'] == '1') : false;
|
|
$scale_id = (isset($fielddata['scale_id'])) ? $fielddata['scale_id'] : '0';
|
|
$preg = (isset($fielddata['preg'])) ? $fielddata['preg'] : NULL; // a perl regular exrpession validation function
|
|
$defaultValue = (isset($fielddata['defaultvalue']) ? $fielddata['defaultvalue'] : NULL);
|
|
if (trim($preg) == '') {
|
|
$preg = NULL;
|
|
}
|
|
$help = (isset($fielddata['help'])) ? $fielddata['help']: '';
|
|
$other = (isset($fielddata['other'])) ? $fielddata['other'] : '';
|
|
|
|
if (isset($this->questionId2groupSeq[$questionNum])) {
|
|
$groupSeq = $this->questionId2groupSeq[$questionNum];
|
|
}
|
|
else {
|
|
$groupSeq = (isset($fielddata['groupSeq'])) ? $fielddata['groupSeq'] : -1;
|
|
$this->questionId2groupSeq[$questionNum] = $groupSeq;
|
|
}
|
|
|
|
if (isset($this->questionId2questionSeq[$questionNum])) {
|
|
$questionSeq = $this->questionId2questionSeq[$questionNum];
|
|
}
|
|
else {
|
|
$questionSeq = (isset($fielddata['questionSeq'])) ? $fielddata['questionSeq'] : -1;
|
|
$this->questionId2questionSeq[$questionNum] = $questionSeq;
|
|
}
|
|
|
|
if (!isset($this->groupSeqInfo[$groupSeq])) {
|
|
$this->groupSeqInfo[$groupSeq] = array(
|
|
'qstart' => $questionSeq,
|
|
'qend' => $questionSeq,
|
|
);
|
|
}
|
|
else {
|
|
$this->groupSeqInfo[$groupSeq]['qend'] = $questionSeq; // with each question, update so know ending value
|
|
}
|
|
|
|
|
|
// Create list of codes associated with each question
|
|
$codeList = (isset($this->qid2code[$questionNum]) ? $this->qid2code[$questionNum] : '');
|
|
if ($codeList == '')
|
|
{
|
|
$codeList = $sgqa;
|
|
}
|
|
else
|
|
{
|
|
$codeList .= '|' . $sgqa;
|
|
}
|
|
$this->qid2code[$questionNum] = $codeList;
|
|
|
|
$readWrite = 'Y';
|
|
|
|
// Set $ansArray
|
|
switch($type)
|
|
{
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
case '1': //Array (Flexible Labels) dual scale // need scale
|
|
case 'H': //ARRAY (Flexible) - Column Format
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'R': //RANKING STYLE
|
|
$ansArray = (isset($this->qans[$questionNum]) ? $this->qans[$questionNum] : NULL);
|
|
if ($other == 'Y' && ($type == 'L' || $type == '!'))
|
|
{
|
|
if (preg_match('/other$/',$sgqa))
|
|
{
|
|
$ansArray = NULL; // since the other variable doesn't need it
|
|
}
|
|
else
|
|
{
|
|
$_qattr = isset($qattr[$questionNum]) ? $qattr[$questionNum] : array();
|
|
if (isset($_qattr['other_replace_text']) && trim($_qattr['other_replace_text']) != '') {
|
|
$othertext = trim($_qattr['other_replace_text']);
|
|
}
|
|
else {
|
|
$othertext = $this->gT('Other:');
|
|
}
|
|
$ansArray['0~-oth-'] = '0|' . $othertext;
|
|
}
|
|
}
|
|
break;
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case '5': //5 POINT CHOICE radio-buttons
|
|
$ansArray=NULL;
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
case 'D': //DATE
|
|
case '*': //Equation
|
|
case 'I': //Language Question
|
|
case '|': //File Upload
|
|
case 'X': //BOILERPLATE QUESTION
|
|
$ansArray = NULL;
|
|
break;
|
|
case 'G': //GENDER drop-down list
|
|
case 'Y': //YES/NO radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
$ansArray = $presets[$type];
|
|
break;
|
|
}
|
|
|
|
// set $subqtext text - for display of primary sub-question
|
|
$subqtext = '';
|
|
switch ($type)
|
|
{
|
|
default:
|
|
$subqtext = (isset($fielddata['subquestion']) ? $fielddata['subquestion'] : '');
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
$subqtext = (isset($fielddata['subquestion1']) ? $fielddata['subquestion1'] : '');
|
|
$ansList = array();
|
|
if (isset($fielddata['answerList']))
|
|
{
|
|
foreach ($fielddata['answerList'] as $ans) {
|
|
$ansList['1~' . $ans['code']] = $ans['code'] . '|' . $ans['answer'];
|
|
}
|
|
$this->multiflexiAnswers[$questionNum] = $ansList;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Set $varName (question code / questions.title), $rowdivid, $csuffix, $sqsuffix, and $question
|
|
$rowdivid=NULL; // so that blank for types not needing it.
|
|
$sqsuffix='';
|
|
switch($type)
|
|
{
|
|
case '!': //List - dropdown
|
|
case '5': //5 POINT CHOICE radio-buttons
|
|
case 'D': //DATE
|
|
case 'G': //GENDER drop-down list
|
|
case 'I': //Language Question
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
case 'X': //BOILERPLATE QUESTION
|
|
case 'Y': //YES/NO radio-buttons
|
|
case '|': //File Upload
|
|
case '*': //Equation
|
|
$csuffix = '';
|
|
$sqsuffix = '';
|
|
$varName = $fielddata['title'];
|
|
if ($fielddata['aid'] != '') {
|
|
$varName .= '_' . $fielddata['aid'];
|
|
}
|
|
$question = $fielddata['question'];
|
|
break;
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
$csuffix = $fielddata['aid'] . '#' . $fielddata['scale_id'];
|
|
$sqsuffix = '_' . $fielddata['aid'];
|
|
$varName = $fielddata['title'] . '_' . $fielddata['aid'] . '_' . $fielddata['scale_id'];;
|
|
$question = $fielddata['subquestion'] . '[' . $fielddata['scale'] . ']';
|
|
// $question = $fielddata['question'] . ': ' . $fielddata['subquestion'] . '[' . $fielddata['scale'] . ']';
|
|
$rowdivid = substr($sgqa,0,-2);
|
|
break;
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'H': //ARRAY (Flexible) - Column Format // note does not have javatbd equivalent - so array filters don't work on it
|
|
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
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
case 'Q': //MULTIPLE SHORT TEXT // note does not have javatbd equivalent - so array filters don't work on it
|
|
case 'R': //RANKING STYLE // note does not have javatbd equivalent - so array filters don't work on it
|
|
$csuffix = $fielddata['aid'];
|
|
$varName = $fielddata['title'] . '_' . $fielddata['aid'];
|
|
$question = $fielddata['subquestion'];
|
|
// $question = $fielddata['question'] . ': ' . $fielddata['subquestion'];
|
|
if ($type != 'H') {
|
|
if ($type == 'P' && preg_match("/comment$/", $sgqa)) {
|
|
// $rowdivid = substr($sgqa,0,-7);
|
|
}
|
|
else {
|
|
$sqsuffix = '_' . $fielddata['aid'];
|
|
$rowdivid = $sgqa;
|
|
}
|
|
}
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
$csuffix = $fielddata['aid'];
|
|
$sqsuffix = '_' . substr($fielddata['aid'],0,strpos($fielddata['aid'],'_'));
|
|
$varName = $fielddata['title'] . '_' . $fielddata['aid'];
|
|
$question = $fielddata['subquestion1'] . '[' . $fielddata['subquestion2'] . ']';
|
|
// $question = $fielddata['question'] . ': ' . $fielddata['subquestion1'] . '[' . $fielddata['subquestion2'] . ']';
|
|
$rowdivid = substr($sgqa,0,strpos($sgqa,'_'));
|
|
break;
|
|
}
|
|
|
|
// $onlynum
|
|
$onlynum=false; // the default
|
|
switch($type)
|
|
{
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
$onlynum=true;
|
|
break;
|
|
case '*': // Equation
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case 'S': //SHORT FREE TEXT
|
|
if (isset($qattr[$questionNum]['numbers_only']) && $qattr[$questionNum]['numbers_only']=='1')
|
|
{
|
|
$onlynum=true;
|
|
}
|
|
break;
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if (isset($qattr[$questionNum]['other_numbers_only']) && $qattr[$questionNum]['other_numbers_only']=='1' && preg_match('/other$/',$sgqa))
|
|
{
|
|
$onlynum=true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Set $jsVarName_on (for on-page variables - e.g. answerSGQA) and $jsVarName (for off-page variables; the primary name - e.g. javaSGQA)
|
|
switch($type)
|
|
{
|
|
case 'R': //RANKING STYLE
|
|
$jsVarName_on = 'answer' . $sgqa;
|
|
$jsVarName = 'java' . $sgqa;
|
|
break;
|
|
case 'D': //DATE
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'X': //BOILERPLATE QUESTION
|
|
$jsVarName_on = 'answer' . $sgqa;
|
|
$jsVarName = 'java' . $sgqa;
|
|
break;
|
|
case '!': //List - dropdown
|
|
if (preg_match("/other$/",$sgqa))
|
|
{
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = 'othertext' . substr($sgqa,0,-5);
|
|
}
|
|
else
|
|
{
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = $jsVarName;
|
|
}
|
|
break;
|
|
case 'L': //LIST drop-down/radio-button list
|
|
if (preg_match("/other$/",$sgqa))
|
|
{
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = 'answer' . $sgqa . "text";
|
|
}
|
|
else
|
|
{
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = $jsVarName;
|
|
}
|
|
break;
|
|
case '5': //5 POINT CHOICE radio-buttons
|
|
case 'G': //GENDER drop-down list
|
|
case 'I': //Language Question
|
|
case 'Y': //YES/NO radio-buttons
|
|
case '*': //Equation
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'H': //ARRAY (Flexible) - Column Format
|
|
case 'M': //Multiple choice checkbox
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
if ($type == 'O' && preg_match('/_comment$/', $varName))
|
|
{
|
|
$jsVarName_on = 'answer' . $sgqa;
|
|
}
|
|
else
|
|
{
|
|
$jsVarName_on = 'java' . $sgqa;
|
|
}
|
|
$jsVarName = 'java' . $sgqa;
|
|
break;
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
$jsVarName = 'java' . str_replace('#','_',$sgqa);
|
|
$jsVarName_on = $jsVarName;
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = 'answer' . $sgqa;;
|
|
break;
|
|
case '|': //File Upload
|
|
$jsVarName = $sgqa;
|
|
$jsVarName_on = $jsVarName;
|
|
break;
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if (preg_match("/(other|comment)$/",$sgqa))
|
|
{
|
|
$jsVarName_on = 'answer' . $sgqa; // is this true for survey.php and not for group.php?
|
|
$jsVarName = 'java' . $sgqa;
|
|
}
|
|
else
|
|
{
|
|
$jsVarName = 'java' . $sgqa;
|
|
$jsVarName_on = $jsVarName;
|
|
}
|
|
break;
|
|
}
|
|
// Hidden question are never on same page (except for equation)
|
|
if($hidden && $type!="*"){
|
|
$jsVarName_on = '';
|
|
}
|
|
|
|
if (!is_null($rowdivid) || $type == 'L' || $type == 'N' || $type == '!' || !is_null($preg)
|
|
|| $type == 'S' || $type == 'D' || $type == 'T' || $type == 'U' || $type == '|') {
|
|
if (!isset($q2subqInfo[$questionNum])) {
|
|
$q2subqInfo[$questionNum] = array(
|
|
'qid' => $questionNum,
|
|
'qseq' => $questionSeq,
|
|
'gseq' => $groupSeq,
|
|
'sgqa' => $surveyid . 'X' . $groupNum . 'X' . $questionNum,
|
|
'mandatory'=>$mandatory,
|
|
'varName' => $varName,
|
|
'type' => $type,
|
|
'fieldname' => $sgqa,
|
|
'preg' => $preg,
|
|
'rootVarName' => $fielddata['title'],
|
|
);
|
|
}
|
|
if (!isset($q2subqInfo[$questionNum]['subqs'])) {
|
|
$q2subqInfo[$questionNum]['subqs'] = array();
|
|
}
|
|
if ($type == 'L' || $type == '!')
|
|
{
|
|
if (!is_null($ansArray))
|
|
{
|
|
foreach (array_keys($ansArray) as $key)
|
|
{
|
|
$parts = explode('~',$key);
|
|
if ($parts[1] == '-oth-') {
|
|
$parts[1] = 'other';
|
|
}
|
|
$q2subqInfo[$questionNum]['subqs'][] = array(
|
|
'rowdivid' => $surveyid . 'X' . $groupNum . 'X' . $questionNum . $parts[1],
|
|
'varName' => $varName,
|
|
'sqsuffix' => '_' . $parts[1],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else if ($type == 'N'
|
|
|| $type == 'S' || $type == 'D' || $type == 'T' || $type == 'U') // for $preg
|
|
{
|
|
$q2subqInfo[$questionNum]['subqs'][] = array(
|
|
'varName' => $varName,
|
|
'rowdivid' => $surveyid . 'X' . $groupNum . 'X' . $questionNum,
|
|
'jsVarName' => 'java' . $surveyid . 'X' . $groupNum . 'X' . $questionNum,
|
|
'jsVarName_on' => $jsVarName_on,
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$q2subqInfo[$questionNum]['subqs'][] = array(
|
|
'rowdivid' => $rowdivid,
|
|
'varName' => $varName,
|
|
'jsVarName_on' => $jsVarName_on,
|
|
'jsVarName' => $jsVarName,
|
|
'csuffix' => $csuffix,
|
|
'sqsuffix' => $sqsuffix,
|
|
);
|
|
}
|
|
}
|
|
|
|
$ansList = '';
|
|
if (isset($ansArray) && !is_null($ansArray)) {
|
|
$answers = array();
|
|
foreach ($ansArray as $key => $value) {
|
|
$answers[] = "'" . $key . "':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$value),ENT_QUOTES) . "'";
|
|
}
|
|
$ansList = ",'answers':{ " . implode(",",$answers) . "}";
|
|
}
|
|
|
|
// Set mappings of variable names to needed attributes
|
|
$varInfo_Code = array(
|
|
'jsName_on'=>$jsVarName_on,
|
|
'jsName'=>$jsVarName,
|
|
'readWrite'=>$readWrite,
|
|
'hidden'=>$hidden,
|
|
'question'=>$question,
|
|
'qid'=>$questionNum,
|
|
'gid'=>$groupNum,
|
|
'grelevance'=>$grelevance,
|
|
'relevance'=>$relevance,
|
|
'qcode'=>$varName,
|
|
'qseq'=>$questionSeq,
|
|
'gseq'=>$groupSeq,
|
|
'type'=>$type,
|
|
'sgqa'=>$sgqa,
|
|
'ansList'=>$ansList,
|
|
'ansArray'=>$ansArray,
|
|
'scale_id'=>$scale_id,
|
|
'default'=>$defaultValue,
|
|
'rootVarName'=>$fielddata['title'],
|
|
'subqtext'=>$subqtext,
|
|
'rowdivid'=>(is_null($rowdivid) ? '' : $rowdivid),
|
|
'onlynum'=>$onlynum,
|
|
);
|
|
|
|
$this->questionSeq2relevance[$questionSeq] = array(
|
|
'relevance'=>$relevance,
|
|
'grelevance'=>$grelevance,
|
|
'qid'=>$questionNum,
|
|
'qseq'=>$questionSeq,
|
|
'gseq'=>$groupSeq,
|
|
'jsResultVar_on'=>$jsVarName_on,
|
|
'jsResultVar'=>$jsVarName,
|
|
'type'=>$type,
|
|
'hidden'=>$hidden,
|
|
'gid'=>$groupNum,
|
|
'mandatory'=>$mandatory,
|
|
'eqn'=>(($type == '*') ? $question : ''),
|
|
'help'=>$help,
|
|
'qtext'=>$fielddata['question'], // $question,
|
|
'code'=>$varName,
|
|
'other'=>$other,
|
|
'default'=>$defaultValue,
|
|
'rootVarName'=>$fielddata['title'],
|
|
'rowdivid'=>(is_null($rowdivid) ? '' : $rowdivid),
|
|
'aid'=>$aid,
|
|
'sqid'=>$sqid,
|
|
);
|
|
|
|
$this->knownVars[$sgqa] = $varInfo_Code;
|
|
$this->qcode2sgqa[$varName]=$sgqa;
|
|
$this->jsVar2qid[$jsVarName] = $questionNum;
|
|
$this->qcode2sgq[$fielddata['title']] = $surveyid . 'X' . $groupNum . 'X' . $questionNum;
|
|
|
|
// Create JavaScript arrays
|
|
$this->alias2varName[$varName] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $varName . "':'" . $jsVarName . "'");
|
|
$this->alias2varName[$sgqa] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $sgqa . "':'" . $jsVarName . "'");
|
|
|
|
$this->varNameAttr[$jsVarName] = "'" . $jsVarName . "':{ "
|
|
. "'jsName':'" . $jsVarName
|
|
. "','jsName_on':'" . $jsVarName_on
|
|
. "','sgqa':'" . $sgqa
|
|
. "','qid':" . $questionNum
|
|
. ",'gid':" . $groupNum
|
|
// . ",'mandatory':'" . $mandatory
|
|
// . "','question':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$question),ENT_QUOTES)
|
|
. ",'type':'" . $type
|
|
// . "','relevance':'" . (($relevance != '') ? htmlspecialchars(preg_replace('/[[:space:]]/',' ',$relevance),ENT_QUOTES) : 1)
|
|
// . "','readWrite':'" . $readWrite
|
|
// . "','grelevance':'" . (($grelevance != '') ? htmlspecialchars(preg_replace('/[[:space:]]/',' ',$grelevance),ENT_QUOTES) : 1)
|
|
. "','default':'" . (is_null($defaultValue) ? '' : str_replace("'", "\'", $defaultValue))
|
|
. "','rowdivid':'" . (is_null($rowdivid) ? '' : $rowdivid)
|
|
. "','onlynum':'" . ($onlynum ? '1' : '')
|
|
. "','gseq':" . $groupSeq
|
|
// . ",'qseq':" . $questionSeq
|
|
.$ansList;
|
|
|
|
if ($type == 'M' || $type == 'P')
|
|
{
|
|
$this->varNameAttr[$jsVarName] .= ",'question':'" . htmlspecialchars(preg_replace('/[[:space:]]/',' ',$question),ENT_QUOTES) . "'";
|
|
}
|
|
$this->varNameAttr[$jsVarName] .= "}";
|
|
}
|
|
|
|
$this->q2subqInfo = $q2subqInfo;
|
|
|
|
// Now set tokens
|
|
if (Survey::model()->hasTokens($surveyid) && isset($_SESSION[$this->sessid]['token']) && $_SESSION[$this->sessid]['token'] != '')
|
|
{
|
|
//Gather survey data for tokenised surveys, for use in presenting questions
|
|
$this->knownVars['TOKEN:TOKEN'] = array(
|
|
'code'=>$_SESSION[$this->sessid]['token'],
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
|
|
$token = Token::model($surveyid)->findByToken($_SESSION[$this->sessid]['token']);
|
|
foreach ($token as $key => $val)
|
|
{
|
|
$this->knownVars["TOKEN:" . strtoupper($key)] = array(
|
|
'code' => $anonymized ? '' : $val,
|
|
'jsName_on' => '',
|
|
'jsName' => '',
|
|
'readWrite' => 'N',
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read list of available tokens from the tokens table so that preview and error checking works correctly
|
|
$attrs = array_keys(getTokenFieldsAndNames($surveyid));
|
|
|
|
$blankVal = array(
|
|
'code'=>'',
|
|
'type'=>'',
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
|
|
foreach ($attrs as $key)
|
|
{
|
|
if (preg_match('/^(firstname|lastname|email|usesleft|token|attribute_\d+)$/',$key))
|
|
{
|
|
$this->knownVars['TOKEN:' . strtoupper($key)] = $blankVal;
|
|
}
|
|
}
|
|
}
|
|
// set default value for reserved 'this' variable
|
|
$this->knownVars['this'] = array(
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'',
|
|
'hidden'=>'',
|
|
'question'=>'this',
|
|
'qid'=>'',
|
|
'gid'=>'',
|
|
'grelevance'=>'',
|
|
'relevance'=>'',
|
|
'qcode'=>'this',
|
|
'qseq'=>'',
|
|
'gseq'=>'',
|
|
'type'=>'',
|
|
'sgqa'=>'',
|
|
'rowdivid'=>'',
|
|
'ansList'=>'',
|
|
'ansArray'=>array(),
|
|
'scale_id'=>'',
|
|
'default'=>'',
|
|
'rootVarName'=>'this',
|
|
'subqtext'=>'',
|
|
'rowdivid'=>'',
|
|
);
|
|
|
|
$this->runtimeTimings[] = array(__METHOD__ . ' - process fieldMap',(microtime(true) - $now));
|
|
usort($this->questionSeq2relevance,'cmpQuestionSeq');
|
|
$this->numQuestions = count($this->questionSeq2relevance);
|
|
$this->numGroups = count($this->groupSeqInfo);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return whether a sub-question is relevant
|
|
* @param <type> $sgqa
|
|
* @return <boolean>
|
|
*/
|
|
static function SubQuestionIsRelevant($sgqa)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if (!isset($LEM->knownVars[$sgqa]))
|
|
{
|
|
return false;
|
|
}
|
|
$var = $LEM->knownVars[$sgqa];
|
|
$sqrel=1;
|
|
if (isset($var['rowdivid']) && $var['rowdivid'] != '')
|
|
{
|
|
$sqrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$var['rowdivid']]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$var['rowdivid']] : 1);
|
|
}
|
|
$qid = $var['qid'];
|
|
$qrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
|
|
$gseq = $var['gseq'];
|
|
$grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
|
|
return ($grel && $qrel && $sqrel);
|
|
}
|
|
|
|
/**
|
|
* Return whether question $qid is relevanct
|
|
* @param <type> $qid
|
|
* @return boolean
|
|
*/
|
|
static function QuestionIsRelevant($qid)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$qrel = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
|
|
$gseq = (isset($LEM->questionId2groupSeq[$qid]) ? $LEM->questionId2groupSeq[$qid] : -1);
|
|
$grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
|
|
return ($grel && $qrel);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the group is relevant and should be shown
|
|
*
|
|
* @param int $gid
|
|
* @return boolean
|
|
*/
|
|
static function GroupIsRelevant($gid)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$gseq = $LEM->GetGroupSeq($gid);
|
|
return !$LEM->GroupIsIrrelevantOrHidden($gseq);
|
|
}
|
|
|
|
/**
|
|
* Return whether group $gseq is relevant
|
|
* @param <type> $gseq
|
|
* @return boolean
|
|
*/
|
|
static function GroupIsIrrelevantOrHidden($gseq)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$grel = (isset($_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq]) ? $_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] : 1); // group-level relevance based upon grelevance equation
|
|
$gshow = (isset($LEM->indexGseq[$gseq]['show']) ? $LEM->indexGseq[$gseq]['show'] : true); // default to true?
|
|
return !($grel && $gshow);
|
|
}
|
|
|
|
/**
|
|
* Check the relevance status of all questions on or before the current group.
|
|
* This generates needed JavaScript for dynamic relevance, and sets flags about which questions and groups are relevant
|
|
*/
|
|
function ProcessAllNeededRelevance($onlyThisQseq=NULL)
|
|
{
|
|
// 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?
|
|
// $now = microtime(true);
|
|
|
|
$grelComputed=array(); // so only process it once per group
|
|
foreach($this->questionSeq2relevance as $rel)
|
|
{
|
|
if (!is_null($onlyThisQseq) && $onlyThisQseq!=$rel['qseq']) {
|
|
continue;
|
|
}
|
|
$qid = $rel['qid'];
|
|
$gseq = $rel['gseq'];
|
|
if ($this->allOnOnePage) {
|
|
; // process relevance for all questions
|
|
}
|
|
else if ($gseq != $this->currentGroupSeq) {
|
|
continue;
|
|
}
|
|
$result = $this->_ProcessRelevance(htmlspecialchars_decode($rel['relevance'],ENT_QUOTES),
|
|
$qid,
|
|
$gseq,
|
|
$rel['jsResultVar'],
|
|
$rel['type'],
|
|
$rel['hidden']
|
|
);
|
|
$_SESSION[$this->sessid]['relevanceStatus'][$qid] = $result;
|
|
|
|
if (!isset($grelComputed[$gseq])) {
|
|
$this->_ProcessGroupRelevance($gseq);
|
|
$grelComputed[$gseq]=true;
|
|
}
|
|
}
|
|
// $this->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
}
|
|
|
|
/**
|
|
* Translate all Expressions, Macros, registered variables, etc. in $string
|
|
* @param <type> $string - the string to be replaced
|
|
* @param <type> $questionNum - the $qid of question being replaced - needed for properly alignment of question-level relevance and tailoring
|
|
* @param <type> $replacementFields - optional replacement values
|
|
* @param <boolean> $debug - if true,write translations for this page to html-formatted log file
|
|
* @param <type> $numRecursionLevels - the number of times to recursively subtitute values in this string
|
|
* @param <type> $whichPrettyPrintIteration - if want to pretty-print the source string, which recursion level should be pretty-printed
|
|
* @param <type> $noReplacements - true if we already know that no replacements are needed (e.g. there are no curly braces)
|
|
* @return <type> - the original $string with all replacements done.
|
|
*/
|
|
|
|
static function ProcessString($string, $questionNum=NULL, $replacementFields=array(), $debug=false, $numRecursionLevels=1, $whichPrettyPrintIteration=1, $noReplacements=false, $timeit=true, $staticReplacement=false)
|
|
{
|
|
$now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
if ($noReplacements) {
|
|
$LEM->em->SetPrettyPrintSource($string);
|
|
return $string;
|
|
}
|
|
|
|
if (isset($replacementFields) && is_array($replacementFields) && count($replacementFields) > 0)
|
|
{
|
|
$replaceArray = array();
|
|
foreach ($replacementFields as $key => $value) {
|
|
$replaceArray[$key] = array(
|
|
'code'=>$value,
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
}
|
|
$LEM->tempVars = $replaceArray;
|
|
}
|
|
$questionSeq = -1;
|
|
$groupSeq = -1;
|
|
if (!is_null($questionNum)) {
|
|
$questionSeq = isset($LEM->questionId2questionSeq[$questionNum]) ? $LEM->questionId2questionSeq[$questionNum] : -1;
|
|
$groupSeq = isset($LEM->questionId2groupSeq[$questionNum]) ? $LEM->questionId2groupSeq[$questionNum] : -1;
|
|
}
|
|
$stringToParse = $string; // decode called later htmlspecialchars_decode($string,ENT_QUOTES);
|
|
$qnum = is_null($questionNum) ? 0 : $questionNum;
|
|
$result = $LEM->em->sProcessStringContainingExpressions($stringToParse,$qnum, $numRecursionLevels, $whichPrettyPrintIteration, $groupSeq, $questionSeq, $staticReplacement);
|
|
|
|
if ($timeit) {
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute Relevance, processing $eqn to get a boolean value. If there are syntax errors, return false.
|
|
* @param <type> $eqn - the relevance equation
|
|
* @param <type> $questionNum - needed to align question-level relevance and tailoring
|
|
* @param <type> $jsResultVar - this variable determines whether irrelevant questions are hidden
|
|
* @param <type> $type - question type
|
|
* @param <type> $hidden - whether question should always be hidden
|
|
* @return <type>
|
|
*/
|
|
static function ProcessRelevance($eqn,$questionNum=NULL,$jsResultVar=NULL,$type=NULL,$hidden=0)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->_ProcessRelevance($eqn,$questionNum,NULL,$jsResultVar,$type,$hidden);
|
|
}
|
|
|
|
/**
|
|
* Compute Relevance, processing $eqn to get a boolean value. If there are syntax errors, return false.
|
|
* @param <type> $eqn - the relevance equation
|
|
* @param <type> $questionNum - needed to align question-level relevance and tailoring
|
|
* @param <type> $jsResultVar - this variable determines whether irrelevant questions are hidden
|
|
* @param <type> $type - question type
|
|
* @param <type> $hidden - whether question should always be hidden
|
|
* @return <type>
|
|
*/
|
|
private function _ProcessRelevance($eqn,$questionNum=NULL,$gseq=NULL,$jsResultVar=NULL,$type=NULL,$hidden=0)
|
|
{
|
|
// These will be called in the order that questions are supposed to be asked
|
|
// TODO - cache results and generated JavaScript equations?
|
|
if (!isset($eqn) || trim($eqn=='') || trim($eqn)=='1')
|
|
{
|
|
$this->groupRelevanceInfo[] = array(
|
|
'qid' => $questionNum,
|
|
'gseq' => $gseq,
|
|
'eqn' => $eqn,
|
|
'result' => true,
|
|
'numJsVars' => 0,
|
|
'relevancejs' => '',
|
|
'relevanceVars' => '',
|
|
'jsResultVar'=> $jsResultVar,
|
|
'type'=>$type,
|
|
'hidden'=>$hidden,
|
|
'hasErrors'=>false,
|
|
);
|
|
return true;
|
|
}
|
|
$questionSeq = -1;
|
|
$groupSeq = -1;
|
|
if (!is_null($questionNum)) {
|
|
$questionSeq = isset($this->questionId2questionSeq[$questionNum]) ? $this->questionId2questionSeq[$questionNum] : -1;
|
|
$groupSeq = isset($this->questionId2groupSeq[$questionNum]) ? $this->questionId2groupSeq[$questionNum] : -1;
|
|
}
|
|
|
|
$stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
|
|
$result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq, $questionSeq);
|
|
$hasErrors = $this->em->HasErrors();
|
|
|
|
if (!is_null($questionNum) && !is_null($jsResultVar)) { // so if missing either, don't generate JavaScript for this - means off-page relevance.
|
|
$jsVars = $this->em->GetJSVarsUsed();
|
|
$relevanceVars = implode('|',$this->em->GetJSVarsUsed());
|
|
$relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
$this->groupRelevanceInfo[] = array(
|
|
'qid' => $questionNum,
|
|
'gseq' => $gseq,
|
|
'eqn' => $eqn,
|
|
'result' => $result,
|
|
'numJsVars' => count($jsVars),
|
|
'relevancejs' => $relevanceJS,
|
|
'relevanceVars' => $relevanceVars,
|
|
'jsResultVar' => $jsResultVar,
|
|
'type'=>$type,
|
|
'hidden'=>$hidden,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Create JavaScript needed to process sub-question-level relevance (e.g. for array_filter and _exclude)
|
|
* @param <type> $eqn - the equation to parse
|
|
* @param <type> $questionNum - the question number - needed to align relavance and tailoring blocks
|
|
* @param <type> $rowdivid - the javascript ID that needs to be shown/hidden in order to control array_filter visibility
|
|
* @param <type> $type - the type of sub-question relevance (e.g. 'array_filter', 'array_filter_exclude')
|
|
* @return <type>
|
|
*/
|
|
private function _ProcessSubQRelevance($eqn,$questionNum=NULL,$rowdivid=NULL, $type=NULL, $qtype=NULL, $sgqa=NULL, $isExclusive='', $irrelevantAndExclusive='')
|
|
{
|
|
// These will be called in the order that questions are supposed to be asked
|
|
if (!isset($eqn) || trim($eqn=='') || trim($eqn)=='1')
|
|
{
|
|
return true;
|
|
}
|
|
$questionSeq = -1;
|
|
$groupSeq = -1;
|
|
if (!is_null($questionNum)) {
|
|
$questionSeq = isset($this->questionId2questionSeq[$questionNum]) ? $this->questionId2questionSeq[$questionNum] : -1;
|
|
$groupSeq = isset($this->questionId2groupSeq[$questionNum]) ? $this->questionId2groupSeq[$questionNum] : -1;
|
|
}
|
|
|
|
$stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
|
|
$result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq, $questionSeq);
|
|
$hasErrors = $this->em->HasErrors();
|
|
$prettyPrint = '';
|
|
if (($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX) {
|
|
$prettyPrint= $this->em->GetPrettyPrintString();
|
|
}
|
|
|
|
if (!is_null($questionNum)) {
|
|
// make sure subquestions with errors in relevance equations are always shown and answers recorded #7703
|
|
if ($hasErrors)
|
|
{
|
|
$result=true;
|
|
$relevanceJS=1;
|
|
}
|
|
else
|
|
{
|
|
$relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
}
|
|
$jsVars = $this->em->GetJSVarsUsed();
|
|
$relevanceVars = implode('|',$this->em->GetJSVarsUsed());
|
|
$isExclusiveJS='';
|
|
$irrelevantAndExclusiveJS='';
|
|
// Only need to extract JS, since will already have Vars and error counts from main equation
|
|
if ($isExclusive != '')
|
|
{
|
|
$this->em->ProcessBooleanExpression($isExclusive,$groupSeq, $questionSeq);
|
|
$isExclusiveJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
}
|
|
if ($irrelevantAndExclusive != '')
|
|
{
|
|
$this->em->ProcessBooleanExpression($irrelevantAndExclusive,$groupSeq, $questionSeq);
|
|
$irrelevantAndExclusiveJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
}
|
|
|
|
if (!isset($this->subQrelInfo[$questionNum])) {
|
|
$this->subQrelInfo[$questionNum] = array();
|
|
}
|
|
$this->subQrelInfo[$questionNum][$rowdivid] = array(
|
|
'qid' => $questionNum,
|
|
'eqn' => $eqn,
|
|
'prettyPrintEqn' => $prettyPrint,
|
|
'result' => $result,
|
|
'numJsVars' => count($jsVars),
|
|
'relevancejs' => $relevanceJS,
|
|
'relevanceVars' => $relevanceVars,
|
|
'rowdivid' => $rowdivid,
|
|
'type'=>$type,
|
|
'qtype'=>$qtype,
|
|
'sgqa'=>$sgqa,
|
|
'hasErrors'=>$hasErrors,
|
|
'isExclusiveJS'=>$isExclusiveJS,
|
|
'irrelevantAndExclusiveJS'=>$irrelevantAndExclusiveJS,
|
|
);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
private function _ProcessGroupRelevance($groupSeq)
|
|
{
|
|
// These will be called in the order that questions are supposed to be asked
|
|
if ($groupSeq == -1) {
|
|
return; // invalid group, so ignore
|
|
}
|
|
|
|
$eqn = (isset($this->gseq2info[$groupSeq]['grelevance']) ? $this->gseq2info[$groupSeq]['grelevance'] : 1);
|
|
if (is_null($eqn) || trim($eqn=='') || trim($eqn)=='1')
|
|
{
|
|
$this->gRelInfo[$groupSeq] = array(
|
|
'gseq' => $groupSeq,
|
|
'eqn' => '',
|
|
'result' => 1,
|
|
'numJsVars' => 0,
|
|
'relevancejs' => '',
|
|
'relevanceVars' => '',
|
|
'prettyprint'=> '',
|
|
);
|
|
$_SESSION[$this->sessid]['relevanceStatus']['G' . $groupSeq] = 1;
|
|
return;
|
|
}
|
|
$stringToParse = htmlspecialchars_decode($eqn,ENT_QUOTES);
|
|
$result = $this->em->ProcessBooleanExpression($stringToParse,$groupSeq);
|
|
$hasErrors = $this->em->HasErrors();
|
|
|
|
$jsVars = $this->em->GetJSVarsUsed();
|
|
$relevanceVars = implode('|',$this->em->GetJSVarsUsed());
|
|
$relevanceJS = $this->em->GetJavaScriptEquivalentOfExpression();
|
|
$prettyPrint = $this->em->GetPrettyPrintString();
|
|
|
|
$this->gRelInfo[$groupSeq] = array(
|
|
'gseq' => $groupSeq,
|
|
'eqn' => $stringToParse,
|
|
'result' => $result,
|
|
'numJsVars' => count($jsVars),
|
|
'relevancejs' => $relevanceJS,
|
|
'relevanceVars' => $relevanceVars,
|
|
'prettyprint'=> $prettyPrint,
|
|
'hasErrors' => $hasErrors,
|
|
);
|
|
$_SESSION[$this->sessid]['relevanceStatus']['G' . $groupSeq] = $result;
|
|
}
|
|
|
|
/**
|
|
* Used to show potential syntax errors of processing Relevance or Equations.
|
|
* @return <type>
|
|
*/
|
|
static function GetLastPrettyPrintExpression()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->em->GetLastPrettyPrintExpression();
|
|
}
|
|
|
|
/**
|
|
* Expand "self.suffix" and "that.qcode.suffix" into canonical list of variable names
|
|
* @param type $qseq
|
|
* @param type $varname
|
|
*/
|
|
static function GetAllVarNamesForQ($qseq,$varname)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$parts = explode('.',$varname);
|
|
$qroot = '';
|
|
$suffix = '';
|
|
$sqpatts = array();
|
|
$nosqpatts = array();
|
|
$sqpatt = '';
|
|
$nosqpatt = '';
|
|
$comments = '';
|
|
|
|
if ($parts[0] == 'self')
|
|
{
|
|
$type = 'self';
|
|
}
|
|
else
|
|
{
|
|
$type = 'that';
|
|
array_shift($parts);
|
|
if (isset($parts[0]))
|
|
{
|
|
$qroot = $parts[0];
|
|
}
|
|
else
|
|
{
|
|
return $varname;
|
|
}
|
|
}
|
|
array_shift($parts);
|
|
|
|
if (count($parts) > 0)
|
|
{
|
|
if (preg_match('/^' . ExpressionManager::$RDP_regex_var_attr . '$/',$parts[count($parts)-1]))
|
|
{
|
|
$suffix = '.' . $parts[count($parts)-1];
|
|
array_pop($parts);
|
|
}
|
|
}
|
|
|
|
foreach($parts as $part)
|
|
{
|
|
if ($part == 'nocomments')
|
|
{
|
|
$comments = 'N';
|
|
}
|
|
else if ($part == 'comments')
|
|
{
|
|
$comments = 'Y';
|
|
}
|
|
else if (preg_match('/^sq_.+$/',$part))
|
|
{
|
|
$sqpatts[] = substr($part,3);
|
|
}
|
|
else if (preg_match('/^nosq_.+$/',$part))
|
|
{
|
|
$nosqpatts[] = substr($part,5);
|
|
}
|
|
else
|
|
{
|
|
return $varname; // invalid
|
|
}
|
|
}
|
|
$sqpatt = implode('|',$sqpatts);
|
|
$nosqpatt = implode('|',$nosqpatts);
|
|
$vars = array();
|
|
if(isset($LEM->knownVars))
|
|
{
|
|
foreach ($LEM->knownVars as $kv)
|
|
{
|
|
if ($type == 'self')
|
|
{
|
|
if (!isset($kv['qseq']) || $kv['qseq'] != $qseq || trim($kv['sgqa']) == '')
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isset($kv['rootVarName']) || $kv['rootVarName'] != $qroot)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
if ($comments != '')
|
|
{
|
|
if ($comments == 'Y' && !preg_match('/comment$/',$kv['sgqa']))
|
|
{
|
|
continue;
|
|
}
|
|
if ($comments == 'N' && preg_match('/comment$/',$kv['sgqa']))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
$sgq = $LEM->sid . 'X' . $kv['gid'] . 'X' . $kv['qid'];
|
|
$ext = substr($kv['sgqa'],strlen($sgq));
|
|
|
|
if ($sqpatt != '')
|
|
{
|
|
if (!preg_match('/'.$sqpatt.'/',$ext))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
if ($nosqpatt != '')
|
|
{
|
|
if (preg_match('/'.$nosqpatt.'/',$ext))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$vars[] = $kv['sgqa'] . $suffix;
|
|
}
|
|
}
|
|
if (count($vars) > 0)
|
|
{
|
|
return implode(',',$vars);
|
|
}
|
|
return $varname; // invalid
|
|
}
|
|
|
|
/**
|
|
* Should be first function called on each page - sets/clears internally needed variables
|
|
* @param <type> $allOnOnePage - true if StartProcessingGroup will be called multiple times on this page - does some optimizatinos
|
|
* @param <type> $rooturl - if set, this tells LEM to enable hyperlinking of syntax highlighting to ease editing of questions
|
|
* @param <boolean> $initializeVars - if true, initializes the replacement variables to enable syntax highlighting on admin pages
|
|
*/
|
|
static function StartProcessingPage($allOnOnePage=false,$initializeVars=false)
|
|
{
|
|
// $now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->pageRelevanceInfo=array();
|
|
$LEM->pageTailorInfo=array();
|
|
$LEM->allOnOnePage=$allOnOnePage;
|
|
$LEM->processedRelevance=false;
|
|
$LEM->surveyOptions['hyperlinkSyntaxHighlighting']=true; // this will be temporary - should be reset in running survey
|
|
$LEM->qid2exclusiveAuto=array();
|
|
|
|
$surveyinfo = (isset($LEM->sid) ? getSurveyInfo($LEM->sid) : null);
|
|
if (isset($surveyinfo['assessments']) && $surveyinfo['assessments']=='Y')
|
|
{
|
|
$LEM->surveyOptions['assessments']=true;
|
|
}
|
|
// $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
|
|
$LEM->initialized=true;
|
|
|
|
if ($initializeVars)
|
|
{
|
|
$LEM->em->StartProcessingGroup(
|
|
isset($_SESSION['LEMsid']) ? $_SESSION['LEMsid'] : NULL,
|
|
'',
|
|
true
|
|
);
|
|
$LEM->setVariableAndTokenMappingsForExpressionManager($_SESSION['LEMsid']);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Initialize a survey so can use EM to manage navigation
|
|
* @param int $surveyid
|
|
* @param string $surveyMode
|
|
* @param array $aSurveyOptions
|
|
* @param bool $forceRefresh
|
|
* @param int $debugLevel
|
|
*/
|
|
static function StartSurvey($surveyid,$surveyMode='group',$aSurveyOptions=NULL,$forceRefresh=false,$debugLevel=0)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->sid=sanitize_int($surveyid);
|
|
$LEM->sessid = 'survey_' . $LEM->sid;
|
|
$LEM->em->StartProcessingGroup($surveyid);
|
|
if (is_null($aSurveyOptions)) {
|
|
$aSurveyOptions = array();
|
|
}
|
|
$LEM->surveyOptions['active'] = (isset($aSurveyOptions['active']) ? $aSurveyOptions['active'] : false);
|
|
$LEM->surveyOptions['allowsave'] = (isset($aSurveyOptions['allowsave']) ? $aSurveyOptions['allowsave'] : false);
|
|
$LEM->surveyOptions['anonymized'] = (isset($aSurveyOptions['anonymized']) ? $aSurveyOptions['anonymized'] : false);
|
|
$LEM->surveyOptions['assessments'] = (isset($aSurveyOptions['assessments']) ? $aSurveyOptions['assessments'] : false);
|
|
$LEM->surveyOptions['datestamp'] = (isset($aSurveyOptions['datestamp']) ? $aSurveyOptions['datestamp'] : false);
|
|
$LEM->surveyOptions['deletenonvalues'] = (isset($aSurveyOptions['deletenonvalues']) ? ($aSurveyOptions['deletenonvalues']=='1') : true);
|
|
$LEM->surveyOptions['hyperlinkSyntaxHighlighting'] = (isset($aSurveyOptions['hyperlinkSyntaxHighlighting']) ? $aSurveyOptions['hyperlinkSyntaxHighlighting'] : false);
|
|
$LEM->surveyOptions['ipaddr'] = (isset($aSurveyOptions['ipaddr']) ? $aSurveyOptions['ipaddr'] : false);
|
|
$LEM->surveyOptions['radix'] = (isset($aSurveyOptions['radix']) ? $aSurveyOptions['radix'] : '.');
|
|
$LEM->surveyOptions['refurl'] = (isset($aSurveyOptions['refurl']) ? $aSurveyOptions['refurl'] : NULL);
|
|
$LEM->surveyOptions['savetimings'] = (isset($aSurveyOptions['savetimings']) ? $aSurveyOptions['savetimings'] : '');
|
|
$LEM->sgqaNaming = (isset($aSurveyOptions['sgqaNaming']) ? ($aSurveyOptions['sgqaNaming']=="Y") : true); // TODO default should eventually be false
|
|
$LEM->surveyOptions['startlanguage'] = (isset($aSurveyOptions['startlanguage']) ? $aSurveyOptions['startlanguage'] : 'en');
|
|
$LEM->surveyOptions['surveyls_dateformat'] = (isset($aSurveyOptions['surveyls_dateformat']) ? $aSurveyOptions['surveyls_dateformat'] : 1);
|
|
$LEM->surveyOptions['tablename'] = (isset($aSurveyOptions['tablename']) ? $aSurveyOptions['tablename'] : '{{survey_' . $LEM->sid . '}}');
|
|
$LEM->surveyOptions['tablename_timings'] = ((isset($aSurveyOptions['savetimings']) && $aSurveyOptions['savetimings'] == 'Y') ? '{{survey_' . $LEM->sid . '_timings}}' : '');
|
|
$LEM->surveyOptions['target'] = (isset($aSurveyOptions['target']) ? $aSurveyOptions['target'] : '/temp/files/');
|
|
$LEM->surveyOptions['timeadjust'] = (isset($aSurveyOptions['timeadjust']) ? $aSurveyOptions['timeadjust'] : 0);
|
|
$LEM->surveyOptions['tempdir'] = (isset($aSurveyOptions['tempdir']) ? $aSurveyOptions['tempdir'] : '/temp/');
|
|
$LEM->surveyOptions['token'] = (isset($aSurveyOptions['token']) ? $aSurveyOptions['token'] : NULL);
|
|
|
|
$LEM->debugLevel=$debugLevel;
|
|
$_SESSION[$LEM->sessid]['LEMdebugLevel']=$debugLevel; // need acces to SESSSION to decide whether to cache serialized instance of $LEM
|
|
switch ($surveyMode) {
|
|
case 'survey':
|
|
$LEM->allOnOnePage=true;
|
|
$LEM->surveyMode = 'survey';
|
|
break;
|
|
case 'question':
|
|
$LEM->allOnOnePage=false;
|
|
$LEM->surveyMode = 'question';
|
|
break;
|
|
default:
|
|
case 'group':
|
|
$LEM->allOnOnePage=false;
|
|
$LEM->surveyMode = 'group';
|
|
break;
|
|
}
|
|
|
|
$LEM->setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh,$LEM->surveyOptions['anonymized'],$LEM->allOnOnePage);
|
|
$LEM->currentGroupSeq=-1;
|
|
$LEM->currentQuestionSeq=-1; // for question-by-question mode
|
|
$LEM->indexGseq=array();
|
|
$LEM->indexQseq=array();
|
|
$LEM->qrootVarName2arrayFilter=array();
|
|
templatereplace("{}"); // Needed for coreReplacements in relevance equation (in all mode)
|
|
if (isset($_SESSION[$LEM->sessid]['startingValues']) && is_array($_SESSION[$LEM->sessid]['startingValues']) && count($_SESSION[$LEM->sessid]['startingValues']) > 0)
|
|
{
|
|
$startingValues = array();
|
|
foreach ($_SESSION[$LEM->sessid]['startingValues'] as $k=>$value)
|
|
{
|
|
if (isset($LEM->knownVars[$k]))
|
|
{
|
|
$knownVar = $LEM->knownVars[$k];
|
|
}
|
|
else if (isset($LEM->qcode2sgqa[$k]))
|
|
{
|
|
$knownVar = $LEM->knownVars[$LEM->qcode2sgqa[$k]];
|
|
}
|
|
else if (isset($LEM->tempVars[$k]))
|
|
{
|
|
$knownVar = $LEM->tempVar[$k];
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
if (!isset($knownVar['jsName']))
|
|
{
|
|
continue;
|
|
}
|
|
switch ($knownVar['type'])
|
|
{
|
|
case 'D': //DATE
|
|
if (trim($value)=="")
|
|
{
|
|
$value = NULL;
|
|
}
|
|
elseif ($value!='INVALID')
|
|
{
|
|
$dateformatdatat=getDateFormatData($LEM->surveyOptions['surveyls_dateformat']);
|
|
$datetimeobj = new Date_Time_Converter($value, $dateformatdatat['phpdate']);
|
|
$value=$datetimeobj->convert("Y-m-d H:i");
|
|
}
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if (trim($value)=="") {
|
|
$value = NULL;
|
|
}
|
|
else {
|
|
$value = sanitize_float($value);
|
|
}
|
|
break;
|
|
case '|': //File Upload
|
|
$value=NULL; // can't upload a file via GET
|
|
break;
|
|
}
|
|
$_SESSION[$LEM->sessid][$knownVar['sgqa']] = $value;
|
|
$LEM->updatedValues[$knownVar['sgqa']]=array(
|
|
'type'=>$knownVar['type'],
|
|
'value'=>$value,
|
|
);
|
|
}
|
|
$LEM->_UpdateValuesInDatabase(NULL);
|
|
}
|
|
|
|
return array(
|
|
'hasNext'=>true,
|
|
'hasPrevious'=>false,
|
|
);
|
|
}
|
|
|
|
static function NavigateBackwards()
|
|
{
|
|
$now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
|
|
$LEM->updatedValues = array();
|
|
|
|
switch ($LEM->surveyMode)
|
|
{
|
|
case 'survey':
|
|
// should never be called?
|
|
break;
|
|
case 'group':
|
|
// First validate the current group
|
|
$LEM->StartProcessingPage();
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
$message = '';
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (is_null($LEM->currentGroupSeq)) {$LEM->currentGroupSeq=0;} // If moving backwards in preview mode and a question was removed then $LEM->currentGroupSeq is NULL and an endless loop occurs.
|
|
if (--$LEM->currentGroupSeq < 0)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'at_start'=>true,
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
$result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
|
|
if (is_null($result)) {
|
|
continue; // this is an invalid group - skip it
|
|
}
|
|
$message .= $result['message'];
|
|
if (!$result['relevant'] || $result['hidden'])
|
|
{
|
|
// then skip this group - assume already saved?
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new group
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'at_start'=>false,
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
break;
|
|
case 'question':
|
|
$LEM->StartProcessingPage();
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
$message = '';
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (--$LEM->currentQuestionSeq < 0)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'at_start'=>true,
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
// Set certain variables normally set by StartProcessingGroup()
|
|
$LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
|
|
$qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
|
|
$LEM->currentQID=$qInfo['qid'];
|
|
$LEM->currentGroupSeq=$qInfo['gseq'];
|
|
if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
|
|
$LEM->maxGroupSeq = $LEM->currentGroupSeq;
|
|
}
|
|
|
|
$LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
|
|
$LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
|
|
$result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
|
|
$message .= $result['message'];
|
|
$gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
|
|
$grel = $gRelInfo['result'];
|
|
|
|
if (!$grel || !$result['relevant'] || $result['hidden'])
|
|
{
|
|
// then skip this question - assume already saved?
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new question
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
return array(
|
|
'at_start'=>false,
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param <type> $force - if true, continue to go forward even if there are violations to the mandatory and/or validity rules
|
|
*/
|
|
static function NavigateForwards($force=false) {
|
|
$now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
|
|
$LEM->updatedValues = array();
|
|
|
|
switch ($LEM->surveyMode)
|
|
{
|
|
case 'survey':
|
|
$startingGroup = $LEM->currentGroupSeq;
|
|
$LEM->StartProcessingPage(true);
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
$message = '';
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
$result = $LEM->_ValidateSurvey();
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
if (!$force && !is_null($result) && ($result['mandViolation'] || !$result['valid'] || $startingGroup == -1))
|
|
{
|
|
$finished=false;
|
|
}
|
|
else
|
|
{
|
|
$finished = true;
|
|
}
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,$finished);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>$finished,
|
|
'message'=>$message,
|
|
'gseq'=>1,
|
|
'seq'=>1,
|
|
'mandViolation'=>$result['mandViolation'],
|
|
'valid'=>$result['valid'],
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
break;
|
|
case 'group':
|
|
// First validate the current group
|
|
$LEM->StartProcessingPage();
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
$message = '';
|
|
if (!$force && $LEM->currentGroupSeq != -1)
|
|
{
|
|
$result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
if (!is_null($result) && ($result['mandViolation'] || !$result['valid']))
|
|
{
|
|
// redisplay the current group
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=>$result['mandViolation'],
|
|
'valid'=>$result['valid'],
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (++$LEM->currentGroupSeq >= $LEM->numGroups)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>true,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=>(isset($result['mandViolation']) ? $result['mandViolation'] : false),
|
|
'valid'=>(isset($result['valid']) ? $result['valid'] : false),
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
$result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
|
|
if (is_null($result)) {
|
|
continue; // this is an invalid group - skip it
|
|
}
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
if (!$result['relevant'] || $result['hidden'])
|
|
{
|
|
// then skip this group
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new group
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
break;
|
|
case 'question':
|
|
$LEM->StartProcessingPage();
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
$message = '';
|
|
if (!$force && $LEM->currentQuestionSeq != -1)
|
|
{
|
|
$result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
$gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
|
|
$grel = $gRelInfo['result'];
|
|
|
|
if ($grel && !is_null($result) && ($result['mandViolation'] || !$result['valid']))
|
|
{
|
|
// redisplay the current question
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (++$LEM->currentQuestionSeq >= $LEM->numQuestions)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>true,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
// Set certain variables normally set by StartProcessingGroup()
|
|
$LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
|
|
$qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
|
|
$LEM->currentQID=$qInfo['qid'];
|
|
$LEM->currentGroupSeq=$qInfo['gseq'];
|
|
if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
|
|
$LEM->maxGroupSeq = $LEM->currentGroupSeq;
|
|
}
|
|
|
|
$LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
|
|
$LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
|
|
$result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
$gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
|
|
$grel = $gRelInfo['result'];
|
|
|
|
if (!$grel || !$result['relevant'] || $result['hidden'])
|
|
{
|
|
// then skip this question - assume already saved?
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new question
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write values to database.
|
|
* @param <type> $updatedValues
|
|
* @param <boolean> $finished - true if the survey needs to be finalized
|
|
*/
|
|
private function _UpdateValuesInDatabase($updatedValues, $finished=false)
|
|
{
|
|
// TODO - now that using $this->updatedValues, may be able to remove local copies of it (unless needed by other sub-systems)
|
|
$updatedValues = $this->updatedValues;
|
|
$message = '';
|
|
if (!$this->surveyOptions['active'] || $this->sPreviewMode)
|
|
{
|
|
return $message;
|
|
}
|
|
if (!isset($_SESSION[$this->sessid]['srid']))// Create the response line, and fill Session with primaryKey
|
|
{
|
|
$_SESSION[$this->sessid]['datestamp']=dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']);
|
|
// Create initial insert row for this record
|
|
$today = dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']);
|
|
$sdata = array(
|
|
"startlanguage"=>$this->surveyOptions['startlanguage']
|
|
);
|
|
if ($this->surveyOptions['anonymized'] == false)
|
|
{
|
|
$sdata['token'] = $this->surveyOptions['token'];
|
|
}
|
|
if ($this->surveyOptions['datestamp'] == true)
|
|
{
|
|
$sdata['datestamp'] = $_SESSION[$this->sessid]['datestamp'];
|
|
$sdata['startdate'] = $_SESSION[$this->sessid]['datestamp'];
|
|
}
|
|
if ($this->surveyOptions['ipaddr'] == true)
|
|
{
|
|
$sdata['ipaddr'] = getIPAddress();
|
|
}
|
|
if ($this->surveyOptions['refurl'] == true)
|
|
{
|
|
$sdata['refurl'] = getenv("HTTP_REFERER");
|
|
}
|
|
|
|
$sdata = array_filter($sdata);
|
|
SurveyDynamic::sid($this->sid);
|
|
$oSurvey = new SurveyDynamic;
|
|
|
|
$iNewID = $oSurvey->insertRecords($sdata);
|
|
if ($iNewID) // Checked
|
|
{
|
|
$srid = $iNewID;
|
|
$_SESSION[$this->sessid]['srid'] = $iNewID;
|
|
}
|
|
else
|
|
{
|
|
$message .= $this->gT("Unable to insert record into survey table"); // TODO - add SQL error?
|
|
}
|
|
//Insert Row for Timings, if needed
|
|
if ($this->surveyOptions['savetimings']) {
|
|
SurveyTimingDynamic::sid($this->sid);
|
|
$oSurveyTimings = new SurveyTimingDynamic;
|
|
|
|
$tdata = array(
|
|
'id'=>$srid,
|
|
'interviewtime'=>0
|
|
);
|
|
switchMSSQLIdentityInsert("survey_{$this->sid}_timings", true);
|
|
$iNewID = $oSurveyTimings->insertRecords($tdata);
|
|
switchMSSQLIdentityInsert("survey_{$this->sid}_timings", false);
|
|
}
|
|
}
|
|
|
|
if (count($updatedValues) > 0 || $finished)
|
|
{
|
|
$query = 'UPDATE ' . $this->surveyOptions['tablename'] . ' SET ';
|
|
$setter = array();
|
|
switch ($this->surveyMode)
|
|
{
|
|
case 'question':
|
|
$thisstep = $this->currentQuestionSeq;
|
|
break;
|
|
case 'group':
|
|
$thisstep = $this->currentGroupSeq;
|
|
break;
|
|
case 'survey':
|
|
$thisstep = 1;
|
|
break;
|
|
}
|
|
$setter[] = dbQuoteID('lastpage') . "=" . dbQuoteAll($thisstep);
|
|
|
|
if ($this->surveyOptions['datestamp'] && isset($_SESSION[$this->sessid]['datestamp'])) {
|
|
$_SESSION[$this->sessid]['datestamp']=dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']);
|
|
$setter[] = dbQuoteID('datestamp') . "=" . dbQuoteAll(dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']));
|
|
}
|
|
if ($this->surveyOptions['ipaddr']) {
|
|
$setter[] = dbQuoteID('ipaddr') . "=" . dbQuoteAll(getIPAddress());
|
|
}
|
|
|
|
foreach ($updatedValues as $key=>$value)
|
|
{
|
|
$val = (is_null($value) ? NULL : $value['value']);
|
|
$type = (is_null($value) ? NULL : $value['type']);
|
|
|
|
// Clean up the values to cope with database storage requirements
|
|
switch($type)
|
|
{
|
|
case 'D': //DATE
|
|
if (trim($val)=='') {
|
|
$val=NULL; // since some databases can't store blanks in date fields
|
|
}
|
|
// otherwise will already be in yyyy-mm-dd format after ProcessCurrentResponses()
|
|
break;
|
|
case '|': //File upload
|
|
// This block can be removed once we require 5.3 or later
|
|
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
|
|
$val=addslashes($val);
|
|
}
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
if (trim($val)=='') {
|
|
$val=NULL; // since some databases can't store blanks in numerical inputs
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (is_null($val))
|
|
{
|
|
$setter[] = dbQuoteID($key) . "=NULL";
|
|
}
|
|
else
|
|
{
|
|
$setter[] = dbQuoteID($key) . "=" . dbQuoteAll($val);
|
|
}
|
|
}
|
|
$query .= implode(', ', $setter);
|
|
$query .= " WHERE ID=";
|
|
|
|
if (isset($_SESSION[$this->sessid]['srid']) && $this->surveyOptions['active'])
|
|
{
|
|
$query .= $_SESSION[$this->sessid]['srid'];
|
|
|
|
if (!dbExecuteAssoc($query))
|
|
{
|
|
echo submitfailed(''); // TODO - report SQL error?
|
|
|
|
if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
|
|
$message .= $this->gT('Error in SQL update'); // TODO - add SQL error?
|
|
}
|
|
}
|
|
// Save Timings if needed
|
|
elseif ($this->surveyOptions['savetimings']) {
|
|
Yii::import("application.libraries.Save");
|
|
$cSave = new Save();
|
|
$cSave->set_answer_time();
|
|
}
|
|
|
|
if ($finished)
|
|
{
|
|
// Delete the save control record if successfully finalize the submission
|
|
$query = "DELETE FROM {{saved_control}} where srid=".$_SESSION[$this->sessid]['srid'].' and sid='.$this->sid;
|
|
Yii::app()->db->createCommand($query)->execute();
|
|
|
|
if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
|
|
$message .= ';<br />'.$query;
|
|
}
|
|
|
|
}
|
|
else if ($this->surveyOptions['allowsave'] && isset($_SESSION[$this->sessid]['scid']))
|
|
{
|
|
SavedControl::model()->updateByPk($_SESSION[$this->sessid]['scid'], array('saved_thisstep'=>$thisstep));
|
|
}
|
|
// Check Quotas
|
|
$bQuotaMatched = false;
|
|
$aQuotas = checkQuota('return', $this->sid);
|
|
if ($aQuotas !== false)
|
|
{
|
|
if ($aQuotas != false)
|
|
{
|
|
foreach ($aQuotas as $aQuota)
|
|
{
|
|
if (isset($aQuota['status']) && $aQuota['status'] == 'matched') {
|
|
$bQuotaMatched = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($bQuotaMatched)
|
|
{
|
|
checkQuota('enforce',$this->sid); // will create a page and quit.
|
|
}
|
|
else
|
|
{
|
|
if ($finished) {
|
|
$sQuery = 'UPDATE '.$this->surveyOptions['tablename'] . " SET ";
|
|
if($this->surveyOptions['datestamp'])
|
|
{
|
|
// Replace with date("Y-m-d H:i:s") ? See timeadjust
|
|
$sQuery .= dbQuoteID('submitdate') . "=" . dbQuoteAll(dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", $this->surveyOptions['timeadjust']));
|
|
}
|
|
else
|
|
{
|
|
$sQuery .= dbQuoteID('submitdate') . "=" . dbQuoteAll(date("Y-m-d H:i:s",mktime(0,0,0,1,1,1980)));
|
|
}
|
|
$sQuery .= " WHERE ID=".$_SESSION[$this->sessid]['srid'];
|
|
dbExecuteAssoc($sQuery); // Checked
|
|
}
|
|
}
|
|
|
|
}
|
|
if (($this->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY) {
|
|
$message .= $query;
|
|
}
|
|
}
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Get last move information, optionally clearing the substitution cache
|
|
* @param type $clearSubstitutionInfo
|
|
* @return type
|
|
*/
|
|
static function GetLastMoveResult($clearSubstitutionInfo=false)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if ($clearSubstitutionInfo)
|
|
{
|
|
$LEM->em->ClearSubstitutionInfo(); // need to avoid double-generation of tailoring info
|
|
}
|
|
return (isset($LEM->lastMoveResult) ? $LEM->lastMoveResult : NULL);
|
|
}
|
|
|
|
/**
|
|
* Jump to a specific question or group sequence. If jumping forward, it re-validates everything in between
|
|
* @param <type> $seq
|
|
* @param <type> $force - if true, then skip validation of current group (e.g. will jump even if there are errors)
|
|
* @param <type> $preview - if true, then treat this group/question as relevant, even if it is not, so that it can be displayed
|
|
* @return <type>
|
|
*/
|
|
static function JumpTo($seq,$preview=false,$processPOST=true,$force=false,$changeLang=false) {
|
|
$now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
if(!$preview)
|
|
$preview=$LEM->sPreviewMode;
|
|
if(!$LEM->sPreviewMode && $preview)
|
|
$LEM->sPreviewMode=$preview;
|
|
|
|
if ($changeLang)
|
|
{
|
|
$LEM->setVariableAndTokenMappingsForExpressionManager($LEM->sid,true,$LEM->surveyOptions['anonymized'],$LEM->allOnOnePage);
|
|
}
|
|
|
|
$LEM->ParseResultCache=array(); // to avoid running same test more than once for a given group
|
|
$LEM->updatedValues = array();
|
|
--$seq; // convert to 0-based numbering
|
|
|
|
switch ($LEM->surveyMode)
|
|
{
|
|
case 'survey':
|
|
// This only happens if saving data so far, so don't want to submit it, just validate and return
|
|
$startingGroup = $LEM->currentGroupSeq;
|
|
$LEM->StartProcessingPage(true);
|
|
if ($processPOST) {
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
}
|
|
else {
|
|
$updatedValues = array();
|
|
}
|
|
$message = '';
|
|
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
$result = $LEM->_ValidateSurvey();
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
$finished=false;
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,$finished);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>$finished,
|
|
'message'=>$message,
|
|
'gseq'=>1,
|
|
'seq'=>1,
|
|
'mandViolation'=>$result['mandViolation'],
|
|
'valid'=>$result['valid'],
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
break;
|
|
case 'group':
|
|
// First validate the current group
|
|
$LEM->StartProcessingPage();
|
|
if ($processPOST) {
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
}
|
|
else {
|
|
$updatedValues = array();
|
|
}
|
|
$message = '';
|
|
if (!$force && $LEM->currentGroupSeq != -1 && $seq > $LEM->currentGroupSeq) // only re-validate if jumping forward
|
|
{
|
|
$result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
if (!is_null($result) && ($result['mandViolation'] || !$result['valid']))
|
|
{
|
|
// redisplay the current group
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=>$result['mandViolation'],
|
|
'valid'=>$result['valid'],
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
if ($seq <= $LEM->currentGroupSeq || $preview) {
|
|
$LEM->currentGroupSeq = $seq-1; // Try to jump to the requested group, but navigate to next if needed
|
|
}
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (++$LEM->currentGroupSeq >= $LEM->numGroups)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>true,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=>(isset($result['mandViolation']) ? $result['mandViolation'] : false),
|
|
'valid'=>(isset($result['valid']) ? $result['valid'] : false),
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
$result = $LEM->_ValidateGroup($LEM->currentGroupSeq);
|
|
if (is_null($result)) {
|
|
return NULL; // invalid group - either bad number, or no questions within it
|
|
}
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
if (!$preview && (!$result['relevant'] || $result['hidden']))
|
|
{
|
|
// then skip this group - assume already saved?
|
|
continue;
|
|
}
|
|
elseif (!($result['mandViolation'] || !$result['valid']) && $LEM->currentGroupSeq < $seq) {
|
|
// if there is a violation while moving forward, need to stop and ask that set of questions
|
|
// if there are no violations, can skip this group as long as changed values are saved.
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new group
|
|
if(!$preview){ // Save only if not in preview mode
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
}
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentGroupSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
break;
|
|
case 'question':
|
|
$LEM->StartProcessingPage();
|
|
if ($processPOST) {
|
|
$updatedValues=$LEM->ProcessCurrentResponses();
|
|
}
|
|
else {
|
|
$updatedValues = array();
|
|
}
|
|
$message = '';
|
|
if (!$force && $LEM->currentQuestionSeq != -1 && $seq > $LEM->currentQuestionSeq)
|
|
{
|
|
$result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
$gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
|
|
$grel = $gRelInfo['result'];
|
|
if ($grel && ($result['mandViolation'] || !$result['valid']))
|
|
{
|
|
// redisplay the current question
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
if ($seq <= $LEM->currentQuestionSeq || $preview) {
|
|
$LEM->currentQuestionSeq = $seq-1; // Try to jump to the requested group, but navigate to next if needed
|
|
}
|
|
while (true)
|
|
{
|
|
$LEM->currentQset = array(); // reset active list of questions
|
|
if (++$LEM->currentQuestionSeq >= $LEM->numQuestions)
|
|
{
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,true);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>true,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>(isset($result['unansweredSQs']) ? $result['unansweredSQs'] : ''),
|
|
'invalidSQs'=>(isset($result['invalidSQs']) ? $result['invalidSQs'] : ''),
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
|
|
// Set certain variables normally set by StartProcessingGroup()
|
|
$LEM->groupRelevanceInfo=array(); // TODO only important thing from StartProcessingGroup?
|
|
if (!isset($LEM->questionSeq2relevance[$LEM->currentQuestionSeq])) {
|
|
return NULL; // means an invalid question - probably no sub-quetions
|
|
}
|
|
$qInfo = $LEM->questionSeq2relevance[$LEM->currentQuestionSeq];
|
|
$LEM->currentQID=$qInfo['qid'];
|
|
$LEM->currentGroupSeq=$qInfo['gseq'];
|
|
if ($LEM->currentGroupSeq > $LEM->maxGroupSeq) {
|
|
$LEM->maxGroupSeq = $LEM->currentGroupSeq;
|
|
}
|
|
|
|
$LEM->ProcessAllNeededRelevance($LEM->currentQuestionSeq);
|
|
$LEM->_CreateSubQLevelRelevanceAndValidationEqns($LEM->currentQuestionSeq);
|
|
$result = $LEM->_ValidateQuestion($LEM->currentQuestionSeq);
|
|
$message .= $result['message'];
|
|
$updatedValues = array_merge($updatedValues,$result['updatedValues']);
|
|
$gRelInfo = $LEM->gRelInfo[$LEM->currentGroupSeq];
|
|
$grel = $gRelInfo['result'];
|
|
|
|
if (!$preview && (!$grel || !$result['relevant'] || $result['hidden']))
|
|
{
|
|
// then skip this question
|
|
continue;
|
|
}
|
|
else if (!$preview && !$grel)
|
|
{
|
|
continue;
|
|
}
|
|
else if (!$preview && !($result['mandViolation'] || !$result['valid']) && $LEM->currentQuestionSeq < $seq)
|
|
{
|
|
// if there is a violation while moving forward, need to stop and ask that set of questions
|
|
// if there are no violations, can skip this group as long as changed values are saved.
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// display new question
|
|
$message .= $LEM->_UpdateValuesInDatabase($updatedValues,false);
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
$LEM->lastMoveResult = array(
|
|
'finished'=>false,
|
|
'message'=>$message,
|
|
'qseq'=>$LEM->currentQuestionSeq,
|
|
'gseq'=>$LEM->currentGroupSeq,
|
|
'seq'=>$LEM->currentQuestionSeq,
|
|
'mandViolation'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['mandViolation'] : false),
|
|
'valid'=> (($LEM->maxGroupSeq > $LEM->currentGroupSeq) ? $result['valid'] : false),
|
|
'unansweredSQs'=>$result['unansweredSQs'],
|
|
'invalidSQs'=>$result['invalidSQs'],
|
|
);
|
|
return $LEM->lastMoveResult;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check the entire survey
|
|
* @return <type>
|
|
*/
|
|
private function _ValidateSurvey()
|
|
{
|
|
$LEM =& $this;
|
|
|
|
$message = '';
|
|
$srel=false;
|
|
$shidden=true;
|
|
$smandViolation=false;
|
|
$svalid=true;
|
|
$unansweredSQs = array();
|
|
$invalidSQs = array();
|
|
$updatedValues = array();
|
|
$sanyUnanswered = false;
|
|
|
|
///////////////////////////////////////////////////////
|
|
// CHECK EACH GROUP, AND SET SURVEY-LEVEL PROPERTIES //
|
|
///////////////////////////////////////////////////////
|
|
for ($i=0;$i<$LEM->numGroups;++$i) {
|
|
$LEM->currentGroupSeq=$i;
|
|
$gStatus = $LEM->_ValidateGroup($i);
|
|
if (is_null($gStatus)) {
|
|
continue; // invalid group, so skip it
|
|
}
|
|
$message .= $gStatus['message'];
|
|
|
|
if ($gStatus['relevant']) {
|
|
$srel = true;
|
|
}
|
|
if ($gStatus['relevant'] && !$gStatus['hidden']) {
|
|
$shidden=false;
|
|
}
|
|
if ($gStatus['relevant'] && !$gStatus['hidden'] && $gStatus['mandViolation']) {
|
|
$smandViolation = true;
|
|
}
|
|
if ($gStatus['relevant'] && !$gStatus['hidden'] && !$gStatus['valid']) {
|
|
$svalid=false;
|
|
}
|
|
if ($gStatus['anyUnanswered']) {
|
|
$sanyUnanswered = true;
|
|
}
|
|
|
|
if (strlen($gStatus['unansweredSQs']) > 0) {
|
|
$unansweredSQs = array_merge($unansweredSQs, explode('|',$gStatus['unansweredSQs']));
|
|
}
|
|
if (strlen($gStatus['invalidSQs']) > 0) {
|
|
$invalidSQs = array_merge($invalidSQs, explode('|',$gStatus['invalidSQs']));
|
|
}
|
|
$updatedValues = array_merge($updatedValues, $gStatus['updatedValues']);
|
|
// array_merge destroys the key, so do it manually
|
|
foreach ($gStatus['qset'] as $key=>$value) {
|
|
$LEM->currentQset[$key] = $value;
|
|
}
|
|
|
|
$LEM->FinishProcessingGroup();
|
|
}
|
|
return array(
|
|
'relevant' => $srel,
|
|
'hidden' => $shidden,
|
|
'mandViolation' => $smandViolation,
|
|
'valid' => $svalid,
|
|
'anyUnanswered' => $sanyUnanswered,
|
|
'message' => $message,
|
|
'unansweredSQs' => implode('|',$unansweredSQs),
|
|
'invalidSQs' => implode('|',$invalidSQs),
|
|
'updatedValues' => $updatedValues,
|
|
'seq'=>1,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check a group and all of the questions it contains
|
|
* @param <type> $groupSeq - the index-0 sequence number for this group
|
|
* @return <array> - detailed information about this group
|
|
*/
|
|
function _ValidateGroup($groupSeq)
|
|
{
|
|
$LEM =& $this;
|
|
|
|
if ($groupSeq < 0 || $groupSeq >= $LEM->numGroups) {
|
|
return NULL; // TODO - what is desired behavior?
|
|
}
|
|
$groupSeqInfo = (isset($LEM->groupSeqInfo[$groupSeq]) ? $LEM->groupSeqInfo[$groupSeq] : NULL);
|
|
if (is_null($groupSeqInfo)) {
|
|
// then there are no questions in this group
|
|
return NULL;
|
|
}
|
|
$qInfo = $LEM->questionSeq2relevance[$groupSeqInfo['qstart']];
|
|
$gseq = $qInfo['gseq'];
|
|
$gid = $qInfo['gid'];
|
|
$LEM->StartProcessingGroup($gseq, $LEM->surveyOptions['anonymized'], $LEM->sid); // analyze the data we have about this group
|
|
|
|
$grel=false; // assume irrelevant until find a relevant question
|
|
$ghidden=true; // assume hidden until find a non-hidden question. If there are no relevant questions on this page, $ghidden will stay true
|
|
$gmandViolation=false; // assume that the group contains no manditory questions that have not been fully answered
|
|
$gvalid=true; // assume valid until discover otherwise
|
|
$debug_message = '';
|
|
$messages = array();
|
|
$currentQset = array();
|
|
$unansweredSQs = array();
|
|
$invalidSQs = array();
|
|
$updatedValues = array();
|
|
$ganyUnanswered = false;
|
|
|
|
$gRelInfo = $LEM->gRelInfo[$groupSeq];
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// CHECK EACH QUESTION, AND SET GROUP-LEVEL PROPERTIES //
|
|
/////////////////////////////////////////////////////////
|
|
for ($i=$groupSeqInfo['qstart'];$i<=$groupSeqInfo['qend']; ++$i)
|
|
{
|
|
$qStatus = $LEM->_ValidateQuestion($i);
|
|
|
|
$updatedValues = array_merge($updatedValues,$qStatus['updatedValues']);
|
|
|
|
if ($gRelInfo['result']==true && $qStatus['relevant']==true) {
|
|
$grel = $gRelInfo['result']; // true; // at least one question relevant
|
|
}
|
|
if ($qStatus['hidden']==false && $qStatus['relevant'] == true) {
|
|
$ghidden=false; // at least one question is visible
|
|
}
|
|
if ($qStatus['relevant']==true && $qStatus['hidden']==false && $qStatus['mandViolation']==true) {
|
|
$gmandViolation=true; // at least one relevant question fails mandatory test
|
|
}
|
|
if ($qStatus['anyUnanswered']==true) {
|
|
$ganyUnanswered=true;
|
|
}
|
|
if ($qStatus['relevant']==true && $qStatus['hidden']==false && $qStatus['valid']==false) {
|
|
$gvalid=false; // at least one question fails validity constraints
|
|
}
|
|
$currentQset[$qStatus['info']['qid']] = $qStatus;
|
|
$messages[] = $qStatus['message'];
|
|
if (strlen($qStatus['unansweredSQs']) > 0) {
|
|
$unansweredSQs[] = $qStatus['unansweredSQs'];
|
|
}
|
|
if (strlen($qStatus['invalidSQs']) > 0) {
|
|
$invalidSQs[] = $qStatus['invalidSQs'];
|
|
}
|
|
}
|
|
$unansweredSQList = implode('|',$unansweredSQs);
|
|
$invalidSQList = implode('|',$invalidSQs);
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// OPTIONALLY DISPLAY (DETAILED) DEBUGGING INFORMATION //
|
|
/////////////////////////////////////////////////////////
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY)
|
|
{
|
|
$editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid);
|
|
$debug_message .= '<br />[G#' . $LEM->currentGroupSeq . ']'
|
|
. '[' . $groupSeqInfo['qstart'] . '-' . $groupSeqInfo['qend'] . ']'
|
|
. "[<a href='$editlink'>"
|
|
. 'GID:' . $gid . "</a>]: "
|
|
. ($grel ? 'relevant ' : " <span style='color:red'>irrelevant</span> ")
|
|
. (($gRelInfo['eqn'] != '') ? $gRelInfo['prettyprint'] : '')
|
|
. (($ghidden && $grel) ? " <span style='color:red'>always-hidden</span> " : ' ')
|
|
. ($gmandViolation ? " <span style='color:red'>(missing a relevant mandatory)</span> " : ' ')
|
|
. ($gvalid ? '' : " <span style='color:red'>(fails at least one validation rule)</span> ")
|
|
. "<br />\n"
|
|
. implode('', $messages);
|
|
|
|
if ($grel == true)
|
|
{
|
|
if (!$gvalid)
|
|
{
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$debug_message .= "**At least one relevant question was invalid, so re-show this group<br />\n";
|
|
$debug_message .= "**Validity Violators: " . implode(', ', explode('|',$invalidSQList)) . "<br />\n";
|
|
}
|
|
}
|
|
if ($gmandViolation)
|
|
{
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$debug_message .= "**At least one relevant question was mandatory but unanswered, so re-show this group<br />\n";
|
|
$debug_message .= '**Mandatory Violators: ' . implode(', ', explode('|',$unansweredSQList)). "<br />\n";
|
|
}
|
|
}
|
|
|
|
if ($ghidden == true)
|
|
{
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$debug_message .= '** Page is relevant but hidden, so NULL irrelevant values and save relevant Equation results:</br>';
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$debug_message .= '** Page is irrelevant, so NULL all questions in this group<br />';
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// STORE METADATA NEEDED FOR SUBSEQUENT PROCESSING AND DISPLAY PURPOSES //
|
|
//////////////////////////////////////////////////////////////////////////
|
|
$currentGroupInfo = array(
|
|
'gseq' => $groupSeq,
|
|
'message' => $debug_message,
|
|
'relevant' => $grel,
|
|
'hidden' => $ghidden,
|
|
'mandViolation' => $gmandViolation,
|
|
'valid' => $gvalid,
|
|
'qset' => $currentQset,
|
|
'unansweredSQs' => $unansweredSQList,
|
|
'anyUnanswered' => $ganyUnanswered,
|
|
'invalidSQs' => $invalidSQList,
|
|
'updatedValues' => $updatedValues,
|
|
);
|
|
|
|
////////////////////////////////////////////////////////
|
|
// STORE METADATA NEEDED TO GENERATE NAVIGATION INDEX //
|
|
////////////////////////////////////////////////////////
|
|
$LEM->indexGseq[$groupSeq] = array(
|
|
'gtext' => $LEM->gseq2info[$groupSeq]['description'],
|
|
'gname' => $LEM->gseq2info[$groupSeq]['group_name'],
|
|
'gid' => $LEM->gseq2info[$groupSeq]['gid'], // TODO how used if random?
|
|
'anyUnanswered' => $ganyUnanswered,
|
|
'anyErrors' => (($gmandViolation || !$gvalid) ? true : false),
|
|
'valid' => $gvalid,
|
|
'mandViolation' => $gmandViolation,
|
|
'show' => (($grel && !$ghidden) ? true : false),
|
|
);
|
|
|
|
$LEM->gseq2relevanceStatus[$gseq] = $grel;
|
|
|
|
return $currentGroupInfo;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* For the current set of questions (whether in survey, gtoup, or question-by-question mode), assesses the following:
|
|
* (a) mandatory - if so, then all relevant sub-questions must be answered (e.g. pay attention to array_filter and array_filter_exclude)
|
|
* (b) always-hidden
|
|
* (c) relevance status - including sub-question-level relevance
|
|
* (d) answered - if $_SESSION[$LEM->sessid][sgqa]=='' or NULL, then it is not answered
|
|
* (e) validity - whether relevant questions pass their validity tests
|
|
* @param <type> $questionSeq - the 0-index sequence number for this question
|
|
* @return <array> of information about this question and its sub-questions
|
|
*/
|
|
|
|
function _ValidateQuestion($questionSeq)
|
|
{
|
|
$LEM =& $this;
|
|
$qInfo = $LEM->questionSeq2relevance[$questionSeq]; // this array is by group and question sequence
|
|
$qrel=true; // assume relevant unless discover otherwise
|
|
$prettyPrintRelEqn=''; // assume no relevance eqn by default
|
|
$qid=$qInfo['qid'];
|
|
|
|
$gid=$qInfo['gid'];
|
|
$qhidden = $qInfo['hidden'];
|
|
$debug_qmessage='';
|
|
|
|
$gRelInfo = $LEM->gRelInfo[$qInfo['gseq']];
|
|
$grel = $gRelInfo['result'];
|
|
|
|
///////////////////////////
|
|
// IS QUESTION RELEVANT? //
|
|
///////////////////////////
|
|
if (!isset($qInfo['relevance']) || $qInfo['relevance'] == '' )
|
|
{
|
|
$relevanceEqn = 1;
|
|
}
|
|
else
|
|
{
|
|
$relevanceEqn = $qInfo['relevance'];
|
|
}
|
|
|
|
// cache results
|
|
$relevanceEqn = htmlspecialchars_decode($relevanceEqn,ENT_QUOTES); // TODO is this needed?
|
|
if (isset($LEM->ParseResultCache[$relevanceEqn]))
|
|
{
|
|
$qrel = $LEM->ParseResultCache[$relevanceEqn]['result'];
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintRelEqn = $LEM->ParseResultCache[$relevanceEqn]['prettyprint'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$qrel = $LEM->em->ProcessBooleanExpression($relevanceEqn,$qInfo['gseq'], $qInfo['qseq']); // assumes safer to re-process relevance and not trust POST values
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintRelEqn = $LEM->em->GetPrettyPrintString();
|
|
}
|
|
$LEM->ParseResultCache[$relevanceEqn] = array(
|
|
'result'=>$qrel,
|
|
'prettyprint'=>$prettyPrintRelEqn,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
// Do NOT hide the questions if there is an error in the relevance equation
|
|
if ($LEM->ParseResultCache[$relevanceEqn]['hasErrors'] == true)
|
|
{
|
|
$qrel=true;
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// ARE ANY SUB-QUESTION IRRELEVANT? //
|
|
//////////////////////////////////////
|
|
// identify the relevant subquestions (array_filter and array_filter_exclude may make some irrelevant)
|
|
$relevantSQs=array();
|
|
$irrelevantSQs=array();
|
|
$prettyPrintSQRelEqns=array();
|
|
$prettyPrintSQRelEqn='';
|
|
$prettyPrintValidTip='';
|
|
$anyUnanswered = false;
|
|
if (!$qrel)
|
|
{
|
|
// All sub-questions are irrelevant
|
|
$irrelevantSQs = explode('|', $LEM->qid2code[$qid]);
|
|
}
|
|
else
|
|
{
|
|
// Check filter status to determine which subquestions are relevant
|
|
if ($qInfo['type'] == 'X') {
|
|
$sgqas = array(); // Boilerplate questions can be ignored
|
|
}
|
|
else {
|
|
$sgqas = explode('|',$LEM->qid2code[$qid]);
|
|
}
|
|
foreach ($sgqas as $sgqa)
|
|
{
|
|
// for each subq, see if it is part of an array_filter or array_filter_exclude
|
|
if (!isset($LEM->subQrelInfo[$qid]))
|
|
{
|
|
$relevantSQs[] = $sgqa;
|
|
continue;
|
|
}
|
|
$foundSQrelevance=false;
|
|
foreach ($LEM->subQrelInfo[$qid] as $sq)
|
|
{
|
|
switch ($sq['qtype'])
|
|
{
|
|
case '1': //Array (Flexible Labels) dual scale
|
|
if ($sgqa == ($sq['rowdivid'] . '#0') || $sgqa == ($sq['rowdivid'] . '#1')) {
|
|
$foundSQrelevance=true;
|
|
if (isset($LEM->ParseResultCache[$sq['eqn']]))
|
|
{
|
|
$sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
|
|
$sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
// make sure subquestions with errors in relevance equations are always shown and answers recorded #7703
|
|
if ($hasErrors)
|
|
{
|
|
$sqrel=true;
|
|
}
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
|
|
}
|
|
$LEM->ParseResultCache[$sq['eqn']] = array(
|
|
'result'=>$sqrel,
|
|
'prettyprint'=>$prettyPrintSQRelEqn,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
if ($sqrel)
|
|
{
|
|
$relevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
|
|
}
|
|
else
|
|
{
|
|
$irrelevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
|
|
}
|
|
}
|
|
break;
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
if (preg_match('/^' . $sq['rowdivid'] . '_/', $sgqa))
|
|
{
|
|
$foundSQrelevance=true;
|
|
if (isset($LEM->ParseResultCache[$sq['eqn']]))
|
|
{
|
|
$sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
|
|
$sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
// make sure subquestions with errors in relevance equations are always shown and answers recorded #7703
|
|
if ($hasErrors)
|
|
{
|
|
$sqrel=true;
|
|
}
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
|
|
}
|
|
$LEM->ParseResultCache[$sq['eqn']] = array(
|
|
'result'=>$sqrel,
|
|
'prettyprint'=>$prettyPrintSQRelEqn,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
if ($sqrel)
|
|
{
|
|
$relevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
|
|
}
|
|
else
|
|
{
|
|
$irrelevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
|
|
}
|
|
}
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
// Note, for M and P, Mandatory should mean that at least one answer was picked - not that all were checked
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
if ($sgqa == $sq['rowdivid'] || $sgqa == ($sq['rowdivid'] . 'comment')) // to catch case 'P'
|
|
{
|
|
$foundSQrelevance=true;
|
|
if (isset($LEM->ParseResultCache[$sq['eqn']]))
|
|
{
|
|
$sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
|
|
$sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
// make sure subquestions with errors in relevance equations are always shown and answers recorded #7703
|
|
if ($hasErrors)
|
|
{
|
|
$sqrel=true;
|
|
}
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
|
|
}
|
|
$LEM->ParseResultCache[$sq['eqn']] = array(
|
|
'result'=>$sqrel,
|
|
'prettyprint'=>$prettyPrintSQRelEqn,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
if ($sqrel)
|
|
{
|
|
$relevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=true;
|
|
}
|
|
else
|
|
{
|
|
$irrelevantSQs[] = $sgqa;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]=false;
|
|
}
|
|
}
|
|
break;
|
|
case 'L': //LIST drop-down/radio-button list
|
|
if ($sgqa == ($sq['sgqa'] . 'other') && $sgqa == $sq['rowdivid']) // don't do sub-q level validition to main question, just to other option
|
|
{
|
|
$foundSQrelevance=true;
|
|
if (isset($LEM->ParseResultCache[$sq['eqn']]))
|
|
{
|
|
$sqrel = $LEM->ParseResultCache[$sq['eqn']]['result'];
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $LEM->ParseResultCache[$sq['eqn']]['prettyprint'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$stringToParse = htmlspecialchars_decode($sq['eqn'],ENT_QUOTES); // TODO is this needed?
|
|
$sqrel = $LEM->em->ProcessBooleanExpression($stringToParse,$qInfo['gseq'], $qInfo['qseq']);
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
// make sure subquestions with errors in relevance equations are always shown and answers recorded #7703
|
|
if ($hasErrors)
|
|
{
|
|
$sqrel=true;
|
|
}
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
$prettyPrintSQRelEqn = $LEM->em->GetPrettyPrintString();
|
|
$prettyPrintSQRelEqns[$sq['rowdivid']] = $prettyPrintSQRelEqn;
|
|
}
|
|
$LEM->ParseResultCache[$sq['eqn']] = array(
|
|
'result'=>$sqrel,
|
|
'prettyprint'=>$prettyPrintSQRelEqn,
|
|
'hasErrors'=>$hasErrors,
|
|
);
|
|
}
|
|
if ($sqrel)
|
|
{
|
|
$relevantSQs[] = $sgqa;
|
|
}
|
|
else
|
|
{
|
|
$irrelevantSQs[] = $sgqa;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} // end foreach($LEM->subQrelInfo) [checking array-filters]
|
|
if (!$foundSQrelevance)
|
|
{
|
|
// then this question is relevant
|
|
$relevantSQs[] = $sgqa; // TODO - check this
|
|
}
|
|
}
|
|
} // end of processing relevant question for sub-questions
|
|
|
|
if (($LEM->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX)
|
|
{
|
|
// TODO - why is array_unique needed here?
|
|
// $prettyPrintSQRelEqns = array_unique($prettyPrintSQRelEqns);
|
|
}
|
|
// These array_unique only apply to array_filter of type L (list)
|
|
$relevantSQs = array_unique($relevantSQs);
|
|
$irrelevantSQs = array_unique($irrelevantSQs);
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// WHICH RELEVANT, VISIBLE (SUB)-QUESTIONS HAVEN'T BEEN ANSWERED? //
|
|
////////////////////////////////////////////////////////////////////
|
|
// check that all mandatories have been fully answered (but don't require answers for sub-questions that are irrelevant
|
|
$unansweredSQs = array(); // list of sub-questions that weren't answered
|
|
foreach ($relevantSQs as $sgqa)
|
|
{
|
|
if (($qInfo['type'] != '*') && (!isset($_SESSION[$LEM->sessid][$sgqa]) || ($_SESSION[$LEM->sessid][$sgqa] === '' || is_null($_SESSION[$LEM->sessid][$sgqa]))))
|
|
{
|
|
// then a relevant, visible, mandatory question hasn't been answered
|
|
// Equations are ignored, since set automatically
|
|
$unansweredSQs[] = $sgqa;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
// DETECT ANY VIOLATIONS OF MANDATORY RULES //
|
|
//////////////////////////////////////////////
|
|
$qmandViolation = false; // assume there is no mandatory violation until discover otherwise
|
|
$mandatoryTip = '';
|
|
if ($qrel && !$qhidden && ($qInfo['mandatory'] == 'Y'))
|
|
{
|
|
$mandatoryTip = "<strong><br /><span class='errormandatory'>".$LEM->gT('This question is mandatory').'. ';
|
|
switch ($qInfo['type'])
|
|
{
|
|
case 'M':
|
|
case 'P':
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
// If at least one checkbox is checked, we're OK
|
|
if (count($relevantSQs) > 0 && (count($relevantSQs) == count($unansweredSQs)))
|
|
{
|
|
$qmandViolation = true;
|
|
}
|
|
if (!($qInfo['type'] == '!' || $qInfo['type'] == 'L'))
|
|
{
|
|
$mandatoryTip .= $LEM->gT('Please check at least one item.');
|
|
}
|
|
if ($qInfo['other']=='Y')
|
|
{
|
|
$qattr = isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array();
|
|
if (isset($qattr['other_replace_text']) && trim($qattr['other_replace_text']) != '') {
|
|
$othertext = trim($qattr['other_replace_text']);
|
|
}
|
|
else {
|
|
$othertext = $LEM->gT('Other:');
|
|
}
|
|
$mandatoryTip .= "<br />\n".sprintf($this->gT("If you choose '%s' please also specify your choice in the accompanying text field."),$othertext);
|
|
}
|
|
break;
|
|
case 'X': // Boilerplate can never be mandatory
|
|
case '*': // Equation is auto-computed, so can't violate mandatory rules
|
|
break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'Q':
|
|
case 'K':
|
|
case 'E':
|
|
case 'F':
|
|
case 'J':
|
|
case 'H':
|
|
case ';':
|
|
case '1':
|
|
// In general, if any relevant questions aren't answered, then it violates the mandatory rule
|
|
if (count($unansweredSQs) > 0)
|
|
{
|
|
$qmandViolation = true; // TODO - what about 'other'?
|
|
}
|
|
$mandatoryTip .= $LEM->gT('Please complete all parts').'.';
|
|
break;
|
|
case ':':
|
|
$qattr = isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array();
|
|
if (isset($qattr['multiflexible_checkbox']) && $qattr['multiflexible_checkbox'] == 1)
|
|
{
|
|
// Need to check whether there is at least one checked box per row
|
|
foreach ($LEM->q2subqInfo[$qid]['subqs'] as $sq)
|
|
{
|
|
if (!isset($_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']]) || $_SESSION[$LEM->sessid]['relevanceStatus'][$sq['rowdivid']])
|
|
{
|
|
$rowCount=0;
|
|
$numUnanswered=0;
|
|
foreach ($sgqas as $s)
|
|
{
|
|
if (strpos($s, $sq['rowdivid']) !== false)
|
|
{
|
|
++$rowCount;
|
|
if (array_search($s,$unansweredSQs) !== false) {
|
|
++$numUnanswered;
|
|
}
|
|
}
|
|
}
|
|
if ($rowCount > 0 && $rowCount == $numUnanswered)
|
|
{
|
|
$qmandViolation = true;
|
|
}
|
|
}
|
|
}
|
|
$mandatoryTip .= $LEM->gT('Please check at least one box per row').'.';
|
|
}
|
|
else
|
|
{
|
|
if (count($unansweredSQs) > 0)
|
|
{
|
|
$qmandViolation = true; // TODO - what about 'other'?
|
|
}
|
|
$mandatoryTip .= $LEM->gT('Please complete all parts').'.';
|
|
}
|
|
break;
|
|
case 'R':
|
|
if (count($unansweredSQs) > 0)
|
|
{
|
|
$qmandViolation = true; // TODO - what about 'other'?
|
|
}
|
|
$mandatoryTip .= $LEM->gT('Please rank all items').'.';
|
|
break;
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
$_count=0;
|
|
for ($i=0;$i<count($unansweredSQs);++$i)
|
|
{
|
|
if (preg_match("/comment$/",$unansweredSQs[$i])) {
|
|
continue;
|
|
}
|
|
++$_count;
|
|
}
|
|
if ($_count > 0)
|
|
{
|
|
$qmandViolation = true;
|
|
}
|
|
break;
|
|
default:
|
|
if (count($unansweredSQs) > 0)
|
|
{
|
|
$qmandViolation = true;
|
|
}
|
|
break;
|
|
}
|
|
$mandatoryTip .= "</span></strong>\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// DETECT WHETHER QUESTION SHOULD BE FLAGGED AS UNANSWERED //
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
if ($qrel && !$qhidden)
|
|
{
|
|
switch ($qInfo['type'])
|
|
{
|
|
case 'M':
|
|
case 'P':
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
// If at least one checkbox is checked, we're OK
|
|
if (count($relevantSQs) > 0 && (count($relevantSQs) == count($unansweredSQs)))
|
|
{
|
|
$anyUnanswered = true;
|
|
}
|
|
// what about optional vs. mandatory comment and 'other' fields?
|
|
break;
|
|
default:
|
|
$anyUnanswered = (count($unansweredSQs) > 0);
|
|
break;
|
|
}
|
|
}
|
|
///////////////////////////////////////////////
|
|
// DETECT ANY VIOLATIONS OF VALIDATION RULES //
|
|
///////////////////////////////////////////////
|
|
$qvalid=true; // assume valid unless discover otherwise
|
|
$hasValidationEqn=false;
|
|
$prettyPrintValidEqn=''; // assume no validation eqn by default
|
|
$validationEqn='';
|
|
$validationJS=''; // assume can't generate JavaScript to validate equation
|
|
$validTip=''; // default is none
|
|
if (isset($LEM->qid2validationEqn[$qid]))
|
|
{
|
|
$hasValidationEqn=true;
|
|
if (!$qhidden) // do this even is starts irrelevant, else will never show this information.
|
|
{
|
|
$validationEqns = $LEM->qid2validationEqn[$qid]['eqn'];
|
|
$validationEqn = implode(' and ', $validationEqns);
|
|
$qvalid = $LEM->em->ProcessBooleanExpression($validationEqn,$qInfo['gseq'], $qInfo['qseq']);
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
if (!$hasErrors)
|
|
{
|
|
$validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
|
|
}
|
|
$prettyPrintValidEqn = $validationEqn;
|
|
if ((($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX))
|
|
{
|
|
$prettyPrintValidEqn = $LEM->em->GetPrettyPrintString();
|
|
}
|
|
|
|
$stringToParse = '';
|
|
foreach ($LEM->qid2validationEqn[$qid]['tips'] as $vclass=>$vtip)
|
|
{
|
|
$stringToParse .= "<div id='vmsg_" . $qid . '_' . $vclass . "' class='em_" . $vclass . " emtip'>" . $vtip . "</div>\n";
|
|
}
|
|
$prettyPrintValidTip = $stringToParse;
|
|
$validTip = $LEM->ProcessString($stringToParse, $qid,NULL,false,1,1,false,false);
|
|
// TODO check for errors?
|
|
if ((($this->debugLevel & LEM_PRETTY_PRINT_ALL_SYNTAX) == LEM_PRETTY_PRINT_ALL_SYNTAX))
|
|
{
|
|
$prettyPrintValidTip = $LEM->GetLastPrettyPrintExpression();
|
|
}
|
|
$sumEqn = $LEM->qid2validationEqn[$qid]['sumEqn'];
|
|
$sumRemainingEqn = $LEM->qid2validationEqn[$qid]['sumRemainingEqn'];
|
|
// $countEqn = $LEM->qid2validationEqn[$qid]['countEqn'];
|
|
// $countRemainingEqn = $LEM->qid2validationEqn[$qid]['countRemainingEqn'];
|
|
|
|
}
|
|
else
|
|
{
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$prettyPrintValidEqn = 'Question is Irrelevant, so no need to further validate it';
|
|
}
|
|
}
|
|
}
|
|
if (!$qvalid)
|
|
{
|
|
$invalidSQs = $LEM->qid2code[$qid]; // TODO - currently invalidates all - should only invalidate those that truly fail validation rules.
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// OPTIONALLY DISPLAY (DETAILED) DEBUGGING INFORMATION //
|
|
/////////////////////////////////////////////////////////
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_SUMMARY) == LEM_DEBUG_VALIDATION_SUMMARY)
|
|
{
|
|
$editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid . '/qid/' . $qid);
|
|
$debug_qmessage .= '--[Q#' . $qInfo['qseq'] . ']'
|
|
. "[<a href='$editlink'>"
|
|
. 'QID:'. $qid . '</a>][' . $qInfo['type'] . ']: '
|
|
. ($qrel ? 'relevant' : " <span style='color:red'>irrelevant</span> ")
|
|
. ($qhidden ? " <span style='color:red'>always-hidden</span> " : ' ')
|
|
. (($qInfo['mandatory'] == 'Y')? ' mandatory' : ' ')
|
|
. (($hasValidationEqn) ? (!$qvalid ? " <span style='color:red'>(fails validation rule)</span> " : ' valid') : '')
|
|
. ($qmandViolation ? " <span style='color:red'>(missing a relevant mandatory)</span> " : ' ')
|
|
. $prettyPrintRelEqn
|
|
. "<br />\n";
|
|
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
if ($mandatoryTip != '')
|
|
{
|
|
$debug_qmessage .= '----Mandatory Tip: ' . flattenText($mandatoryTip) . "<br />\n";
|
|
}
|
|
|
|
if ($prettyPrintValidTip != '')
|
|
{
|
|
$debug_qmessage .= '----Pretty Validation Tip: <br />' . $prettyPrintValidTip . "<br />\n";
|
|
}
|
|
if ($validTip != '')
|
|
{
|
|
$debug_qmessage .= '----Validation Tip: <br />' . $validTip . "<br />\n";
|
|
}
|
|
|
|
if ($prettyPrintValidEqn != '')
|
|
{
|
|
$debug_qmessage .= '----Validation Eqn: ' . $prettyPrintValidEqn . "<br />\n";
|
|
}
|
|
if ($validationJS != '')
|
|
{
|
|
$debug_qmessage .= '----Validation JavaScript: ' . $validationJS . "<br />\n";
|
|
}
|
|
|
|
// what are the database question codes for this question?
|
|
$subQList = '{' . implode('}, {', explode('|',$LEM->qid2code[$qid])) . '}';
|
|
// pretty-print them
|
|
$LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
|
|
$prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
|
|
$debug_qmessage .= '----SubQs=> ' . $prettyPrintSubQList . "<br />\n";
|
|
|
|
if (count($prettyPrintSQRelEqns) > 0)
|
|
{
|
|
$debug_qmessage .= "----Array Filters Applied:<br />\n";
|
|
foreach ($prettyPrintSQRelEqns as $key => $value)
|
|
{
|
|
$debug_qmessage .= '------' . $key . ': ' . $value . "<br />\n";
|
|
}
|
|
$debug_qmessage .= "<br />\n";
|
|
}
|
|
|
|
if (count($relevantSQs) > 0)
|
|
{
|
|
$subQList = '{' . implode('}, {', $relevantSQs) . '}';
|
|
// pretty-print them
|
|
$LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
|
|
$prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
|
|
$debug_qmessage .= '----Relevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
|
|
}
|
|
|
|
if (count($irrelevantSQs) > 0)
|
|
{
|
|
$subQList = '{' . implode('}, {', $irrelevantSQs) . '}';
|
|
// pretty-print them
|
|
$LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
|
|
$prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
|
|
$debug_qmessage .= '----Irrelevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
|
|
}
|
|
|
|
// show which relevant subQs were not answered
|
|
if (count($unansweredSQs) > 0)
|
|
{
|
|
$subQList = '{' . implode('}, {', $unansweredSQs) . '}';
|
|
// pretty-print them
|
|
$LEM->ProcessString($subQList, $qid,NULL,false,1,1,false,false);
|
|
$prettyPrintSubQList = $LEM->GetLastPrettyPrintExpression();
|
|
$debug_qmessage .= '----Unanswered Relevant SubQs: ' . $prettyPrintSubQList . "<br />\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// CREATE ARRAY OF VALUES THAT NEED TO BE SILENTLY UPDATED //
|
|
/////////////////////////////////////////////////////////////
|
|
$updatedValues=array();
|
|
if ((!$qrel || !$grel) && $LEM->surveyOptions['deletenonvalues'])
|
|
{
|
|
// If not relevant, then always NULL it in the database
|
|
$sgqas = explode('|',$LEM->qid2code[$qid]);
|
|
foreach ($sgqas as $sgqa)
|
|
{
|
|
$_SESSION[$LEM->sessid][$sgqa] = NULL;
|
|
$updatedValues[$sgqa] = NULL;
|
|
$LEM->updatedValues[$sgqa] = NULL;
|
|
}
|
|
}
|
|
else if ($qInfo['type'] == '*')
|
|
{
|
|
// Process relevant equations, even if hidden, and write the result to the database
|
|
$result = flattenText($LEM->ProcessString($qInfo['eqn'], $qInfo['qid'],NULL,false,1,1,false,false));
|
|
$sgqa = $LEM->qid2code[$qid]; // there will be only one, since Equation
|
|
// Store the result of the Equation in the SESSION
|
|
$_SESSION[$LEM->sessid][$sgqa] = $result;
|
|
$_update = array(
|
|
'type'=>'*',
|
|
'value'=>$result,
|
|
);
|
|
$updatedValues[$sgqa] = $_update;
|
|
$LEM->updatedValues[$sgqa] = $_update;
|
|
|
|
if (($LEM->debugLevel & LEM_DEBUG_VALIDATION_DETAIL) == LEM_DEBUG_VALIDATION_DETAIL)
|
|
{
|
|
$prettyPrintEqn = $LEM->em->GetPrettyPrintString();
|
|
$debug_qmessage .= '** Process Hidden but Relevant Equation [' . $sgqa . '](' . $prettyPrintEqn . ') => ' . $result . "<br />\n";
|
|
}
|
|
}
|
|
if ( $LEM->surveyOptions['deletenonvalues'] )
|
|
{
|
|
foreach ($irrelevantSQs as $sq)
|
|
{
|
|
// NULL irrelevant sub-questions
|
|
$_SESSION[$LEM->sessid][$sq] = NULL;
|
|
$updatedValues[$sq] = NULL;
|
|
$LEM->updatedValues[$sq]= NULL;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// Also, set this AFTER testing relevance
|
|
$sgqas = explode('|',$LEM->qid2code[$qid]);
|
|
foreach ($sgqas as $sgqa)
|
|
{
|
|
if (!is_null($LEM->knownVars[$sgqa]['default']) && !isset($_SESSION[$LEM->sessid][$sgqa])) {
|
|
// add support for replacements
|
|
$defaultVal = $LEM->ProcessString($LEM->knownVars[$sgqa]['default'], NULL, NULL, false, 1, 1, false, false, true);
|
|
$_SESSION[$LEM->sessid][$sgqa] = $defaultVal;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// STORE METADATA NEEDED FOR SUBSEQUENT PROCESSING AND DISPLAY PURPOSES //
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
$qStatus = array(
|
|
'info' => $qInfo, // collect all questions within the group - includes mandatory and always-hiddden status
|
|
'relevant' => $qrel,
|
|
'hidden' => $qInfo['hidden'],
|
|
'relEqn' => $prettyPrintRelEqn,
|
|
'sgqa' => $LEM->qid2code[$qid],
|
|
'unansweredSQs' => implode('|',$unansweredSQs),
|
|
'valid' => $qvalid,
|
|
'validEqn' => $validationEqn,
|
|
'prettyValidEqn' => $prettyPrintValidEqn,
|
|
'validTip' => $validTip,
|
|
'prettyValidTip' => $prettyPrintValidTip,
|
|
'validJS' => $validationJS,
|
|
'invalidSQs' => (isset($invalidSQs) ? $invalidSQs : ''),
|
|
'relevantSQs' => implode('|',$relevantSQs),
|
|
'irrelevantSQs' => implode('|',$irrelevantSQs),
|
|
'subQrelEqn' => implode('<br />',$prettyPrintSQRelEqns),
|
|
'mandViolation' => $qmandViolation,
|
|
'anyUnanswered' => $anyUnanswered,
|
|
'mandTip' => $mandatoryTip,
|
|
'message' => $debug_qmessage,
|
|
'updatedValues' => $updatedValues,
|
|
'sumEqn' => (isset($sumEqn) ? $sumEqn : ''),
|
|
'sumRemainingEqn' => (isset($sumRemainingEqn) ? $sumRemainingEqn : ''),
|
|
// 'countEqn' => (isset($countEqn) ? $countEqn : ''),
|
|
// 'countRemainingEqn' => (isset($countRemainingEqn) ? $countRemainingEqn : ''),
|
|
|
|
);
|
|
|
|
$LEM->currentQset[$qid] = $qStatus;
|
|
|
|
////////////////////////////////////////////////////////
|
|
// STORE METADATA NEEDED TO GENERATE NAVIGATION INDEX //
|
|
////////////////////////////////////////////////////////
|
|
|
|
$groupSeq = $qInfo['gseq'];
|
|
$LEM->indexQseq[$questionSeq] = array(
|
|
'qid' => $qInfo['qid'],
|
|
'qtext' => $qInfo['qtext'],
|
|
'qcode' => $qInfo['code'],
|
|
'qhelp' => $qInfo['help'],
|
|
'anyUnanswered' => $anyUnanswered,
|
|
'anyErrors' => (($qmandViolation || !$qvalid) ? true : false),
|
|
'show' => (($qrel && !$qInfo['hidden']) ? true : false),
|
|
'gseq' => $groupSeq,
|
|
'gtext' => $LEM->gseq2info[$groupSeq]['description'],
|
|
'gname' => $LEM->gseq2info[$groupSeq]['group_name'],
|
|
'gid' => $LEM->gseq2info[$groupSeq]['gid'],
|
|
'mandViolation' => $qmandViolation,
|
|
'valid' => $qvalid,
|
|
);
|
|
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$qid] = $qrel;
|
|
return $qStatus;
|
|
}
|
|
|
|
static function GetQuestionStatus($qid)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if (isset($LEM->currentQset[$qid]))
|
|
{
|
|
return $LEM->currentQset[$qid];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Get array of info needed to display the Group Index
|
|
* @return <type>
|
|
*/
|
|
static function GetGroupIndexInfo($gseq=NULL)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if (is_null($gseq)) {
|
|
return $LEM->indexGseq;
|
|
}
|
|
else {
|
|
return $LEM->indexGseq[$gseq];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translate GID to 0-index Group Sequence number
|
|
* @param <type> $gid
|
|
* @return <type>
|
|
*/
|
|
static function GetGroupSeq($gid)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return (isset($LEM->groupId2groupSeq[$gid]) ? $LEM->groupId2groupSeq[$gid] : -1);
|
|
}
|
|
|
|
/**
|
|
* Get question sequence number from QID
|
|
* @param <type> $qid
|
|
* @return <type>
|
|
*/
|
|
static function GetQuestionSeq($qid)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return (isset($LEM->questionId2questionSeq[$qid]) ? $LEM->questionId2questionSeq[$qid] : -1);
|
|
}
|
|
|
|
/**
|
|
* Get array of info needed to display the Question Index
|
|
* @return <type>
|
|
*/
|
|
static function GetQuestionIndexInfo()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->indexQseq;
|
|
}
|
|
|
|
/**
|
|
* Return entries needed to build the navigation index
|
|
* @param <type> $step - if specified, return a single value, otherwise return entire array
|
|
* @return <type> - will be either question or group-level, depending upon $surveyMode
|
|
*/
|
|
static function GetStepIndexInfo($step=NULL)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
switch ($LEM->surveyMode)
|
|
{
|
|
case 'survey':
|
|
return $LEM->lastMoveResult;
|
|
break;
|
|
case 'group':
|
|
if (is_null($step)) {
|
|
return $LEM->indexGseq;
|
|
}
|
|
return $LEM->indexGseq[$step];
|
|
break;
|
|
case 'question':
|
|
if (is_null($step)) {
|
|
return $LEM->indexQseq;
|
|
}
|
|
return $LEM->indexQseq[$step];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This should be called each time a new group is started, whether on same or different pages. Sets/Clears needed internal parameters.
|
|
* @param <type> $gseq - the group sequence
|
|
* @param <type> $anonymized - whether anonymized
|
|
* @param <type> $surveyid - the surveyId
|
|
* @param <type> $forceRefresh - whether to force refresh of setting variable and token mappings (should be done rarely)
|
|
*/
|
|
static function StartProcessingGroup($gseq=NULL,$anonymized=false,$surveyid=NULL,$forceRefresh=false)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->em->StartProcessingGroup(
|
|
isset($surveyid) ? $surveyid : NULL,
|
|
'',
|
|
isset($LEM->surveyOptions['hyperlinkSyntaxHighlighting']) ? $LEM->surveyOptions['hyperlinkSyntaxHighlighting'] : false
|
|
);
|
|
$LEM->groupRelevanceInfo = array();
|
|
if (!is_null($gseq))
|
|
{
|
|
$LEM->currentGroupSeq = $gseq;
|
|
|
|
if (!is_null($surveyid))
|
|
{
|
|
$LEM->setVariableAndTokenMappingsForExpressionManager($surveyid,$forceRefresh,$anonymized,$LEM->allOnOnePage);
|
|
if ($gseq > $LEM->maxGroupSeq) {
|
|
$LEM->maxGroupSeq = $gseq;
|
|
}
|
|
|
|
if (!$LEM->allOnOnePage || ($LEM->allOnOnePage && !$LEM->processedRelevance)) {
|
|
$LEM->ProcessAllNeededRelevance(); // TODO - what if this is called using Survey or Data Entry format?
|
|
$LEM->_CreateSubQLevelRelevanceAndValidationEqns();
|
|
$LEM->processedRelevance=true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should be called after each group finishes
|
|
*/
|
|
static function FinishProcessingGroup($skipReprocessing=false)
|
|
{
|
|
// $now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if ($skipReprocessing && $LEM->surveyMode != 'survey')
|
|
{
|
|
$LEM->pageTailorInfo=array();
|
|
$LEM->pageRelevanceInfo=array();
|
|
}
|
|
$LEM->pageTailorInfo[] = $LEM->em->GetCurrentSubstitutionInfo();
|
|
$LEM->pageRelevanceInfo[] = $LEM->groupRelevanceInfo;
|
|
// $LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns an array of string parts, splitting out expressions
|
|
* @param type $src
|
|
* @return type
|
|
*/
|
|
static function SplitStringOnExpressions($src)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->em->asSplitStringOnExpressions($src);
|
|
}
|
|
|
|
/**
|
|
* Return a formatted table showing how much time each part of EM consumed
|
|
* @return <type>
|
|
*/
|
|
static function GetDebugTimingMessage()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->debugTimingMsg;
|
|
}
|
|
|
|
/**
|
|
* Should be called at end of each page
|
|
*/
|
|
static function FinishProcessingPage()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$totalTime = 0.;
|
|
if ((($LEM->debugLevel & LEM_DEBUG_TIMING) == LEM_DEBUG_TIMING) && count($LEM->runtimeTimings)>0) {
|
|
$LEM->debugTimingMsg='';
|
|
foreach($LEM->runtimeTimings as $unit) {
|
|
$totalTime += $unit[1];
|
|
}
|
|
$LEM->debugTimingMsg .= "<table border='1'><tr><td colspan=2><b>Total time attributable to EM = " . $totalTime . "</b></td></tr>\n";
|
|
foreach ($LEM->runtimeTimings as $t)
|
|
{
|
|
$LEM->debugTimingMsg .= "<tr><td>" . $t[0] . "</td><td>" . $t[1] . "</td></tr>\n";
|
|
}
|
|
$LEM->debugTimingMsg .= "</table>\n";
|
|
}
|
|
|
|
$LEM->runtimeTimings = array(); // reset them
|
|
|
|
$LEM->initialized=false; // so detect calls after done
|
|
$LEM->ParseResultCache=array(); // don't need to persist it in session
|
|
$_SESSION['LEMsingleton']=serialize($LEM);
|
|
}
|
|
|
|
/*
|
|
* Generate JavaScript needed to do dynamic relevance and tailoring
|
|
* Also create list of variables that need to be declared
|
|
*/
|
|
static function GetRelevanceAndTailoringJavaScript()
|
|
{
|
|
$now = microtime(true);
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$jsParts=array();
|
|
$allJsVarsUsed=array();
|
|
$rowdividList=array(); // list of subquestions needing relevance entries
|
|
App()->getClientScript()->registerScriptFile(Yii::app()->getConfig('generalscripts')."expressions/em_javascript.js");;
|
|
$jsParts[] = "\n<script type='text/javascript'>\n<!--\n";
|
|
$jsParts[] = "var LEMmode='" . $LEM->surveyMode . "';\n";
|
|
if ($LEM->surveyMode == 'group')
|
|
{
|
|
$jsParts[] = "var LEMgseq=" . $LEM->currentGroupSeq . ";\n";
|
|
}
|
|
if ($LEM->surveyMode == 'question' && isset($LEM->currentQID))
|
|
{
|
|
$jsParts[] = "var LEMqid=" . $LEM->currentQID . ";\n"; // current group num so can compute isOnCurrentPage
|
|
}
|
|
|
|
$jsParts[] = "function ExprMgr_process_relevance_and_tailoring(evt_type,sgqa,type){\n";
|
|
$jsParts[] = "if (typeof LEM_initialized == 'undefined') {\nLEM_initialized=true;\nLEMsetTabIndexes();\n}\n";
|
|
$jsParts[] = "if (evt_type == 'onchange' && (typeof last_sgqa !== 'undefined' && sgqa==last_sgqa) && (typeof last_evt_type !== 'undefined' && last_evt_type == 'TAB' && type != 'checkbox')) {\n";
|
|
$jsParts[] = " last_evt_type='onchange';\n";
|
|
$jsParts[] = " last_sgqa=sgqa;\n";
|
|
$jsParts[] = " return;\n";
|
|
$jsParts[] = "}\n";
|
|
$jsParts[] = "last_evt_type = evt_type;\n";
|
|
$jsParts[] = "last_sgqa=sgqa;\n";
|
|
|
|
// flatten relevance array, keeping proper order
|
|
|
|
$pageRelevanceInfo=array();
|
|
$qidList = array(); // list of questions used in relevance and tailoring
|
|
$gseqList = array(); // list of gseqs on this page
|
|
$gseq_qidList = array(); // list of qids using relevance/tailoring within each group
|
|
|
|
if (is_array($LEM->pageRelevanceInfo))
|
|
{
|
|
foreach($LEM->pageRelevanceInfo as $prel)
|
|
{
|
|
foreach($prel as $rel)
|
|
{
|
|
$pageRelevanceInfo[] = $rel;
|
|
}
|
|
}
|
|
}
|
|
|
|
$valEqns = array();
|
|
$relEqns = array();
|
|
$relChangeVars = array();
|
|
|
|
$dynamicQinG = array(); // array of questions, per group, that might affect group-level visibility in all-in-one mode
|
|
$GalwaysRelevant = array(); // checks whether a group is always relevant (e.g. has at least one question that is always shown)
|
|
|
|
if (is_array($pageRelevanceInfo))
|
|
{
|
|
foreach ($pageRelevanceInfo as $arg)
|
|
{
|
|
if (!$LEM->allOnOnePage && $LEM->currentGroupSeq != $arg['gseq']) {
|
|
continue;
|
|
}
|
|
|
|
$gseqList[$arg['gseq']] = $arg['gseq']; // so keep them in order
|
|
// First check if there is any tailoring and construct the tailoring JavaScript if needed
|
|
$tailorParts = array();
|
|
$relParts = array(); // relevance equation
|
|
$valParts = array(); // validation
|
|
$relJsVarsUsed = array(); // vars used in relevance and tailoring
|
|
$valJsVarsUsed = array(); // vars used in validations
|
|
foreach ($LEM->pageTailorInfo as $tailor)
|
|
{
|
|
if (is_array($tailor))
|
|
{
|
|
foreach ($tailor as $sub)
|
|
{
|
|
if ($sub['questionNum'] == $arg['qid'])
|
|
{
|
|
$tailorParts[] = $sub['js'];
|
|
$vars = explode('|',$sub['vars']);
|
|
if (is_array($vars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
|
|
$relJsVarsUsed = array_merge($relJsVarsUsed,$vars);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now check whether there is sub-question relevance to perform for this question
|
|
$subqParts = array();
|
|
if (isset($LEM->subQrelInfo[$arg['qid']]))
|
|
{
|
|
foreach ($LEM->subQrelInfo[$arg['qid']] as $subq)
|
|
{
|
|
$subqParts[$subq['rowdivid']] = $subq;
|
|
}
|
|
}
|
|
|
|
$qidList[$arg['qid']] = $arg['qid'];
|
|
if (!isset($gseq_qidList[$arg['gseq']]))
|
|
{
|
|
$gseq_qidList[$arg['gseq']] = array();
|
|
}
|
|
$gseq_qidList[$arg['gseq']][$arg['qid']] = '0'; // means the qid is within this gseq, but may not have a relevance equation
|
|
|
|
// Now check whether any sub-question validation needs to be performed
|
|
$subqValidations = array();
|
|
$validationEqns = array();
|
|
if (isset($LEM->qid2validationEqn[$arg['qid']]))
|
|
{
|
|
if (isset($LEM->qid2validationEqn[$arg['qid']]['subqValidEqns']))
|
|
{
|
|
$_veqs = $LEM->qid2validationEqn[$arg['qid']]['subqValidEqns'];
|
|
foreach($_veqs as $_veq)
|
|
{
|
|
// generate JavaScript for each - tests whether invalid.
|
|
if (strlen(trim($_veq['subqValidEqn'])) == 0) {
|
|
continue;
|
|
}
|
|
$subqValidations[] = array(
|
|
'subqValidEqn' => $_veq['subqValidEqn'],
|
|
'subqValidSelector' => $_veq['subqValidSelector'],
|
|
);
|
|
}
|
|
}
|
|
$validationEqns = $LEM->qid2validationEqn[$arg['qid']]['eqn'];
|
|
}
|
|
|
|
// Process relevance for question $arg['qid'];
|
|
$relevance = $arg['relevancejs'];
|
|
|
|
$relChangeVars[] = " relChange" . $arg['qid'] . "=false;\n"; // detect change in relevance status
|
|
|
|
if (($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)) && count($tailorParts) == 0 && count($subqParts) == 0 && count($subqValidations) == 0 && count($validationEqns) == 0)
|
|
{
|
|
// Only show constitutively true relevances if there is tailoring that should be done.
|
|
// After we can assign var with EM and change again relevance : then doing it second time (see bug #08315).
|
|
$relParts[] = "$('#relevance" . $arg['qid'] . "').val('1'); // always true\n";
|
|
$GalwaysRelevant[$arg['gseq']] = true;
|
|
continue;
|
|
}
|
|
$relevance = ($relevance == '' || ($arg['result'] == true && $arg['numJsVars']==0)) ? '1' : $relevance;
|
|
$relParts[] = "\nif (" . $relevance . ")\n{\n";
|
|
////////////////////////////////////////////////////////////////////////
|
|
// DO ALL ARRAY FILTERING FIRST - MAY AFFECT VALIDATION AND TAILORING //
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Do all sub-question filtering (e..g array_filter)
|
|
/**
|
|
* $afHide - if true, then use jQuery.show(). If false, then disable/enable the row
|
|
*/
|
|
$afHide = (isset($LEM->qattr[$arg['qid']]['array_filter_style']) ? ($LEM->qattr[$arg['qid']]['array_filter_style'] == '0') : true);
|
|
$inputSelector = (($arg['type'] == 'R') ? '' : ' :input:not(:hidden)');
|
|
foreach ($subqParts as $sq)
|
|
{
|
|
$rowdividList[$sq['rowdivid']] = $sq['result'];
|
|
|
|
// make sure to update array_filter headings and colors
|
|
if( ! empty($LEM->qattr[$arg['qid']]['array_filter'])) {
|
|
// js to fix colors
|
|
$relParts[] = "updateColors($('#question".$arg['qid']."').find('table.question'));\n";
|
|
// js to fix headings
|
|
$repeatheadings = Yii::app()->getConfig("repeatheadings");
|
|
if(isset($LEM->qattr[$arg['qid']]['repeat_headings']) && $LEM->qattr[$arg['qid']]['repeat_headings'] !== "") {
|
|
$repeatheadings = $LEM->qattr[$arg['qid']]['repeat_headings'];
|
|
}
|
|
if($repeatheadings > 0)
|
|
{
|
|
$relParts[] = "updateHeadings($('#question".$arg['qid']."').find('table.question'), "
|
|
.$repeatheadings.");\n";
|
|
}
|
|
}
|
|
// end
|
|
//this change is optional....changes to array should prevent "if( )"
|
|
$relParts[] = " if ( " . (empty($sq['relevancejs'])?'1':$sq['relevancejs']) . " ) {\n";
|
|
if ($afHide)
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').show();\n";
|
|
}
|
|
else
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
|
|
}
|
|
if ($sq['isExclusiveJS'] != '')
|
|
{
|
|
$relParts[] = " if ( " . $sq['isExclusiveJS'] . " ) {\n";
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
|
|
$relParts[] = " }\n";
|
|
$relParts[] = " else {\n";
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
|
|
$relParts[] = " }\n";
|
|
}
|
|
$relParts[] = " relChange" . $arg['qid'] . "=true;\n";
|
|
if($arg['type']!='R') // Ranking: rowdivid are subquestion, but array filter apply to answers and not SQ.
|
|
$relParts[] = " $('#relevance" . $sq['rowdivid'] . "').val('1');\n";
|
|
$relParts[] = " }\n else {\n";
|
|
if ($sq['isExclusiveJS'] != '')
|
|
{
|
|
if ($sq['irrelevantAndExclusiveJS'] != '')
|
|
{
|
|
$relParts[] = " if ( " . $sq['irrelevantAndExclusiveJS'] . " ) {\n";
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
|
|
$relParts[] = " }\n";
|
|
$relParts[] = " else {\n";
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').removeAttr('disabled');\n";
|
|
if ($afHide)
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').hide();\n";
|
|
}
|
|
else
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
|
|
}
|
|
$relParts[] = " }\n";
|
|
}
|
|
else
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($afHide)
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "').hide();\n";
|
|
}
|
|
else
|
|
{
|
|
$relParts[] = " $('#javatbd" . $sq['rowdivid'] . "$inputSelector').attr('disabled','disabled');\n";
|
|
}
|
|
}
|
|
$relParts[] = " relChange" . $arg['qid'] . "=true;\n";
|
|
if($arg['type']!='R') // Ranking: rowdivid are subquestion, but array filter apply to answers and not SQ.
|
|
$relParts[] = " $('#relevance" . $sq['rowdivid'] . "').val('');\n";
|
|
switch ($sq['qtype'])
|
|
{
|
|
case 'L': //LIST drop-down/radio-button list
|
|
$listItem = substr($sq['rowdivid'],strlen($sq['sgqa'])); // gets the part of the rowdiv id past the end of the sgqa code.
|
|
$relParts[] = " if (($('#java" . $sq['sgqa'] ."').val() == '" . $listItem . "')";
|
|
if ($listItem == 'other') {
|
|
$relParts[] = " || ($('#java" . $sq['sgqa'] ."').val() == '-oth-')";
|
|
}
|
|
$relParts[] = "){\n";
|
|
$relParts[] = " $('#java" . $sq['sgqa'] . "').val('');\n";
|
|
$relParts[] = " $('#answer" . $sq['sgqa'] . "NANS').attr('checked',true);\n";
|
|
$relParts[] = " }\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
$relParts[] = " }\n";
|
|
|
|
$sqvars = explode('|',$sq['relevanceVars']);
|
|
if (is_array($sqvars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$sqvars);
|
|
$relJsVarsUsed = array_merge($relJsVarsUsed,$sqvars);
|
|
}
|
|
}
|
|
|
|
// Do all tailoring
|
|
$relParts[] = implode("\n",$tailorParts);
|
|
|
|
// Do custom validation
|
|
foreach ($subqValidations as $_veq)
|
|
{
|
|
if ($_veq['subqValidSelector'] == '') {
|
|
continue;
|
|
}
|
|
$isValid = $LEM->em->ProcessBooleanExpression($_veq['subqValidEqn'],$arg['gseq'],$LEM->questionId2questionSeq[$arg['qid']]);
|
|
$_sqValidVars = $LEM->em->GetJSVarsUsed();
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$_sqValidVars);
|
|
$valJsVarsUsed = array_merge($valJsVarsUsed,$_sqValidVars);
|
|
$validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
|
|
if($validationJS!='')
|
|
{
|
|
$valParts[] = "\n if(" . $validationJS . "){\n";
|
|
$valParts[] = " $('#" . $_veq['subqValidSelector'] . "').addClass('em_sq_validation').removeClass('error').addClass('good');;\n";
|
|
$valParts[] = " }\n else {\n";
|
|
$valParts[] = " $('#" . $_veq['subqValidSelector'] . "').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
|
|
$valParts[] = " }\n";
|
|
}
|
|
}
|
|
|
|
// Set color-coding for validation equations
|
|
if (count($validationEqns) > 0) {
|
|
$_hasSumRange=false;
|
|
$_hasOtherValidation=false;
|
|
$_hasOther2Validation=false;
|
|
$valParts[] = " isValidSum" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
|
|
$valParts[] = " isValidOther" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
|
|
$valParts[] = " isValidOtherComment" . $arg['qid'] . "=true;\n"; // assume valid until proven otherwise
|
|
foreach ($validationEqns as $vclass=>$validationEqn)
|
|
{
|
|
if ($validationEqn == '') {
|
|
continue;
|
|
}
|
|
if ($vclass == 'sum_range')
|
|
{
|
|
$_hasSumRange = true;
|
|
}
|
|
else if ($vclass == 'other_comment_mandatory')
|
|
{
|
|
$_hasOther2Validation = true;
|
|
}
|
|
else
|
|
{
|
|
$_hasOtherValidation = true;
|
|
}
|
|
|
|
$_isValid = $LEM->em->ProcessBooleanExpression($validationEqn,$arg['gseq'],$LEM->questionId2questionSeq[$arg['qid']]);
|
|
$_vars = $LEM->em->GetJSVarsUsed();
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$_vars);
|
|
$valJsVarsUsed = array_merge($valJsVarsUsed,$_vars);
|
|
$_validationJS = $LEM->em->GetJavaScriptEquivalentOfExpression();
|
|
if($_validationJS!='')
|
|
{
|
|
$valParts[] = "\n if(" . $_validationJS . "){\n";
|
|
$valParts[] = " $('#vmsg_" . $arg['qid'] . '_' . $vclass . "').removeClass('error').addClass('good');\n";
|
|
$valParts[] = " }\n else {\n";
|
|
$valParts[] = " $('#vmsg_" . $arg['qid'] . '_' . $vclass ."').removeClass('good').addClass('error');\n";
|
|
switch ($vclass)
|
|
{
|
|
case 'sum_range':
|
|
$valParts[] = " isValidSum" . $arg['qid'] . "=false;\n";
|
|
break;
|
|
case 'other_comment_mandatory':
|
|
$valParts[] = " isValidOtherComment" . $arg['qid'] . "=false;\n";
|
|
break;
|
|
// case 'num_answers':
|
|
// case 'value_range':
|
|
// case 'sq_fn_validation':
|
|
// case 'q_fn_validation':
|
|
// case 'regex_validation':
|
|
default:
|
|
$valParts[] = " isValidOther" . $arg['qid'] . "=false;\n";
|
|
break;
|
|
|
|
}
|
|
$valParts[] = " }\n";
|
|
}
|
|
}
|
|
|
|
$valParts[] = "\n if(isValidSum" . $arg['qid'] . "){\n";
|
|
$valParts[] = " $('#totalvalue_" . $arg['qid'] . "').removeClass('error').addClass('good');\n";
|
|
$valParts[] = " }\n else {\n";
|
|
$valParts[] = " $('#totalvalue_" . $arg['qid'] . "').removeClass('good').addClass('error');\n";
|
|
$valParts[] = " }\n";
|
|
|
|
// color-code single-entry fields as needed
|
|
switch ($arg['type'])
|
|
{
|
|
case 'N':
|
|
case 'S':
|
|
case 'D':
|
|
case 'T':
|
|
case 'U':
|
|
$valParts[] = "\n if(isValidOther" . $arg['qid'] . "){\n";
|
|
$valParts[] = " $('#question" . $arg['qid'] . " :input').addClass('em_sq_validation').removeClass('error').addClass('good');\n";
|
|
$valParts[] = " }\n else {\n";
|
|
$valParts[] = " $('#question" . $arg['qid'] . " :input').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
|
|
$valParts[] = " }\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// color-code mandatory other comment fields
|
|
switch ($arg['type'])
|
|
{
|
|
case '!':
|
|
case 'L':
|
|
case 'P':
|
|
switch ($arg['type'])
|
|
{
|
|
case '!':
|
|
$othervar = 'othertext' . substr($arg['jsResultVar'],4,-5);
|
|
break;
|
|
case 'L':
|
|
$othervar = 'answer' . substr($arg['jsResultVar'],4) . 'text';
|
|
break;
|
|
case 'P':
|
|
$othervar = 'answer' . substr($arg['jsResultVar'],4);
|
|
break;
|
|
}
|
|
$valParts[] = "\n if(isValidOtherComment" . $arg['qid'] . "){\n";
|
|
$valParts[] = " $('#" . $othervar . "').addClass('em_sq_validation').removeClass('error').addClass('good');\n";
|
|
$valParts[] = " }\n else {\n";
|
|
$valParts[] = " $('#" . $othervar . "').addClass('em_sq_validation').removeClass('good').addClass('error');\n";
|
|
$valParts[] = " }\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (count($valParts) > 0)
|
|
{
|
|
$valJsVarsUsed = array_unique($valJsVarsUsed);
|
|
$qvalJS = "function LEMval" . $arg['qid'] . "(sgqa){\n";
|
|
// $qvalJS .= " var UsesVars = ' " . implode(' ', $valJsVarsUsed) . " ';\n";
|
|
// $qvalJS .= " if (typeof sgqa !== 'undefined' && !LEMregexMatch('/ java' + sgqa + ' /', UsesVars)) {\n return;\n }\n";
|
|
$qvalJS .= implode("",$valParts);
|
|
$qvalJS .= "}\n";
|
|
$valEqns[] = $qvalJS;
|
|
|
|
$relParts[] = " LEMval" . $arg['qid'] . "(sgqa);\n";
|
|
}
|
|
|
|
if ($arg['hidden']) {
|
|
$relParts[] = " // This question should always be hidden\n";
|
|
$relParts[] = " $('#question" . $arg['qid'] . "').hide();\n";
|
|
}
|
|
else {
|
|
if (!($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)))
|
|
{
|
|
// In such cases, PHP will make the question visible by default. By not forcing a re-show(), template.js can hide questions with impunity
|
|
$relParts[] = " $('#question" . $arg['qid'] . "').show();\n";
|
|
if ($arg['type'] == 'S')
|
|
{
|
|
$relParts[] = " if($('#question" . $arg['qid'] . " div[id^=\"gmap_canvas\"]').length > 0)\n";
|
|
$relParts[] = " {\n";
|
|
$relParts[] = " resetMap(" . $arg['qid'] . ");\n";
|
|
$relParts[] = " }\n";
|
|
}
|
|
}
|
|
}
|
|
// If it is an equation, and relevance is true, then write the value from the question to the answer field storing the result
|
|
if ($arg['type'] == '*')
|
|
{
|
|
$relParts[] = " // Write value from the question into the answer field\n";
|
|
$jsResultVar = $LEM->em->GetJsVarFor($arg['jsResultVar']);
|
|
// Note, this will destroy embedded HTML in the equation (e.g. if it is a report)
|
|
// Should be possible to use jQuery to remove just the LEMtailoring span, but not easy since done (the following doesn't work)
|
|
// _tmpval = $('#question801 .em_equation').clone()
|
|
// $(_tmpval).find('[id^=LEMtailor]').each(function(){ $(this).replaceWith(function(){ $(this).contents; }); })
|
|
$relParts[] = " $('#" . substr($jsResultVar,1,-1) . "').val($.trim(LEMstrip_tags($('#question" . $arg['qid'] . " .em_equation').html())));\n";
|
|
}
|
|
$relParts[] = " relChange" . $arg['qid'] . "=true;\n"; // any change to this value should trigger a propagation of changess
|
|
$relParts[] = " $('#relevance" . $arg['qid'] . "').val('1');\n";
|
|
|
|
$relParts[] = "}\n";
|
|
if (!($relevance == '' || $relevance == '1' || ($arg['result'] == true && $arg['numJsVars']==0)))
|
|
{
|
|
if (!isset($dynamicQinG[$arg['gseq']]))
|
|
{
|
|
$dynamicQinG[$arg['gseq']] = array();
|
|
}
|
|
if( !($arg['hidden'] && $arg['type']=="*"))// Equation question type don't update visibility of group if hidden ( child of bug #08315).
|
|
$dynamicQinG[$arg['gseq']][$arg['qid']]=true;
|
|
$relParts[] = "else {\n";
|
|
$relParts[] = " $('#question" . $arg['qid'] . "').hide();\n";
|
|
$relParts[] = " if ($('#relevance" . $arg['qid'] . "').val()=='1') { relChange" . $arg['qid'] . "=true; }\n"; // only propagate changes if changing from relevant to irrelevant
|
|
$relParts[] = " $('#relevance" . $arg['qid'] . "').val('0');\n";
|
|
$relParts[] = "}\n";
|
|
}
|
|
else
|
|
{
|
|
// Second time : now if relevance is true: Group is allways visible (see bug #08315).
|
|
$relParts[] = "$('#relevance" . $arg['qid'] . "').val('1'); // always true\n";
|
|
if( !($arg['hidden'] && $arg['type']=="*"))// Equation question type don't update visibility of group if hidden ( child of bug #08315).
|
|
$GalwaysRelevant[$arg['gseq']] = true;
|
|
}
|
|
|
|
$vars = explode('|',$arg['relevanceVars']);
|
|
if (is_array($vars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
|
|
$relJsVarsUsed = array_merge($relJsVarsUsed,$vars);
|
|
}
|
|
|
|
$relJsVarsUsed = array_merge($relJsVarsUsed,$valJsVarsUsed);
|
|
$relJsVarsUsed = array_unique($relJsVarsUsed);
|
|
$qrelQIDs = array();
|
|
$qrelgseqs = array(); // so that any group-level change is also propagated
|
|
foreach ($relJsVarsUsed as $jsVar)
|
|
{
|
|
if ($jsVar != '' && isset($LEM->knownVars[substr($jsVar,4)]['qid']))
|
|
{
|
|
$knownVar = $LEM->knownVars[substr($jsVar,4)];
|
|
if ($LEM->surveyMode=='group' && $knownVar['gseq'] != $LEM->currentGroupSeq) {
|
|
continue; // don't make dependent upon off-page variables
|
|
}
|
|
$_qid = $knownVar['qid'];
|
|
if ($_qid == $arg['qid']) {
|
|
continue; // don't make dependent upon itself
|
|
}
|
|
$qrelQIDs[] = 'relChange' . $_qid;
|
|
$qrelgseqs[] = 'relChangeG' . $knownVar['gseq'];
|
|
}
|
|
}
|
|
$qrelgseqs[] = 'relChangeG' . $arg['gseq']; // so if current group changes visibility, re-tailor it.
|
|
$qrelQIDs = array_unique($qrelQIDs);
|
|
$qrelgseqs = array_unique($qrelgseqs);
|
|
if ($LEM->surveyMode=='question')
|
|
{
|
|
$qrelQIDs=array(); // in question-by-questin mode, should never test for dependencies on self or other questions.
|
|
$qrelgseqs=array();
|
|
}
|
|
|
|
$qrelJS = "function LEMrel" . $arg['qid'] . "(sgqa){\n";
|
|
$qrelJS .= " var UsesVars = ' " . implode(' ', $relJsVarsUsed) . " ';\n";
|
|
if (count($qrelQIDs) > 0)
|
|
{
|
|
$qrelJS .= " if(" . implode(' || ', $qrelQIDs) . "){\n ;\n }\n else";
|
|
}
|
|
if (count($qrelgseqs) > 0)
|
|
{
|
|
$qrelJS .= " if(" . implode(' || ', $qrelgseqs) . "){\n ;\n }\n else";
|
|
}
|
|
$qrelJS .= " if (typeof sgqa !== 'undefined' && !LEMregexMatch('/ java' + sgqa + ' /', UsesVars)) {\n";
|
|
$qrelJS .= " return;\n }\n";
|
|
$qrelJS .= implode("",$relParts);
|
|
$qrelJS .= "}\n";
|
|
$relEqns[] = $qrelJS;
|
|
|
|
$gseq_qidList[$arg['gseq']][$arg['qid']] = '1'; // means has an explicit LEMrel() function
|
|
}
|
|
}
|
|
|
|
foreach(array_keys($gseq_qidList) as $_gseq)
|
|
{
|
|
$relChangeVars[] = " relChangeG" . $_gseq . "=false;\n";
|
|
}
|
|
$jsParts[] = implode("",$relChangeVars);
|
|
|
|
// Process relevance for each group; and if group is relevant, process each contained question in order
|
|
foreach ($LEM->gRelInfo as $gr)
|
|
{
|
|
if (!array_key_exists($gr['gseq'],$gseqList)) {
|
|
continue;
|
|
}
|
|
if ($gr['relevancejs'] != '')
|
|
{
|
|
// $jsParts[] = "\n// Process Relevance for Group " . $gr['gid'];
|
|
// $jsParts[] = ": { " . $gr['eqn'] . " }";
|
|
$jsParts[] = "\nif (" . $gr['relevancejs'] . ") {\n";
|
|
$jsParts[] = " $('#group-" . $gr['gseq'] . "').show();\n";
|
|
$jsParts[] = " relChangeG" . $gr['gseq'] . "=true;\n";
|
|
$jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(1);\n";
|
|
|
|
$qids = $gseq_qidList[$gr['gseq']];
|
|
foreach ($qids as $_qid=>$_val)
|
|
{
|
|
$qid2exclusiveAuto = (isset($LEM->qid2exclusiveAuto[$_qid]) ? $LEM->qid2exclusiveAuto[$_qid] : array());
|
|
if ($_val==1)
|
|
{
|
|
$jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
|
|
if (isset($LEM->qattr[$_qid]['exclude_all_others_auto']) && $LEM->qattr[$_qid]['exclude_all_others_auto'] == '1'
|
|
&& isset($qid2exclusiveAuto['js']) && strlen($qid2exclusiveAuto['js']) > 0)
|
|
{
|
|
$jsParts[] = $qid2exclusiveAuto['js'];
|
|
$vars = explode('|',$qid2exclusiveAuto['relevanceVars']);
|
|
if (is_array($vars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
|
|
}
|
|
if (!isset($rowdividList[$qid2exclusiveAuto['rowdivid']]))
|
|
{
|
|
$rowdividList[$qid2exclusiveAuto['rowdivid']] = true;
|
|
}
|
|
}
|
|
if (isset($LEM->qattr[$_qid]['exclude_all_others']))
|
|
{
|
|
foreach (explode(';',trim($LEM->qattr[$_qid]['exclude_all_others'])) as $eo)
|
|
{
|
|
// then need to call the function twice so that cascading of array filter onto an excluded option works
|
|
$jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$jsParts[] = "}\nelse {\n";
|
|
$jsParts[] = " $('#group-" . $gr['gseq'] . "').hide();\n";
|
|
$jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='1') { relChangeG" . $gr['gseq'] . "=true; }\n";
|
|
$jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(0);\n";
|
|
$jsParts[] = "}\n";
|
|
}
|
|
else
|
|
{
|
|
$qids = $gseq_qidList[$gr['gseq']];
|
|
foreach ($qids as $_qid=>$_val)
|
|
{
|
|
$qid2exclusiveAuto = (isset($LEM->qid2exclusiveAuto[$_qid]) ? $LEM->qid2exclusiveAuto[$_qid] : array());
|
|
if ($_val == 1)
|
|
{
|
|
$jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
|
|
if (isset($LEM->qattr[$_qid]['exclude_all_others_auto']) && $LEM->qattr[$_qid]['exclude_all_others_auto'] == '1'
|
|
&& isset($qid2exclusiveAuto['js']) && strlen($qid2exclusiveAuto['js']) > 0)
|
|
{
|
|
$jsParts[] = $qid2exclusiveAuto['js'];
|
|
$vars = explode('|',$qid2exclusiveAuto['relevanceVars']);
|
|
if (is_array($vars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
|
|
}
|
|
if (!isset($rowdividList[$qid2exclusiveAuto['rowdivid']]))
|
|
{
|
|
$rowdividList[$qid2exclusiveAuto['rowdivid']] = true;
|
|
}
|
|
}
|
|
if (isset($LEM->qattr[$_qid]['exclude_all_others']))
|
|
{
|
|
foreach (explode(';',trim($LEM->qattr[$_qid]['exclude_all_others'])) as $eo)
|
|
{
|
|
// then need to call the function twice so that cascading of array filter onto an excluded option works
|
|
$jsParts[] = " LEMrel" . $_qid . "(sgqa);\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// Only do this if there is no explicit group-level relevance equation, else may override group-level relevance
|
|
$dynamicQidsInG = (isset($dynamicQinG[$gr['gseq']]) ? $dynamicQinG[$gr['gseq']] : array());
|
|
$GalwaysVisible = (isset($GalwaysRelevant[$gr['gseq']]) ? $GalwaysRelevant[$gr['gseq']] : false);
|
|
if ($LEM->surveyMode == 'survey' && !$GalwaysVisible && count($dynamicQidsInG) > 0 && strlen(trim($gr['relevancejs']))== 0)
|
|
{
|
|
// check whether any dependent questions have changed
|
|
$relStatusTest = "($('#relevance" . implode("').val()=='1' || $('#relevance", array_keys($dynamicQidsInG)) . "').val()=='1')";
|
|
|
|
$jsParts[] = "\nif (" . $relStatusTest . ") {\n";
|
|
$jsParts[] = " $('#group-" . $gr['gseq'] . "').show();\n";
|
|
$jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='0') { relChangeG" . $gr['gseq'] . "=true; }\n";
|
|
$jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(1);\n";
|
|
$jsParts[] = "}\nelse {\n";
|
|
$jsParts[] = " $('#group-" . $gr['gseq'] . "').hide();\n";
|
|
$jsParts[] = " if ($('#relevanceG" . $gr['gseq'] . "').val()=='1') { relChangeG" . $gr['gseq'] . "=true; }\n";
|
|
$jsParts[] = " $('#relevanceG" . $gr['gseq'] . "').val(0);\n";
|
|
$jsParts[] = "}\n";
|
|
}
|
|
|
|
// now make sure any needed variables are accessible
|
|
$vars = explode('|',$gr['relevanceVars']);
|
|
if (is_array($vars))
|
|
{
|
|
$allJsVarsUsed = array_merge($allJsVarsUsed,$vars);
|
|
}
|
|
}
|
|
|
|
$jsParts[] = "\n}\n";
|
|
|
|
$jsParts[] = implode("\n",$relEqns);
|
|
$jsParts[] = implode("\n",$valEqns);
|
|
|
|
$allJsVarsUsed = array_unique($allJsVarsUsed);
|
|
|
|
// Add JavaScript Mapping Arrays
|
|
if (isset($LEM->alias2varName) && count($LEM->alias2varName) > 0)
|
|
{
|
|
$neededAliases=array();
|
|
$neededCanonical=array();
|
|
$neededCanonicalAttr=array();
|
|
foreach ($allJsVarsUsed as $jsVar)
|
|
{
|
|
if ($jsVar == '') {
|
|
continue;
|
|
}
|
|
if (preg_match("/^.*\.NAOK$/", $jsVar)) {
|
|
$jsVar = preg_replace("/\.NAOK$/","",$jsVar);
|
|
}
|
|
$neededCanonical[] = $jsVar;
|
|
foreach ($LEM->alias2varName as $key=>$value)
|
|
{
|
|
if ($jsVar == $value['jsName'])
|
|
{
|
|
$neededAliases[] = $value['jsPart'];
|
|
}
|
|
}
|
|
}
|
|
$neededCanonical = array_unique($neededCanonical);
|
|
foreach ($neededCanonical as $nc)
|
|
{
|
|
$neededCanonicalAttr[] = $LEM->varNameAttr[$nc];
|
|
}
|
|
$neededAliases = array_unique($neededAliases);
|
|
if (count($neededAliases) > 0)
|
|
{
|
|
$jsParts[] = "var LEMalias2varName = {\n";
|
|
$jsParts[] = implode(",\n",$neededAliases);
|
|
$jsParts[] = "};\n";
|
|
}
|
|
if (count($neededCanonicalAttr) > 0)
|
|
{
|
|
$jsParts[] = "var LEMvarNameAttr = {\n";
|
|
$jsParts[] = implode(",\n",$neededCanonicalAttr);
|
|
$jsParts[] = "};\n";
|
|
}
|
|
}
|
|
|
|
$jsParts[] = "//-->\n</script>\n";
|
|
|
|
// Now figure out which variables have not been declared (those not on the current page)
|
|
$undeclaredJsVars = array();
|
|
$undeclaredVal = array();
|
|
if (!$LEM->allOnOnePage)
|
|
{
|
|
foreach ($LEM->knownVars as $key=>$knownVar)
|
|
{
|
|
if (!is_numeric($key[0])) {
|
|
continue;
|
|
}
|
|
if ($knownVar['jsName'] == '') {
|
|
continue;
|
|
}
|
|
foreach ($allJsVarsUsed as $jsVar)
|
|
{
|
|
if ($jsVar == $knownVar['jsName'])
|
|
{
|
|
if ($LEM->surveyMode=='group' && $knownVar['gseq'] == $LEM->currentGroupSeq) {
|
|
if ($knownVar['hidden'] && $knownVar['type'] != '*') {
|
|
; // need to declare a hidden variable for non-equation hidden variables so can do dynamic lookup.
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
if ($LEM->surveyMode=='question' && $knownVar['qid'] == $LEM->currentQID) {
|
|
continue;
|
|
}
|
|
$undeclaredJsVars[] = $jsVar;
|
|
$sgqa = $knownVar['sgqa'];
|
|
$codeValue = (isset($_SESSION[$LEM->sessid][$sgqa])) ? $_SESSION[$LEM->sessid][$sgqa] : '';
|
|
$undeclaredVal[$jsVar] = $codeValue;
|
|
|
|
if (isset($LEM->jsVar2qid[$jsVar])) {
|
|
$qidList[$LEM->jsVar2qid[$jsVar]] = $LEM->jsVar2qid[$jsVar];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$undeclaredJsVars = array_unique($undeclaredJsVars);
|
|
foreach ($undeclaredJsVars as $jsVar)
|
|
{
|
|
// TODO - is different type needed for text? Or process value to striphtml?
|
|
if ($jsVar == '') continue;
|
|
$jsParts[] = "<input type='hidden' id='" . $jsVar . "' name='" . substr($jsVar,4) . "' value='" . htmlspecialchars($undeclaredVal[$jsVar],ENT_QUOTES) . "'/>\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For all-in-one mode, declare the always-hidden variables, since qanda will not be called for them.
|
|
foreach ($LEM->knownVars as $key=>$knownVar)
|
|
{
|
|
if (!is_numeric($key[0])) {
|
|
continue;
|
|
}
|
|
if ($knownVar['jsName'] == '') {
|
|
continue;
|
|
}
|
|
if ($knownVar['hidden'])
|
|
{
|
|
$jsVar = $knownVar['jsName'];
|
|
$undeclaredJsVars[] = $jsVar;
|
|
$sgqa = $knownVar['sgqa'];
|
|
$codeValue = (isset($_SESSION[$LEM->sessid][$sgqa])) ? $_SESSION[$LEM->sessid][$sgqa] : '';
|
|
$undeclaredVal[$jsVar] = $codeValue;
|
|
}
|
|
}
|
|
|
|
$undeclaredJsVars = array_unique($undeclaredJsVars);
|
|
foreach ($undeclaredJsVars as $jsVar)
|
|
{
|
|
if ($jsVar == '') continue;
|
|
$jsParts[] = "<input type='hidden' id='" . $jsVar . "' name='" . $jsVar . "' value='" . htmlspecialchars($undeclaredVal[$jsVar],ENT_QUOTES) . "'/>\n";
|
|
}
|
|
}
|
|
foreach ($qidList as $qid)
|
|
{
|
|
if (isset($_SESSION[$LEM->sessid]['relevanceStatus'])) {
|
|
$relStatus = (isset($_SESSION[$LEM->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$LEM->sessid]['relevanceStatus'][$qid] : 1);
|
|
}
|
|
else {
|
|
$relStatus = 1;
|
|
}
|
|
$jsParts[] = "<input type='hidden' id='relevance" . $qid . "' name='relevance" . $qid . "' value='" . $relStatus . "'/>\n";
|
|
}
|
|
|
|
foreach ($gseqList as $gseq)
|
|
{
|
|
if (isset($_SESSION['relevanceStatus'])) {
|
|
$relStatus = (isset($_SESSION['relevanceStatus']['G' . $gseq]) ? $_SESSION['relevanceStatus']['G' . $gseq] : 1);
|
|
}
|
|
else {
|
|
$relStatus = 1;
|
|
}
|
|
$jsParts[] = "<input type='hidden' id='relevanceG" . $gseq . "' name='relevanceG" . $gseq . "' value='" . $relStatus . "'/>\n";
|
|
}
|
|
foreach ($rowdividList as $key=>$val)
|
|
{
|
|
$jsParts[] = "<input type='hidden' id='relevance" . $key . "' name='relevance" . $key . "' value='" . $val . "'/>\n";
|
|
}
|
|
$LEM->runtimeTimings[] = array(__METHOD__,(microtime(true) - $now));
|
|
|
|
return implode('',$jsParts);
|
|
}
|
|
|
|
static function setTempVars($vars)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->tempVars = $vars;
|
|
}
|
|
|
|
/**
|
|
* Unit test strings containing expressions
|
|
*/
|
|
static function UnitTestProcessStringContainingExpressions()
|
|
{
|
|
$vars = array(
|
|
'name' => array('sgqa'=>'name', 'code'=>'Peter', 'jsName'=>'java61764X1X1', 'readWrite'=>'N', 'type'=>'X', 'question'=>'What is your first/given name?', 'qseq'=>10, 'gseq'=>1),
|
|
'surname' => array('sgqa'=>'surname', 'code'=>'Smith', 'jsName'=>'java61764X1X1', 'readWrite'=>'Y', 'type'=>'X', 'question'=>'What is your last/surname?', 'qseq'=>20, 'gseq'=>1),
|
|
'age' => array('sgqa'=>'age', 'code'=>45, 'jsName'=>'java61764X1X2', 'readWrite'=>'Y', 'type'=>'X', 'question'=>'How old are you?', 'qseq'=>30, 'gseq'=>2),
|
|
'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),
|
|
'numPets' => array('sgqa'=>'numPets', 'code'=>1, 'jsName'=>'java61764X1X4', 'readWrite'=>'Y', 'type'=>'X','question'=>'How many pets do you have?', 'qseq'=>50, 'gseq'=>2),
|
|
'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),
|
|
'notSetYet' => array('sgqa'=>'notSetYet', 'code'=>'?', 'jsName'=>'java61764X3X6', 'readWrite'=>'Y', 'type'=>'X', 'shown'=>'Unknown','question'=>'Who will win the next election?', 'qseq'=>200, 'gseq'=>3),
|
|
// Constants
|
|
'61764X1X1' => array('sgqa'=>'61764X1X1', 'code'=> '<Sergei>', 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>70, 'gseq'=>2),
|
|
'61764X1X2' => array('sgqa'=>'61764X1X2', 'code'=> 45, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>80, 'gseq'=>2),
|
|
'61764X1X3' => array('sgqa'=>'61764X1X3', 'code'=> 2, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>15, 'gseq'=>1),
|
|
'61764X1X4' => array('sgqa'=>'61764X1X4', 'code'=> 1, 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X', 'qseq'=>100, 'gseq'=>2),
|
|
'TOKEN:ATTRIBUTE_1' => array('code'=> 'worker', 'jsName'=>'', 'readWrite'=>'N', 'type'=>'X'),
|
|
);
|
|
|
|
$tests = <<<EOD
|
|
This example shows escaping of the curly braces: \{\{test\}\} {if(1==1,'{{test}}', '1 is not 1?')} should not throw any errors.
|
|
<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?
|
|
<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))}
|
|
<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']}
|
|
<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]
|
|
<b>Unknown/Misspelled Variables, Functions, and Operators</b><br />{if(sex=='M','Mr.','Mrs.')} {surname}, next year you will be {age++} years old.
|
|
<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.
|
|
<b>Wrong number of arguments for functions:</b><br />{if(gender=='M','Mr.','Mrs.','Other')} {surname}, sum(age,numKids,numPets)={sum(age,numKids,numPets,)}
|
|
<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))}
|
|
<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']}
|
|
<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'}
|
|
<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}
|
|
<b>Question attributes:</b><br />numKids.question={numKids.question}; Question#={numKids.qid}; .relevance={numKids.relevance}
|
|
<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)}
|
|
<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')}
|
|
<b>Dates:</b><br />{name}, the current date/time is: {date('F j, Y, g:i a',time())}
|
|
<b>Conditional:</b><br />Hello, {if(gender=='M','Mr.','Mrs.')} {surname}, may I call you {name}?
|
|
<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>
|
|
<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}'/];
|
|
<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 }
|
|
{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.
|
|
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?
|
|
{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.
|
|
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') }
|
|
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>.
|
|
Substitions that begin or end with a space should be ignored: { name} {age }
|
|
EOD;
|
|
$alltests = explode("\n",$tests);
|
|
|
|
$javascript1 = <<<EOST
|
|
var job='{TOKEN:ATTRIBUTE_1}';
|
|
if (job=='worker') {
|
|
document.write('BOSSES')
|
|
} else {
|
|
document.write('WORKERS')
|
|
}
|
|
EOST;
|
|
$javascript2 = <<<EOST
|
|
var job='{TOKEN:ATTRIBUTE_1}';
|
|
if (job=='worker') {
|
|
document.write('BOSSES')
|
|
} else { document.write('WORKERS') }
|
|
EOST;
|
|
$alltests[] = 'This line should have no errors - the Javascript has curly braces followed by line feeds:' . $javascript1;
|
|
$alltests[] = 'This line should also be OK: ' . $javascript2;
|
|
$alltests[] = 'This line has a hidden script: <script type="text/javascript" language="Javascript">' . $javascript1 . '</script>';
|
|
$alltests[] = 'This line has a hidden script: <script type="text/javascript" language="Javascript">' . $javascript2 . '</script>';
|
|
|
|
LimeExpressionManager::StartProcessingPage();
|
|
LimeExpressionManager::StartProcessingGroup(1);
|
|
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->tempVars = $vars;
|
|
|
|
$LEM->questionId2questionSeq = array();
|
|
$LEM->questionId2groupSeq = array();
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'] = array();
|
|
foreach ($vars as $var) {
|
|
if (isset($var['qseq'])) {
|
|
$LEM->questionId2questionSeq[$var['qseq']] = $var['qseq'];
|
|
$LEM->questionId2groupSeq[$var['qseq']] = $var['gseq'];
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$var['qseq']] = 1;
|
|
}
|
|
}
|
|
|
|
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>";
|
|
print '<table border="1"><tr><th>Source</th><th>Pretty Print</th><th>Result</th><th>Vars Used</th></tr>';
|
|
for ($i=0;$i<count($alltests);++$i)
|
|
{
|
|
$test = $alltests[$i];
|
|
$result = LimeExpressionManager::ProcessString($test, 40, NULL, false, 1, 1);
|
|
$prettyPrint = LimeExpressionManager::GetLastPrettyPrintExpression();
|
|
$varsUsed = $LEM->em->GetAllVarsUsed();
|
|
if (count($varsUsed) > 0) {
|
|
sort($varsUsed);
|
|
$varList = implode(',<br />', $varsUsed);
|
|
}
|
|
else {
|
|
$varList = ' ';
|
|
}
|
|
|
|
print "<tr><td>" . htmlspecialchars($test,ENT_QUOTES) . "</td>\n";
|
|
print "<td>" . $prettyPrint . "</td>\n";
|
|
print "<td>" . $result . "</td>\n";
|
|
if ($LEM->em->HasErrors()) {
|
|
print "<td style='background-color: red'>";
|
|
}
|
|
else {
|
|
print "<td>";
|
|
}
|
|
print $varList . "</td>\n";
|
|
print "</tr>\n";
|
|
}
|
|
print '</table>';
|
|
LimeExpressionManager::FinishProcessingGroup();
|
|
LimeExpressionManager::FinishProcessingPage();
|
|
}
|
|
|
|
/**
|
|
* Unit test Relevance using a simplified syntax to represent questions.
|
|
*/
|
|
static function UnitTestRelevance()
|
|
{
|
|
// Tests: varName~relevance~inputType~message
|
|
$tests = <<<EOT
|
|
name~1~text~What is your name?
|
|
age~1~text~How old are you (must be 16-80)?
|
|
badage~1~expr~{badage=((age<16) || (age>80))}
|
|
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.
|
|
kids~!((age<16) || (age>80))~yesno~Do you have children (Y/N)?
|
|
kidsO~!is_empty(kids) && !(kids=='Y' or kids=='N')~message~Please answer the question about whether you have children with 'Y' or 'N'.
|
|
wantsKids~kids=='N'~yesno~Do you hope to have kids some day (Y/N)?
|
|
wantsKidsY~wantsKids=='Y'~message~{name}, I hope you are able to have children some day!
|
|
wantsKidsN~wantsKids=='N'~message~{name}, I hope you have a wonderfully fulfilling life!
|
|
wantsKidsO~!is_empty(wantsKids) && !(wantsKids=='Y' or wantsKids=='N')~message~Please answer the question about whether you want children with 'Y' or 'N'.
|
|
parents~1~expr~{parents = (!badage && kids=='Y')}
|
|
numKids~kids=='Y'~text~How many children do you have?
|
|
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.
|
|
kid1~numKids >= 1~text~How old is your first child?
|
|
kid2~numKids >= 2~text~How old is your second child?
|
|
kid3~numKids >= 3~text~How old is your third child?
|
|
kid4~numKids >= 4~text~How old is your fourth child?
|
|
kid5~numKids >= 5~text~How old is your fifth child?
|
|
sumage~1~expr~{sumage=sum(kid1.NAOK,kid2.NAOK,kid3.NAOK,kid4.NAOK,kid5.NAOK)}
|
|
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}.
|
|
EOT;
|
|
|
|
$vars = array();
|
|
$varsNAOK = array();
|
|
$varSeq = array();
|
|
$testArgs = array();
|
|
$argInfo = array();
|
|
|
|
LimeExpressionManager::SetDirtyFlag();
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
|
|
LimeExpressionManager::StartProcessingPage(true);
|
|
LimeExpressionManager::StartProcessingGroup(1); // pretending this is group 1
|
|
|
|
// collect variables
|
|
$i=0;
|
|
foreach(explode("\n",$tests) as $test)
|
|
{
|
|
$args = explode("~",$test);
|
|
$type = (($args[1]=='expr') ? '*' : ($args[1]=='message') ? 'X' : 'S');
|
|
$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);
|
|
$varSeq[] = $args[0];
|
|
$testArgs[] = $args;
|
|
$LEM->questionId2questionSeq[$i] = $i;
|
|
$LEM->questionId2groupSeq[$i] = 1;
|
|
$LEM->questionSeq2relevance[$i] = array(
|
|
'relevance'=>htmlspecialchars(preg_replace('/[[:space:]]/',' ',$args[1]),ENT_QUOTES),
|
|
'qid'=>$i,
|
|
'qseq'=>$i,
|
|
'gseq'=>1,
|
|
'jsResultVar'=>'java' . $args[0],
|
|
'type'=>$type,
|
|
'hidden'=>false,
|
|
'gid'=>1, // ($i % 3),
|
|
);
|
|
++$i;
|
|
}
|
|
|
|
$LEM->knownVars = $vars;
|
|
$LEM->gRelInfo[1] = array(
|
|
'gid' => 1,
|
|
'gseq' => 1,
|
|
'eqn' => '',
|
|
'result' => 1,
|
|
'numJsVars' => 0,
|
|
'relevancejs' => '',
|
|
'relevanceVars' => '',
|
|
'prettyPrint'=> '',
|
|
);
|
|
$LEM->ProcessAllNeededRelevance();
|
|
|
|
// collect relevance
|
|
$alias2varName = array();
|
|
$varNameAttr = array();
|
|
for ($i=0;$i<count($testArgs);++$i)
|
|
{
|
|
$testArg = $testArgs[$i];
|
|
$var = $testArg[0];
|
|
$rel = LimeExpressionManager::QuestionIsRelevant($i);
|
|
$question = LimeExpressionManager::ProcessString($testArg[3], $i, NULL, true, 1, 1);
|
|
|
|
$jsVarName='java' . str_replace('#','_',$testArg[0]);
|
|
|
|
$argInfo[] = array(
|
|
'num' => $i,
|
|
'name' => $jsVarName,
|
|
'sgqa' => $testArg[0],
|
|
'type' => $testArg[2],
|
|
'question' => $question,
|
|
'relevance' => $testArg[1],
|
|
'relevanceStatus' => $rel
|
|
);
|
|
$alias2varName[$var] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $var . "':'" . $jsVarName . "'");
|
|
$alias2varName[$jsVarName] = array('jsName'=>$jsVarName, 'jsPart' => "'" . $jsVarName . "':'" . $jsVarName . "'");
|
|
$varNameAttr[$jsVarName] = "'" . $jsVarName . "':{"
|
|
. "'jsName':'" . $jsVarName
|
|
. "','jsName_on':'" . $jsVarName
|
|
. "','sgqa':'" . substr($jsVarName,4)
|
|
. "','qid':" . $i
|
|
. ",'gid':". 1 // ($i % 3) // so have 3 possible group numbers
|
|
. "}";
|
|
}
|
|
$LEM->alias2varName = $alias2varName;
|
|
$LEM->varNameAttr = $varNameAttr;
|
|
LimeExpressionManager::FinishProcessingGroup();
|
|
LimeExpressionManager::FinishProcessingPage();
|
|
|
|
|
|
print <<< EOD
|
|
<script type='text/javascript'>
|
|
<!--
|
|
var LEMradix='.';
|
|
function checkconditions(value, name, type, evt_type)
|
|
{
|
|
if (typeof evt_type === 'undefined')
|
|
{
|
|
evt_type = 'onchange';
|
|
}
|
|
ExprMgr_process_relevance_and_tailoring(evt_type,name,type);
|
|
}
|
|
// -->
|
|
</script>
|
|
EOD;
|
|
|
|
print LimeExpressionManager::GetRelevanceAndTailoringJavaScript();
|
|
|
|
// Print Table of questions
|
|
print "<h3>This is a test of dynamic relevance.</h3>";
|
|
print "Enter your name and age, and try all the permutations of answers to whether you have or want children.<br />\n";
|
|
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 />";
|
|
print "<table border='1'><tr><td>";
|
|
foreach ($argInfo as $arg)
|
|
{
|
|
$rel = LimeExpressionManager::QuestionIsRelevant($arg['num']);
|
|
print "<div id='question" . $arg['num'] . (($rel) ? "'" : "' style='display: none'") . ">\n";
|
|
print "<input type='hidden' id='display" . $arg['num'] . "' name='" . $arg['num'] . "' value='" . (($rel) ? 'on' : '') . "'/>\n";
|
|
if ($arg['type'] == 'expr')
|
|
{
|
|
// Hack for testing purposes - rather than using LimeSurvey internals to store the results of equations, process them via a hidden <div>
|
|
print "<div style='display: none' id='hack_" . $arg['name'] . "'>" . $arg['question'];
|
|
print "<input type='hidden' id='" . $arg['name'] . "' name='" . $arg['name'] . "' value=''/></div>\n";
|
|
}
|
|
else {
|
|
print "<table border='1' width='100%'>\n<tr>\n<td>[Q" . $arg['num'] . "] " . $arg['question'] . "</td>\n";
|
|
switch($arg['type'])
|
|
{
|
|
case 'yesno':
|
|
case 'text':
|
|
print "<td><input type='text' id='" . $arg['name'] . "' name='" . $arg['sgqa'] . "' value='' onchange='checkconditions(this.value, this.name, this.type)'/></td>\n";
|
|
break;
|
|
case 'message':
|
|
print "<input type='hidden' id='" . $arg['name'] . "' name='" . $arg['sgqa'] . "' value=''/>\n";
|
|
break;
|
|
}
|
|
print "</tr>\n</table>\n";
|
|
}
|
|
print "</div>\n";
|
|
}
|
|
print "</table>";
|
|
LimeExpressionManager::SetDirtyFlag(); // so subsequent tests don't try to access these variables
|
|
}
|
|
|
|
/**
|
|
* Set the 'this' variable as an alias for SGQA within the code.
|
|
* @param <type> $sgqa
|
|
*/
|
|
public static function SetThisAsAliasForSGQA($sgqa)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if (isset($LEM->knownVars[$sgqa]))
|
|
{
|
|
$LEM->qcode2sgqa['this']=$sgqa;
|
|
}
|
|
}
|
|
|
|
public static function ShowStackTrace($msg=NULL,&$args=NULL)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
$msg = array("**Stack Trace**" . (is_null($msg) ? '' : ' - ' . $msg));
|
|
|
|
$count = 0;
|
|
foreach (debug_backtrace(false) as $log)
|
|
{
|
|
if ($count++ == 0){
|
|
continue; // skip this call
|
|
}
|
|
$LEM->debugStack = array();
|
|
|
|
$subargs=array();
|
|
if (!is_null($args) && $log['function'] == 'templatereplace') {
|
|
foreach ($args as $arg)
|
|
{
|
|
if (isset($log['args'][2][$arg])) {
|
|
$subargs[$arg] = $log['args'][2][$arg];
|
|
}
|
|
}
|
|
if (count($subargs) > 0) {
|
|
$arglist = print_r($subargs,true);
|
|
}
|
|
else {
|
|
$arglist = '';
|
|
}
|
|
}
|
|
else {
|
|
$arglist = '';
|
|
}
|
|
$msg[] = ' '
|
|
. (isset($log['file']) ? '[' . basename($log['file']) . ']': '')
|
|
. (isset($log['class']) ? $log['class'] : '')
|
|
. (isset($log['type']) ? $log['type'] : '')
|
|
. (isset($log['function']) ? $log['function'] : '')
|
|
. (isset($log['line']) ? '[' . $log['line'] . ']' : '')
|
|
. $arglist;
|
|
}
|
|
}
|
|
|
|
private function gT($string, $escapemode = 'html')
|
|
{
|
|
// eventually replace this with i8n
|
|
if (isset(Yii::app()->lang))
|
|
{
|
|
return Yii::app()->lang->gT($string, $escapemode);
|
|
}
|
|
else
|
|
{
|
|
return $string;
|
|
}
|
|
}
|
|
|
|
|
|
private function ngT($single, $plural, $number, $escapemode = 'html')
|
|
{
|
|
// eventually replace this with i8n
|
|
if (isset(Yii::app()->lang))
|
|
{
|
|
return Yii::app()->lang->ngT($single, $plural, $number, $escapemode);
|
|
}
|
|
else
|
|
{
|
|
return $string;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the survey is using comma as the radix
|
|
* @return type
|
|
*/
|
|
public static function usingCommaAsRadix()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$usingCommaAsRadix = (($LEM->surveyOptions['radix']==',') ? true : false);
|
|
return $usingCommaAsRadix;
|
|
}
|
|
|
|
private static function getConditionsForEM($surveyid=NULL, $qid=NULL)
|
|
{
|
|
if (!is_null($qid)) {
|
|
$where = " c.qid = ".$qid." and ";
|
|
}
|
|
else if (!is_null($surveyid)) {
|
|
$where = " c.qid in (select qid from {{questions}} where sid = ".$surveyid.") and ";
|
|
}
|
|
else {
|
|
$where = "";
|
|
}
|
|
|
|
$query = "select distinct c.*"
|
|
.", q.sid, q.type"
|
|
." from {{conditions}} as c"
|
|
.", {{questions}} as q"
|
|
." where " . $where
|
|
." c.cqid=q.qid"
|
|
." union "
|
|
." select c.*, q.sid, '' as type"
|
|
." from {{conditions}} as c"
|
|
.", {{questions}} as q"
|
|
." where ". $where
|
|
." c.cqid = 0 and c.qid = q.qid";
|
|
|
|
$databasetype = Yii::app()->db->getDriverName();
|
|
if ($databasetype=='mssql' || $databasetype=='dblib')
|
|
{
|
|
$query .= " order by sid, c.qid, scenario, cqid, cfieldname, value";
|
|
}
|
|
else
|
|
{
|
|
$query .= " order by sid, qid, scenario, cqid, cfieldname, value";
|
|
}
|
|
|
|
$data = dbExecuteAssoc($query);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Deprecate obsolete question attributes.
|
|
* @param boolean $changedb - if true, updates parameters and deletes old ones
|
|
* @param type $surveyid - if set, then only for that survey
|
|
* @param type $onlythisqid - if set, then only for this question ID
|
|
*/
|
|
public static function UpgradeQuestionAttributes($changeDB=false,$surveyid=NULL,$onlythisqid=NULL)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$qattrs = $LEM->getQuestionAttributesForEM($surveyid,$onlythisqid,$_SESSION['LEMlang']);
|
|
|
|
$qupdates = array();
|
|
|
|
$attibutemap = array(
|
|
'max_num_value_sgqa' => 'max_num_value',
|
|
'min_num_value_sgqa' => 'min_num_value',
|
|
'num_value_equals_sgqa' => 'equals_num_value',
|
|
);
|
|
$reverseAttributeMap = array_flip($attibutemap);
|
|
|
|
foreach ($qattrs as $qid => $qattr)
|
|
{
|
|
$updates = array();
|
|
foreach ($attibutemap as $src=>$target)
|
|
{
|
|
if (isset($qattr[$src]) && trim($qattr[$src]) != '')
|
|
{
|
|
$updates[$target] = $qattr[$src];
|
|
}
|
|
}
|
|
if (count($updates) > 0)
|
|
{
|
|
$qupdates[$qid] = $updates;
|
|
}
|
|
}
|
|
if ($changeDB)
|
|
{
|
|
$queries = array();
|
|
foreach ($qupdates as $qid=>$updates)
|
|
{
|
|
foreach ($updates as $key=>$value)
|
|
{
|
|
$query = "UPDATE {{question_attributes}} SET value=".Yii::app()->db->quoteValue($value)." WHERE qid=".$qid." and attribute=".Yii::app()->db->quoteValue($key);
|
|
$queries[] = $query;
|
|
$query = "DELETE FROM {{question_attributes}} WHERE qid=".$qid." and attribute=".Yii::app()->db->quoteValue($reverseAttributeMap[$key]);
|
|
$queries[] = $query;
|
|
|
|
}
|
|
}
|
|
// now update the datbase
|
|
foreach ($queries as $query)
|
|
{
|
|
dbExecuteAssoc($query);
|
|
}
|
|
return $queries;
|
|
}
|
|
else
|
|
{
|
|
return $qupdates;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return array of language-specific answer codes
|
|
* @param int $surveyid
|
|
* @param int $qid
|
|
* @param string $lang
|
|
* @return <type>
|
|
*/
|
|
private function getQuestionAttributesForEM($surveyid=0,$qid=0, $lang='')
|
|
{
|
|
// Fix old param (NULL)
|
|
if(is_null($surveyid)) $surveyid=0;
|
|
if(is_null($qid)) $qid=0;
|
|
if(is_null($lang)) $lang='';
|
|
// Fill $lang if possible
|
|
if(!$lang && isset($_SESSION['LEMlang']))
|
|
$lang=$_SESSION['LEMlang'];
|
|
// Actually seem uncesserry : only one call for each page, then commented
|
|
# static $aStaticQuestionAttributesForEM=array();
|
|
# if(isset($aStaticQuestionAttributesForEM[$surveyid][$qid][$lang]))
|
|
# {
|
|
# return $aStaticQuestionAttributesForEM[$surveyid][$qid][$lang];
|
|
# }
|
|
# if($qid && isset($aStaticQuestionAttributesForEM[$surveyid][0][$lang]))
|
|
# {
|
|
# return $aStaticQuestionAttributesForEM[$surveyid][0][$lang][$qid];
|
|
# }
|
|
$aQid=array();
|
|
if($qid)
|
|
{
|
|
$oQids= Question::model()->findAll(array(
|
|
'select'=>'qid',
|
|
'group'=>'qid',
|
|
'distinct'=>true,
|
|
'condition'=>"qid=:qid and parent_qid=0",
|
|
'params'=>array(':qid'=>$qid)
|
|
));
|
|
}
|
|
elseif($surveyid)
|
|
{
|
|
$oQids= Question::model()->findAll(array(
|
|
'select'=>'qid',
|
|
'group'=>'qid',
|
|
'distinct'=>true,
|
|
'condition'=>"sid=:sid and parent_qid=0",
|
|
'params'=>array(':sid'=>$surveyid)
|
|
));
|
|
}
|
|
else
|
|
{
|
|
$oQids= Question::model()->findAll(array(
|
|
'select'=>'qid',
|
|
'group'=>'qid',
|
|
'distinct'=>true,
|
|
'condition'=>"parent_qid=0",
|
|
));
|
|
}
|
|
$aQuestionAttributesForEM=array();
|
|
foreach($oQids as $oQid)
|
|
{
|
|
$aAttributesValues=QuestionAttribute::model()->getQuestionAttributes($oQid->qid);
|
|
// Change array lang to value
|
|
foreach($aAttributesValues as &$aAttributeValue)
|
|
{
|
|
if(is_array($aAttributeValue))
|
|
{
|
|
if(isset($aAttributeValue[$lang]))
|
|
$aAttributeValue=$aAttributeValue[$lang];
|
|
else
|
|
{
|
|
reset($aAttributeValue);
|
|
$aAttributeValue=current($aAttributeValue);
|
|
}
|
|
}
|
|
}
|
|
$aQuestionAttributesForEM[$oQid->qid]=$aAttributesValues;
|
|
}
|
|
# $aStaticQuestionAttributesForEM[$surveyid][$qid][$lang]=$aQuestionAttributesForEM;
|
|
return $aQuestionAttributesForEM;
|
|
# if (!is_null($qid)) {
|
|
# $where = " a.qid = ".$qid." and a.qid=b.qid";
|
|
# }
|
|
# else if (!is_null($surveyid)) {
|
|
# $where = " a.qid=b.qid and b.sid=".$surveyid;
|
|
# }
|
|
# else {
|
|
# $where = " a.qid=b.qid";
|
|
# }
|
|
# if (!is_null($lang)) {
|
|
# $lang = " and a.language='".$lang."' and b.language='".$lang."'";
|
|
# }
|
|
|
|
# $databasetype = Yii::app()->db->getDriverName();
|
|
# if ($databasetype=='mssql' || $databasetype=="sqlsrv")
|
|
# {
|
|
# $query = "select distinct a.qid, a.attribute, CAST(a.value as varchar(max)) as value";
|
|
# }
|
|
# else
|
|
# {
|
|
# $query = "select distinct a.qid, a.attribute, a.value";
|
|
# }
|
|
|
|
# $query .= " from {{question_attributes}} as a, {{questions}} as b"
|
|
# ." where " . $where
|
|
# .$lang
|
|
# ." order by a.qid, a.attribute";
|
|
|
|
# $data = dbExecuteAssoc($query);
|
|
# $qattr = array();
|
|
|
|
# foreach($data->readAll() as $row) {
|
|
# $qattr[$row['qid']][$row['attribute']] = $row['value'];
|
|
# }
|
|
|
|
# if (!is_null($lang))
|
|
# {
|
|
# // Then get non-language specific first, and overwrite with language-specific
|
|
# $qattr2 = $qattr;
|
|
# $qattr = $this->getQuestionAttributesForEM($surveyid,$qid);
|
|
# foreach ($qattr2 as $q => $qattrs) {
|
|
# if (isset($qattrs) && is_array($qattrs)) {
|
|
# foreach ($qattrs as $attr=>$value) {
|
|
# $qattr[$q][$attr] = $value;
|
|
# }
|
|
# }
|
|
# }
|
|
# }
|
|
# return $qattr;
|
|
}
|
|
|
|
/**
|
|
* Return array of language-specific answer codes
|
|
* @param int $surveyid
|
|
* @param int $qid
|
|
* @param string $lang
|
|
* @return <type>
|
|
*/
|
|
|
|
function getAnswerSetsForEM($surveyid=NULL,$qid=NULL,$lang=NULL)
|
|
{
|
|
if (!is_null($qid)) {
|
|
$where = "a.qid = ".$qid;
|
|
}
|
|
else if (!is_null($surveyid)) {
|
|
$where = "a.qid = q.qid and q.sid = ".$surveyid;
|
|
}
|
|
else {
|
|
$where = "1";
|
|
}
|
|
if (!is_null($lang)) {
|
|
$lang = " and a.language='".$lang."' and q.language='".$lang."'";
|
|
}
|
|
|
|
$query = "SELECT a.qid, a.code, a.answer, a.scale_id, a.assessment_value"
|
|
." FROM {{answers}} AS a, {{questions}} as q"
|
|
." WHERE ".$where
|
|
.$lang
|
|
." ORDER BY a.qid, a.scale_id, a.sortorder";
|
|
|
|
$data = dbExecuteAssoc($query);
|
|
|
|
$qans = array();
|
|
|
|
$useAssessments = ((isset($this->surveyOptions['assessments'])) ? $this->surveyOptions['assessments'] : false);
|
|
|
|
foreach($data->readAll() as $row) {
|
|
if (!isset($qans[$row['qid']])) {
|
|
$qans[$row['qid']] = array();
|
|
}
|
|
$qans[$row['qid']][$row['scale_id'].'~'.$row['code']] = ($useAssessments ? $row['assessment_value'] : '0') . '|' . $row['answer'];
|
|
}
|
|
|
|
return $qans;
|
|
}
|
|
|
|
/**
|
|
* Returns group info needed for indexes
|
|
* @param <type> $surveyid
|
|
* @param string $lang
|
|
* @return <type>
|
|
*/
|
|
|
|
function getGroupInfoForEM($surveyid,$lang=NULL)
|
|
{
|
|
if (!is_null($lang)) {
|
|
$lang = " and a.language='".$lang."'";
|
|
}
|
|
$query = "SELECT a.group_name, a.description, a.gid, a.group_order, a.grelevance"
|
|
." FROM {{groups}} AS a"
|
|
." WHERE a.sid=".$surveyid
|
|
.$lang
|
|
." ORDER BY group_order";
|
|
|
|
$data = dbExecuteAssoc($query);
|
|
|
|
$qinfo = array();
|
|
$_order=0;
|
|
foreach ($data as $d)
|
|
{
|
|
$gid[$d['gid']] = array(
|
|
'group_order' => $_order,
|
|
'gid' => $d['gid'],
|
|
'group_name' => $d['group_name'],
|
|
'description' => $d['description'],
|
|
'grelevance' => (!($this->sPreviewMode=='question' || $this->sPreviewMode=='group')) ? $d['grelevance']:1,
|
|
);
|
|
$qinfo[$_order] = $gid[$d['gid']];
|
|
++$_order;
|
|
}
|
|
if (isset($_SESSION['survey_'.$surveyid]) && isset($_SESSION['survey_'.$surveyid]['grouplist'])) {
|
|
$_order=0;
|
|
$qinfo = array();
|
|
foreach ($_SESSION['survey_'.$surveyid]['grouplist'] as $info)
|
|
{
|
|
$gid[$info['gid']]['group_order'] = $_order;
|
|
$qinfo[$_order] = $gid[$info['gid']];
|
|
++$_order;
|
|
}
|
|
}
|
|
|
|
return $qinfo;
|
|
}
|
|
|
|
/**
|
|
* Cleanse the $_POSTed data and update $_SESSION variables accordingly
|
|
*/
|
|
static function ProcessCurrentResponses()
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
if (!isset($LEM->currentQset)) {
|
|
return array();
|
|
}
|
|
$updatedValues=array();
|
|
$radixchange = (($LEM->surveyOptions['radix']==',') ? true : false);
|
|
foreach ($LEM->currentQset as $qinfo)
|
|
{
|
|
$relevant=false;
|
|
$qid = $qinfo['info']['qid'];
|
|
$gseq = $qinfo['info']['gseq'];
|
|
$relevant = (isset($_POST['relevance' . $qid]) ? ($_POST['relevance' . $qid] == 1) : false);
|
|
$grelevant = (isset($_POST['relevanceG' . $gseq]) ? ($_POST['relevanceG' . $gseq] == 1) : false);
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$qid] = $relevant;
|
|
$_SESSION[$LEM->sessid]['relevanceStatus']['G' . $gseq] = $grelevant;
|
|
foreach (explode('|',$qinfo['sgqa']) as $sq)
|
|
{
|
|
$sqrelevant=true;
|
|
if (isset($LEM->subQrelInfo[$qid][$sq]['rowdivid']))
|
|
{
|
|
$rowdivid = $LEM->subQrelInfo[$qid][$sq]['rowdivid'];
|
|
if ($rowdivid!='' && isset($_POST['relevance' . $rowdivid]))
|
|
{
|
|
$sqrelevant = ($_POST['relevance' . $rowdivid] == 1);
|
|
$_SESSION[$LEM->sessid]['relevanceStatus'][$rowdivid] = $sqrelevant;
|
|
}
|
|
}
|
|
$type = $qinfo['info']['type'];
|
|
if (($relevant && $grelevant && $sqrelevant) || !$LEM->surveyOptions['deletenonvalues'])
|
|
{
|
|
if ($qinfo['info']['hidden'] && !isset($_POST[$sq]))
|
|
{
|
|
$value = (isset($_SESSION[$LEM->sessid][$sq]) ? $_SESSION[$LEM->sessid][$sq] : ''); // if always hidden, use the default value, if any
|
|
}
|
|
else
|
|
{
|
|
$value = (isset($_POST[$sq]) ? $_POST[$sq] : '');
|
|
}
|
|
if ($radixchange && isset($LEM->knownVars[$sq]['onlynum']) && $LEM->knownVars[$sq]['onlynum']=='1')
|
|
{
|
|
// convert from comma back to decimal
|
|
$value = implode('.',explode(',',$value));
|
|
}
|
|
switch($type)
|
|
{
|
|
case 'D': //DATE
|
|
if (isset($_POST['qattribute_answer'.$sq])) // push validation message (see qanda_helper) to $_SESSION
|
|
{
|
|
$_SESSION[$LEM->sessid]['qattribute_answer'.$sq]=($_POST['qattribute_answer'.$sq]);
|
|
}
|
|
$value=trim($value);
|
|
if ($value!="" && $value!='INVALID')
|
|
{
|
|
$aAttributes=$LEM->getQuestionAttributesForEM($LEM->sid, $qid,$_SESSION['LEMlang']);
|
|
if (!isset($aAttributes[$qid])) {
|
|
$aAttributes[$qid]=array();
|
|
}
|
|
$aDateFormatData=getDateFormatDataForQID($aAttributes[$qid],$LEM->surveyOptions);
|
|
$oDateTimeConverter = new Date_Time_Converter(trim($value), $aDateFormatData['phpdate']);
|
|
$value=$oDateTimeConverter->convert("Y-m-d H:i"); // TODO : control if inverse function original value
|
|
}
|
|
break;
|
|
# case 'N': //NUMERICAL QUESTION TYPE
|
|
# case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
# if (trim($value)=="") {
|
|
# $value = "";
|
|
# }
|
|
# else {
|
|
# $value = sanitize_float($value);
|
|
# }
|
|
break;
|
|
case '|': //File Upload
|
|
if (!preg_match('/_filecount$/', $sq))
|
|
{
|
|
$json = $value;
|
|
$phparray = json_decode(stripslashes($json));
|
|
|
|
// if the files have not been saved already,
|
|
// move the files from tmp to the files folder
|
|
|
|
$tmp = $LEM->surveyOptions['tempdir'] . 'upload'. DIRECTORY_SEPARATOR;
|
|
if (!is_null($phparray) && count($phparray) > 0)
|
|
{
|
|
// Move the (unmoved, temp) files from temp to files directory.
|
|
// Check all possible file uploads
|
|
for ($i = 0; $i < count($phparray); $i++)
|
|
{
|
|
if (file_exists($tmp . $phparray[$i]->filename))
|
|
{
|
|
$sDestinationFileName = 'fu_' . randomChars(15);
|
|
if (!is_dir($LEM->surveyOptions['target']))
|
|
{
|
|
mkdir($LEM->surveyOptions['target'], 0777, true);
|
|
}
|
|
if (!rename($tmp . $phparray[$i]->filename, $LEM->surveyOptions['target'] . $sDestinationFileName))
|
|
{
|
|
echo "Error moving file to target destination";
|
|
}
|
|
$phparray[$i]->filename = $sDestinationFileName;
|
|
}
|
|
}
|
|
$value = ls_json_encode($phparray); // so that EM doesn't try to parse it.
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
$_SESSION[$LEM->sessid][$sq] = $value;
|
|
$_update = array (
|
|
'type'=>$type,
|
|
'value'=>$value,
|
|
);
|
|
$updatedValues[$sq] = $_update;
|
|
$LEM->updatedValues[$sq] = $_update;
|
|
}
|
|
else { // irrelevant, so database will be NULLed separately
|
|
// Must unset the value, rather than setting to '', so that EM can re-use the default value as needed.
|
|
unset($_SESSION[$LEM->sessid][$sq]);
|
|
$_update = array (
|
|
'type'=>$type,
|
|
'value'=>NULL,
|
|
);
|
|
$updatedValues[$sq] = $_update;
|
|
$LEM->updatedValues[$sq] = $_update;
|
|
}
|
|
}
|
|
}
|
|
if (isset($_POST['timerquestion']))
|
|
{
|
|
$_SESSION[$LEM->sessid][$_POST['timerquestion']]=sanitize_float($_POST[$_POST['timerquestion']]);
|
|
}
|
|
return $updatedValues;
|
|
}
|
|
|
|
static public function isValidVariable($varName)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
if (isset($LEM->knownVars[$varName]))
|
|
{
|
|
return true;
|
|
}
|
|
else if (isset($LEM->qcode2sgqa[$varName]))
|
|
{
|
|
return true;
|
|
}
|
|
else if (isset($LEM->tempVars[$varName]))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static public function GetVarAttribute($name,$attr,$default,$gseq,$qseq)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->_GetVarAttribute($name,$attr,$default,$gseq,$qseq);
|
|
}
|
|
|
|
private function _GetVarAttribute($name,$attr,$default,$gseq,$qseq)
|
|
{
|
|
$args = explode(".", $name);
|
|
$varName = $args[0];
|
|
$varName = preg_replace("/^(?:INSERTANS:)?(.*?)$/", "$1", $varName);
|
|
|
|
if (isset($this->knownVars[$varName]))
|
|
{
|
|
$var = $this->knownVars[$varName];
|
|
}
|
|
else if (isset($this->qcode2sgqa[$varName]))
|
|
{
|
|
$var = $this->knownVars[$this->qcode2sgqa[$varName]];
|
|
}
|
|
else if (isset($this->tempVars[$varName]))
|
|
{
|
|
$var = $this->tempVars[$varName];
|
|
}
|
|
else
|
|
{
|
|
return '{' . $name . '}';
|
|
}
|
|
$sgqa = isset($var['sgqa']) ? $var['sgqa'] : NULL;
|
|
if (is_null($attr))
|
|
{
|
|
// then use the requested attribute, if any
|
|
$_attr = 'code';
|
|
if (preg_match("/INSERTANS:/",$args[0]))
|
|
{
|
|
$_attr = 'shown';
|
|
}
|
|
$attr = (count($args)==2) ? $args[1] : $_attr;
|
|
}
|
|
|
|
// Like JavaScript, if an answer is irrelevant, always return ''
|
|
if (preg_match('/^code|NAOK|shown|valueNAOK|value$/',$attr) && isset($var['qid']) && $var['qid']!='')
|
|
{
|
|
if (!$this->_GetVarAttribute($varName,'relevanceStatus',false,$gseq,$qseq))
|
|
{
|
|
return '';
|
|
}
|
|
}
|
|
switch ($attr)
|
|
{
|
|
case 'varName':
|
|
return $name;
|
|
break;
|
|
case 'code':
|
|
case 'NAOK':
|
|
if (isset($var['code'])) {
|
|
return $var['code']; // for static values like TOKEN
|
|
}
|
|
else {
|
|
if (isset($_SESSION[$this->sessid][$sgqa])) {
|
|
$type = $var['type'];
|
|
switch($type)
|
|
{
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'D': //DATE
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
return htmlspecialchars($_SESSION[$this->sessid][$sgqa],ENT_NOQUOTES);// Minimum sanitizing the string entered by user
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if (preg_match('/comment$/',$sgqa) || preg_match('/other$/',$sgqa) || preg_match('/_other$/',$name))
|
|
{
|
|
return htmlspecialchars($_SESSION[$this->sessid][$sgqa],ENT_NOQUOTES);// Minimum sanitizing the string entered by user
|
|
}
|
|
else
|
|
{
|
|
return $_SESSION[$this->sessid][$sgqa];
|
|
}
|
|
default:
|
|
return $_SESSION[$this->sessid][$sgqa];
|
|
}
|
|
}
|
|
elseif (isset($var['default']) && !is_null($var['default'])) {
|
|
return $var['default'];
|
|
}
|
|
return $default;
|
|
}
|
|
break;
|
|
case 'value':
|
|
case 'valueNAOK':
|
|
{
|
|
$type = $var['type'];
|
|
$code = $this->_GetVarAttribute($name,'code',$default,$gseq,$qseq);
|
|
switch($type)
|
|
{
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
case '1': //Array (Flexible Labels) dual scale // need scale
|
|
case 'H': //ARRAY (Flexible) - Column Format
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'R': //RANKING STYLE
|
|
if ($type == 'O' && preg_match('/comment\.value/',$name))
|
|
{
|
|
$value = $code;
|
|
}
|
|
else if (($type == 'L' || $type == '!') && preg_match('/_other\.value/',$name))
|
|
{
|
|
$value = $code;
|
|
}
|
|
else
|
|
{
|
|
$scale_id = $this->_GetVarAttribute($name,'scale_id','0',$gseq,$qseq);
|
|
$which_ans = $scale_id . '~' . $code;
|
|
$ansArray = $var['ansArray'];
|
|
if (is_null($ansArray))
|
|
{
|
|
$value = $default;
|
|
}
|
|
else
|
|
{
|
|
if (isset($ansArray[$which_ans])) {
|
|
$answerInfo = explode('|',$ansArray[$which_ans]);
|
|
$answer = $answerInfo[0];
|
|
}
|
|
else {
|
|
$answer = $default;
|
|
}
|
|
$value = $answer;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
$value = $code;
|
|
break;
|
|
}
|
|
return $value;
|
|
}
|
|
break;
|
|
case 'jsName':
|
|
if ($this->surveyMode=='survey'
|
|
|| ($this->surveyMode=='group' && $gseq != -1 && isset($var['gseq']) && $gseq == $var['gseq'])
|
|
|| ($this->surveyMode=='question' && $qseq != -1 && isset($var['qseq']) && $qseq == $var['qseq']))
|
|
{
|
|
return (isset($var['jsName_on']) ? $var['jsName_on'] : (isset($var['jsName'])) ? $var['jsName'] : $default);
|
|
}
|
|
else {
|
|
return (isset($var['jsName']) ? $var['jsName'] : $default);
|
|
}
|
|
break;
|
|
case 'sgqa':
|
|
case 'mandatory':
|
|
case 'qid':
|
|
case 'gid':
|
|
case 'grelevance':
|
|
case 'question':
|
|
case 'readWrite':
|
|
case 'relevance':
|
|
case 'rowdivid':
|
|
case 'type':
|
|
case 'qcode':
|
|
case 'gseq':
|
|
case 'qseq':
|
|
case 'ansList':
|
|
case 'scale_id':
|
|
return (isset($var[$attr])) ? $var[$attr] : $default;
|
|
case 'shown':
|
|
if (isset($var['shown']))
|
|
{
|
|
return $var['shown']; // for static values like TOKEN
|
|
}
|
|
else
|
|
{
|
|
$type = $var['type'];
|
|
$code = $this->_GetVarAttribute($name,'code',$default,$gseq,$qseq);
|
|
switch($type)
|
|
{
|
|
case '!': //List - dropdown
|
|
case 'L': //LIST drop-down/radio-button list
|
|
case 'O': //LIST WITH COMMENT drop-down/radio-button list + textarea
|
|
case '1': //Array (Flexible Labels) dual scale // need scale
|
|
case 'H': //ARRAY (Flexible) - Column Format
|
|
case 'F': //ARRAY (Flexible) - Row Format
|
|
case 'R': //RANKING STYLE
|
|
if ($type == 'O' && preg_match('/comment$/',$name))
|
|
{
|
|
$shown = $code;
|
|
}
|
|
else if (($type == 'L' || $type == '!') && preg_match('/_other$/',$name))
|
|
{
|
|
$shown = $code;
|
|
}
|
|
else
|
|
{
|
|
$scale_id = $this->_GetVarAttribute($name,'scale_id','0',$gseq,$qseq);
|
|
$which_ans = $scale_id . '~' . $code;
|
|
$ansArray = $var['ansArray'];
|
|
if (is_null($ansArray))
|
|
{
|
|
$shown=$code;
|
|
}
|
|
else
|
|
{
|
|
if (isset($ansArray[$which_ans])) {
|
|
$answerInfo = explode('|',$ansArray[$which_ans]);
|
|
array_shift($answerInfo);
|
|
$answer = join('|',$answerInfo);
|
|
}
|
|
else {
|
|
$answer = $code;
|
|
}
|
|
$shown = $answer;
|
|
}
|
|
}
|
|
break;
|
|
case 'A': //ARRAY (5 POINT CHOICE) radio-buttons
|
|
case 'B': //ARRAY (10 POINT CHOICE) radio-buttons
|
|
case ':': //ARRAY (Multi Flexi) 1 to 10
|
|
case '5': //5 POINT CHOICE radio-buttons
|
|
$shown = $code;
|
|
break;
|
|
case 'D': //DATE
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$aAttributes=$LEM->getQuestionAttributesForEM($LEM->sid, $var['qid'],$_SESSION['LEMlang']);
|
|
$aDateFormatData=getDateFormatDataForQID($aAttributes[$var['qid']],$LEM->surveyOptions);
|
|
$shown='';
|
|
if (strtotime($code))
|
|
{
|
|
$shown=date($aDateFormatData['phpdate'], strtotime($code));
|
|
}
|
|
break;
|
|
case 'N': //NUMERICAL QUESTION TYPE
|
|
case 'K': //MULTIPLE NUMERICAL QUESTION
|
|
case 'Q': //MULTIPLE SHORT TEXT
|
|
case ';': //ARRAY (Multi Flexi) Text
|
|
case 'S': //SHORT FREE TEXT
|
|
case 'T': //LONG FREE TEXT
|
|
case 'U': //HUGE FREE TEXT
|
|
case '*': //Equation
|
|
case 'I': //Language Question
|
|
case '|': //File Upload
|
|
case 'X': //BOILERPLATE QUESTION
|
|
$shown = $code;
|
|
break;
|
|
case 'M': //Multiple choice checkbox
|
|
case 'P': //Multiple choice with comments checkbox + text
|
|
if ($code == 'Y' && isset($var['question']) && !preg_match('/comment$/',$sgqa))
|
|
{
|
|
$shown = $var['question'];
|
|
}
|
|
elseif (preg_match('/comment$/',$sgqa)) {
|
|
$shown=$code; // This one return sgqa.code
|
|
}
|
|
else
|
|
{
|
|
$shown = $default;
|
|
}
|
|
break;
|
|
case 'G': //GENDER drop-down list
|
|
case 'Y': //YES/NO radio-buttons
|
|
case 'C': //ARRAY (YES/UNCERTAIN/NO) radio-buttons
|
|
case 'E': //ARRAY (Increase/Same/Decrease) radio-buttons
|
|
$ansArray = $var['ansArray'];
|
|
if (is_null($ansArray))
|
|
{
|
|
$shown=$default;
|
|
}
|
|
else
|
|
{
|
|
if (isset($ansArray[$code])) {
|
|
$answer = $ansArray[$code];
|
|
}
|
|
else {
|
|
$answer = $default;
|
|
}
|
|
$shown = $answer;
|
|
}
|
|
break;
|
|
}
|
|
return $shown;
|
|
}
|
|
case 'relevanceStatus':
|
|
$gseq = (isset($var['gseq'])) ? $var['gseq'] : -1;
|
|
$qid = (isset($var['qid'])) ? $var['qid'] : -1;
|
|
$rowdivid = (isset($var['rowdivid']) && $var['rowdivid']!='') ? $var['rowdivid'] : -1;
|
|
if ($qid == -1 || $gseq == -1) {
|
|
return 1;
|
|
}
|
|
if (isset($args[1]) && $args[1]=='NAOK') {
|
|
return 1;
|
|
}
|
|
$grel = (isset($_SESSION[$this->sessid]['relevanceStatus']['G'.$gseq]) ? $_SESSION[$this->sessid]['relevanceStatus']['G'.$gseq] : 1); // true by default
|
|
$qrel = (isset($_SESSION[$this->sessid]['relevanceStatus'][$qid]) ? $_SESSION[$this->sessid]['relevanceStatus'][$qid] : 0);
|
|
$sqrel = (isset($_SESSION[$this->sessid]['relevanceStatus'][$rowdivid]) ? $_SESSION[$this->sessid]['relevanceStatus'][$rowdivid] : 1); // true by default - only want false if a subquestion is irrelevant
|
|
return ($grel && $qrel && $sqrel);
|
|
case 'onlynum':
|
|
if (isset($args[1]) && ($args[1]=='value' || $args[1]=='valueNAOK')) {
|
|
return 1;
|
|
}
|
|
return (isset($var[$attr])) ? $var[$attr] : $default;
|
|
break;
|
|
default:
|
|
print 'UNDEFINED ATTRIBUTE: ' . $attr . "<br />\n";
|
|
return $default;
|
|
}
|
|
return $default; // and throw and error?
|
|
}
|
|
|
|
public static function SetVariableValue($op,$name,$value)
|
|
{
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
if (isset($LEM->tempVars[$name]))
|
|
{
|
|
switch($op)
|
|
{
|
|
case '=':
|
|
$LEM->tempVars[$name]['code'] = $value;
|
|
break;
|
|
case '*=':
|
|
$LEM->tempVars[$name]['code'] *= $value;
|
|
break;
|
|
case '/=':
|
|
$LEM->tempVars[$name]['code'] /= $value;
|
|
break;
|
|
case '+=':
|
|
$LEM->tempVars[$name]['code'] += $value;
|
|
break;
|
|
case '-=':
|
|
$LEM->tempVars[$name]['code'] -= $value;
|
|
break;
|
|
}
|
|
$_result = $LEM->tempVars[$name]['code'];
|
|
$_SESSION[$LEM->sessid][$name] = $_result;
|
|
$LEM->updatedValues[$name] = array(
|
|
'type'=>'*',
|
|
'value'=>$_result,
|
|
);
|
|
return $_result;
|
|
}
|
|
else
|
|
{
|
|
if (!isset($LEM->knownVars[$name]))
|
|
{
|
|
if (isset($LEM->qcode2sgqa[$name]))
|
|
{
|
|
$name = $LEM->qcode2sgqa[$name];
|
|
}
|
|
else
|
|
{
|
|
return ''; // shouldn't happen
|
|
}
|
|
}
|
|
if (isset($_SESSION[$LEM->sessid][$name]))
|
|
{
|
|
$_result = $_SESSION[$LEM->sessid][$name];
|
|
}
|
|
else
|
|
{
|
|
$_result = (isset($LEM->knownVars[$name]['default']) ? $LEM->knownVars[$name]['default'] : 0);
|
|
}
|
|
|
|
switch($op)
|
|
{
|
|
case '=':
|
|
$_result = $value;
|
|
break;
|
|
case '*=':
|
|
$_result *= $value;
|
|
break;
|
|
case '/=':
|
|
$_result /= $value;
|
|
break;
|
|
case '+=':
|
|
$_result += $value;
|
|
break;
|
|
case '-=':
|
|
$_result -= $value;
|
|
break;
|
|
}
|
|
$_SESSION[$LEM->sessid][$name] = $_result;
|
|
$_type = $LEM->knownVars[$name]['type'];
|
|
$LEM->updatedValues[$name] = array(
|
|
'type'=>$_type,
|
|
'value'=>$_result,
|
|
);
|
|
return $_result;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Create HTML view of the survey, showing everything that uses EM
|
|
* @param <type> $sid
|
|
* @param <type> $gid
|
|
* @param <type> $qid
|
|
*/
|
|
static public function ShowSurveyLogicFile($sid, $gid=NULL, $qid=NULL,$LEMdebugLevel=0,$assessments=false)
|
|
{
|
|
// Title
|
|
// Welcome
|
|
// G1, name, relevance, text
|
|
// *Q1, name [type], relevance [validation], text, help, default, help_msg
|
|
// SQ1, name [scale], relevance [validation], text
|
|
// A1, code, assessment_value, text
|
|
// End Message
|
|
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
$LEM->sPreviewMode='logic';
|
|
$aSurveyInfo=getSurveyInfo($sid,$_SESSION['LEMlang']);
|
|
|
|
$allErrors = array();
|
|
$warnings = 0;
|
|
|
|
$surveyOptions = array(
|
|
'assessments'=>($aSurveyInfo['assessments']=='Y'),
|
|
'hyperlinkSyntaxHighlighting'=>true,
|
|
);
|
|
|
|
$varNamesUsed = array(); // keeps track of whether variables have been declared
|
|
|
|
if (!is_null($qid))
|
|
{
|
|
$surveyMode='question';
|
|
LimeExpressionManager::StartSurvey($sid, 'question', $surveyOptions, false,$LEMdebugLevel);
|
|
$qseq = LimeExpressionManager::GetQuestionSeq($qid);
|
|
$moveResult = LimeExpressionManager::JumpTo($qseq+1,true,false,true);
|
|
}
|
|
else if (!is_null($gid))
|
|
{
|
|
$surveyMode='group';
|
|
LimeExpressionManager::StartSurvey($sid, 'group', $surveyOptions, false,$LEMdebugLevel);
|
|
$gseq = LimeExpressionManager::GetGroupSeq($gid);
|
|
$moveResult = LimeExpressionManager::JumpTo($gseq+1,true,false,true);
|
|
}
|
|
else
|
|
{
|
|
$surveyMode='survey';
|
|
LimeExpressionManager::StartSurvey($sid, 'survey', $surveyOptions, false,$LEMdebugLevel);
|
|
$moveResult = LimeExpressionManager::NavigateForwards();
|
|
}
|
|
|
|
$qtypes=getQuestionTypeList('','array');
|
|
|
|
if (is_null($moveResult) || is_null($LEM->currentQset) || count($LEM->currentQset) == 0) {
|
|
return array(
|
|
'errors'=>1,
|
|
'html'=>sprintf($LEM->gT('Invalid question - probably missing sub-questions or language-specific settings for language %s'),$_SESSION['LEMlang'])
|
|
);
|
|
}
|
|
|
|
$surveyname = templatereplace('{SURVEYNAME}',array('SURVEYNAME'=>$aSurveyInfo['surveyls_title']));
|
|
|
|
$out = '<div id="showlogicfilediv" ><H3>' . $LEM->gT('Logic File for Survey # ') . '[' . $LEM->sid . "]: $surveyname</H3>\n";
|
|
$out .= "<table id='logicfiletable'>";
|
|
|
|
if (is_null($gid) && is_null($qid))
|
|
{
|
|
if ($aSurveyInfo['surveyls_description'] != '')
|
|
{
|
|
$LEM->ProcessString($aSurveyInfo['surveyls_description'],0);
|
|
$sPrint= $LEM->GetLastPrettyPrintExpression();
|
|
$errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
|
|
$out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("Description:") . "</td><td colspan=2>" . $sPrint . "</td></tr>";
|
|
}
|
|
if ($aSurveyInfo['surveyls_welcometext'] != '')
|
|
{
|
|
$LEM->ProcessString($aSurveyInfo['surveyls_welcometext'],0);
|
|
$sPrint= $LEM->GetLastPrettyPrintExpression();
|
|
$errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
|
|
$out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("Welcome:") . "</td><td colspan=2>" . $sPrint . "</td></tr>";
|
|
}
|
|
if ($aSurveyInfo['surveyls_endtext'] != '')
|
|
{
|
|
$LEM->ProcessString($aSurveyInfo['surveyls_endtext']);
|
|
$sPrint= $LEM->GetLastPrettyPrintExpression();
|
|
$errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
|
|
$out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("End message:") . "</td><td colspan=2>" . $sPrint . "</td></tr>";
|
|
}
|
|
if ($aSurveyInfo['surveyls_url'] != '')
|
|
{
|
|
$LEM->ProcessString($aSurveyInfo['surveyls_urldescription']." - ".$aSurveyInfo['surveyls_url']);
|
|
$sPrint= $LEM->GetLastPrettyPrintExpression();
|
|
$errClass = ($LEM->em->HasErrors() ? 'LEMerror' : '');
|
|
$out .= "<tr class='LEMgroup $errClass'><td colspan=2>" . $LEM->gT("End URL:") . "</td><td colspan=2>" . $sPrint . "</td></tr>";
|
|
}
|
|
}
|
|
|
|
$out .= "<tr><th>#</th><th>".$LEM->gT('Name [ID]')."</th><th>".$LEM->gT('Relevance [Validation] (Default value)')."</th><th>".$LEM->gT('Text [Help] (Tip)')."</th></tr>\n";
|
|
|
|
$_gseq=-1;
|
|
foreach ($LEM->currentQset as $q) {
|
|
$gseq = $q['info']['gseq'];
|
|
$gid = $q['info']['gid'];
|
|
$qid = $q['info']['qid'];
|
|
$qseq = $q['info']['qseq'];
|
|
|
|
$errorCount=0;
|
|
|
|
//////
|
|
// SHOW GROUP-LEVEL INFO
|
|
//////
|
|
if ($gseq != $_gseq) {
|
|
$LEM->ParseResultCache=array(); // reset for each group so get proper color coding?
|
|
$_gseq = $gseq;
|
|
$ginfo = $LEM->gseq2info[$gseq];
|
|
|
|
$grelevance = '{' . (($ginfo['grelevance']=='') ? 1 : $ginfo['grelevance']) . '}';
|
|
$gtext = ((trim($ginfo['description']) == '') ? ' ' : $ginfo['description']);
|
|
|
|
$editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $gid);
|
|
$groupRow = "<tr class='LEMgroup'>"
|
|
. "<td>G-$gseq</td>"
|
|
. "<td><b>".$ginfo['group_name']."</b><br />[<a target='_blank' href='$editlink'>GID ".$gid."</a>]</td>"
|
|
. "<td>".$grelevance."</td>"
|
|
. "<td>".$gtext."</td>"
|
|
. "</tr>\n";
|
|
|
|
$LEM->ProcessString($groupRow, $qid,NULL,false,1,1,false,false);
|
|
$out .= $LEM->GetLastPrettyPrintExpression();
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
}
|
|
|
|
//////
|
|
// SHOW QUESTION-LEVEL INFO
|
|
//////
|
|
$mandatory = (($q['info']['mandatory']=='Y') ? "<span class='mandatory'>*</span>" : '');
|
|
$type = $q['info']['type'];
|
|
$typedesc = $qtypes[$type]['description'];
|
|
|
|
$sgqas = explode('|',$q['sgqa']);
|
|
if (count($sgqas) == 1 && !is_null($q['info']['default']))
|
|
{
|
|
$LEM->ProcessString(htmlspecialchars($q['info']['default']), $qid,NULL,false,1,1,false,false);// Default value is Y or answer code or go to input/textarea, then we can filter it
|
|
$_default = $LEM->GetLastPrettyPrintExpression();
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
$default = '<br />(' . $LEM->gT('Default:') . ' ' . viewHelper::filterScript($_default) . ')';
|
|
}
|
|
else
|
|
{
|
|
$default = '';
|
|
}
|
|
|
|
$qtext = (($q['info']['qtext'] != '') ? $q['info']['qtext'] : ' ');
|
|
$help = (($q['info']['help'] != '') ? '<hr/>[' . $LEM->gT("Help:") . ' ' . $q['info']['help'] . ']': '');
|
|
$prettyValidTip = (($q['prettyValidTip'] == '') ? '' : '<hr/>(' . $LEM->gT("Tip:") . ' ' . $q['prettyValidTip'] . ')');
|
|
|
|
//////
|
|
// SHOW QUESTION ATTRIBUTES THAT ARE PROCESSED BY EM
|
|
//////
|
|
$attrTable = '';
|
|
|
|
$attrs = (isset($LEM->qattr[$qid]) ? $LEM->qattr[$qid] : array());
|
|
if (isset($LEM->q2subqInfo[$qid]['preg']))
|
|
{
|
|
$attrs['regex_validation'] = $LEM->q2subqInfo[$qid]['preg'];
|
|
}
|
|
if (isset($LEM->questionSeq2relevance[$qseq]['other']))
|
|
{
|
|
$attrs['other'] = $LEM->questionSeq2relevance[$qseq]['other'];
|
|
}
|
|
if (count($attrs) > 0) {
|
|
$attrTable = "<table id='logicfileattributetable'><tr><th>" . $LEM->gT("Question attribute") . "</th><th>" . $LEM->gT("Value"). "</th></tr>\n";
|
|
$count=0;
|
|
foreach ($attrs as $key=>$value) {
|
|
if (is_null($value) || trim($value) == '') {
|
|
continue;
|
|
}
|
|
switch ($key)
|
|
{
|
|
// @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)
|
|
default:
|
|
case 'exclude_all_others':
|
|
case 'exclude_all_others_auto':
|
|
case 'hidden':
|
|
if ($value == false || $value == '0') {
|
|
$value = NULL; // so can skip this one - just using continue here doesn't work.
|
|
}
|
|
break;
|
|
case 'time_limit_action':
|
|
if ( $value == '1') {
|
|
$value = NULL; // so can skip this one - just using continue here doesn't work.
|
|
}
|
|
case 'relevance':
|
|
$value = NULL; // means an outdate database structure
|
|
break;
|
|
case 'array_filter':
|
|
case 'array_filter_exclude':
|
|
case 'code_filter':
|
|
case 'date_max':
|
|
case 'date_min':
|
|
case 'em_validation_q_tip':
|
|
case 'em_validation_sq_tip':
|
|
break;
|
|
case 'equals_num_value':
|
|
case 'em_validation_q':
|
|
case 'em_validation_sq':
|
|
case 'max_answers':
|
|
case 'max_num_value':
|
|
case 'max_num_value_n':
|
|
case 'min_answers':
|
|
case 'min_num_value':
|
|
case 'min_num_value_n':
|
|
case 'min_num_of_files':
|
|
case 'max_num_of_files':
|
|
case 'multiflexible_max':
|
|
case 'multiflexible_min':
|
|
$value = '{' . $value . '}';
|
|
break;
|
|
case 'other_replace_text':
|
|
case 'show_totals':
|
|
case 'regex_validation':
|
|
break;
|
|
case 'other':
|
|
if ($value == 'N') {
|
|
$value = NULL; // so can skip this one
|
|
}
|
|
break;
|
|
}
|
|
if (is_null($value)) {
|
|
continue; // since continuing from within a switch statement doesn't work
|
|
}
|
|
++$count;
|
|
$attrTable .= "<tr><td>$key</td><td>$value</td></tr>\n";
|
|
}
|
|
$attrTable .= "</table>\n";
|
|
if ($count == 0) {
|
|
$attrTable = '';
|
|
}
|
|
}
|
|
|
|
$LEM->ProcessString($qtext . $help . $prettyValidTip . $attrTable, $qid,NULL,false,1,1,false,false);
|
|
$qdetails = viewHelper::filterScript($LEM->GetLastPrettyPrintExpression());
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
|
|
//////
|
|
// SHOW RELEVANCE
|
|
//////
|
|
// Must parse Relevance this way, otherwise if try to first split expressions, regex equations won't work
|
|
$relevanceEqn = (($q['info']['relevance'] == '') ? 1 : $q['info']['relevance']);
|
|
if (!isset($LEM->ParseResultCache[$relevanceEqn]))
|
|
{
|
|
$result = $LEM->em->ProcessBooleanExpression($relevanceEqn, $gseq, $qseq);
|
|
$prettyPrint = $LEM->em->GetPrettyPrintString();
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
$LEM->ParseResultCache[$relevanceEqn] = array(
|
|
'result' => $result,
|
|
'prettyprint' => $prettyPrint,
|
|
'hasErrors' => $hasErrors,
|
|
);
|
|
}
|
|
$relevance = $LEM->ParseResultCache[$relevanceEqn]['prettyprint'];
|
|
if ($LEM->ParseResultCache[$relevanceEqn]['hasErrors']) {
|
|
++$errorCount;
|
|
}
|
|
|
|
//////
|
|
// SHOW VALIDATION EQUATION
|
|
//////
|
|
// Must parse Validation this way so that regex (preg) works
|
|
$prettyValidEqn = '';
|
|
if ($q['prettyValidEqn'] != '') {
|
|
$validationEqn = $q['validEqn'];
|
|
if (!isset($LEM->ParseResultCache[$validationEqn]))
|
|
{
|
|
$result = $LEM->em->ProcessBooleanExpression($validationEqn, $gseq, $qseq);
|
|
$prettyPrint = $LEM->em->GetPrettyPrintString();
|
|
$hasErrors = $LEM->em->HasErrors();
|
|
$LEM->ParseResultCache[$validationEqn] = array(
|
|
'result' => $result,
|
|
'prettyprint' => $prettyPrint,
|
|
'hasErrors' => $hasErrors,
|
|
);
|
|
}
|
|
$prettyValidEqn = '<hr/>(VALIDATION: ' . $LEM->ParseResultCache[$validationEqn]['prettyprint'] . ')';
|
|
if ($LEM->ParseResultCache[$validationEqn]['hasErrors']) {
|
|
++$errorCount;
|
|
}
|
|
}
|
|
|
|
//////
|
|
// TEST VALIDITY OF ROOT VARIABLE NAME AND WHETHER HAS BEEN USED
|
|
//////
|
|
$rootVarName = $q['info']['rootVarName'];
|
|
$varNameErrorMsg = '';
|
|
$varNameError = NULL;
|
|
if (isset($varNamesUsed[$rootVarName]))
|
|
{
|
|
$varNameErrorMsg .= $LEM->gT('This variable name has already been used.');
|
|
}
|
|
else
|
|
{
|
|
$varNamesUsed[$rootVarName] = array(
|
|
'gseq'=>$gseq,
|
|
'qid'=>$qid
|
|
);
|
|
}
|
|
|
|
if (!preg_match('/^[a-zA-Z][0-9a-zA-Z]*$/', $rootVarName))
|
|
{
|
|
$varNameErrorMsg .= $LEM->gT('Starting in 2.05, variable names should only contain letters and numbers; and may not start with a number. This variable name is deprecated.');
|
|
}
|
|
if ($varNameErrorMsg != '')
|
|
{
|
|
$varNameError = array (
|
|
'message' => $varNameErrorMsg,
|
|
'gseq' => $varNamesUsed[$rootVarName]['gseq'],
|
|
'qid' => $varNamesUsed[$rootVarName]['qid'],
|
|
'gid' => $gid,
|
|
);
|
|
if (!$LEM->sgqaNaming)
|
|
{
|
|
++$errorCount;
|
|
}
|
|
else
|
|
{
|
|
++$warnings;
|
|
}
|
|
}
|
|
|
|
//////
|
|
// SHOW ALL SUB-QUESTIONS
|
|
//////
|
|
$sqRows='';
|
|
$i=0;
|
|
$sawThis = array(); // array of rowdivids already seen so only show them once
|
|
foreach ($sgqas as $sgqa)
|
|
{
|
|
if ($LEM->knownVars[$sgqa]['qcode'] == $rootVarName) {
|
|
continue; // so don't show the main question as a sub-question too
|
|
}
|
|
$rowdivid=$sgqa;
|
|
$varName=$LEM->knownVars[$sgqa]['qcode'];
|
|
switch ($q['info']['type'])
|
|
{
|
|
case '1':
|
|
if (preg_match('/#1$/',$sgqa)) {
|
|
$rowdivid = NULL; // so that doesn't show same message for second scale
|
|
}
|
|
else {
|
|
$rowdivid = substr($sgqa,0,-2); // strip suffix
|
|
$varName = substr($LEM->knownVars[$sgqa]['qcode'],0,-2);
|
|
}
|
|
break;
|
|
case 'P':
|
|
if (preg_match('/comment$/',$sgqa)) {
|
|
$rowdivid = NULL;
|
|
}
|
|
break;
|
|
case ':':
|
|
case ';':
|
|
$_rowdivid = $LEM->knownVars[$sgqa]['rowdivid'];
|
|
if (isset($sawThis[$qid . '~' . $_rowdivid])) {
|
|
$rowdivid = NULL; // so don't show again
|
|
}
|
|
else {
|
|
$sawThis[$qid . '~' . $_rowdivid] = true;
|
|
$rowdivid = $_rowdivid;
|
|
$sgqa_len = strlen($sid . 'X'. $gid . 'X' . $qid);
|
|
$varName = $rootVarName . '_' . substr($_rowdivid,$sgqa_len);
|
|
}
|
|
}
|
|
if (is_null($rowdivid)) {
|
|
continue;
|
|
}
|
|
++$i;
|
|
$subQeqn = ' ';
|
|
if (isset($LEM->subQrelInfo[$qid][$rowdivid]))
|
|
{
|
|
$sq = $LEM->subQrelInfo[$qid][$rowdivid];
|
|
$subQeqn = $sq['prettyPrintEqn']; // {' . $sq['eqn'] . '}'; // $sq['prettyPrintEqn'];
|
|
if ($sq['hasErrors']) {
|
|
++$errorCount;
|
|
}
|
|
}
|
|
|
|
$sgqaInfo = $LEM->knownVars[$sgqa];
|
|
$subqText = $sgqaInfo['subqtext'];
|
|
if (isset($sgqaInfo['default']) && $sgqaInfo['default'] !== '')
|
|
{
|
|
$LEM->ProcessString(htmlspecialchars($sgqaInfo['default']), $qid,NULL,false,1,1,false,false);
|
|
$_default = viewHelper::filterScript($LEM->GetLastPrettyPrintExpression());
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
$subQeqn .= '<br />(' . $LEM->gT('Default:') . ' ' . $_default . ')';
|
|
}
|
|
|
|
$sqRows .= "<tr class='LEMsubq'>"
|
|
. "<td>SQ-$i</td>"
|
|
. "<td><b>" . $varName . "</b></td>"
|
|
. "<td>$subQeqn</td>"
|
|
. "<td>" .$subqText . "</td>"
|
|
. "</tr>";
|
|
}
|
|
$LEM->ProcessString($sqRows, $qid,NULL,false,1,1,false,false);
|
|
$sqRows = viewHelper::filterScript($LEM->GetLastPrettyPrintExpression());
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
|
|
//////
|
|
// SHOW ANSWER OPTIONS FOR ENUMERATED LISTS, AND FOR MULTIFLEXI
|
|
//////
|
|
$answerRows='';
|
|
if (isset($LEM->qans[$qid]) || isset($LEM->multiflexiAnswers[$qid]))
|
|
{
|
|
$_scale=-1;
|
|
if (isset($LEM->multiflexiAnswers[$qid])) {
|
|
$ansList = $LEM->multiflexiAnswers[$qid];
|
|
}
|
|
else {
|
|
$ansList = $LEM->qans[$qid];
|
|
}
|
|
foreach ($ansList as $ans=>$value)
|
|
{
|
|
$ansInfo = explode('~',$ans);
|
|
$valParts = explode('|',$value);
|
|
$valInfo[0] = array_shift($valParts);
|
|
$valInfo[1] = implode('|',$valParts);
|
|
if ($_scale != $ansInfo[0]) {
|
|
$i=1;
|
|
$_scale = $ansInfo[0];
|
|
}
|
|
|
|
$subQeqn = '';
|
|
$rowdivid = $sgqas[0] . $ansInfo[1];
|
|
if ($q['info']['type'] == 'R')
|
|
{
|
|
$rowdivid = $LEM->sid . 'X' . $gid . 'X' . $qid . $ansInfo[1];
|
|
}
|
|
if (isset($LEM->subQrelInfo[$qid][$rowdivid]))
|
|
{
|
|
$sq = $LEM->subQrelInfo[$qid][$rowdivid];
|
|
$subQeqn = ' ' . $sq['prettyPrintEqn'];
|
|
if ($sq['hasErrors']) {
|
|
++$errorCount;
|
|
}
|
|
}
|
|
|
|
$answerRows .= "<tr class='LEManswer'>"
|
|
. "<td>A[" . $ansInfo[0] . "]-" . $i++ . "</td>"
|
|
. "<td><b>" . $ansInfo[1]. "</b></td>"
|
|
. "<td>[VALUE: " . $valInfo[0] . "]".$subQeqn."</td>"
|
|
. "<td>" . $valInfo[1] . "</td>"
|
|
. "</tr>\n";
|
|
}
|
|
$LEM->ProcessString($answerRows, $qid,NULL,false,1,1,false,false);
|
|
$answerRows = viewHelper::filterScript($LEM->GetLastPrettyPrintExpression());
|
|
if ($LEM->em->HasErrors()) {
|
|
++$errorCount;
|
|
}
|
|
}
|
|
|
|
//////
|
|
// FINALLY, SHOW THE QUESTION ROW(S), COLOR-CODING QUESTIONS THAT CONTAIN ERRORS
|
|
//////
|
|
$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) . "'" : '';
|
|
|
|
$questionRow = "<tr class='LEMquestion'>"
|
|
. "<td $errclass>Q-" . $q['info']['qseq'] . "</td>"
|
|
. "<td><b>" . $mandatory;
|
|
|
|
if ($varNameErrorMsg == '')
|
|
{
|
|
$questionRow .= $rootVarName;
|
|
}
|
|
else
|
|
{
|
|
$editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $LEM->sid . '/gid/' . $varNameError['gid'] . '/qid/' . $varNameError['qid']);
|
|
$questionRow .= "<span class='highlighterror' title='" . $varNameError['message'] . "' "
|
|
. "onclick='window.open(\"$editlink\",\"_blank\")'>"
|
|
. $rootVarName . "</span>";
|
|
}
|
|
$editlink = Yii::app()->getController()->createUrl('admin/survey/sa/view/surveyid/' . $sid . '/gid/' . $gid . '/qid/' . $qid);
|
|
$questionRow .= "</b><br />[<a target='_blank' href='$editlink'>QID $qid</a>]<br/>$typedesc [$type]</td>"
|
|
. "<td>" . $relevance . $prettyValidEqn . $default . "</td>"
|
|
. "<td>" . $qdetails . "</td>"
|
|
. "</tr>\n";
|
|
|
|
$out .= $questionRow;
|
|
$out .= $sqRows;
|
|
$out .= $answerRows;
|
|
|
|
if ($errorCount > 0) {
|
|
$allErrors[$gid . '~' . $qid] = $errorCount;
|
|
}
|
|
}
|
|
$out .= "</table>";
|
|
|
|
LimeExpressionManager::FinishProcessingPage();
|
|
if (($LEMdebugLevel & LEM_DEBUG_TIMING) == LEM_DEBUG_TIMING) {
|
|
$out .= LimeExpressionManager::GetDebugTimingMessage();
|
|
}
|
|
|
|
if (count($allErrors) > 0) {
|
|
$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;
|
|
}
|
|
else {
|
|
switch ($surveyMode)
|
|
{
|
|
case 'survey':
|
|
$message = $LEM->gT('No syntax errors detected in this survey');
|
|
break;
|
|
case 'group':
|
|
$message = $LEM->gT('This group, by itself, does not contain any syntax errors');
|
|
break;
|
|
case 'question':
|
|
$message = $LEM->gT('This question, by itself, does not contain any syntax errors');
|
|
break;
|
|
}
|
|
$out = "<p class='LEMheading'>$message</p>\n" . $out."</div>";
|
|
}
|
|
|
|
return array(
|
|
'errors'=>$allErrors,
|
|
'html'=>$out
|
|
);
|
|
}
|
|
|
|
/**
|
|
* TSV survey definition in format readable by TSVSurveyImport
|
|
* one line each per group, question, sub-question, and answer
|
|
* does not use SGQA naming at all.
|
|
* @param type $sid
|
|
* @return type
|
|
*/
|
|
static public function &TSVSurveyExport($sid)
|
|
{
|
|
$fields = array(
|
|
'class',
|
|
'type/scale',
|
|
'name',
|
|
'relevance',
|
|
'text',
|
|
'help',
|
|
'language',
|
|
'validation',
|
|
'mandatory',
|
|
'other',
|
|
'default',
|
|
'same_default',
|
|
// Advanced question attributes
|
|
'allowed_filetypes',
|
|
'alphasort',
|
|
'answer_width',
|
|
'array_filter',
|
|
'array_filter_exclude',
|
|
'array_filter_style',
|
|
'assessment_value',
|
|
'category_separator',
|
|
'choice_title',
|
|
'code_filter',
|
|
'commented_checkbox',
|
|
'commented_checkbox_auto',
|
|
'date_format',
|
|
'date_max',
|
|
'date_min',
|
|
'display_columns',
|
|
'display_rows',
|
|
'dropdown_dates',
|
|
'dropdown_dates_minute_step',
|
|
'dropdown_dates_month_style',
|
|
'dropdown_prefix',
|
|
'dropdown_prepostfix',
|
|
'dropdown_separators',
|
|
'dropdown_size',
|
|
'dualscale_headerA',
|
|
'dualscale_headerB',
|
|
'em_validation_q',
|
|
'em_validation_q_tip',
|
|
'em_validation_sq',
|
|
'em_validation_sq_tip',
|
|
'equals_num_value',
|
|
'exclude_all_others',
|
|
'exclude_all_others_auto',
|
|
'hidden',
|
|
'hide_tip',
|
|
'input_boxes',
|
|
'location_city',
|
|
'location_country',
|
|
'location_defaultcoordinates',
|
|
'location_mapheight',
|
|
'location_mapservice',
|
|
'location_mapwidth',
|
|
'location_mapzoom',
|
|
'location_nodefaultfromip',
|
|
'location_postal',
|
|
'location_state',
|
|
'max_answers',
|
|
'max_filesize',
|
|
'max_num_of_files',
|
|
'max_num_value',
|
|
'max_num_value_n',
|
|
'maximum_chars',
|
|
'min_answers',
|
|
'min_num_of_files',
|
|
'min_num_value',
|
|
'min_num_value_n',
|
|
'multiflexible_checkbox',
|
|
'multiflexible_max',
|
|
'multiflexible_min',
|
|
'multiflexible_step',
|
|
'num_value_int_only',
|
|
'numbers_only',
|
|
'other_comment_mandatory',
|
|
'other_numbers_only',
|
|
'other_replace_text',
|
|
'page_break',
|
|
'parent_order',
|
|
'prefix',
|
|
'printable_help',
|
|
'public_statistics',
|
|
'random_group',
|
|
'random_order',
|
|
'rank_title',
|
|
'repeat_headings',
|
|
'reverse',
|
|
'samechoiceheight',
|
|
'samelistheight',
|
|
'scale_export',
|
|
'show_comment',
|
|
'show_grand_total',
|
|
'show_title',
|
|
'show_totals',
|
|
'showpopups',
|
|
'slider_accuracy',
|
|
'slider_default',
|
|
'slider_layout',
|
|
'slider_max',
|
|
'slider_middlestart',
|
|
'slider_min',
|
|
'slider_rating',
|
|
'slider_reset',
|
|
'slider_separator',
|
|
'slider_showminmax',
|
|
'statistics_graphtype',
|
|
'statistics_showgraph',
|
|
'statistics_showmap',
|
|
'suffix',
|
|
'text_input_width',
|
|
'time_limit',
|
|
'time_limit_action',
|
|
'time_limit_countdown_message',
|
|
'time_limit_disable_next',
|
|
'time_limit_disable_prev',
|
|
'time_limit_message',
|
|
'time_limit_message_delay',
|
|
'time_limit_message_style',
|
|
'time_limit_timer_style',
|
|
'time_limit_warning',
|
|
'time_limit_warning_2',
|
|
'time_limit_warning_2_display_time',
|
|
'time_limit_warning_2_message',
|
|
'time_limit_warning_2_style',
|
|
'time_limit_warning_display_time',
|
|
'time_limit_warning_message',
|
|
'time_limit_warning_style',
|
|
'use_dropdown',
|
|
|
|
);
|
|
|
|
$rows = array();
|
|
$primarylang='en';
|
|
$otherlangs='';
|
|
$langs = array();
|
|
|
|
// Export survey-level information
|
|
$query = "select * from {{surveys}} where sid = " . $sid;
|
|
$data = dbExecuteAssoc($query);
|
|
foreach ($data->readAll() as $r)
|
|
{
|
|
foreach ($r as $key=>$value)
|
|
{
|
|
if ($value != '')
|
|
{
|
|
$row['class'] = 'S';
|
|
$row['name'] = $key;
|
|
$row['text'] = $value;
|
|
$rows[] = $row;
|
|
}
|
|
if ($key=='language')
|
|
{
|
|
$primarylang = $value;
|
|
}
|
|
if ($key=='additional_languages')
|
|
{
|
|
$otherlangs = $value;
|
|
}
|
|
}
|
|
}
|
|
$langs = explode(' ',$primarylang . ' ' . $otherlangs);
|
|
$langs = array_unique($langs);
|
|
|
|
// Export survey language settings
|
|
$query = "select * from {{surveys_languagesettings}} where surveyls_survey_id = " . $sid;
|
|
$data = dbExecuteAssoc($query);
|
|
foreach ($data->readAll() as $r)
|
|
{
|
|
$_lang = $r['surveyls_language'];
|
|
foreach ($r as $key=>$value)
|
|
{
|
|
if ($value != '' && $key != 'surveyls_language' && $key != 'surveyls_survey_id')
|
|
{
|
|
$row['class'] = 'SL';
|
|
$row['name'] = $key;
|
|
$row['text'] = $value;
|
|
$row['language'] = $_lang;
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
}
|
|
|
|
$surveyinfo = getSurveyInfo($sid);
|
|
$assessments = false;
|
|
if (isset($surveyinfo['assessments']) && $surveyinfo['assessments']=='Y')
|
|
{
|
|
$assessments = true;
|
|
}
|
|
|
|
foreach($langs as $lang)
|
|
{
|
|
if (trim($lang) == '')
|
|
{
|
|
continue;
|
|
}
|
|
SetSurveyLanguage($sid,$lang);
|
|
LimeExpressionManager::StartSurvey($sid, 'survey', array('sgqaNaming'=>'N','assessments'=>$assessments), true);
|
|
$moveResult = LimeExpressionManager::NavigateForwards();
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
|
|
if (is_null($moveResult) || is_null($LEM->currentQset) || count($LEM->currentQset) == 0) {
|
|
continue;
|
|
}
|
|
|
|
$_gseq=-1;
|
|
foreach ($LEM->currentQset as $q) {
|
|
$gseq = $q['info']['gseq'];
|
|
$gid = $q['info']['gid'];
|
|
$qid = $q['info']['qid'];
|
|
|
|
//////
|
|
// SHOW GROUP-LEVEL INFO
|
|
//////
|
|
if ($gseq != $_gseq) {
|
|
$_gseq = $gseq;
|
|
$ginfo = $LEM->gseq2info[$gseq];
|
|
|
|
// if relevance equation is using SGQA coding, convert to qcoding
|
|
$grelevance = (($ginfo['grelevance']=='') ? 1 : $ginfo['grelevance']);
|
|
$LEM->em->ProcessBooleanExpression($grelevance, $gseq, 0); // $qseq
|
|
$grelevance = trim(strip_tags($LEM->em->GetPrettyPrintString()));
|
|
$gtext = ((trim($ginfo['description']) == '') ? '' : $ginfo['description']);
|
|
|
|
$row = array();
|
|
$row['class'] = 'G';
|
|
//create a group code to allow proper importing of multi-lang survey TSVs
|
|
$row['type/scale']='G'.$gseq;
|
|
$row['name'] = $ginfo['group_name'];
|
|
$row['relevance'] = $grelevance;
|
|
$row['text'] = $gtext;
|
|
$row['language'] = $lang;
|
|
$rows[] = $row;
|
|
}
|
|
|
|
//////
|
|
// SHOW QUESTION-LEVEL INFO
|
|
//////
|
|
$row = array();
|
|
|
|
$mandatory = (($q['info']['mandatory']=='Y') ? 'Y' : '');
|
|
$type = $q['info']['type'];
|
|
|
|
$sgqas = explode('|',$q['sgqa']);
|
|
if (count($sgqas) == 1 && !is_null($q['info']['default']))
|
|
{
|
|
$default = $q['info']['default'];
|
|
}
|
|
else
|
|
{
|
|
$default = '';
|
|
}
|
|
|
|
$qtext = (($q['info']['qtext'] != '') ? $q['info']['qtext'] : '');
|
|
$help = (($q['info']['help'] != '') ? $q['info']['help']: '');
|
|
|
|
//////
|
|
// SHOW QUESTION ATTRIBUTES THAT ARE PROCESSED BY EM
|
|
//////
|
|
if (isset($LEM->qattr[$qid]) && count($LEM->qattr[$qid]) > 0) {
|
|
foreach ($LEM->qattr[$qid] as $key=>$value) {
|
|
if (is_null($value) || trim($value) == '') {
|
|
continue;
|
|
}
|
|
switch ($key)
|
|
{
|
|
default:
|
|
case 'exclude_all_others':
|
|
case 'exclude_all_others_auto':
|
|
case 'hidden':
|
|
if ($value == false || $value == '0') {
|
|
$value = NULL; // so can skip this one - just using continue here doesn't work.
|
|
}
|
|
break;
|
|
case 'relevance':
|
|
$value = NULL; // means an outdate database structure
|
|
break;
|
|
}
|
|
if (is_null($value) || trim($value) == '') {
|
|
continue; // since continuing from within a switch statement doesn't work
|
|
}
|
|
$row[$key] = $value;
|
|
}
|
|
}
|
|
|
|
// if relevance equation is using SGQA coding, convert to qcoding
|
|
$relevanceEqn = (($q['info']['relevance'] == '') ? 1 : $q['info']['relevance']);
|
|
$LEM->em->ProcessBooleanExpression($relevanceEqn, $gseq, $q['info']['qseq']); // $qseq
|
|
$relevanceEqn = trim(strip_tags($LEM->em->GetPrettyPrintString()));
|
|
$rootVarName = $q['info']['rootVarName'];
|
|
$preg = '';
|
|
if (isset($LEM->q2subqInfo[$q['info']['qid']]['preg']))
|
|
{
|
|
$preg = $LEM->q2subqInfo[$q['info']['qid']]['preg'];
|
|
if (is_null($preg))
|
|
{
|
|
$preg = '';
|
|
}
|
|
}
|
|
|
|
$row['class'] = 'Q';
|
|
$row['type/scale'] = $type;
|
|
$row['name'] = $rootVarName;
|
|
$row['relevance'] = $relevanceEqn;
|
|
$row['text'] = $qtext;
|
|
$row['help'] = $help;
|
|
$row['language'] = $lang;
|
|
$row['validation'] = $preg;
|
|
$row['mandatory'] = $mandatory;
|
|
$row['other'] = $q['info']['other'];
|
|
$row['default'] = $default;
|
|
$row['same_default'] = 1; // TODO - need this: $q['info']['same_default'];
|
|
|
|
$rows[] = $row;
|
|
|
|
//////
|
|
// SHOW ALL SUB-QUESTIONS
|
|
//////
|
|
$sawThis = array(); // array of rowdivids already seen so only show them once
|
|
foreach ($sgqas as $sgqa)
|
|
{
|
|
if ($LEM->knownVars[$sgqa]['qcode'] == $rootVarName) {
|
|
continue; // so don't show the main question as a sub-question too
|
|
}
|
|
$rowdivid=$sgqa;
|
|
$varName=$LEM->knownVars[$sgqa]['qcode'];
|
|
|
|
switch ($q['info']['type'])
|
|
{
|
|
case '1':
|
|
if (preg_match('/#1$/',$sgqa)) {
|
|
$rowdivid = NULL; // so that doesn't show same message for second scale
|
|
}
|
|
else {
|
|
$rowdivid = substr($sgqa,0,-2); // strip suffix
|
|
$varName = substr($LEM->knownVars[$sgqa]['qcode'],0,-2);
|
|
}
|
|
break;
|
|
case 'P':
|
|
if (preg_match('/comment$/',$sgqa)) {
|
|
$rowdivid = NULL;
|
|
}
|
|
break;
|
|
case ':':
|
|
case ';':
|
|
$_rowdivid = $LEM->knownVars[$sgqa]['rowdivid'];
|
|
if (isset($sawThis[$qid . '~' . $_rowdivid])) {
|
|
$rowdivid = NULL; // so don't show again
|
|
}
|
|
else {
|
|
$sawThis[$qid . '~' . $_rowdivid] = true;
|
|
$rowdivid = $_rowdivid;
|
|
$sgqa_len = strlen($sid . 'X'. $gid . 'X' . $qid);
|
|
$varName = $rootVarName . '_' . substr($_rowdivid,$sgqa_len);
|
|
}
|
|
break;
|
|
}
|
|
if (is_null($rowdivid)) {
|
|
continue;
|
|
}
|
|
|
|
$sgqaInfo = $LEM->knownVars[$sgqa];
|
|
$subqText = $sgqaInfo['subqtext'];
|
|
|
|
if (isset($sgqaInfo['default']))
|
|
{
|
|
$default = $sgqaInfo['default'];
|
|
}
|
|
else
|
|
{
|
|
$default = '';
|
|
}
|
|
|
|
$row = array();
|
|
$row['class'] = 'SQ';
|
|
$row['type/scale'] = 0;
|
|
$row['name'] = substr($varName,strlen($rootVarName)+1);
|
|
$row['text'] = $subqText;
|
|
$row['language'] = $lang;
|
|
$row['default'] = $default;
|
|
$rows[] = $row;
|
|
}
|
|
|
|
//////
|
|
// SHOW ANSWER OPTIONS FOR ENUMERATED LISTS, AND FOR MULTIFLEXI
|
|
//////
|
|
if (isset($LEM->qans[$qid]) || isset($LEM->multiflexiAnswers[$qid]))
|
|
{
|
|
$_scale=-1;
|
|
if (isset($LEM->multiflexiAnswers[$qid])) {
|
|
$ansList = $LEM->multiflexiAnswers[$qid];
|
|
}
|
|
else {
|
|
$ansList = $LEM->qans[$qid];
|
|
}
|
|
foreach ($ansList as $ans=>$value)
|
|
{
|
|
$ansInfo = explode('~',$ans);
|
|
$valParts = explode('|',$value);
|
|
$valInfo[0] = array_shift($valParts);
|
|
$valInfo[1] = implode('|',$valParts);
|
|
if ($_scale != $ansInfo[0]) {
|
|
$_scale = $ansInfo[0];
|
|
}
|
|
|
|
$row = array();
|
|
if ($type == ':' || $type == ';')
|
|
{
|
|
$row['class'] = 'SQ';
|
|
}
|
|
else
|
|
{
|
|
$row['class'] = 'A';
|
|
}
|
|
$row['type/scale'] = $_scale;
|
|
$row['name'] = $ansInfo[1];
|
|
$row['relevance'] = $assessments==true ? $valInfo[0] : '';
|
|
$row['text'] = $valInfo[1];
|
|
$row['language'] = $lang;
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Now generate the array out output data
|
|
$out = array();
|
|
$out[] = $fields;
|
|
|
|
foreach ($rows as $row)
|
|
{
|
|
$tsv = array();
|
|
foreach ($fields as $field)
|
|
{
|
|
$val = (isset($row[$field]) ? $row[$field] : '');
|
|
$tsv[] = $val;
|
|
}
|
|
$out[] = $tsv;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Returns the survey ID of the EM singleton
|
|
*/
|
|
public static function getLEMsurveyId() {
|
|
$LEM =& LimeExpressionManager::singleton();
|
|
return $LEM->sid;
|
|
}
|
|
|
|
/**
|
|
* This function loads the relevant data about tokens for a survey.
|
|
* If specific token is not given it loads empty values, this is used for
|
|
* question previewing and the like.
|
|
*
|
|
* @param int $iSurveyId
|
|
* @param string $sToken
|
|
* @param boolean $bAnonymize
|
|
* @return void
|
|
*/
|
|
public function loadTokenInformation($iSurveyId, $sToken = null, $bAnonymize = false)
|
|
{
|
|
if (!Survey::model()->hasTokens($iSurveyId))
|
|
{
|
|
return;
|
|
}
|
|
if ($sToken == null && isset($_SESSION[$this->sessid]['token']))
|
|
{
|
|
$sToken = $_SESSION[$this->sessid]['token'];
|
|
}
|
|
$token = Token::model($iSurveyId)->findByAttributes(array(
|
|
'token' => $sToken
|
|
));
|
|
|
|
$this->knownVars['TOKEN:TOKEN'] = array(
|
|
'code'=> $sToken,
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
if (isset($token))
|
|
{
|
|
foreach ($token->attributes as $key => $val)
|
|
{
|
|
if ($bAnonymize)
|
|
{
|
|
$val = "";
|
|
}
|
|
$key = "TOKEN:" . strtoupper($key);
|
|
$this->knownVars[$key] = array(
|
|
'code'=>$val,
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read list of available tokens from the tokens table so that preview and error checking works correctly
|
|
|
|
$blankVal = array(
|
|
'code'=>'',
|
|
'jsName_on'=>'',
|
|
'jsName'=>'',
|
|
'readWrite'=>'N',
|
|
);
|
|
|
|
foreach (getTokenFieldsAndNames($surveyId) as $field => $details)
|
|
{
|
|
if (preg_match('/^(firstname|lastname|email|usesleft|token|attribute_\d+)$/', $field))
|
|
{
|
|
$this->knownVars['TOKEN:' . strtoupper($field)] = $blankVal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Used by usort() to order $this->questionSeq2relevance in proper order
|
|
* @param <type> $a
|
|
* @param <type> $b
|
|
* @return <type>
|
|
*/
|
|
function cmpQuestionSeq($a, $b)
|
|
{
|
|
if (is_null($a['qseq'])) {
|
|
if (is_null($b['qseq'])) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
if (is_null($b['qseq'])) {
|
|
return -1;
|
|
}
|
|
if ($a['qseq'] == $b['qseq']) {
|
|
return 0;
|
|
}
|
|
return ($a['qseq'] < $b['qseq']) ? -1 : 1;
|
|
}
|
|
|
|
|
|
?>
|