diff --git a/.gitignore b/.gitignore index a09bbfb58e3..6bc4cd2c377 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /upload/surveys/* /styles/gringegreen/images/Thumbs.db /upload/templates/* -/tmp/runtime/HTML +/tmp/runtime/* /application/controllers/admin/updater.php /tmp/*.html /tmp/*.lss diff --git a/application/config/internal.php b/application/config/internal.php index 991ca3811a3..aacce78210b 100644 --- a/application/config/internal.php +++ b/application/config/internal.php @@ -10,14 +10,20 @@ 'runtimePath' => dirname(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'runtime', 'name' => 'LimeSurvey', 'defaultController' => 'survey', - + 'theme' => 'default', 'import' => array( 'application.core.*', 'application.models.*', 'application.controllers.*', 'application.modules.*', ), + 'preload' => array( + 'limescript' + ), 'components' => array( + 'limescript' => array( + 'class' => 'ext.LimeScript.LimeScript' + ), 'bootstrap' => array( 'class' => 'ext.bootstrap.components.Bootstrap', 'responsiveCss' => false, diff --git a/application/controllers/GroupsController.php b/application/controllers/GroupsController.php index 97e4791e6b4..1ccc56d9fb2 100644 --- a/application/controllers/GroupsController.php +++ b/application/controllers/GroupsController.php @@ -11,6 +11,8 @@ public function actionPreview($id, $language = 'en') $renderedQuestions = array(); if (isset($group)) { + App()->getSurveySession()->create($group->sid, true); + if (!isset($language)) { @@ -21,15 +23,70 @@ public function actionPreview($id, $language = 'en') 'gid' => $id, 'parent_id' => null )); + Yii::import('ext.ExpressionManager.ExpressionManager'); + $em = new ExpressionManager(array( + 'isValidVariable' => function($name) { + //echo 'isvalid:
'; var_dump($name); + return true; + }, + 'GetVarAttribute' => function($name, $attr, $default) { + //echo 'getattr:
'; var_dump(func_get_args()); + switch ($attr) { + case 'code': return $name; + case 'jsName' : return 'JS' . $name; + default: return $default; + } + } + + )); + $renderedQuestions = array(); + $data = array(); + $i = 0; foreach ($questions as $question) { $questionObject = App()->getPluginManager()->constructQuestionFromGUID($question->questiontype, $question->qid); - $renderedQuestions[] = $questionObject->render("{$group->group_name}-{$question->qid}", $language, true); + if (!isset($data[$questionObject->getGUID()])) + { + $data[$questionObject->getGUID()] = $questionObject->getJavascript(); + } + $name = "{$group->group_name}-{$question->qid}"; + $renderedQuestions[] = $questionObject->render($name, $language, true); + App()->getLimeScript()->add("p.questions.{$question->code}.type", $questionObject->getGUID()); + App()->getLimeScript()->add("p.questions.{$question->code}.div", "question$i"); + // Register variables. + $code = $question->code; + // More variables shoudl be added here. + App()->getLimeScript()->add("p.VariableToCode.$code", $code); + + + + App()->getLimeScript()->add("p.questions.{$question->code}.id", $name); + if ($question->relevance != null) + { + $em->ProcessBooleanExpression($question->relevance); + $js = $em->GetJavaScriptEquivalentOfExpression(); + App()->getLimeScript()->add("p.questions.{$question->code}.relevanceStatus", new CJavaScriptExpression('function() { return ' . $js . '; }')); + + } + foreach ($questionObject->getVariables() as $variable) + { + App()->getLimeScript()->add("p.VariableToCode.{$code}_{$variable}", $code); + } + $i++; } + App()->getLimeScript()->add("p.QuestionTypes", $data); } $template = Survey::model()->findFieldByPk($group->sid, 'template'); $this->layout = 'survey'; + + /* + var_dump($em->ProcessBooleanExpression('q1 == 1')); + echo '---------------------'; + var_dump($em->GetJavaScriptEquivalentOfExpression()); + echo '---------------------'; + var_dump($em->sProcessStringContainingExpressions('test123 {1+2}')); + */ $this->render('/groups/preview', compact('renderedQuestions', 'template')); } diff --git a/application/controllers/QuestionsController.php b/application/controllers/QuestionsController.php index 9f01c90f2da..fa0f2ae0ada 100644 --- a/application/controllers/QuestionsController.php +++ b/application/controllers/QuestionsController.php @@ -113,6 +113,7 @@ public function actionUpdate($id, $questiontype = null) // Always redirect to prevent reloading from resubmitting. $this->redirect(array($this->route, 'id' => $id)); } + /** * @todo Add support for save & close button; in case of close redirect to overview page. */ @@ -121,8 +122,13 @@ public function actionUpdate($id, $questiontype = null) $this->navData['surveyId'] = $question['sid']; $this->navData['groupId'] = $question['gid']; $this->navData['questionId'] = $id; + + $survey = Survey::model()->findByPk($question['sid']); + + $languages = $survey->getLanguages(); + $groups = Groups::model()->findListByAttributes(array('sid' => $question['sid']), 'group_name'); $questions = Questions::model()->findListByAttributes(array('sid' => $question['sid']), 'code', null, array('order'=> 'sortorder')); if (App()->request->getIsAjaxRequest()) diff --git a/application/core/LSYii_Application.php b/application/core/LSYii_Application.php index a7b868f4b92..bdbe255c13a 100644 --- a/application/core/LSYii_Application.php +++ b/application/core/LSYii_Application.php @@ -109,7 +109,7 @@ public function __construct($config = null) parent::__construct($config); // Load the default and environmental settings from different files into self. - + Yii::setPathOfAlias('bootstrap' , Yii::getPathOfAlias('ext.bootstrap')); $ls_config = require(Yii::getPathOfAlias('application.config') . '/config-defaults.php'); $email_config = require(Yii::getPathOfAlias('application.config') . '/email.php'); $version_config = require(Yii::getPathOfAlias('application.config') . '/version.php'); @@ -253,7 +253,15 @@ public function setLang(Limesurvey_lang $lang) { $this->lang = $lang; } - + + /** + * Get the script manager. + * @return LimeScript + */ + public function getLimeScript() + { + return $this->getComponent('limescript'); + } /** * Get the pluginManager * diff --git a/application/core/LSYii_Controller.php b/application/core/LSYii_Controller.php index 41b447441af..dc136d5d8f7 100644 --- a/application/core/LSYii_Controller.php +++ b/application/core/LSYii_Controller.php @@ -39,9 +39,9 @@ public function __construct($id, $module = null) $this->loadHelper('common'); $this->loadHelper('survey'); $this->loadLibrary('limesurvey_lang'); - $this->loadHelper('expressions.em_manager'); $this->loadHelper('replacements'); $this->_init(); + } /** diff --git a/application/helpers/expressions/em_core_helper.php b/application/extensions/ExpressionManager/ExpressionManager.php similarity index 97% rename from application/helpers/expressions/em_core_helper.php rename to application/extensions/ExpressionManager/ExpressionManager.php index 1785bf6f700..9ea6d17cdf1 100644 --- a/application/helpers/expressions/em_core_helper.php +++ b/application/extensions/ExpressionManager/ExpressionManager.php @@ -75,6 +75,8 @@ class ExpressionManager { function __construct($callbacks = array()) { + App()->getClientScript()->registerScriptFile(Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('ext.ExpressionManager.assets'). '/em.js')); + App()->getClientScript()->registerCssFile(Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('ext.ExpressionManager.assets'). '/em.css')); $this->callbacks = $callbacks; // List of token-matching regular expressions // Note, this is effectively a Lexer using Regular Expressions. Don't change this unless you understand compiler design. @@ -170,12 +172,12 @@ function __construct($callbacks = array()) 'ceil' => array('ceil', 'Math.ceil', $this->gT('Round fractions up'), 'number ceil(number)', 'http://www.php.net/manual/en/function.ceil.php', 1), 'checkdate' => array('checkdate', 'checkdate', $this->gT('Returns true(1) if it is a valid date in gregorian calendar'), 'bool checkdate(month,day,year)', 'http://www.php.net/manual/en/function.checkdate.php', 3), 'cos' => array('cos', 'Math.cos', $this->gT('Cosine'), 'number cos(number)', 'http://www.php.net/manual/en/function.cos.php', 1), -'count' => array('exprmgr_count', 'LEMcount', $this->gT('Count the number of answered questions in the list'), 'number count(arg1, arg2, ... argN)', '', -1), -'countif' => array('exprmgr_countif', 'LEMcountif', $this->gT('Count the number of answered questions in the list equal the first argument'), 'number countif(matches, arg1, arg2, ... argN)', '', -2), -'countifop' => array('exprmgr_countifop', 'LEMcountifop', $this->gT('Count the number of answered questions in the list which pass the critiera (arg op value)'), 'number countifop(op, value, arg1, arg2, ... argN)', '', -3), +'count' => array('exprmgr_count', 'EM.count', $this->gT('Count the number of answered questions in the list'), 'number count(arg1, arg2, ... argN)', '', -1), +'countif' => array('exprmgr_countif', 'EM.countif', $this->gT('Count the number of answered questions in the list equal the first argument'), 'number countif(matches, arg1, arg2, ... argN)', '', -2), +'countifop' => array('exprmgr_countifop', 'EM.countifop', $this->gT('Count the number of answered questions in the list which pass the critiera (arg op value)'), 'number countifop(op, value, arg1, arg2, ... argN)', '', -3), 'date' => array('date', 'date', $this->gT('Format a local date/time'), 'string date(format [, timestamp=time()])', 'http://www.php.net/manual/en/function.date.php', 1,2), 'exp' => array('exp', 'Math.exp', $this->gT('Calculates the exponent of e'), 'number exp(number)', 'http://www.php.net/manual/en/function.exp.php', 1), -'fixnum' => array('exprmgr_fixnum', 'LEMfixnum', $this->gT('Display numbers with comma as decimal separator, if needed'), 'string fixnum(number)', '', 1), +'fixnum' => array('exprmgr_fixnum', 'EM.fixnum', $this->gT('Display numbers with comma as decimal separator, if needed'), 'string fixnum(number)', '', 1), 'floor' => array('floor', 'Math.floor', $this->gT('Round fractions down'), 'number floor(number)', 'http://www.php.net/manual/en/function.floor.php', 1), 'gmdate' => array('gmdate', 'gmdate', $this->gT('Format a GMT date/time'), 'string gmdate(format [, timestamp=time()])', 'http://www.php.net/manual/en/function.gmdate.php', 1,2), 'html_entity_decode' => array('html_entity_decode', 'html_entity_decode', $this->gT('Convert all HTML entities to their applicable characters (always uses ENT_QUOTES and UTF-8)'), 'string html_entity_decode(string)', 'http://www.php.net/manual/en/function.html-entity-decode.php', 1), @@ -183,61 +185,61 @@ function __construct($callbacks = array()) 'htmlspecialchars' => array('expr_mgr_htmlspecialchars', 'htmlspecialchars', $this->gT('Convert special characters to HTML entities (always uses ENT_QUOTES and UTF-8)'), 'string htmlspecialchars(string)', 'http://www.php.net/manual/en/function.htmlspecialchars.php', 1), 'htmlspecialchars_decode' => array('expr_mgr_htmlspecialchars_decode', 'htmlspecialchars_decode', $this->gT('Convert special HTML entities back to characters (always uses ENT_QUOTES and UTF-8)'), 'string htmlspecialchars_decode(string)', 'http://www.php.net/manual/en/function.htmlspecialchars-decode.php', 1), 'idate' => array('idate', 'idate', $this->gT('Format a local time/date as integer'), 'string idate(string [, timestamp=time()])', 'http://www.php.net/manual/en/function.idate.php', 1,2), -'if' => array('exprmgr_if', 'LEMif', $this->gT('Conditional processing'), 'if(test,result_if_true,result_if_false)', '', 3), -'implode' => array('exprmgr_implode', 'LEMimplode', $this->gT('Join array elements with a string'), 'string implode(glue,arg1,arg2,...,argN)', 'http://www.php.net/manual/en/function.implode.php', -2), -'intval' => array('intval', 'LEMintval', $this->gT('Get the integer value of a variable'), 'int intval(number [, base=10])', 'http://www.php.net/manual/en/function.intval.php', 1,2), -'is_empty' => array('exprmgr_empty', 'LEMempty', $this->gT('Determine whether a variable is considered to be empty'), 'bool is_empty(var)', 'http://www.php.net/manual/en/function.empty.php', 1), -'is_float' => array('is_float', 'LEMis_float', $this->gT('Finds whether the type of a variable is float'), 'bool is_float(var)', 'http://www.php.net/manual/en/function.is-float.php', 1), -'is_int' => array('is_int', 'LEMis_int', $this->gT('Find whether the type of a variable is integer'), 'bool is_int(var)', 'http://www.php.net/manual/en/function.is-int.php', 1), +'if' => array('exprmgr_if', 'EM.if', $this->gT('Conditional processing'), 'if(test,result_if_true,result_if_false)', '', 3), +'implode' => array('exprmgr_implode', 'EM.implode', $this->gT('Join array elements with a string'), 'string implode(glue,arg1,arg2,...,argN)', 'http://www.php.net/manual/en/function.implode.php', -2), +'intval' => array('intval', 'EM.intval', $this->gT('Get the integer value of a variable'), 'int intval(number [, base=10])', 'http://www.php.net/manual/en/function.intval.php', 1,2), +'is_empty' => array('exprmgr_empty', 'EM.empty', $this->gT('Determine whether a variable is considered to be empty'), 'bool is_empty(var)', 'http://www.php.net/manual/en/function.empty.php', 1), +'is_float' => array('is_float', 'EM.is_float', $this->gT('Finds whether the type of a variable is float'), 'bool is_float(var)', 'http://www.php.net/manual/en/function.is-float.php', 1), +'is_int' => array('is_int', 'EM.is_int', $this->gT('Find whether the type of a variable is integer'), 'bool is_int(var)', 'http://www.php.net/manual/en/function.is-int.php', 1), 'is_nan' => array('is_nan', 'isNaN', $this->gT('Finds whether a value is not a number'), 'bool is_nan(var)', 'http://www.php.net/manual/en/function.is-nan.php', 1), -'is_null' => array('is_null', 'LEMis_null', $this->gT('Finds whether a variable is NULL'), 'bool is_null(var)', 'http://www.php.net/manual/en/function.is-null.php', 1), -'is_numeric' => array('is_numeric', 'LEMis_numeric', $this->gT('Finds whether a variable is a number or a numeric string'), 'bool is_numeric(var)', 'http://www.php.net/manual/en/function.is-numeric.php', 1), -'is_string' => array('is_string', 'LEMis_string', $this->gT('Find whether the type of a variable is string'), 'bool is_string(var)', 'http://www.php.net/manual/en/function.is-string.php', 1), -'join' => array('exprmgr_join', 'LEMjoin', $this->gT('Join strings, return joined string.This function is an alias of implode("",argN)'), 'string join(arg1,arg2,...,argN)', '', -1), -'list' => array('exprmgr_list', 'LEMlist', $this->gT('Return comma-separated list of values'), 'string list(arg1, arg2, ... argN)', '', -2), -'log' => array('exprmgr_log', 'LEMlog', $this->gT('The logarithm of number to base, if given, or the natural logarithm. '), 'number log(number,base=e)', 'http://www.php.net/manual/en/function.log.php', -2), +'is_null' => array('is_null', 'EM.is_null', $this->gT('Finds whether a variable is NULL'), 'bool is_null(var)', 'http://www.php.net/manual/en/function.is-null.php', 1), +'is_numeric' => array('is_numeric', 'EM.is_numeric', $this->gT('Finds whether a variable is a number or a numeric string'), 'bool is_numeric(var)', 'http://www.php.net/manual/en/function.is-numeric.php', 1), +'is_string' => array('is_string', 'EM.is_string', $this->gT('Find whether the type of a variable is string'), 'bool is_string(var)', 'http://www.php.net/manual/en/function.is-string.php', 1), +'join' => array('exprmgr_join', 'EM.join', $this->gT('Join strings, return joined string.This function is an alias of implode("",argN)'), 'string join(arg1,arg2,...,argN)', '', -1), +'list' => array('exprmgr_list', 'EM.list', $this->gT('Return comma-separated list of values'), 'string list(arg1, arg2, ... argN)', '', -2), +'log' => array('exprmgr_log', 'EM.log', $this->gT('The logarithm of number to base, if given, or the natural logarithm. '), 'number log(number,base=e)', 'http://www.php.net/manual/en/function.log.php', -2), 'ltrim' => array('ltrim', 'ltrim', $this->gT('Strip whitespace (or other characters) from the beginning of a string'), 'string ltrim(string [, charlist])', 'http://www.php.net/manual/en/function.ltrim.php', 1,2), 'max' => array('max', 'Math.max', $this->gT('Find highest value'), 'number max(arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.max.php', -2), 'min' => array('min', 'Math.min', $this->gT('Find lowest value'), 'number min(arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.min.php', -2), 'mktime' => array('mktime', 'mktime', $this->gT('Get UNIX timestamp for a date (each of the 6 arguments are optional)'), 'number mktime([hour [, minute [, second [, month [, day [, year ]]]]]])', 'http://www.php.net/manual/en/function.mktime.php', 0,1,2,3,4,5,6), 'nl2br' => array('nl2br', 'nl2br', $this->gT('Inserts HTML line breaks before all newlines in a string'), 'string nl2br(string)', 'http://www.php.net/manual/en/function.nl2br.php', 1,1), 'number_format' => array('number_format', 'number_format', $this->gT('Format a number with grouped thousands'), 'string number_format(number)', 'http://www.php.net/manual/en/function.number-format.php', 1), -'pi' => array('pi', 'LEMpi', $this->gT('Get value of pi'), 'number pi()', '', 0), +'pi' => array('pi', 'EM.pi', $this->gT('Get value of pi'), 'number pi()', '', 0), 'pow' => array('pow', 'Math.pow', $this->gT('Exponential expression'), 'number pow(base, exp)', 'http://www.php.net/manual/en/function.pow.php', 2), 'quoted_printable_decode' => array('quoted_printable_decode', 'quoted_printable_decode', $this->gT('Convert a quoted-printable string to an 8 bit string'), 'string quoted_printable_decode(string)', 'http://www.php.net/manual/en/function.quoted-printable-decode.php', 1), 'quoted_printable_encode' => array('quoted_printable_encode', 'quoted_printable_encode', $this->gT('Convert a 8 bit string to a quoted-printable string'), 'string quoted_printable_encode(string)', 'http://www.php.net/manual/en/function.quoted-printable-encode.php', 1), 'quotemeta' => array('quotemeta', 'quotemeta', $this->gT('Quote meta characters'), 'string quotemeta(string)', 'http://www.php.net/manual/en/function.quotemeta.php', 1), 'rand' => array('rand', 'rand', $this->gT('Generate a random integer'), 'int rand() OR int rand(min, max)', 'http://www.php.net/manual/en/function.rand.php', 0,2), -'regexMatch' => array('exprmgr_regexMatch', 'LEMregexMatch', $this->gT('Compare a string to a regular expression pattern'), 'bool regexMatch(pattern,input)', '', 2), +'regexMatch' => array('exprmgr_regexMatch', 'EM.regexMatch', $this->gT('Compare a string to a regular expression pattern'), 'bool regexMatch(pattern,input)', '', 2), 'round' => array('round', 'round', $this->gT('Rounds a number to an optional precision'), 'number round(val [, precision])', 'http://www.php.net/manual/en/function.round.php', 1,2), 'rtrim' => array('rtrim', 'rtrim', $this->gT('Strip whitespace (or other characters) from the end of a string'), 'string rtrim(string [, charlist])', 'http://www.php.net/manual/en/function.rtrim.php', 1,2), 'sin' => array('sin', 'Math.sin', $this->gT('Sine'), 'number sin(arg)', 'http://www.php.net/manual/en/function.sin.php', 1), 'sprintf' => array('sprintf', 'sprintf', $this->gT('Return a formatted string'), 'string sprintf(format, arg1, arg2, ... argN)', 'http://www.php.net/manual/en/function.sprintf.php', -2), 'sqrt' => array('sqrt', 'Math.sqrt', $this->gT('Square root'), 'number sqrt(arg)', 'http://www.php.net/manual/en/function.sqrt.php', 1), -'stddev' => array('exprmgr_stddev', 'LEMstddev', $this->gT('Calculate the Sample Standard Deviation for the list of numbers'), 'number stddev(arg1, arg2, ... argN)', '', -2), +'stddev' => array('exprmgr_stddev', 'EM.stddev', $this->gT('Calculate the Sample Standard Deviation for the list of numbers'), 'number stddev(arg1, arg2, ... argN)', '', -2), 'str_pad' => array('str_pad', 'str_pad', $this->gT('Pad a string to a certain length with another string'), 'string str_pad(input, pad_length [, pad_string])', 'http://www.php.net/manual/en/function.str-pad.php', 2,3), 'str_repeat' => array('str_repeat', 'str_repeat', $this->gT('Repeat a string'), 'string str_repeat(input, multiplier)', 'http://www.php.net/manual/en/function.str-repeat.php', 2), -'str_replace' => array('str_replace', 'LEMstr_replace', $this->gT('Replace all occurrences of the search string with the replacement string'), 'string str_replace(search, replace, subject)', 'http://www.php.net/manual/en/function.str-replace.php', 3), +'str_replace' => array('str_replace', 'EM.str_replace', $this->gT('Replace all occurrences of the search string with the replacement string'), 'string str_replace(search, replace, subject)', 'http://www.php.net/manual/en/function.str-replace.php', 3), 'strcasecmp' => array('strcasecmp', 'strcasecmp', $this->gT('Binary safe case-insensitive string comparison'), 'int strcasecmp(str1, str2)', 'http://www.php.net/manual/en/function.strcasecmp.php', 2), 'strcmp' => array('strcmp', 'strcmp', $this->gT('Binary safe string comparison'), 'int strcmp(str1, str2)', 'http://www.php.net/manual/en/function.strcmp.php', 2), 'strip_tags' => array('strip_tags', 'strip_tags', $this->gT('Strip HTML and PHP tags from a string'), 'string strip_tags(str, allowable_tags)', 'http://www.php.net/manual/en/function.strip-tags.php', 1,2), 'stripos' => array('stripos', 'stripos', $this->gT('Find position of first occurrence of a case-insensitive string'), 'int stripos(haystack, needle [, offset=0])', 'http://www.php.net/manual/en/function.stripos.php', 2,3), 'stripslashes' => array('stripslashes', 'stripslashes', $this->gT('Un-quotes a quoted string'), 'string stripslashes(string)', 'http://www.php.net/manual/en/function.stripslashes.php', 1), 'stristr' => array('stristr', 'stristr', $this->gT('Case-insensitive strstr'), 'string stristr(haystack, needle [, before_needle=false])', 'http://www.php.net/manual/en/function.stristr.php', 2,3), -'strlen' => array('strlen', 'LEMstrlen', $this->gT('Get string length'), 'int strlen(string)', 'http://www.php.net/manual/en/function.strlen.php', 1), -'strpos' => array('strpos', 'LEMstrpos', $this->gT('Find position of first occurrence of a string'), 'int strpos(haystack, needle [ offset=0])', 'http://www.php.net/manual/en/function.strpos.php', 2,3), +'strlen' => array('strlen', 'EM.strlen', $this->gT('Get string length'), 'int strlen(string)', 'http://www.php.net/manual/en/function.strlen.php', 1), +'strpos' => array('strpos', 'EM.strpos', $this->gT('Find position of first occurrence of a string'), 'int strpos(haystack, needle [ offset=0])', 'http://www.php.net/manual/en/function.strpos.php', 2,3), 'strrev' => array('strrev', 'strrev', $this->gT('Reverse a string'), 'string strrev(string)', 'http://www.php.net/manual/en/function.strrev.php', 1), 'strstr' => array('strstr', 'strstr', $this->gT('Find first occurrence of a string'), 'string strstr(haystack, needle)', 'http://www.php.net/manual/en/function.strstr.php', 2), -'strtolower' => array('strtolower', 'LEMstrtolower', $this->gT('Make a string lowercase'), 'string strtolower(string)', 'http://www.php.net/manual/en/function.strtolower.php', 1), -'strtoupper' => array('strtoupper', 'LEMstrtoupper', $this->gT('Make a string uppercase'), 'string strtoupper(string)', 'http://www.php.net/manual/en/function.strtoupper.php', 1), +'strtolower' => array('strtolower', 'EM.strtolower', $this->gT('Make a string lowercase'), 'string strtolower(string)', 'http://www.php.net/manual/en/function.strtolower.php', 1), +'strtoupper' => array('strtoupper', 'EM.strtoupper', $this->gT('Make a string uppercase'), 'string strtoupper(string)', 'http://www.php.net/manual/en/function.strtoupper.php', 1), 'substr' => array('substr', 'substr', $this->gT('Return part of a string'), 'string substr(string, start [, length])', 'http://www.php.net/manual/en/function.substr.php', 2,3), -'sum' => array('array_sum', 'LEMsum', $this->gT('Calculate the sum of values in an array'), 'number sum(arg1, arg2, ... argN)', '', -2), -'sumifop' => array('exprmgr_sumifop', 'LEMsumifop', $this->gT('Sum the values of answered questions in the list which pass the critiera (arg op value)'), 'number sumifop(op, value, arg1, arg2, ... argN)', '', -3), +'sum' => array('array_sum', 'EM.sum', $this->gT('Calculate the sum of values in an array'), 'number sum(arg1, arg2, ... argN)', '', -2), +'sumifop' => array('exprmgr_sumifop', 'EM.sumifop', $this->gT('Sum the values of answered questions in the list which pass the critiera (arg op value)'), 'number sumifop(op, value, arg1, arg2, ... argN)', '', -3), 'tan' => array('tan', 'Math.tan', $this->gT('Tangent'), 'number tan(arg)', 'http://www.php.net/manual/en/function.tan.php', 1), 'time' => array('time', 'time', $this->gT('Return current UNIX timestamp'), 'number time()', 'http://www.php.net/manual/en/function.time.php', 0), 'trim' => array('trim', 'trim', $this->gT('Strip whitespace (or other characters) from the beginning and end of a string'), 'string trim(string [, charlist])', 'http://www.php.net/manual/en/function.trim.php', 1,2), 'ucwords' => array('ucwords', 'ucwords', $this->gT('Uppercase the first character of each word in a string'), 'string ucwords(string)', 'http://www.php.net/manual/en/function.ucwords.php', 1), -'unique' => array('exprmgr_unique', 'LEMunique', $this->gT('Returns true if all non-empty responses are unique'), 'boolean unique(arg1, ..., argN)', '', -1), +'unique' => array('exprmgr_unique', 'EM.unique', $this->gT('Returns true if all non-empty responses are unique'), 'boolean unique(arg1, ..., argN)', '', -1), ); } @@ -568,7 +570,7 @@ private function RDP_EvaluateConstantVarOrFunction() } else { - $relStatus = $this->GetVarAttribute($token[0],'relevanceStatus',1); + $relStatus = $this->GetVarAttribute($token[0],'relevanceStatus',1, $this->groupSeq, $this->questionSeq); } if ($relStatus==1) { @@ -1180,15 +1182,6 @@ public function GetJsVarsUsed() return array_unique($jsNames); } - /** - * Return the JavaScript variable name for a named variable - * @param $name - * @return - */ - public function GetJsVarFor($name) - { - return $this->GetVarAttribute($name,'jsName',''); - } /** * Returns array of all variables used when parsing a string via sProcessStringContainingExpressions @@ -1268,7 +1261,7 @@ public function GetJavaScriptEquivalentOfExpression() { // Javascript does concatenation unless both left and right side are numbers, so refactor the equation $varName = $this->GetVarAttribute($token[0],'varName',$token[0]); - $stringParts[] = " = LEMval('" . $varName . "') + "; + $stringParts[] = " = LS.getValue('" . $varName . "') + "; ++$i; } } @@ -1279,7 +1272,7 @@ public function GetJavaScriptEquivalentOfExpression() if ($jsName != '') { $varName = $this->GetVarAttribute($token[0],'varName',$token[0]); - $stringParts[] = "LEMval('" . $varName . "') "; + $stringParts[] = "LS.getValue('" . $varName . "') "; } else { @@ -1330,7 +1323,7 @@ public function GetJavaScriptEquivalentOfExpression() $varsUsed = implode("', '", $nonNAvarsUsed); if ($varsUsed != '') { - $this->jsExpression = "LEMif(LEManyNA('" . $varsUsed . "'),'',(" . $mainClause . "))"; + $this->jsExpression = "EM.if(EM.anyNA('" . $varsUsed . "'),'',(" . $mainClause . "))"; } else { @@ -1354,7 +1347,7 @@ public function GetJavascriptTestforExpression($expected,$num) $jsmultiline_expected = str_replace("\n","\\\n",addslashes($expected)); $jsParts = array(); $jsParts[] = "val = " . $jsmultiline_expr . ";\n"; - $jsParts[] = "klass = (LEMeq(addslashes(val),'" . $jsmultiline_expected . "')) ? 'ok' : 'error';\n"; + $jsParts[] = "klass = (EM.eq(addslashes(val),'" . $jsmultiline_expected . "')) ? 'ok' : 'error';\n"; $jsParts[] = "document.getElementById('test_" . $num . "').innerHTML=(val);\n"; $jsParts[] = "document.getElementById('test_" . $num . "').className=klass;\n"; return implode('',$jsParts); @@ -1371,7 +1364,7 @@ public function GetJavaScriptFunctionForReplacement($questionNum, $name,$eqn) $jsParts = array(); // $jsParts[] = "\n // Tailor Question " . $questionNum . " - " . $name . ": { " . $eqn . " }\n"; $jsParts[] = " try{\n"; - $jsParts[] = " document.getElementById('" . $name . "').innerHTML=LEMfixnum(\n "; + $jsParts[] = " document.getElementById('" . $name . "').innerHTML=EM.fixnum(\n "; $jsParts[] = $this->GetJavaScriptEquivalentOfExpression(); $jsParts[] = ");\n"; $jsParts[] = " } catch (e) { }\n"; @@ -1639,13 +1632,13 @@ public function GetPrettyPrintString() * @param $varname * @return */ - private function GetVarAttribute($name,$attr,$default) + private function GetVarAttribute($name,$attr,$default, $gSeq, $qSeq) { if (isset($this->callbacks['GetVarAttribute']) && is_callable($this->callbacks['GetVarAttribute'])) { - return call_user_func_array($this->callbacks['GetVarAttribute'], array($name, $attr, $default)); + return call_user_func_array($this->callbacks['GetVarAttribute'], array($name, $attr, $default, $gSeq, $qSeq)); } - return LimeExpressionManager::GetVarAttribute($name,$attr,$default,$this->groupSeq,$this->questionSeq); + return LimeExpressionManager::GetVarAttribute($name,$attr,$default, $gSeq, $qSeq); } /** @@ -1750,7 +1743,7 @@ private function RDP_isValidVariable($name) } else { - return LimeExpressionManager::isValidVariable($varName); + return false; } } @@ -1761,7 +1754,7 @@ private function RDP_isValidVariable($name) */ private function RDP_isWritableVariable($name) { - return ($this->GetVarAttribute($name, 'readWrite', 'N') == 'Y'); + return ($this->GetVarAttribute($name, 'readWrite', 'N', $this->groupSeq, $this->questionSeq) == 'Y'); } /** @@ -1790,12 +1783,12 @@ public function ProcessBooleanExpression($expr,$groupSeq=-1,$questionSeq=-1) // } // return !empty($result); - // Check whether any variables are irrelevant - making this comparable to JavaScript which uses LEManyNA(varlist) to do the same thing + // Check whether any variables are irrelevant - making this comparable to JavaScript which uses EM.anyNA(varlist) to do the same thing foreach ($this->GetVarsUsed() as $var) // this function wants to see the NAOK suffix { if (!preg_match("/^.*\.(NAOK|relevanceStatus)$/", $var)) { - if (!LimeExpressionManager::GetVarAttribute($var,'relevanceStatus',false,$groupSeq,$questionSeq)) + if (!$this->GetVarAttribute($var,'relevanceStatus',false,$groupSeq,$questionSeq)) { return false; } diff --git a/application/extensions/ExpressionManager/assets/em.css b/application/extensions/ExpressionManager/assets/em.css new file mode 100644 index 00000000000..c0d7d5c54a0 --- /dev/null +++ b/application/extensions/ExpressionManager/assets/em.css @@ -0,0 +1,6 @@ +/* +During testing we add big red borders around irrelevant questions. +*/ +div.question.irrelevant { + border: 5px solid red; +} diff --git a/application/extensions/ExpressionManager/assets/em.js b/application/extensions/ExpressionManager/assets/em.js new file mode 100644 index 00000000000..a0977db26ce --- /dev/null +++ b/application/extensions/ExpressionManager/assets/em.js @@ -0,0 +1,2558 @@ +/* + * Core JavaScript functions needed by ExpressionManager + * @author Thomas M. White (TMSWhite) + * @author Denis Chenu (Shnoulle) + * @@author Sam Mousa (Sammousa) + * Portion from php.js is copyright 2012 Kevin van Zonneveld. + * php.js is dual licensed under the MIT licenses. + * + * This file is part of LimeSurvey + * Copyright (C) 2007-2013 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. + */ +var EM = EM || {} +EM.count = function() +{ + // takes variable number of arguments - returns count of those arguments that are not null/empty + var result=0; + for (i=0;i=': case 'ge': if (arg >= value) { ++result; } break; + case '>': case 'gt': if (arg > value) { ++result; } break; + case '<=': case 'le': if (arg <= value) { ++result; } break; + case '<': case 'lt': if (arg < value) { ++result; } break; + case '!=': case 'ne': if (arg != value) { ++result; } break; + case 'RX': { + try { + if (reg.test(arg)) { + ++result; + } + } + catch (err) { + return false; + } + } + } + } + return result; +} + +EM.sumifop = function() +{ + // takes variable number of arguments - returns sum of answered questions which meet the criteria (arg op value) + var result=0; + var op=arguments[0]; + var value=arguments[1]; + if (op == 'RX') { + var reg = new RegExp(value.substr(1,value.length-2)); + } + for (i=2;i=': case 'ge': if (arg >= value) { result += arg; } break; + case '>': case 'gt': if (arg > value) { result += arg; } break; + case '<=': case 'le': if (arg <= value) { result += arg; } break; + case '<': case 'lt': if (arg < value) { result += arg; } break; + case '!=': case 'ne': if (arg != value) { result += arg; } break; + case 'RX': { + try { + if (reg.test(arg)) { + result += arg; + } + } + catch (err) { + return false; + } + } + } + } + return result; +} + +EM.pi = function() +{ + return Math.PI; +} + +EM.sum = function() +{ + // takes variable number of arguments, returns their sum + var result=0; + for (i=0;i 1) { + result += joiner + arg; + } + else { + result += arg; + } + ++j; + } + } + return result; +} + +/** + * Returns Natural logarithm of a number + */ +EM.log = function() +{ + // takes variable number of arguments + if (arguments.length < 1) { + return NaN; + } + var base=Math.exp(1); + if(arguments.length>1){ + base = arguments[1]; + if (isNaN(base)) { return NaN;} + if (base<=0 ) { return NaN;} + base=Math.abs(parseFloat(arguments[1])); + } + if(base==Math.exp(1)){// Not needed + return Math.log(arguments[0]); + }else{ + return Math.log(arguments[0])/Math.log(base); + } +} + + /** + * Returns concatenates list + */ +EM.join = function() +{ + var result=""; + for (i=0;i 1) { + result += joiner + arg; + } + else { + result += arg; + } + } + return result; +} + +/* + * Returns true if within matches the pattern. Pattern must start and end with the '/' character + */ +EM.regexMatch = function(pattern,within) +{ + try { + var reg = new RegExp(pattern.substr(1,pattern.length-2)); + return reg.test(within); + } + catch (err) { + return false; + } +} + +EM.strlen = function(a) +{ + var str = new String(a); + return str.length; +} + +EM.str_replace = function(needle, replace, haystack) +{ + var str = new String(haystack); + return str.replace(needle, replace); +} + +EM.strpos = function(haystack,needle) +{ + var str = new String(haystack); + return str.search(needle); +} + +EM.empty = function(v) +{ + if (v === "" || v === false) { + return true; + } + return false; +} + +EM.bool = function(v) +{ + bool = new Boolean(v); + if (v.valueOf() && v != 'false') { + return true; // fix for JavaScript native processing that considers the value "false" to be true + } + return false; +} + +/** + * Match even if different types - especially true vs. 1, and false vs. 0 or blank + */ +EM.eq = function(a,b) +{ + if ((a==="true" && b==="1") || (a==="1" && b==="true")) { + return true; + } + if ((a==="false" && (b==="0" || b==="")) || ((a==="0" || a==="") && b ==="false")) { + return true; + } + return a==b; +} + +/** + * Return the value for data element jsName, treating it as blank if its question is irrelevant on the current page. + * Also convert the string 'false' to '' to cope with a JavaScript type casting issue + */ +EM.val = function(alias) +{ + // first find out whether it is using a suffix + var str = new String(alias); + var varName = alias; + var suffix = 'code'; // the default + + /* If passed a number, return that number */ + if (str == '') return ''; + newval = str; + if (LEMradix === ',') { + newval = str.split(',').join('.'); + } + if (newval == parseFloat(newval)) { + if (newval.length > 0 && newval[0]==0) { + return newval; // so keep 0 prefixes on numbers + } + return +newval; + } + + if (str.match(/^INSERTANS:/)) { + suffix = 'shown'; + varName = varName.substr(10); + } + else if (str.match(/\.(code|gid|grelevance|gseq|jsName|mandatory|NAOK|qid|qseq|question|readWrite|relevanceStatus|relevance|rowdivid|sgqa|shown|valueNAOK|value)$/)) { + varName = str.replace(/\.(code|gid|grelevance|gseq|jsName|mandatory|NAOK|qid|qseq|question|readWrite|relevanceStatus|relevance|rowdivid|sgqa|shown|valueNAOK|value)$/,'') + suffix = str.replace(/^(.+)\./,''); + } + + jsName = LEMalias2varName[varName]; + attr = LEMvarNameAttr[jsName]; + if ((suffix.match(/^code|NAOK|shown|valueNAOK|value$/)) && attr.qid!='') { + if (!LEMval(varName + '.relevanceStatus')) { + return ''; + } + } + var whichJsName; // correct name whether on- or off-page + if (LEMmode=='survey' || (LEMmode=='group' && attr.gseq == LEMgseq) || (LEMmode=='question' && attr.qid == LEMqid)) { + whichJsName = (typeof attr.jsName_on === 'undefined') ? attr.jsName : attr.jsName_on; + } + else { + whichJsName = attr.jsName; + } + if (whichJsName === null || typeof document.getElementById(whichJsName) === 'undefined' || document.getElementById(whichJsName) === null) { + an_error = true; // this line is here to make debugging easier + return ''; + } + + // values should always be stored encoded with htmlspecialchars() + switch (suffix) { + case 'relevanceStatus': { + grel = qrel = sgqarel = 1; + if (!(typeof attr.gseq === 'undefined') && !(document.getElementById('relevanceG' + attr.gseq) === null)) { + grel = parseInt(document.getElementById('relevanceG' + attr.gseq).value); + } + if (!(typeof attr.qid === 'undefined') && !(document.getElementById('relevance' + attr.qid) === null)) { + qrel = parseInt(document.getElementById('relevance' + attr.qid).value); + } + if (!(typeof attr.rowdivid === 'undefined') && !(document.getElementById('relevance' + attr.rowdivid) === null)) { + sgqarel = parseInt(document.getElementById('relevance' + attr.rowdivid).value); + } + return (grel && qrel && sgqarel); + } + case 'shown': { + value = htmlspecialchars_decode(document.getElementById(whichJsName).value); + shown = attr.shownscript(value, attr, varName); + return htmlspecialchars_decode(shown); + } + case 'gid': + return attr.gid; + case 'grelevance': + return attr.grelevance; + case 'mandatory': + return attr.mandatory; + case 'qid': + return attr.qid; + case 'question': + return htmlspecialchars_decode(attr.question); + case 'readWrite': + return attr.readWrite; + case 'relevance': + return htmlspecialchars_decode(attr.relevance); + case 'sgqa': + return attr.sgqa; + case 'gseq': + return attr.gseq; + case 'qseq': + return attr.qseq; + case 'jsName': + return whichJsName; + case 'code': + case 'NAOK': + case 'value': + case 'valueNAOK': + { + value = htmlspecialchars_decode(document.getElementById(whichJsName).value); + if (value === '') { + return ''; + } + + if (suffix == 'value' || suffix == 'valueNAOK') { + // if in assessment mode, this returns the assessment value + // in non-assessment mode, this is identical to .code + value = attr.valuescript(value, attr, varName); + } + + if (typeof attr.onlynum !== 'undefined' && attr.onlynum==1) { + newval = value; + if (LEMradix === ',') { + newval = value.split(',').join('.'); + } + if (newval != parseFloat(newval)) { + newval = ''; + } + return +newval; + } + else if (isNaN(value)) { + if (value==='false') { + return ''; // so Boolean operations will treat it as false. In JavaScript, Boolean("false") is true since "false" is not a zero-length string + } + return value; + } + else { + if (value.length > 0 && value[0]==0) { + return value; // so keep 0 prefixes on numbers + } + return +value; // convert it to numeric return type + } + } + case 'rowdivid': + if (typeof attr.rowdivid === 'undefined' || attr.rowdivid == '') { + return ''; + } + return attr.rowdivid; + default: + return 'Unknown Attribute: ' . suffix; + } +} + +/** Display number with comma as radix separator, if needed + */ +EM.fixnum = function(value) +{ + var newval = String(value); + if (parseFloat(newval) != value) { + return value; // unchanged + } + if (LEMradix===',') { + newval = newval.split('.').join(','); + return newval; + } + return value; +} + +/* + * Remove HTML and PHP tags from string + */ +EM.strip_tags = function(htmlString) +{ + var tmp = document.createElement("DIV"); + tmp.innerHTML = htmlString; + return tmp.textContent||tmp.innerText; +} + +/** + * Compute sample standard deviation of the input variables + */ +EM.stddev = function() +{ + vals = new Array(); + j = 0; + for (i=0;i': '>'} + var entities = {}, + hash_map = {}, + decimal = 0, + symbol = ''; + var constMappingTable = {}, + constMappingQuoteStyle = {}; + var useTable = {}, + useQuoteStyle = {}; + + // Translate arguments + constMappingTable[0] = 'HTML_SPECIALCHARS'; + constMappingTable[1] = 'HTML_ENTITIES'; + constMappingQuoteStyle[0] = 'ENT_NOQUOTES'; + constMappingQuoteStyle[2] = 'ENT_COMPAT'; + constMappingQuoteStyle[3] = 'ENT_QUOTES'; + + useTable = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS'; + useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT'; + + if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') { + throw new Error("Table: " + useTable + ' not supported'); + // return false; + } + + entities['38'] = '&'; + if (useTable === 'HTML_ENTITIES') { + entities['160'] = ' '; + entities['161'] = '¡'; + entities['162'] = '¢'; + entities['163'] = '£'; + entities['164'] = '¤'; + entities['165'] = '¥'; + entities['166'] = '¦'; + entities['167'] = '§'; + entities['168'] = '¨'; + entities['169'] = '©'; + entities['170'] = 'ª'; + entities['171'] = '«'; + entities['172'] = '¬'; + entities['173'] = '­'; + entities['174'] = '®'; + entities['175'] = '¯'; + entities['176'] = '°'; + entities['177'] = '±'; + entities['178'] = '²'; + entities['179'] = '³'; + entities['180'] = '´'; + entities['181'] = 'µ'; + entities['182'] = '¶'; + entities['183'] = '·'; + entities['184'] = '¸'; + entities['185'] = '¹'; + entities['186'] = 'º'; + entities['187'] = '»'; + entities['188'] = '¼'; + entities['189'] = '½'; + entities['190'] = '¾'; + entities['191'] = '¿'; + entities['192'] = 'À'; + entities['193'] = 'Á'; + entities['194'] = 'Â'; + entities['195'] = 'Ã'; + entities['196'] = 'Ä'; + entities['197'] = 'Å'; + entities['198'] = 'Æ'; + entities['199'] = 'Ç'; + entities['200'] = 'È'; + entities['201'] = 'É'; + entities['202'] = 'Ê'; + entities['203'] = 'Ë'; + entities['204'] = 'Ì'; + entities['205'] = 'Í'; + entities['206'] = 'Î'; + entities['207'] = 'Ï'; + entities['208'] = 'Ð'; + entities['209'] = 'Ñ'; + entities['210'] = 'Ò'; + entities['211'] = 'Ó'; + entities['212'] = 'Ô'; + entities['213'] = 'Õ'; + entities['214'] = 'Ö'; + entities['215'] = '×'; + entities['216'] = 'Ø'; + entities['217'] = 'Ù'; + entities['218'] = 'Ú'; + entities['219'] = 'Û'; + entities['220'] = 'Ü'; + entities['221'] = 'Ý'; + entities['222'] = 'Þ'; + entities['223'] = 'ß'; + entities['224'] = 'à'; + entities['225'] = 'á'; + entities['226'] = 'â'; + entities['227'] = 'ã'; + entities['228'] = 'ä'; + entities['229'] = 'å'; + entities['230'] = 'æ'; + entities['231'] = 'ç'; + entities['232'] = 'è'; + entities['233'] = 'é'; + entities['234'] = 'ê'; + entities['235'] = 'ë'; + entities['236'] = 'ì'; + entities['237'] = 'í'; + entities['238'] = 'î'; + entities['239'] = 'ï'; + entities['240'] = 'ð'; + entities['241'] = 'ñ'; + entities['242'] = 'ò'; + entities['243'] = 'ó'; + entities['244'] = 'ô'; + entities['245'] = 'õ'; + entities['246'] = 'ö'; + entities['247'] = '÷'; + entities['248'] = 'ø'; + entities['249'] = 'ù'; + entities['250'] = 'ú'; + entities['251'] = 'û'; + entities['252'] = 'ü'; + entities['253'] = 'ý'; + entities['254'] = 'þ'; + entities['255'] = 'ÿ'; + } + + if (useQuoteStyle !== 'ENT_NOQUOTES') { + entities['34'] = '"'; + } + if (useQuoteStyle === 'ENT_QUOTES') { + entities['39'] = '''; + } + entities['60'] = '<'; + entities['62'] = '>'; + + + // ascii decimals to real symbols + for (decimal in entities) { + symbol = String.fromCharCode(decimal); + hash_map[symbol] = entities[decimal]; + } + + return hash_map; +} + +function htmlspecialchars (string, quote_style, charset, double_encode) { + // Convert special characters to HTML entities + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/htmlspecialchars + // + original by: Mirek Slugen + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Nathan + // + bugfixed by: Arno + // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + input by: Ratheous + // + input by: Mailfaker (http://www.weedem.fr/) + // + reimplemented by: Brett Zamir (http://brett-zamir.me) + // + input by: felix + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // % note 1: charset argument not supported + // * example 1: htmlspecialchars("Test", 'ENT_QUOTES'); + // * returns 1: '<a href='test'>Test</a>' + // * example 2: htmlspecialchars("ab\"c'd", ['ENT_NOQUOTES', 'ENT_QUOTES']); + // * returns 2: 'ab"c'd' + // * example 3: htmlspecialchars("my "&entity;" is still here", null, null, false); + // * returns 3: 'my "&entity;" is still here' + var optTemp = 0, + i = 0, + noquotes = false; + if (typeof quote_style === 'undefined' || quote_style === null) { + quote_style = 3; // default for PHP is 2 - using 3 as default for LimeSurvey + } + string = string.toString(); + if (double_encode !== false) { // Put this first to avoid double-encoding + string = string.replace(/&/g, '&'); + } + string = string.replace(//g, '>'); + + var OPTS = { + 'ENT_NOQUOTES': 0, + 'ENT_HTML_QUOTE_SINGLE': 1, + 'ENT_HTML_QUOTE_DOUBLE': 2, + 'ENT_COMPAT': 2, + 'ENT_QUOTES': 3, + 'ENT_IGNORE': 4 + }; + if (quote_style === 0) { + noquotes = true; + } + if (typeof quote_style !== 'number') { // Allow for a single string or an array of string flags + quote_style = [].concat(quote_style); + for (i = 0; i < quote_style.length; i++) { + // Resolve string input to bitwise e.g. 'ENT_IGNORE' becomes 4 + if (OPTS[quote_style[i]] === 0) { + noquotes = true; + } + else if (OPTS[quote_style[i]]) { + optTemp = optTemp | OPTS[quote_style[i]]; + } + } + quote_style = optTemp; + } + if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) { + string = string.replace(/'/g, '''); + } + if (!noquotes) { + string = string.replace(/"/g, '"'); + } + + return string; +} + +function htmlspecialchars_decode (string, quote_style) { + // Convert special HTML entities back to characters + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/htmlspecialchars_decode + // + original by: Mirek Slugen + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Mateusz "loonquawl" Zalega + // + input by: ReverseSyntax + // + input by: Slawomir Kaniecki + // + input by: Scott Cariss + // + input by: Francois + // + bugfixed by: Onno Marsman + // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + input by: Ratheous + // + input by: Mailfaker (http://www.weedem.fr/) + // + reimplemented by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // * example 1: htmlspecialchars_decode("

