diff --git a/application/helpers/admin/activate_helper.php b/application/helpers/admin/activate_helper.php index d5c1ad3e516..d8f23b39410 100644 --- a/application/helpers/admin/activate_helper.php +++ b/application/helpers/admin/activate_helper.php @@ -273,6 +273,9 @@ function activateSurvey($iSurveyID, $simulate = false) { switch($aRow['type']) { + case 'seed': + $aTableDefinition[$aRow['fieldname']] = "string(31)"; + break; case 'startlanguage': $aTableDefinition[$aRow['fieldname']] = "string(20) NOT NULL"; break; diff --git a/application/helpers/common_helper.php b/application/helpers/common_helper.php index 750b320df8b..271854252ec 100644 --- a/application/helpers/common_helper.php +++ b/application/helpers/common_helper.php @@ -1810,6 +1810,14 @@ function createFieldMap($surveyid, $style='short', $force_refresh=false, $questi $fieldmap["startlanguage"]['group_name']=""; } + $fieldmap['seed'] = array('fieldname' => 'seed', 'sid' => $surveyid, 'type' => 'seed', 'gid' => '', 'qid' => '', 'aid' => ''); + if ($style == 'full') + { + $fieldmap["seed"]['title']=""; + $fieldmap["seed"]['question']=gT("Seed"); + $fieldmap["seed"]['group_name']=""; + } + //Check for any additional fields for this survey and create necessary fields (token and datestamp and ipaddr) $prow = Survey::model()->findByPk($surveyid)->getAttributes(); //Checked diff --git a/application/helpers/frontend_helper.php b/application/helpers/frontend_helper.php index b458b9f0fe5..1840fb1c460 100644 --- a/application/helpers/frontend_helper.php +++ b/application/helpers/frontend_helper.php @@ -11,6 +11,9 @@ * See COPYRIGHT.php for copyright notices and details. */ +// TODO: Why needed? +require_once(Yii::app()->basePath . '/libraries/MersenneTwister.php'); + function loadanswers() { Yii::trace('start', 'survey.loadanswers'); @@ -1060,6 +1063,8 @@ function buildsurveysession($surveyid,$preview=false) $qtypes=getQuestionTypeList('','array'); $fieldmap=createFieldMap($surveyid,'full',true,false,$_SESSION['survey_'.$surveyid]['s_lang']); + $seed = ls\mersenne\getSeed($surveyid, $preview); + // Randomization groups for groups list($fieldmap, $randomized1) = randomizationGroup($surveyid, $fieldmap, $preview); diff --git a/application/libraries/MersenneTwister.php b/application/libraries/MersenneTwister.php new file mode 100644 index 00000000000..02efb3c8cd5 --- /dev/null +++ b/application/libraries/MersenneTwister.php @@ -0,0 +1,109 @@ +getTableSchema()->getColumnNames(); + traceVar($columnNames); + // Get columns + // Check if we have seed column + // Yes: check value + // have value, use it + // no value, generate new seed + // No: create column + // generate seed + // insert seed + // Use seed to instantiate twister +} + +/** + * Shuffle with seed + * @param array $arr + * @param $seed + * @return array + */ +function shuffle($arr, $seed=-1) +{ + if ( $seed == -1 ) return $arr; + $mt = new MersenneTwister($seed); + $new = $arr; + for ($i = count($new) - 1; $i > 0; $i--) + { + $j = $mt->getNext(0,$i); + $tmp = $new[$i]; + $new[$i] = $new[$j]; + $new[$j] = $tmp; + } + return $new; +} + +/** + * Custom random algorithm to get consistent behaviour between PHP versions. + * + * Copied from: http://www.dr-chuck.com/csev-blog/2015/09/a-mersenne_twister-implementation-in-php/ + */ +class MersenneTwister +{ + private $state = array (); + private $index = 0; + + public function __construct($seed = null) { + if ($seed === null) + $seed = mt_rand(); + + $this->setSeed($seed); + } + + public function setSeed($seed) { + $this->state[0] = $seed & 0xffffffff; + + for ($i = 1; $i < 624; $i++) { + $this->state[$i] = (((0x6c078965 * ($this->state[$i - 1] ^ ($this->state[$i - 1] >> 30))) + $i)) & 0xffffffff; + } + + $this->index = 0; + } + + private function generateTwister() { + for ($i = 0; $i < 624; $i++) { + $y = (($this->state[$i] & 0x1) + ($this->state[$i] & 0x7fffffff)) & 0xffffffff; + $this->state[$i] = ($this->state[($i + 397) % 624] ^ ($y >> 1)) & 0xffffffff; + + if (($y % 2) == 1) { + $this->state[$i] = ($this->state[$i] ^ 0x9908b0df) & 0xffffffff; + } + } + } + + public function getNext($min = null, $max = null) { + if (($min === null && $max !== null) || ($min !== null && $max === null)) + throw new Exception('Invalid arguments'); + + if ($this->index === 0) { + $this->generateTwister(); + } + + $y = $this->state[$this->index]; + $y = ($y ^ ($y >> 11)) & 0xffffffff; + $y = ($y ^ (($y << 7) & 0x9d2c5680)) & 0xffffffff; + $y = ($y ^ (($y << 15) & 0xefc60000)) & 0xffffffff; + $y = ($y ^ ($y >> 18)) & 0xffffffff; + + $this->index = ($this->index + 1) % 624; + + if ($min === null && $max === null) + return $y; + + $range = abs($max - $min); + + return min($min, $max) + ($y % ($range + 1)); + } +} diff --git a/application/models/SurveyDynamic.php b/application/models/SurveyDynamic.php index 3ac38cd7260..a7519c699c3 100644 --- a/application/models/SurveyDynamic.php +++ b/application/models/SurveyDynamic.php @@ -540,7 +540,7 @@ public function getTokenForGrid() // Get the list of default columns for surveys public function getDefaultColumns() { - return array('id', 'token', 'submitdate', 'lastpage','startlanguage', 'completed'); + return array('id', 'token', 'submitdate', 'lastpage','startlanguage', 'completed', 'seed'); } /**