this -> "

", 'ENT_NOQUOTES'); + // * returns 1: '

this -> "

' + // * example 2: htmlspecialchars_decode("&quot;"); + // * returns 2: '"' + var optTemp = 0, + i = 0, + noquotes = false; + if (typeof quote_style === 'undefined') { + quote_style = 3; // default for PHP is 2 - using 3 as default for LimeSurvey + } + string = string.toString().replace(/</g, '<').replace(/>/g, '>'); + var OPTS = { + 'ENT_NOQUOTES': 0, + 'ENT_HTML_QUOTE_SINGLE': 1, + 'ENT_HTML_QUOTE_DOUBLE': 2, + 'ENT_COMPAT': 2, + 'ENT_QUOTES': 3, + 'ENT_IGNORE': 4 + }; + if (quote_style === 0) { + noquotes = true; + } + if (typeof quote_style !== 'number') { // Allow for a single string or an array of string flags + quote_style = [].concat(quote_style); + for (i = 0; i < quote_style.length; i++) { + // Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4 + if (OPTS[quote_style[i]] === 0) { + noquotes = true; + } else if (OPTS[quote_style[i]]) { + optTemp = optTemp | OPTS[quote_style[i]]; + } + } + quote_style = optTemp; + } + if (quote_style & OPTS.ENT_HTML_QUOTE_SINGLE) { + string = string.replace(/�*39;/g, "'"); // PHP doesn't currently escape if more than one 0, but it should + // string = string.replace(/'|�*27;/g, "'"); // This would also be useful here, but not a part of PHP + } + if (!noquotes) { + string = string.replace(/"/g, '"'); + } + // Put this in last place to avoid escape being double-decoded + string = string.replace(/&/g, '&'); + + return string; +} + +function ltrim (str, charlist) { + // Strips whitespace from the beginning of a string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/ltrim + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Erkekjetter + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // * example 1: ltrim(' Kevin van Zonneveld '); + // * returns 1: 'Kevin van Zonneveld ' + charlist = !charlist ? ' \\s\u00A0' : (charlist + '').replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '$1'); + var re = new RegExp('^[' + charlist + ']+', 'g'); + return (str + '').replace(re, ''); +} + +function nl2br (str, is_xhtml) { + // Converts newlines to HTML line breaks + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/nl2br + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Philip Peterson + // + improved by: Onno Marsman + // + improved by: Atli Þór + // + bugfixed by: Onno Marsman + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: Maximusya + // * example 1: nl2br('Kevin\nvan\nZonneveld'); + // * returns 1: 'Kevin\nvan\nZonneveld' + // * example 2: nl2br("\nOne\nTwo\n\nThree\n", false); + // * returns 2: '
\nOne
\nTwo
\n
\nThree
\n' + // * example 3: nl2br("\nOne\nTwo\n\nThree\n", true); + // * returns 3: '\nOne\nTwo\n\nThree\n' + var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '' : '
'; + + return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2'); +} + +function number_format (number, decimals, dec_point, thousands_sep) { + // http://kevin.vanzonneveld.net + // + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfix by: Michael White (http://getsprink.com) + // + bugfix by: Benjamin Lupton + // + bugfix by: Allan Jensen (http://www.winternet.no) + // + revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + // + bugfix by: Howard Yeend + // + revised by: Luke Smith (http://lucassmith.name) + // + bugfix by: Diogo Resende + // + bugfix by: Rival + // + input by: Kheang Hok Chin (http://www.distantia.ca/) + // + improved by: davook + // + improved by: Brett Zamir (http://brett-zamir.me) + // + input by: Jay Klehr + // + improved by: Brett Zamir (http://brett-zamir.me) + // + input by: Amir Habibi (http://www.residence-mixte.com/) + // + bugfix by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // + input by: Amirouche + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: number_format(1234.56); + // * returns 1: '1,235' + // * example 2: number_format(1234.56, 2, ',', ' '); + // * returns 2: '1 234,56' + // * example 3: number_format(1234.5678, 2, '.', ''); + // * returns 3: '1234.57' + // * example 4: number_format(67, 2, ',', '.'); + // * returns 4: '67,00' + // * example 5: number_format(1000); + // * returns 5: '1,000' + // * example 6: number_format(67.311, 2); + // * returns 6: '67.31' + // * example 7: number_format(1000.55, 1); + // * returns 7: '1,000.6' + // * example 8: number_format(67000, 5, ',', '.'); + // * returns 8: '67.000,00000' + // * example 9: number_format(0.9, 0); + // * returns 9: '1' + // * example 10: number_format('1.20', 2); + // * returns 10: '1.20' + // * example 11: number_format('1.20', 4); + // * returns 11: '1.2000' + // * example 12: number_format('1.2000', 3); + // * returns 12: '1.200' + // * example 13: number_format('1 000,50', 2, '.', ' '); + // * returns 13: '100 050.00' + // Strip all characters but numerical ones. + number = (number + '').replace(/[^0-9+\-Ee.]/g, ''); + var n = !isFinite(+number) ? 0 : +number, + prec = !isFinite(+decimals) ? 0 : Math.abs(decimals), + sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep, + dec = (typeof dec_point === 'undefined') ? '.' : dec_point, + s = '', + toFixedFix = function (n, prec) { + var k = Math.pow(10, prec); + return '' + Math.round(n * k) / k; + }; + // Fix for IE parseFloat(0.55).toFixed(0) = 0; + s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.'); + if (s[0].length > 3) { + s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep); + } + if ((s[1] || '').length < prec) { + s[1] = s[1] || ''; + s[1] += new Array(prec - s[1].length + 1).join('0'); + } + return s.join(dec); +} + +function quoted_printable_decode (str) { + // Convert a quoted-printable string to an 8 bit string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/quoted_printable_decode + // + original by: Ole Vrijenhoek + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + reimplemented by: Theriault + // + improved by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Theriault + // * example 1: quoted_printable_decode('a=3Db=3Dc'); + // * returns 1: 'a=b=c' + // * example 2: quoted_printable_decode('abc =20\r\n123 =20\r\n'); + // * returns 2: 'abc \r\n123 \r\n' + // * example 3: quoted_printable_decode('012345678901234567890123456789012345678901234567890123456789012345678901234=\r\n56789'); + // * returns 3: '01234567890123456789012345678901234567890123456789012345678901234567890123456789' + // * example 4: quoted_printable_decode("Lorem ipsum dolor sit amet=23, consectetur adipisicing elit"); + // * returns 4: Lorem ipsum dolor sit amet#, consectetur adipisicing elit + // Removes softline breaks + var RFC2045Decode1 = /=\r\n/gm, + // Decodes all equal signs followed by two hex digits + RFC2045Decode2IN = /=([0-9A-F]{2})/gim, + // the RFC states against decoding lower case encodings, but following apparent PHP behavior + // RFC2045Decode2IN = /=([0-9A-F]{2})/gm, + RFC2045Decode2OUT = function (sMatch, sHex) { + return String.fromCharCode(parseInt(sHex, 16)); + }; + return str.replace(RFC2045Decode1, '').replace(RFC2045Decode2IN, RFC2045Decode2OUT); +} + +function quoted_printable_encode (str) { + // + original by: Theriault + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // * example 1: quoted_printable_encode('a=b=c'); + // * returns 1: 'a=3Db=3Dc' + // * example 2: quoted_printable_encode('abc \r\n123 \r\n'); + // * returns 2: 'abc =20\r\n123 =20\r\n' + // * example 3: quoted_printable_encode('0123456789012345678901234567890123456789012345678901234567890123456789012345'); + // * returns 3: '012345678901234567890123456789012345678901234567890123456789012345678901234=\r\n5' + // RFC 2045: 6.7.2: Octets with decimal values of 33 through 60 (bang to less-than) inclusive, and 62 through 126 (greater-than to tilde), inclusive, MAY be represented as the US-ASCII characters + // PHP does not encode any of the above; as does this function. + // RFC 2045: 6.7.3: Octets with values of 9 and 32 MAY be represented as US-ASCII TAB (HT) and SPACE characters, respectively, but MUST NOT be so represented at the end of an encoded line + // PHP does not encode spaces (octet 32) except before a CRLF sequence as stated above. PHP always encodes tabs (octet 9). This function replicates PHP. + // RFC 2045: 6.7.4: A line break in a text body, represented as a CRLF sequence in the text canonical form, must be represented by a (RFC 822) line break + // PHP does not encode a CRLF sequence, as does this function. + // RFC 2045: 6.7.5: The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks must be used. + // PHP breaks lines greater than 76 characters; as does this function. + var hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'], + RFC2045Encode1IN = / \r\n|\r\n|[^!-<>-~ ]/gm, + RFC2045Encode1OUT = function (sMatch) { + // Encode space before CRLF sequence to prevent spaces from being stripped + // Keep hard line breaks intact; CRLF sequences + if (sMatch.length > 1) { + return sMatch.replace(' ', '=20'); + } + // Encode matching character + var chr = sMatch.charCodeAt(0); + return '=' + hexChars[((chr >>> 4) & 15)] + hexChars[(chr & 15)]; + }, + // Split lines to 75 characters; the reason it's 75 and not 76 is because softline breaks are preceeded by an equal sign; which would be the 76th character. + // However, if the last line/string was exactly 76 characters, then a softline would not be needed. PHP currently softbreaks anyway; so this function replicates PHP. + RFC2045Encode2IN = /.{1,72}(?!\r\n)[^=]{0,3}/g, + RFC2045Encode2OUT = function (sMatch) { + if (sMatch.substr(sMatch.length - 2) === '\r\n') { + return sMatch; + } + return sMatch + '=\r\n'; + }; + str = str.replace(RFC2045Encode1IN, RFC2045Encode1OUT).replace(RFC2045Encode2IN, RFC2045Encode2OUT); + // Strip last softline break + return str.substr(0, str.length - 3); +} + +function quotemeta (str) { + // Quotes meta characters + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/quotemeta + // + original by: Paulo Freitas + // * example 1: quotemeta(". + * ? ^ ( $ )"); + // * returns 1: '\. \+ \* \? \^ \( \$ \)' + return (str + '').replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); +} + +function round (value, precision, mode) { + // http://kevin.vanzonneveld.net + // + original by: Philip Peterson + // + revised by: Onno Marsman + // + input by: Greenseed + // + revised by: T.Wild + // + input by: meo + // + input by: William + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + input by: Josep Sanz (http://www.ws3.es/) + // + revised by: Rafał Kukawski (http://blog.kukawski.pl/) + // % note 1: Great work. Ideas for improvement: + // % note 1: - code more compliant with developer guidelines + // % note 1: - for implementing PHP constant arguments look at + // % note 1: the pathinfo() function, it offers the greatest + // % note 1: flexibility & compatibility possible + // * example 1: round(1241757, -3); + // * returns 1: 1242000 + // * example 2: round(3.6); + // * returns 2: 4 + // * example 3: round(2.835, 2); + // * returns 3: 2.84 + // * example 4: round(1.1749999999999, 2); + // * returns 4: 1.17 + // * example 5: round(58551.799999999996, 2); + // * returns 5: 58551.8 + var m, f, isHalf, sgn; // helper variables + precision |= 0; // making sure precision is integer + m = Math.pow(10, precision); + value *= m; + sgn = (value > 0) | -(value < 0); // sign of the number + isHalf = value % 1 === 0.5 * sgn; + f = Math.floor(value); + + if (isHalf) { + switch (mode) { + case 'PHP_ROUND_HALF_DOWN': + value = f + (sgn < 0); // rounds .5 toward zero + break; + case 'PHP_ROUND_HALF_EVEN': + value = f + (f % 2 * sgn); // rouds .5 towards the next even integer + break; + case 'PHP_ROUND_HALF_ODD': + value = f + !(f % 2); // rounds .5 towards the next odd integer + break; + default: + value = f + (sgn > 0); // rounds .5 away from zero + } + } + + return (isHalf ? value : Math.round(value)) / m; +} + +function rtrim (str, charlist) { + // Removes trailing whitespace + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/rtrim + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Erkekjetter + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + input by: rem + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // * example 1: rtrim(' Kevin van Zonneveld '); + // * returns 1: ' Kevin van Zonneveld' + charlist = !charlist ? ' \\s\u00A0' : (charlist + '').replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '\\$1'); + var re = new RegExp('[' + charlist + ']+$', 'g'); + return (str + '').replace(re, ''); +} + +function sprintf () { + // Return a formatted string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/sprintf + // + original by: Ash Searle (http://hexmen.com/blog/) + // + namespaced by: Michael White (http://getsprink.com) + // + tweaked by: Jack + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Paulo Freitas + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Brett Zamir (http://brett-zamir.me) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: sprintf("%01.2f", 123.1); + // * returns 1: 123.10 + // * example 2: sprintf("[%10s]", 'monkey'); + // * returns 2: '[ monkey]' + // * example 3: sprintf("[%'#10s]", 'monkey'); + // * returns 3: '[####monkey]' + var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g; + var a = arguments, + i = 0, + format = a[i++]; + + // pad() + var pad = function (str, len, chr, leftJustify) { + if (!chr) { + chr = ' '; + } + var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr); + return leftJustify ? str + padding : padding + str; + }; + + // justify() + var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) { + var diff = minWidth - value.length; + if (diff > 0) { + if (leftJustify || !zeroPad) { + value = pad(value, minWidth, customPadChar, leftJustify); + } else { + value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length); + } + } + return value; + }; + + // formatBaseX() + var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) { + // Note: casts negative numbers to positive ones + var number = value >>> 0; + prefix = prefix && number && { + '2': '0b', + '8': '0', + '16': '0x' + }[base] || ''; + value = prefix + pad(number.toString(base), precision || 0, '0', false); + return justify(value, prefix, leftJustify, minWidth, zeroPad); + }; + + // formatString() + var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) { + if (precision != null) { + value = value.slice(0, precision); + } + return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar); + }; + + // doFormat() + var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) { + var number; + var prefix; + var method; + var textTransform; + var value; + + if (substring == '%%') { + return '%'; + } + + // parse flags + var leftJustify = false, + positivePrefix = '', + zeroPad = false, + prefixBaseX = false, + customPadChar = ' '; + var flagsl = flags.length; + for (var j = 0; flags && j < flagsl; j++) { + switch (flags.charAt(j)) { + case ' ': + positivePrefix = ' '; + break; + case '+': + positivePrefix = '+'; + break; + case '-': + leftJustify = true; + break; + case "'": + customPadChar = flags.charAt(j + 1); + break; + case '0': + zeroPad = true; + break; + case '#': + prefixBaseX = true; + break; + } + } + + // parameters may be null, undefined, empty-string or real valued + // we want to ignore null, undefined and empty-string values + if (!minWidth) { + minWidth = 0; + } else if (minWidth == '*') { + minWidth = +a[i++]; + } else if (minWidth.charAt(0) == '*') { + minWidth = +a[minWidth.slice(1, -1)]; + } else { + minWidth = +minWidth; + } + + // Note: undocumented perl feature: + if (minWidth < 0) { + minWidth = -minWidth; + leftJustify = true; + } + + if (!isFinite(minWidth)) { + throw new Error('sprintf: (minimum-)width must be finite'); + } + + if (!precision) { + precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined; + } else if (precision == '*') { + precision = +a[i++]; + } else if (precision.charAt(0) == '*') { + precision = +a[precision.slice(1, -1)]; + } else { + precision = +precision; + } + + // grab value using valueIndex if required? + value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++]; + + switch (type) { + case 's': + return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar); + case 'c': + return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad); + case 'b': + return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad); + case 'o': + return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad); + case 'x': + return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad); + case 'X': + return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase(); + case 'u': + return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad); + case 'i': + case 'd': + number = (+value) | 0; + prefix = number < 0 ? '-' : positivePrefix; + value = prefix + pad(String(Math.abs(number)), precision, '0', false); + return justify(value, prefix, leftJustify, minWidth, zeroPad); + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + number = +value; + prefix = number < 0 ? '-' : positivePrefix; + method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())]; + textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2]; + value = prefix + Math.abs(number)[method](precision); + return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform](); + default: + return substring; + } + }; + + return format.replace(regex, doFormat); +} + +function str_pad (input, pad_length, pad_string, pad_type) { + // Returns input string padded on the left or right to specified length with pad_string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/str_pad + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + namespaced by: Michael White (http://getsprink.com) + // + input by: Marco van Oort + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // * example 1: str_pad('Kevin van Zonneveld', 30, '-=', 'STR_PAD_LEFT'); + // * returns 1: '-=-=-=-=-=-Kevin van Zonneveld' + // * example 2: str_pad('Kevin van Zonneveld', 30, '-', 'STR_PAD_BOTH'); + // * returns 2: '------Kevin van Zonneveld-----' + var half = '', + pad_to_go; + + var str_pad_repeater = function (s, len) { + var collect = '', + i; + + while (collect.length < len) { + collect += s; + } + collect = collect.substr(0, len); + + return collect; + }; + + input += ''; + pad_string = pad_string !== undefined ? pad_string : ' '; + + if (pad_type != 'STR_PAD_LEFT' && pad_type != 'STR_PAD_RIGHT' && pad_type != 'STR_PAD_BOTH') { + pad_type = 'STR_PAD_RIGHT'; + } + if ((pad_to_go = pad_length - input.length) > 0) { + if (pad_type == 'STR_PAD_LEFT') { + input = str_pad_repeater(pad_string, pad_to_go) + input; + } else if (pad_type == 'STR_PAD_RIGHT') { + input = input + str_pad_repeater(pad_string, pad_to_go); + } else if (pad_type == 'STR_PAD_BOTH') { + half = str_pad_repeater(pad_string, Math.ceil(pad_to_go / 2)); + input = half + input + half; + input = input.substr(0, pad_length); + } + } + + return input; +} + +function str_repeat (input, multiplier) { + // Returns the input string repeat mult times + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/str_repeat + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + // * example 1: str_repeat('-=', 10); + // * returns 1: '-=-=-=-=-=-=-=-=-=-=' + return new Array(multiplier + 1).join(input); +} + +function strcasecmp (f_string1, f_string2) { + // Binary safe case-insensitive string comparison + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/strcasecmp + // + original by: Martijn Wieringa + // + bugfixed by: Onno Marsman + // * example 1: strcasecmp('Hello', 'hello'); + // * returns 1: 0 + var string1 = (f_string1 + '').toLowerCase(); + var string2 = (f_string2 + '').toLowerCase(); + + if (string1 > string2) { + return 1; + } else if (string1 == string2) { + return 0; + } + + return -1; +} + +function strcmp (str1, str2) { + // Binary safe string comparison + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/strcmp + // + original by: Waldo Malqui Silva + // + input by: Steve Hilder + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + revised by: gorthaur + // * example 1: strcmp( 'waldo', 'owald' ); + // * returns 1: 1 + // * example 2: strcmp( 'owald', 'waldo' ); + // * returns 2: -1 + return ((str1 == str2) ? 0 : ((str1 > str2) ? 1 : -1)); +} + +function strip_tags (input, allowed) { + // Strips HTML and PHP tags from a string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/strip_tags + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Luke Godfrey + // + input by: Pul + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + input by: Alex + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Marc Palau + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Eric Nagel + // + input by: Bobby Drake + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Tomasz Wesolowski + // + input by: Evertjan Garretsen + // + revised by: Rafał Kukawski (http://blog.kukawski.pl/) + // * example 1: strip_tags('

Kevin

van Zonneveld', ''); + // * returns 1: 'Kevin van Zonneveld' + // * example 2: strip_tags('

Kevin van Zonneveld

', '

'); + // * returns 2: '

Kevin van Zonneveld

' + // * example 3: strip_tags("Kevin van Zonneveld", ""); + // * returns 3: 'Kevin van Zonneveld' + // * example 4: strip_tags('1 < 5 5 > 1'); + // * returns 4: '1 < 5 5 > 1' + // * example 5: strip_tags('1
1'); + // * returns 5: '1 1' + // * example 6: strip_tags('1
1', '
'); + // * returns 6: '1 1' + // * example 7: strip_tags('1
1', '

'); + // * returns 7: '1
1' + allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase () + var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, + commentsAndPhpTags = /|<\?(?:php)?[\s\S]*?\?>/gi; + return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) { + return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; + }); +} + +function stripslashes (str) { + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Ates Goral (http://magnetiq.com) + // + fixed by: Mick@el + // + improved by: marrtins + // + bugfixed by: Onno Marsman + // + improved by: rezna + // + input by: Rick Waldron + // + reimplemented by: Brett Zamir (http://brett-zamir.me) + // + input by: Brant Messenger (http://www.brantmessenger.com/) + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // * example 1: stripslashes('Kevin\'s code'); + // * returns 1: "Kevin's code" + // * example 2: stripslashes('Kevin\\\'s code'); + // * returns 2: "Kevin\'s code" + return (str + '').replace(/\\(.?)/g, function (s, n1) { + switch (n1) { + case '\\': + return '\\'; + case '0': + return '\u0000'; + case '': + return ''; + default: + return n1; + } + }); +} + +function stripos (f_haystack, f_needle, f_offset) { + // Finds position of first occurrence of a string within another, case insensitive + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/stripos + // + original by: Martijn Wieringa + // + revised by: Onno Marsman + // * example 1: stripos('ABC', 'a'); + // * returns 1: 0 + var haystack = (f_haystack + '').toLowerCase(); + var needle = (f_needle + '').toLowerCase(); + var index = 0; + + if ((index = haystack.indexOf(needle, f_offset)) !== -1) { + return index; + } + return false; +} + +function stristr (haystack, needle, bool) { + // Finds first occurrence of a string within another, case insensitive + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/stristr + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfxied by: Onno Marsman + // * example 1: stristr('Kevin van Zonneveld', 'Van'); + // * returns 1: 'van Zonneveld' + // * example 2: stristr('Kevin van Zonneveld', 'VAN', true); + // * returns 2: 'Kevin ' + var pos = 0; + + haystack += ''; + pos = haystack.toLowerCase().indexOf((needle + '').toLowerCase()); + if (pos == -1) { + return false; + } else { + if (bool) { + return haystack.substr(0, pos); + } else { + return haystack.slice(pos); + } + } +} + +function strrev (string) { + // Reverse a string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/strrev + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + reimplemented by: Brett Zamir (http://brett-zamir.me) + // * example 1: strrev('Kevin van Zonneveld'); + // * returns 1: 'dlevennoZ nav niveK' + // * example 2: strrev('a\u0301haB') === 'Baha\u0301'; // combining + // * returns 2: true + // * example 3: strrev('A\uD87E\uDC04Z') === 'Z\uD87E\uDC04A'; // surrogates + // * returns 2: true + string = string + ''; + + // Performance will be enhanced with the next two lines of code commented + // out if you don't care about combining characters + // Keep Unicode combining characters together with the character preceding + // them and which they are modifying (as in PHP 6) + // See http://unicode.org/reports/tr44/#Property_Table (Me+Mn) + // We also add the low surrogate range at the beginning here so it will be + // maintained with its preceding high surrogate + var grapheme_extend = /(.)([\uDC00-\uDFFF\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065E\u0670\u06D6-\u06DC\u06DE-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B6-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u192B\u1930-\u193B\u19B0-\u19C0\u19C8\u19C9\u1A17-\u1A1B\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAA\u1C24-\u1C37\u1DC0-\u1DE6\u1DFE\u1DFF\u20D0-\u20F0\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA67C\uA67D\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA926-\uA92D\uA947-\uA953\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uFB1E\uFE00-\uFE0F\uFE20-\uFE26]+)/g; + string = string.replace(grapheme_extend, '$2$1'); // Temporarily reverse + return string.split('').reverse().join(''); +} + +function strstr (haystack, needle, bool) { + // Finds first occurrence of a string within another + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/strstr + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: strstr('Kevin van Zonneveld', 'van'); + // * returns 1: 'van Zonneveld' + // * example 2: strstr('Kevin van Zonneveld', 'van', true); + // * returns 2: 'Kevin ' + // * example 3: strstr('name@example.com', '@'); + // * returns 3: '@example.com' + // * example 4: strstr('name@example.com', '@', true); + // * returns 4: 'name' + var pos = 0; + + haystack += ''; + pos = haystack.indexOf(needle); + if (pos == -1) { + return false; + } else { + if (bool) { + return haystack.substr(0, pos); + } else { + return haystack.slice(pos); + } + } +} + +function substr (str, start, len) { + // Returns part of a string + // + // version: 909.322 + // discuss at: http://phpjs.org/functions/substr + // + original by: Martijn Wieringa + // + bugfixed by: T.Wild + // + tweaked by: Onno Marsman + // + revised by: Theriault + // + improved by: Brett Zamir (http://brett-zamir.me) + // % note 1: Handles rare Unicode characters if 'unicode.semantics' ini (PHP6) is set to 'on' + // * example 1: substr('abcdef', 0, -1); + // * returns 1: 'abcde' + // * example 2: substr(2, 0, -6); + // * returns 2: false + // * example 3: ini_set('unicode.semantics', 'on'); + // * example 3: substr('a\uD801\uDC00', 0, -1); + // * returns 3: 'a' + // * example 4: ini_set('unicode.semantics', 'on'); + // * example 4: substr('a\uD801\uDC00', 0, 2); + // * returns 4: 'a\uD801\uDC00' + // * example 5: ini_set('unicode.semantics', 'on'); + // * example 5: substr('a\uD801\uDC00', -1, 1); + // * returns 5: '\uD801\uDC00' + // * example 6: ini_set('unicode.semantics', 'on'); + // * example 6: substr('a\uD801\uDC00z\uD801\uDC00', -3, 2); + // * returns 6: '\uD801\uDC00z' + // * example 7: ini_set('unicode.semantics', 'on'); + // * example 7: substr('a\uD801\uDC00z\uD801\uDC00', -3, -1) + // * returns 7: '\uD801\uDC00z' + // Add: (?) Use unicode.runtime_encoding (e.g., with string wrapped in "binary" or "Binary" class) to + // allow access of binary (see file_get_contents()) by: charCodeAt(x) & 0xFF (see https://developer.mozilla.org/En/Using_XMLHttpRequest ) or require conversion first? + var i = 0, + allBMP = true, + es = 0, + el = 0, + se = 0, + ret = ''; + str += ''; + var end = str.length; + + // BEGIN REDUNDANT + this.php_js = this.php_js || {}; + this.php_js.ini = this.php_js.ini || {}; + // END REDUNDANT + switch ((this.php_js.ini['unicode.semantics'] && this.php_js.ini['unicode.semantics'].local_value.toLowerCase())) { + case 'on': + // Full-blown Unicode including non-Basic-Multilingual-Plane characters + // strlen() + for (i = 0; i < str.length; i++) { + if (/[\uD800-\uDBFF]/.test(str.charAt(i)) && /[\uDC00-\uDFFF]/.test(str.charAt(i + 1))) { + allBMP = false; + break; + } + } + + if (!allBMP) { + if (start < 0) { + for (i = end - 1, es = (start += end); i >= es; i--) { + if (/[\uDC00-\uDFFF]/.test(str.charAt(i)) && /[\uD800-\uDBFF]/.test(str.charAt(i - 1))) { + start--; + es--; + } + } + } else { + var surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; + while ((surrogatePairs.exec(str)) != null) { + var li = surrogatePairs.lastIndex; + if (li - 2 < start) { + start++; + } else { + break; + } + } + } + + if (start >= end || start < 0) { + return false; + } + if (len < 0) { + for (i = end - 1, el = (end += len); i >= el; i--) { + if (/[\uDC00-\uDFFF]/.test(str.charAt(i)) && /[\uD800-\uDBFF]/.test(str.charAt(i - 1))) { + end--; + el--; + } + } + if (start > end) { + return false; + } + return str.slice(start, end); + } else { + se = start + len; + for (i = start; i < se; i++) { + ret += str.charAt(i); + if (/[\uD800-\uDBFF]/.test(str.charAt(i)) && /[\uDC00-\uDFFF]/.test(str.charAt(i + 1))) { + se++; // Go one further, since one of the "characters" is part of a surrogate pair + } + } + return ret; + } + break; + } + // Fall-through + case 'off': + // assumes there are no non-BMP characters; + // if there may be such characters, then it is best to turn it on (critical in true XHTML/XML) + default: + if (start < 0) { + start += end; + } + end = typeof len === 'undefined' ? end : (len < 0 ? len + end : len + start); + // PHP returns false if start does not fall within the string. + // PHP returns false if the calculated end comes before the calculated start. + // PHP returns an empty string if start and end are the same. + // Otherwise, PHP returns the portion of the string from start to end. + return start >= str.length || start < 0 || start > end ? !1 : str.slice(start, end); + } + return undefined; // Please Netbeans +} + +function trim (str, charlist) { +// Strips whitespace from the beginning and end of a string +// +// version: 1107.2516 +// discuss at: http://phpjs.org/functions/trim +// + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) +// + improved by: mdsjack (http://www.mdsjack.bo.it) +// + improved by: Alexander Ermolaev (http://snippets.dzone.com/user/AlexanderErmolaev) +// + input by: Erkekjetter +// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) +// + input by: DxGx +// + improved by: Steven Levithan (http://blog.stevenlevithan.com) +// + tweaked by: Jack +// + bugfixed by: Onno Marsman +// * example 1: trim(' Kevin van Zonneveld '); +// * returns 1: 'Kevin van Zonneveld' +// * example 2: trim('Hello World', 'Hdle'); +// * returns 2: 'o Wor' +// * example 3: trim(16, 1); +// * returns 3: 6 +var whitespace, l = 0, +i = 0; +str += ''; + +if (!charlist) { +// default list +whitespace = " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000"; +} else { +// preg_quote custom list +charlist += ''; +whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '$1'); +} + +l = str.length; +for (i = 0; i < l; i++) { +if (whitespace.indexOf(str.charAt(i)) === -1) { +str = str.substring(i); +break; +} +} + +l = str.length; +for (i = l - 1; i >= 0; i--) { +if (whitespace.indexOf(str.charAt(i)) === -1) { +str = str.substring(0, i + 1); +break; +} +} + +return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''; +} + +function ucwords (str) { + // Uppercase the first character of every word in a string + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/ucwords + // + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + // + improved by: Waldo Malqui Silva + // + bugfixed by: Onno Marsman + // + improved by: Robin + // + input by: James (http://www.james-bell.co.uk/) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: ucwords('kevin van zonneveld'); + // * returns 1: 'Kevin Van Zonneveld' + // * example 2: ucwords('HELLO WORLD'); + // * returns 2: 'HELLO WORLD' + return (str + '').replace(/^([a-z])|\s+([a-z])/g, function ($1) { + return $1.toUpperCase(); + }); +} + +function checkdate (m, d, y) { + // Returns true(1) if it is a valid date in gregorian calendar + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/checkdate + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Pyerre + // + improved by: Theriault + // * example 1: checkdate(12, 31, 2000); + // * returns 1: true + // * example 2: checkdate(2, 29, 2001); + // * returns 2: false + // * example 3: checkdate(3, 31, 2008); + // * returns 3: true + // * example 4: checkdate(1, 390, 2000); + // * returns 4: false + return m > 0 && m < 13 && y > 0 && y < 32768 && d > 0 && d <= (new Date(y, m, 0)).getDate(); +} + +function date (format, timestamp) { + // http://kevin.vanzonneveld.net + // + original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com) + // + parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: MeEtc (http://yass.meetcweb.com) + // + improved by: Brad Touesnard + // + improved by: Tim Wiel + // + improved by: Bryan Elliott + // + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: David Randall + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // + derived from: gettimeofday + // + input by: majak + // + bugfixed by: majak + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Alex + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // + improved by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // + improved by: Thomas Beaucourt (http://www.webapp.fr) + // + improved by: JT + // + improved by: Theriault + // + improved by: Rafał Kukawski (http://blog.kukawski.pl) + // + input by: Martin + // + input by: Alex Wilson + // % note 1: Uses global: php_js to store the default timezone + // % note 2: Although the function potentially allows timezone info (see notes), it currently does not set + // % note 2: per a timezone specified by date_default_timezone_set(). Implementers might use + // % note 2: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function + // % note 2: in order to adjust the dates in this function (or our other date functions!) accordingly + // * example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400); + // * returns 1: '09:09:40 m is month' + // * example 2: date('F j, Y, g:i a', 1062462400); + // * returns 2: 'September 2, 2003, 2:26 am' + // * example 3: date('Y W o', 1062462400); + // * returns 3: '2003 36 2003' + // * example 4: x = date('Y m d', (new Date()).getTime()/1000); + // * example 4: (x+'').length == 10 // 2009 01 09 + // * returns 4: true + // * example 5: date('W', 1104534000); + // * returns 5: '53' + // * example 6: date('B t', 1104534000); + // * returns 6: '999 31' + // * example 7: date('W U', 1293750000.82); // 2010-12-31 + // * returns 7: '52 1293750000' + // * example 8: date('W', 1293836400); // 2011-01-01 + // * returns 8: '52' + // * example 9: date('W Y-m-d', 1293974054); // 2011-01-02 + // * returns 9: '52 2011-01-02' + var that = this, + jsdate, f, formatChr = /\\?([a-z])/gi, + formatChrCb, + // Keep this here (works, but for code commented-out + // below for file size reasons) + //, tal= [], + _pad = function (n, c) { + if ((n = n + '').length < c) { + return new Array((++c) - n.length).join('0') + n; + } + return n; + }, + txt_words = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + formatChrCb = function (t, s) { + return f[t] ? f[t]() : s; + }; + f = { + // Day + d: function () { // Day of month w/leading 0; 01..31 + return _pad(f.j(), 2); + }, + D: function () { // Shorthand day name; Mon...Sun + return f.l().slice(0, 3); + }, + j: function () { // Day of month; 1..31 + return jsdate.getDate(); + }, + l: function () { // Full day name; Monday...Sunday + return txt_words[f.w()] + 'day'; + }, + N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun] + return f.w() || 7; + }, + S: function () { // Ordinal suffix for day of month; st, nd, rd, th + var j = f.j(); + return j > 4 || j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th'; + }, + w: function () { // Day of week; 0[Sun]..6[Sat] + return jsdate.getDay(); + }, + z: function () { // Day of year; 0..365 + var a = new Date(f.Y(), f.n() - 1, f.j()), + b = new Date(f.Y(), 0, 1); + return Math.round((a - b) / 864e5) + 1; + }, + + // Week + W: function () { // ISO-8601 week number + var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3), + b = new Date(a.getFullYear(), 0, 4); + return _pad(1 + Math.round((a - b) / 864e5 / 7), 2); + }, + + // Month + F: function () { // Full month name; January...December + return txt_words[6 + f.n()]; + }, + m: function () { // Month w/leading 0; 01...12 + return _pad(f.n(), 2); + }, + M: function () { // Shorthand month name; Jan...Dec + return f.F().slice(0, 3); + }, + n: function () { // Month; 1...12 + return jsdate.getMonth() + 1; + }, + t: function () { // Days in month; 28...31 + return (new Date(f.Y(), f.n(), 0)).getDate(); + }, + + // Year + L: function () { // Is leap year?; 0 or 1 + return new Date(f.Y(), 1, 29).getMonth() === 1 | 0; + }, + o: function () { // ISO-8601 year + var n = f.n(), + W = f.W(), + Y = f.Y(); + return Y + (n === 12 && W < 9 ? -1 : n === 1 && W > 9); + }, + Y: function () { // Full year; e.g. 1980...2010 + return jsdate.getFullYear(); + }, + y: function () { // Last two digits of year; 00...99 + return (f.Y() + "").slice(-2); + }, + + // Time + a: function () { // am or pm + return jsdate.getHours() > 11 ? "pm" : "am"; + }, + A: function () { // AM or PM + return f.a().toUpperCase(); + }, + B: function () { // Swatch Internet time; 000..999 + var H = jsdate.getUTCHours() * 36e2, + // Hours + i = jsdate.getUTCMinutes() * 60, + // Minutes + s = jsdate.getUTCSeconds(); // Seconds + return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3); + }, + g: function () { // 12-Hours; 1..12 + return f.G() % 12 || 12; + }, + G: function () { // 24-Hours; 0..23 + return jsdate.getHours(); + }, + h: function () { // 12-Hours w/leading 0; 01..12 + return _pad(f.g(), 2); + }, + H: function () { // 24-Hours w/leading 0; 00..23 + return _pad(f.G(), 2); + }, + i: function () { // Minutes w/leading 0; 00..59 + return _pad(jsdate.getMinutes(), 2); + }, + s: function () { // Seconds w/leading 0; 00..59 + return _pad(jsdate.getSeconds(), 2); + }, + u: function () { // Microseconds; 000000-999000 + return _pad(jsdate.getMilliseconds() * 1000, 6); + }, + + // Timezone + e: function () { // Timezone identifier; e.g. Atlantic/Azores, ... + // The following works, but requires inclusion of the very large + // timezone_abbreviations_list() function. +/* return this.date_default_timezone_get(); +*/ + throw 'Not supported (see source code of date() for timezone on how to add support)'; + }, + I: function () { // DST observed?; 0 or 1 + // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. + // If they are not equal, then DST is observed. + var a = new Date(f.Y(), 0), + // Jan 1 + c = Date.UTC(f.Y(), 0), + // Jan 1 UTC + b = new Date(f.Y(), 6), + // Jul 1 + d = Date.UTC(f.Y(), 6); // Jul 1 UTC + return 0 + ((a - c) !== (b - d)); + }, + O: function () { // Difference to GMT in hour format; e.g. +0200 + var a = jsdate.getTimezoneOffset(); + return (a > 0 ? "-" : "+") + _pad(Math.abs(a / 60 * 100), 4); + }, + P: function () { // Difference to GMT w/colon; e.g. +02:00 + var O = f.O(); + return (O.substr(0, 3) + ":" + O.substr(3, 2)); + }, + T: function () { // Timezone abbreviation; e.g. EST, MDT, ... + // The following works, but requires inclusion of the very + // large timezone_abbreviations_list() function. +/* var abbr = '', i = 0, os = 0, default = 0; + if (!tal.length) { + tal = that.timezone_abbreviations_list(); + } + if (that.php_js && that.php_js.default_timezone) { + default = that.php_js.default_timezone; + for (abbr in tal) { + for (i=0; i < tal[abbr].length; i++) { + if (tal[abbr][i].timezone_id === default) { + return abbr.toUpperCase(); + } + } + } + } + for (abbr in tal) { + for (i = 0; i < tal[abbr].length; i++) { + os = -jsdate.getTimezoneOffset() * 60; + if (tal[abbr][i].offset === os) { + return abbr.toUpperCase(); + } + } + } +*/ + return 'UTC'; + }, + Z: function () { // Timezone offset in seconds (-43200...50400) + return -jsdate.getTimezoneOffset() * 60; + }, + + // Full Date/Time + c: function () { // ISO-8601 date. + return 'Y-m-d\\Th:i:sP'.replace(formatChr, formatChrCb); + }, + r: function () { // RFC 2822 + return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); + }, + U: function () { // Seconds since UNIX epoch + return jsdate.getTime() / 1000 | 0; + } + }; + this.date = function (format, timestamp) { + that = this; + jsdate = ((typeof timestamp === 'undefined') ? new Date() : // Not provided + (timestamp instanceof Date) ? new Date(timestamp) : // JS Date() + new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) + ); + return format.replace(formatChr, formatChrCb); + }; + return this.date(format, timestamp); +} + +function gmdate (format, timestamp) { + // Format a GMT date/time + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/gmdate + // + original by: Brett Zamir (http://brett-zamir.me) + // + input by: Alex + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // - depends on: date + // * example 1: gmdate('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400); // Return will depend on your timezone + // * returns 1: '07:09:40 m is month' + var dt = typeof timestamp === 'undefined' ? new Date() : // Not provided + typeof timestamp === 'object' ? new Date(timestamp) : // Javascript Date() + new Date(timestamp * 1000); // UNIX timestamp (auto-convert to int) + timestamp = Date.parse(dt.toUTCString().slice(0, -4)) / 1000; + return this.date(format, timestamp); +} + +function idate (format, timestamp) { + // Format a local time/date as integer + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/idate + // + original by: Brett Zamir (http://brett-zamir.me) + // + input by: Alex + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + improved by: Theriault + // + derived from: date + // + derived from: gettimeofday + // * example 1: idate('y'); + // * returns 1: 9 + if (format === undefined) { + throw 'idate() expects at least 1 parameter, 0 given'; + } + if (!format.length || format.length > 1) { + throw 'idate format is one char'; + } + + // Fix: Need to allow date_default_timezone_set() (check for this.php_js.default_timezone and use) + var date = ((typeof timestamp === 'undefined') ? new Date() : // Not provided + (timestamp instanceof Date) ? new Date(timestamp) : // Javascript Date() + new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) + ), + a; + + switch (format) { + case 'B': + return Math.floor(((date.getUTCHours() * 36e2) + (date.getUTCMinutes() * 60) + date.getUTCSeconds() + 36e2) / 86.4) % 1e3; + case 'd': + return date.getDate(); + case 'h': + return date.getHours() % 12 || 12; + case 'H': + return date.getHours(); + case 'i': + return date.getMinutes(); + case 'I': + // capital 'i' + // Logic derived from getimeofday(). + // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. + // If they are not equal, then DST is observed. + a = date.getFullYear(); + return 0 + (((new Date(a, 0)) - Date.UTC(a, 0)) !== ((new Date(a, 6)) - Date.UTC(a, 6))); + case 'L': + a = date.getFullYear(); + return (!(a & 3) && (a % 1e2 || !(a % 4e2))) ? 1 : 0; + case 'm': + return date.getMonth() + 1; + case 's': + return date.getSeconds(); + case 't': + return (new Date(date.getFullYear(), date.getMonth() + 1, 0)).getDate(); + case 'U': + return Math.round(date.getTime() / 1000); + case 'w': + return date.getDay(); + case 'W': + a = new Date(date.getFullYear(), date.getMonth(), date.getDate() - (date.getDay() || 7) + 3); + return 1 + Math.round((a - (new Date(a.getFullYear(), 0, 4))) / 864e5 / 7); + case 'y': + return parseInt((date.getFullYear() + '').slice(2), 10); // This function returns an integer, unlike date() + case 'Y': + return date.getFullYear(); + case 'z': + return Math.floor((date - new Date(date.getFullYear(), 0, 1)) / 864e5); + case 'Z': + return -date.getTimezoneOffset() * 60; + default: + throw 'Unrecognized date format token'; + } +} + +function mktime () { + // Get UNIX timestamp for a date + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/mktime + // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: baris ozdil + // + input by: gabriel paderni + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: FGFEmperor + // + input by: Yannoo + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: jakes + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Marc Palau + // + improved by: Brett Zamir (http://brett-zamir.me) + // + input by: 3D-GRAF + // + bugfixed by: Brett Zamir (http://brett-zamir.me) + // + input by: Chris + // + revised by: Theriault + // % note 1: The return values of the following examples are + // % note 1: received only if your system's timezone is UTC. + // * example 1: mktime(14, 10, 2, 2, 1, 2008); + // * returns 1: 1201875002 + // * example 2: mktime(0, 0, 0, 0, 1, 2008); + // * returns 2: 1196467200 + // * example 3: make = mktime(); + // * example 3: td = new Date(); + // * example 3: real = Math.floor(td.getTime() / 1000); + // * example 3: diff = (real - make); + // * results 3: diff < 5 + // * example 4: mktime(0, 0, 0, 13, 1, 1997) + // * returns 4: 883612800 + // * example 5: mktime(0, 0, 0, 1, 1, 1998) + // * returns 5: 883612800 + // * example 6: mktime(0, 0, 0, 1, 1, 98) + // * returns 6: 883612800 + // * example 7: mktime(23, 59, 59, 13, 0, 2010) + // * returns 7: 1293839999 + // * example 8: mktime(0, 0, -1, 1, 1, 1970) + // * returns 8: -1 + var d = new Date(), + r = arguments, + i = 0, + e = ['Hours', 'Minutes', 'Seconds', 'Month', 'Date', 'FullYear']; + + for (i = 0; i < e.length; i++) { + if (typeof r[i] === 'undefined') { + r[i] = d['get' + e[i]](); + r[i] += (i === 3); // +1 to fix JS months. + } else { + r[i] = parseInt(r[i], 10); + if (isNaN(r[i])) { + return false; + } + } + } + + // Map years 0-69 to 2000-2069 and years 70-100 to 1970-2000. + r[5] += (r[5] >= 0 ? (r[5] <= 69 ? 2e3 : (r[5] <= 100 ? 1900 : 0)) : 0); + + // Set year, month (-1 to fix JS months), and date. + // !This must come before the call to setHours! + d.setFullYear(r[5], r[3] - 1, r[4]); + + // Set hours, minutes, and seconds. + d.setHours(r[0], r[1], r[2]); + + // Divide milliseconds by 1000 to return seconds and drop decimal. + // Add 1 second if negative or it'll be off from PHP by 1 second. + return (d.getTime() / 1e3 >> 0) - (d.getTime() < 0); +} + +function rand (min, max) { + // http://kevin.vanzonneveld.net + // + original by: Leslie Hoare + // + bugfixed by: Onno Marsman + // % note 1: See the commented out code below for a version which will work with our experimental (though probably unnecessary) srand() function) + // * example 1: rand(1, 1); + // * returns 1: 1 + var argc = arguments.length; + if (argc === 0) { + min = 0; + max = 2147483647; + } else if (argc === 1) { + throw new Error('Warning: rand() expects exactly 2 parameters, 1 given'); + } + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function time () { + // Return current UNIX timestamp + // + // version: 1107.2516 + // discuss at: http://phpjs.org/functions/time + // + original by: GeekFG (http://geekfg.blogspot.com) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: metjay + // + improved by: HKM + // * example 1: timeStamp = time(); + // * results 1: timeStamp > 1000000000 && timeStamp < 2000000000 + return Math.floor(new Date().getTime() / 1000); +} diff --git a/application/extensions/LimeScript/LimeScript.php b/application/extensions/LimeScript/LimeScript.php index 1013d92d198..2ac81b076e4 100644 --- a/application/extensions/LimeScript/LimeScript.php +++ b/application/extensions/LimeScript/LimeScript.php @@ -1,23 +1,41 @@ getClientScript()->registerScriptFile(App()->getAssetManager()->publish(Yii::getPathOfAlias('ext.LimeScript.assets'). '/script.js')); + /* @var CClientScript $cs */ + $cs = Yii::app()->getClientScript(); + + $cs->registerScriptFile(Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('ext.LimeScript.assets'). '/script.js')); - $data = array(); - $data['baseUrl'] = Yii::app()->getBaseUrl(true); - $data['showScriptName'] = Yii::app()->urlManager->showScriptName; - $data['urlFormat'] = Yii::app()->urlManager->urlFormat; - $data['adminImageUrl'] = Yii::app()->getConfig('adminimageurl'); - $data['replacementFields']['path'] = App()->createUrl("admin/limereplacementfields/sa/index/"); - $json = json_encode($data, JSON_FORCE_OBJECT); - $script = "LS.data = $json"; - App()->getClientScript()->registerScript('LimeScript', $script, CClientScript::POS_HEAD); + $this->data['data']['baseUrl'] = Yii::app()->getBaseUrl(true); + $this->data['data']['showScriptName'] = Yii::app()->urlManager->showScriptName; + $this->data['data']['urlFormat'] = Yii::app()->urlManager->urlFormat; + $this->data['data']['adminImageUrl'] = Yii::app()->getConfig('adminimageurl'); + $this->data['data']['replacementFields']['path'] = Yii::app()->createUrl("admin/limereplacementfields/sa/index/"); + $cs->registerScript('LimeScript', $this, CClientScript::POS_HEAD); + } + + + public function add($key, $value) + { + $this->data = Hash::insert($this->data, $key, $value); + } + + /** + * We pass a reference to this object to CClientScript. + * CClientScript will call __toString when it's rendering the script. + * @return string + */ + public function __toString() { + $script = "$.extend(LS, " . CJavaScript::encode($this->data) . ");\n"; + return $script; } } diff --git a/application/extensions/LimeScript/assets/script.js b/application/extensions/LimeScript/assets/script.js index 40eb6ef95fb..39b55723ce6 100644 --- a/application/extensions/LimeScript/assets/script.js +++ b/application/extensions/LimeScript/assets/script.js @@ -1,5 +1,5 @@ // Initial definition of Limesurvey javascript object. -var LS = {}; +var LS = LS || {}; LS.createUrl = function (route, params) { @@ -38,4 +38,95 @@ LS.createUrl = function (route, params) } return result; -} \ No newline at end of file +} + +/** + * This function returns the value of a variable. + */ +LS.getValue = function (variableName) +{ + if (LS.variableIsRelevant(variableName)) + { + var code = LS.p.VariableToCode[variableName]; + if (LS.p.questions[code].type) + { + var GUID = LS.p.questions[code].type; + var id = LS.p.questions[code].id; + return LS.p.QuestionTypes[GUID].get.call($('#' + id), variableName); + } + } +} + + +/** + * This function sets the value of a variable (if possible). + */ +LS.setValue = function (variableName, value) +{ + if (LS.p.VariableToCode[variableName]) + { + var code = LS.p.VariableToCode[variableName]; + if (LS.p.questions[code].type) + + { + var GUID = LS.p.questions[code].type; + var id = LS.p.questions[code].id; + LS.p.QuestionTypes[GUID].set.call($('#' + id), variableName, value); + } + } +} + +LS.questionIsRelevant = function(questionCode) +{ + if (LS.p.questions && LS.p.questions[questionCode] && LS.p.questions[questionCode].relevanceStatus) + { + return LS.p.questions[questionCode].relevanceStatus(); + } + else + { // If the variable is known but there is no relevance status, it is always relevant. + return true; + } +} +LS.variableIsRelevant = function(variableName) +{ + // Check if variable is known (ie belongs to a question.) + if (LS.p.VariableToCode[variableName]) + { + var questionCode = LS.p.VariableToCode[variableName]; + return LS.questionIsRelevant(questionCode); + + } + // If the variable is unknown it is irrelevant. + return false; +} + +// Iterate over each question in order and update its relevance. +LS.updateRelevance = function() +{ + if (LS.p && LS.p.questions) { + for (var code in LS.p.questions) + { + var q = LS.p.questions[code]; + if (LS.questionIsRelevant(code)) + { + $('div.question#' + q.div).removeClass('irrelevant'); + } + else + { + $('div.question#' + q.div).addClass('irrelevant'); + } + } + } +} +// Bind change events. +$(document).ready(function() +{ + if (LS.p && LS.p.questions) { + for (var code in LS.p.questions) + { + var q = LS.p.questions[code]; + LS.p.QuestionTypes[q.type].bindChange.call($('#' + q.id), function () { LS.updateRelevance(); }); + } + } + LS.updateRelevance(); +}); \ No newline at end of file diff --git a/application/extensions/LimeScript/assets/script_1.js b/application/extensions/LimeScript/assets/script_1.js deleted file mode 100644 index 40eb6ef95fb..00000000000 --- a/application/extensions/LimeScript/assets/script_1.js +++ /dev/null @@ -1,41 +0,0 @@ -// Initial definition of Limesurvey javascript object. -var LS = {}; - -LS.createUrl = function (route, params) -{ - if (typeof params === 'undefined') { - params = {}; - } - var result = LS.data.baseUrl; - - if (LS.data.showScriptName) - { - result = result + '/index.php'; - } - - - if (LS.data.urlFormat == 'get') - { - // Configure route. - result += '?r=' + route; - - // Configure params. - for (var key in params) - { - result = result + '&' + key+ '=' + params[key]; - } - } - else - { - // Configure route. - result += route; - - // Configure params. - for (var key in params) - { - result = result + '/' + key + '/' + params[key]; - } - } - - return result; -} \ No newline at end of file diff --git a/application/extensions/Menu/MenuWidget.php b/application/extensions/Menu/MenuWidget.php index 875c71329de..1b264be9cf0 100644 --- a/application/extensions/Menu/MenuWidget.php +++ b/application/extensions/Menu/MenuWidget.php @@ -444,21 +444,25 @@ protected function renderSelect($item) { $listData = $item['values']; } - $result .= $this->widget('ext.bootstrap.widgets.TbSelect2', array( - 'name' => $item['name'], - 'value' => $item['value'], - 'data' => $listData, - 'options' => array( - 'minimumResultsForSearch' => 20, - 'placeholder' => gT('Please choose...') - ), - 'htmlOptions' => array( - 'class' => 'select', - 'id' => $item['name'], - 'prompt' => '' // Required for placeholder to work. - ) - ), true); - return $result; + if (!empty($listData)) + { + $result .= $this->widget('ext.bootstrap.widgets.TbSelect2', array( + 'name' => $item['name'], + 'value' => $item['value'], + 'data' => $listData, + 'options' => array( + 'minimumResultsForSearch' => 20, + 'placeholder' => gT('Please choose...') + ), + 'htmlOptions' => array( + 'class' => 'select', + 'id' => $item['name'], + 'prompt' => '' // Required for placeholder to work. + ) + ), true); + return $result; + } + } protected function renderSub($item, $imageUrl, $level) diff --git a/application/helpers/PluginSettingsHelper.php b/application/helpers/PluginSettingsHelper.php index 0ef0f8ab343..5643e11dff0 100644 --- a/application/helpers/PluginSettingsHelper.php +++ b/application/helpers/PluginSettingsHelper.php @@ -7,7 +7,10 @@ public function renderSetting($name, array $metaData, $form = null, $return = fa { $defaults = array( 'class' => array(), - 'type' => 'string' + 'type' => 'string', + 'labelOptions' => array( + 'class' => 'control-label' + ) ); $metaData = array_merge($defaults, $metaData); if (is_string($metaData['class'])) @@ -69,7 +72,7 @@ public function renderFloat($name, array $metaData, $form = null) $value = isset($metaData['current']) ? $metaData['current'] : ''; if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= CHtml::textField($id, $value, array( 'id' => $id, @@ -91,7 +94,7 @@ public function renderHtml($name, array $metaData, $form = null) $readOnly = isset($metaData['readOnly']) ? $metaData['readOnly'] : false; if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= Chtml::tag('div', array('class' => implode(' ', $metaData['class'])), CHtml::textArea($id, $value, array('id' => $id, 'form' => $form, 'readonly' => $readOnly))); return $out; @@ -104,7 +107,7 @@ public function renderInt($name, array $metaData, $form = null) $value = isset($metaData['current']) ? $metaData['current'] : ''; if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= CHtml::textField($id, $value, array( 'id' => $id, @@ -128,7 +131,7 @@ public function renderRelevance($name, array $metaData, $form = null) if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $value = isset($metaData['current']) ? $metaData['current'] : ''; @@ -144,7 +147,7 @@ public function renderSelect($name, array $metaData, $form = null) $value = isset($metaData['current']) ? $metaData['current'] : (isset($metaData['default']) ? $metaData['default'] : null); if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= CHtml::dropDownList($name, $value, $metaData['options'], array('form' => $form)); @@ -159,7 +162,7 @@ public function renderString($name, array $metaData, $form = null) $readOnly = isset($metaData['readOnly']) ? $metaData['readOnly'] : false; if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= CHtml::textField($id, $value, array('id' => $id, 'form' => $form, 'class' => implode(' ', $metaData['class']), 'readonly' => $readOnly)); @@ -173,7 +176,7 @@ public function renderPassword($name, array $metaData, $form = null) $value = isset($metaData['current']) ? $metaData['current'] : ''; if (isset($metaData['label'])) { - $out .= CHtml::label($metaData['label'], $id); + $out .= CHtml::label($metaData['label'], $id, $metaData['labelOptions']); } $out .= CHtml::passwordField($id, $value, array('id' => $id, 'form' => $form)); diff --git a/application/helpers/SurveyRuntimeHelper.php b/application/helpers/SurveyRuntimeHelper.php index 221b52b2e10..6dbc7d9113b 100644 --- a/application/helpers/SurveyRuntimeHelper.php +++ b/application/helpers/SurveyRuntimeHelper.php @@ -13,11 +13,6 @@ class SurveyRuntimeHelper { - /** - * - * @var Twig_Environment; - */ - private $twig; private $context = array(); /** * Main function diff --git a/application/helpers/SurveySessionHelper.php b/application/helpers/SurveySessionHelper.php index a135161e2be..e79adb9a57b 100644 --- a/application/helpers/SurveySessionHelper.php +++ b/application/helpers/SurveySessionHelper.php @@ -26,7 +26,11 @@ protected function check($surveyId, $key = '') } return false; } - + + + public function __construct() { + + } /** * This function initializes a new session for a survey. * If reset = true any existing session for the survey is first removed. @@ -34,7 +38,7 @@ protected function check($surveyId, $key = '') * @param boolean $reset * @return boolean True if a new session was started. */ - public function create($surveyId, $reset = false, $language = null) + public function create($surveyId, $reset = false, $language = null, $preview = false) { if ($reset || !$this->exists($surveyId)) { @@ -119,7 +123,7 @@ public function getGroupOrder($surveyId) asort($mapGroupOrder); return array_flip($mapGroupOrder); } - + /** * This function initializes an empty survey session. * @param int $surveyId @@ -182,7 +186,7 @@ public function read($surveyId, $key = null) return $array; } } - + protected function write($surveyId, $key, $value) { if ($this->exists($surveyId)) diff --git a/application/helpers/twig_helper.php b/application/helpers/twig_helper.php deleted file mode 100644 index b13fad700f9..00000000000 --- a/application/helpers/twig_helper.php +++ /dev/null @@ -1,113 +0,0 @@ -getConfig('debug') > 0) - { - $options['debug'] = true; - $options['cache'] = false; - } - else - { - $options['cache'] = App()->getConfig('twigdir'); - } - - - if (!self::$initialized) - { - Yii::import('application.libraries.Twig.Autoloader', true); - spl_autoload_unregister(array('YiiBase','autoload')); - Twig_Autoloader::register(); - spl_autoload_register(array('YiiBase','autoload')); - self::$initialized = true; - } - - if (!isset(self::$environment)) - { - $loader = new Twig_Loader_Filesystem(array( - App()->getConfig('standardtemplaterootdir'), - App()->getConfig('usertemplaterootdir') - )); - $twig = new Twig_Environment($loader, $options); - - // Make language data available. - foreach (getLanguageData() as $code => $data) - { - $direction[$code] = $data['rtl'] ? 'rtl' : 'ltr'; - } - $twig->addGlobal('direction', $direction); - - // Register custom functions. - $twig->addFunction(new Twig_SimpleFunction('trans', 'Twig::gT')); - $twig->addFunction(new Twig_SimpleFunction('ntrans', 'Twig::ngT')); - $twig->addFunction(new Twig_SimpleFunction('em', 'Twig::em')); - - - self::$environment = $twig; - } - return self::$environment; - } - - public static function ngt($singular, $plural, $count) - { - - // First translate using proper translation string. - $translated = ngT($singular, $plural, $count); - // Then apply printf. - $args = func_get_args(); - array_shift($args); - array_shift($args); - return vsprintf($translated, $args); - } - - public static function gT($txt) - { - // First translate text. - $translated = gT($txt); - - // Then apply printf. - if (func_num_args() > 1) - { - $args = func_get_args(); - array_shift($args); - return vsprintf($translated, $args); - } - else - { - return $translated; - } - } - - public static function em($txt) - { - - return LimeExpressionManager::ProcessString($txt); - } - - /** - * - Adds the path of a template to the auto loader. - * - Sets the global variable template.url to the url of the template. - * @param string $template Name of the template to activate. - */ - public static function activateTemplate($template) - { - self::getTwigEnvironment()->getLoader()->addPath(getTemplatePath($template)); - self::getTwigEnvironment()->addGlobal('template', array('url' => getTemplateURL($template), 'name' => $template)); - - } - } -?> \ No newline at end of file diff --git a/application/libraries/PluginManager/LimesurveyApi.php b/application/libraries/PluginManager/LimesurveyApi.php index 143feaf54fd..178918cdff0 100644 --- a/application/libraries/PluginManager/LimesurveyApi.php +++ b/application/libraries/PluginManager/LimesurveyApi.php @@ -26,7 +26,8 @@ public function setFlash($message, $key ='api') */ public function EMevaluateExpression($expression) { - $result = LimeExpressionManager::ProcessString($expression); + return $expression; + //$result = LimeExpressionManager::ProcessString($expression); return $result; } diff --git a/application/libraries/PluginManager/Question/QuestionBase.php b/application/libraries/PluginManager/Question/QuestionBase.php index 9146ebd3456..bc7214ce77d 100644 --- a/application/libraries/PluginManager/Question/QuestionBase.php +++ b/application/libraries/PluginManager/Question/QuestionBase.php @@ -212,7 +212,15 @@ public static function getGUID() // We use json_encode because it is faster than serialize. return md5(json_encode(static::$signature)); } - + + public static function getJavascript() { + $functions = array( + 'get' => 'js:function(variable) { return $(this).val(); }', + 'set' => 'js:function(variable, value) { return $(this).val(value); }', + 'bindChange' => 'js:function(callback) { $(this).bind("change", callback) }' + ); + return $functions; + } /** * Gets the response for the current response id. * @return type @@ -239,12 +247,10 @@ public function getVariables() { if (isset($this->questionId)) { + $code = $this->get('code'); return array( - $this->get('code') => array( - 'id' => $this->questionId, - 'relevance' => $this->get('relevance') - ) ); + } return array(); } @@ -266,6 +272,14 @@ public function loadSubQuestions($questionId) } } + /** + * Renders a view file. + * @param type $file + */ + protected function renderFile($file) + { + + } public function saveAttributes(array $attributeValues, $qid = null) { $attributes = $this->getAttributes(); diff --git a/application/libraries/PluginManager/Question/iQuestion.php b/application/libraries/PluginManager/Question/iQuestion.php index ed8462fc8f0..1ec043a7634 100644 --- a/application/libraries/PluginManager/Question/iQuestion.php +++ b/application/libraries/PluginManager/Question/iQuestion.php @@ -25,21 +25,21 @@ public function getCount(); */ public static function getGUID(); + public static function getJavascript(); /** * Returns the variables exposed by this question. * The returned array contains a key for each variable name and the value is an array with meta data. * @return array */ public function getVariables(); + /** - * @param Twig_Environment $twig A reference to configured Twig Environment. - * This Twig environment will have a correctly configured translation environment. - * This Twig environment will have the plugin view path configured in its loader. + * @param string $name The name (or name prefix) the inputs rendered must use. + * @param string $language The language code. * @param bool $return If true, return the content instead of outputting it. */ public function render($name, $language, $return = false); - /** * This function must save the custom question attributes for a question. * The default implementation just iterates over the array and saves each property. diff --git a/application/views/groups/preview.php b/application/views/groups/preview.php index d309e79e6fa..d9d0a86169d 100644 --- a/application/views/groups/preview.php +++ b/application/views/groups/preview.php @@ -1,16 +1,13 @@ - - - - 'form-horizontal')); + $i = 0; foreach ($renderedQuestions as $renderedQuestion) { - echo CHtml::tag('div', array('class' => 'question'), $renderedQuestion); - + echo CHtml::tag('div', array( + 'class' => 'question', + 'id' => "question$i"), $renderedQuestion); + $i++; } - echo templatereplace(file_get_contents($path . '/endgroup.pstpl'), array()); - echo templatereplace(file_get_contents($path . '/endpage.pstpl'), array()); -?> - + echo CHtml::endForm(); +?> \ No newline at end of file diff --git a/application/views/layouts/main.php b/application/views/layouts/main.php index 53d5f75581c..2e30c4114a6 100644 --- a/application/views/layouts/main.php +++ b/application/views/layouts/main.php @@ -24,7 +24,6 @@ - widget('ext.LimeScript.LimeScript'); ?> widget('ext.LimeDebug.LimeDebug'); ?> Limesurvey Administration diff --git a/application/views/layouts/survey.php b/application/views/layouts/survey.php index ca431f1f713..08675635b60 100644 --- a/application/views/layouts/survey.php +++ b/application/views/layouts/survey.php @@ -5,46 +5,19 @@ getComponent('bootstrap'); $cs=Yii::app()->getClientScript(); $cs->registerCoreScript('jquery'); - $cs->registerScriptFile(Yii::app()->getConfig('third_party') . 'jqueryui/js/jquery-ui-1.10.0.custom.js'); - $cs->registerScriptFile(Yii::app()->getConfig('generalscripts') . 'jquery/jquery.ui.touch-punch.min.js'); - $cs->registerScriptFile(Yii::app()->getConfig('generalscripts') . 'jquery/jquery.qtip.js'); - $cs->registerScriptFile(Yii::app()->getConfig('generalscripts') . 'jquery/jquery.notify.js'); - $cs->registerScriptFile(Yii::app()->createUrl('config/script')); - $cs->registerScriptFile(Yii::app()->getConfig('adminscripts') . 'admin_core.js'); + $cs->registerCoreScript('bootstrap'); ?> - - - - - - - - - - - - - - Limesurvey widget('ext.FlashMessage.FlashMessage'); ?> -
+
-
+ diff --git a/application/views/plugins/configure.php b/application/views/plugins/configure.php index bd04e1d2169..76deeff4dbe 100644 --- a/application/views/plugins/configure.php +++ b/application/views/plugins/configure.php @@ -9,7 +9,7 @@ 'ok')); - echo CHtml::submitButton('Cancel', array('name'=>'cancel')); + echo CHtml::submitButton(gT('Save plugin settings'), array('name'=>'ok')); + echo CHtml::submitButton(gT('Cancel'), array('name'=>'cancel')); echo CHtml::endForm(); ?> diff --git a/application/views/plugins/index.php b/application/views/plugins/index.php index f4d778fe473..62d2ee8ecfe 100644 --- a/application/views/plugins/index.php +++ b/application/views/plugins/index.php @@ -2,21 +2,43 @@ 'CLinkColumn', - 'header' => 'Plugin', - 'labelExpression' => function($data) { return $data['name']; }, - 'urlExpression' => function($data) { return array("/plugins/configure", "id" => $data['id']); } + 'header' => gT('Status'), + 'labelExpression' => function($data) { return ($data['active'] == 1 ? CHtml::image(App()->getConfig('adminimageurl') . 'active.png', gT('Active'), array('width' => 32, 'height' => 32)) : CHtml::image(App()->getConfig('adminimageurl') . 'inactive.png', gT('Inactive'), array('width' => 32, 'height' => 32))); }, + 'url' => '#' ), array(// display the activation link - 'class' => 'CLinkColumn', - 'header' => 'Activation status', - 'labelExpression' => function($data) { return ($data['active'] == 0 ? 'activate' : 'deactivate'); }, - 'urlExpression' => function($data) { return $data['active'] == 0 ? array("/plugins/activate", "id" => $data['id']) : array("/plugins/activate", "id" => $data['id']); } - ) - ); + 'class' => 'CDataColumn', + 'type' => 'raw', + 'header' => gT('Action'), + 'value' => function($data) { + if ($data['active'] == 0) + { + $output = CHtml::link(CHtml::image(App()->getConfig('adminimageurl') . 'active.png', gT('Activate'), array('width' => 16, 'height' => 16)), array("/plugins/activate", "id" => $data['id'])); + } else { + $output = CHtml::link(CHtml::image(App()->getConfig('adminimageurl') . 'inactive.png', gT('Deactivate'), array('width' => 16, 'height' => 16)), array("/plugins/deactivate", "id" => $data['id'])); + } + $output .= CHtml::link(CHtml::image(App()->getConfig('adminimageurl') . 'survey_settings_30.png', gT('Configure'), array('width' => 16, 'height' => 16, 'style' => 'margin-left: 8px;')), array("/plugins/configure", "id" => $data['id'])); + return $output; + } + ), + array(// display the 'name' attribute + 'class' => 'CDataColumn', + 'header' => gT('Plugin'), + 'name' => 'name' + ), + array(// display the 'name' attribute + 'class' => 'CDataColumn', + 'header' => gT('Description'), + 'name' => 'description' + ), + ); + /* array( // display a column with "view", "update" and "delete" buttons 'class' => 'CallbackColumn', @@ -25,7 +47,8 @@ ) ); */ - $this->widget('application.extensions.GridViewWidget', array( + + $this->widget('bootstrap.widgets.TbExtendedGridView', array( 'dataProvider'=>$dataProvider, 'columns'=>$gridColumns, 'rowCssClassExpression'=> function ($data, $row) { return ($row % 2 ? 'even' : 'odd') . ' ' . ($data['new']==1 ? "new" : "old"); }, diff --git a/application/views/questions/settingsblock.php b/application/views/questions/settingsblock.php new file mode 100644 index 00000000000..fa0b26f09a4 --- /dev/null +++ b/application/views/questions/settingsblock.php @@ -0,0 +1,11 @@ +
+
    + $setting) + { + $setting['language'] = $language; + echo CHtml::tag('div', array('class' => 'control-group'), $PluginSettings->renderSetting($name, $setting, $form, true)); + } + ?> +
+
\ No newline at end of file diff --git a/application/views/questions/update.php b/application/views/questions/update.php index a0144a80bd6..d296349d85b 100644 --- a/application/views/questions/update.php +++ b/application/views/questions/update.php @@ -5,6 +5,8 @@ Yii::import('application.helpers.PluginSettingsHelper'); $PluginSettings = new PluginSettingsHelper(); + echo CHtml::beginForm('', 'post', array('id' => $form, 'class' => 'form-horizontal')); + // Render basic and advanced non localized settings. $this->renderPartial('/questions/update_nonlocalized', compact('basicSettings', 'question', 'survey', 'groups', 'questions', 'questiontypes', 'form', 'attributes', 'PluginSettings')); @@ -31,7 +33,7 @@
$form, 'class' => 'modern')); + echo CHtml::submitButton(gT('Save')); echo CHtml::endForm(); diff --git a/application/views/questions/update_localized.php b/application/views/questions/update_localized.php index 7969fce3c96..a2d7c05b272 100644 --- a/application/views/questions/update_localized.php +++ b/application/views/questions/update_localized.php @@ -1,42 +1,58 @@ -
- -
-
    - $setting) - { - if ($setting['localized'] && !$setting['advanced']) - { - $setting['language'] = $language; - echo CHtml::tag('li', array(), $PluginSettings->renderSetting($name, $setting, $form, true)); - } - } - ?> -
+ $setting) + { + if ($setting['localized'] && !$setting['advanced']) + { + $basic[$name] = $setting; + } + elseif ($setting['localized'] && $setting['advanced']) + { + $advanced[$name] = $setting; + } + } -
-
-
    - $setting) - { - if ($setting['localized'] && $setting['advanced']) - { - $setting['language'] = $language; - echo CHtml::tag('li', array(), $PluginSettings->renderSetting($name, $setting, $form, true)); - } - } - ?> -
-
-
- \ No newline at end of file + $id = "localized-$language"; + $idbasic = "basic" . isset($language) ? $language : ''; + $idadvanced = "advanced" . isset($language) ? $language : ''; + $class[] = "localized"; + + // Decide if we need basic / advanced tab. + $out = ''; + if (!empty($basic) && !empty($advanced)) + { + $class[] = "tabs"; + $tabs[] = CHtml::link(CHtml::tag('span', array(), gT('Basic settings')), "#$idbasic"); + $tabs[] = CHtml::link(CHtml::tag('span', array(), gT('Advanced settings')), "#$idadvanced"); + $out .= CHtml::openTag('ul'); + foreach ($tabs as $tab) + { + $out .= CHtml::tag('li', array(), $tab); + + + } + $out.= CHtml::closeTag('ul'); + } + echo CHtml::openTag('div', array( + 'id' => $id, + 'class' => explode(' ', $class) + )); + echo $out; + + $this->renderPartial('/questions/settingsblock', array( + 'id' => $idbasic, + 'settings' => $basic, + 'language' => $language, + 'form' => $form, + 'PluginSettings' => $PluginSettings + )); + $this->renderPartial('/questions/settingsblock', array( + 'id' => $idadvanced, + 'settings' => $advanced, + 'language' => $language, + 'form' => $form, + 'PluginSettings' => $PluginSettings + )); + echo CHtml::closeTag('div'); +?> diff --git a/application/views/questions/update_nonlocalized.php b/application/views/questions/update_nonlocalized.php index 6cceb2c320c..32b395010d9 100644 --- a/application/views/questions/update_nonlocalized.php +++ b/application/views/questions/update_nonlocalized.php @@ -1,34 +1,53 @@ -
-
    -
  • -
  • -
-
-
    - $setting) - { - if (!$setting['localized'] && !$setting['advanced']) - { - echo CHtml::tag('li', array(), $PluginSettings->renderSetting($name, $setting, $form, true)); - } - } - ?> - -
-
-
-
    - $setting) + $setting) + { + if (!$setting['localized'] && !$setting['advanced']) { - if (!$setting['localized'] && $setting['advanced']) - { - echo CHtml::tag('li', array(), $PluginSettings->renderSetting($name, $setting, $form, true)); - } + $basic[$name] = $setting; } - ?> -
-
-
+ elseif (!$setting['localized'] && $setting['advanced']) + { + $advanced[$name] = $setting; + } + } + + $class[] = "localized"; + + // Decide if we need basic / advanced tab. + $out = ''; + if (!empty($basic) && !empty($advanced)) + { + $class[] = "tabs"; + $tabs[] = CHtml::link(CHtml::tag('span', array(), gT('Basic settings')), '#basic'); + $tabs[] = CHtml::link(CHtml::tag('span', array(), gT('Advanced settings')), '#advanced'); + $out .= CHtml::openTag('ul'); + foreach ($tabs as $tab) + { + $out .= CHtml::tag('li', array(), $tab); + + + } + $out.= CHtml::closeTag('ul'); + } + echo CHtml::openTag('div', array( + 'class' => explode(' ', $class) + )); + echo $out; + $this->renderPartial('/questions/settingsblock', array( + 'id' => 'basic', + 'settings' => $basic, + 'language' => $language, + 'form' => $form, + 'PluginSettings' => $PluginSettings + )); + $this->renderPartial('/questions/settingsblock', array( + 'id' => 'advanced', + 'settings' => $advanced, + 'language' => $language, + 'form' => $form, + 'PluginSettings' => $PluginSettings + )); + echo CHtml::closeTag('div'); +?> \ No newline at end of file diff --git a/plugins/YesNoQuestion/FreeTextQuestionObject.php b/plugins/YesNoQuestion/FreeTextQuestionObject.php index 65457220def..785d006730b 100644 --- a/plugins/YesNoQuestion/FreeTextQuestionObject.php +++ b/plugins/YesNoQuestion/FreeTextQuestionObject.php @@ -33,7 +33,15 @@ class FreeTextQuestionObject extends QuestionBase implements iQuestion 'originalName' => 'Free Text', 'startDev' => '2013-30-1' ); - + + + public static function getJavascript() + { + $functions = parent::getJavascript(); + // Override get and set if using checkbox layout. + $functions['bindChange'] = 'js:function(callback) { $(this).bind("change keyup", callback) }'; + return $functions; + } /** * * @param Twig_Environment $twig @@ -41,20 +49,23 @@ class FreeTextQuestionObject extends QuestionBase implements iQuestion * @param string $name Unique string prefix to be used for all elements with a name and or id attribute. * @return null|html */ - - public function render($name, $language, $return = false) + public function render($name, $language, $return = false) { - $context = array( - 'default' => $this->default, - 'name' => $name - ); - if (!$return) + $questionText = $this->get('question', '', $language); + + $value = $this->getResponse(); + + + $out = CHtml::label($this->api->EMevaluateExpression($questionText), $name); + + $out .= CHtml::textField($name, $value, $data); + if ($return) { - $twig->display('default.twig', $context); + return $out; } else { - return $twig->render('default.twig', $context); + echo $out; } } } diff --git a/plugins/YesNoQuestion/YesNoQuestionObject.php b/plugins/YesNoQuestion/YesNoQuestionObject.php index ad0c330a98a..2b09c9f227d 100644 --- a/plugins/YesNoQuestion/YesNoQuestionObject.php +++ b/plugins/YesNoQuestion/YesNoQuestionObject.php @@ -21,7 +21,8 @@ class YesNoQuestionObject extends QuestionBase implements iQuestion 'type' => 'select', 'options' => array( 'radio' => 'Radio buttons', - 'dropdown' => 'Dropdown list' + 'dropdown' => 'Dropdown list', + 'checkbox' => 'Checkbox' ), 'localized' => false, @@ -49,9 +50,29 @@ class YesNoQuestionObject extends QuestionBase implements iQuestion 'originalName' => 'Yes / No', 'startDev' => '2013-30-1' ); - + + + public static function getJavascript() + { + $functions = parent::getJavascript(); + // Override get and set if using checkbox layout. + $functions['get'] = + 'js:function(variable) { + if (!($(this).prop("type") == "checkbox")) { + return $(this).val(); + } else { + var a = $("input[name=" + $(this).prop("name") + "]").serializeArray(); return a[a.length-1].value;} + }'; + $functions['set'] = 'js:function(variable, value) { + if (!($(this).prop("type") == "checkbox")) {$(this).val(value); + } else { + $(this).prop("checked", $(this).prop("value") == value)} + }'; + return $functions; + } /** - * + * Renders the question object. The question object MUST create an element + * with an id equal to $name. * @param boolean $return * @param string $name Unique string prefix to be used for all elements with a name and or id attribute. * @return null|html @@ -70,23 +91,25 @@ public function render($name, $language, $return = false) 1 => 'Yes', 0 => 'No' ); - if ($this->get('display') == 'dropdown') - { - $out .= CHtml::dropDownList($name, $value, $data); - + switch ($this->get('display')) { + case 'dropdown': + $out .= CHtml::dropDownList($name, $value, $data); + break; + case 'checkbox': + $out .= CHtml::checkBox($name, $value, array('uncheckValue' => 0, 'value' => 1)); + break; + case 'radio' : + default: + $out .= CHtml::radioButtonList($name, $value, $data); + } - else - { - $out .= CHtml::radioButtonList($name, $value, $data); - } - if ($return) { - return CHtml::tag('div', array('class' => 'question'), $out); + return $out; } else { - echo CHtml::tag('div', array('class' => 'question'), $out); + echo $out; } } } diff --git a/plugins/YesNoQuestion/views/default.twig b/plugins/YesNoQuestion/views/default.twig deleted file mode 100644 index c345da2c799..00000000000 --- a/plugins/YesNoQuestion/views/default.twig +++ /dev/null @@ -1,4 +0,0 @@ -{# Default view file for Yes/No pluggable question. #} -{{ trans("Yes") }} -{{ trans("No") }} - diff --git a/themes/default/views/surveys/publiclist_twig.view b/themes/default/views/surveys/publiclist_twig.view new file mode 100644 index 00000000000..e36daa7c2b4 --- /dev/null +++ b/themes/default/views/surveys/publiclist_twig.view @@ -0,0 +1,13 @@ + + + Basic template: {{this.pageTitle}} + + +
    + {% for survey in surveys %} +
  • {{ survey.languagesettings[0].surveyls_title }}
  • + {% endfor %} +
+ + +