1<?php if (!defined('BASEPATH')) { 2 exit('No direct script access allowed'); 3} 4/* 5* LimeSurvey 6* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz 7* All rights reserved. 8* License: GNU/GPL License v2 or later, see LICENSE.php 9* LimeSurvey is free software. This version may have been modified pursuant 10* to the GNU General Public License, and as distributed it includes or 11* is derivative of works licensed under the GNU General Public License or 12* other free or open source software licenses. 13* See COPYRIGHT.php for copyright notices and details. 14*/ 15Yii::import('application.helpers.sanitize_helper', true); 16 17 18/** 19 * Translation helper function 20 * @param string $sToTranslate 21 * @param string $sEscapeMode Valid values are html (this is the default, js and unescaped) 22 * @param string $sLanguage 23 * @return mixed|string 24 */ 25function gT($sToTranslate, $sEscapeMode = 'html', $sLanguage = null) 26{ 27 return quoteText(Yii::t('', $sToTranslate, array(), null, $sLanguage), $sEscapeMode); 28} 29 30/** 31 * Translation helper function which outputs right away. 32 * @param string $sToTranslate 33 * @param string $sEscapeMode 34 */ 35function eT($sToTranslate, $sEscapeMode = 'html') 36{ 37 echo gT($sToTranslate, $sEscapeMode); 38} 39 40/** 41 * Translation helper function for plural forms 42 * @param string $sTextToTranslate 43 * @param integer $iCount 44 * @param string $sEscapeMode 45 * @return string 46 */ 47function ngT($sTextToTranslate, $iCount, $sEscapeMode = 'html') 48{ 49 return quoteText(Yii::t('', $sTextToTranslate, $iCount), $sEscapeMode); 50} 51 52/** 53 * Translation helper function for plural forms which outputs right away 54 * @param string $sToTranslate 55 * @param integer $iCount 56 * @param string $sEscapeMode 57 */ 58function neT($sToTranslate, $iCount, $sEscapeMode = 'html') 59{ 60 echo ngT($sToTranslate, $iCount, $sEscapeMode); 61} 62 63 64/** 65 * Quotes a translation according to purpose 66 * if sEscapeMode is null, we use HTML method because probably we had to specify null as sEscapeMode upstream 67 * 68 * @param mixed $sText Text to quote 69 * @param string $sEscapeMode Optional - One of the values 'html','js' or 'unescaped' - defaults to 'html' 70 * @return mixed|string 71 */ 72function quoteText($sText, $sEscapeMode = 'html') 73{ 74 if ($sEscapeMode === null) { 75 $sEscapeMode = 'html'; 76 } 77 78 switch ($sEscapeMode) { 79 case 'html': 80 return HTMLEscape($sText); 81 case 'js': 82 return javascriptEscape($sText); 83 case 'json': 84 return jsonEscape($sText); 85 case 'unescaped': 86 return $sText; 87 default: 88 return "Unsupported EscapeMode in gT method"; 89 } 90} 91 92/** 93* getQuestionTypeList() Returns list of question types available in LimeSurvey. Edit this if you are adding a new 94* question type 95* 96* @param string $SelectedCode Value of the Question Type (defaults to "T") 97* @param string $ReturnType Type of output from this function (defaults to selector) 98* @param string $language Language for translation 99* 100* @return array|string depending on $ReturnType param, returns a straight "array" of question types, or an <option></option> list 101* 102* Explanation of questiontype array: 103* 104* description : Question description 105* subquestions : 0= Does not support subquestions x=Number of subquestion scales 106* answerscales : 0= Does not need answers x=Number of answer scales (usually 1, but e.g. for dual scale question set to 2) 107* assessable : 0=Does not support assessment values when editing answerd 1=Support assessment values 108*/ 109function getQuestionTypeList($SelectedCode = "T", $ReturnType = "selector", $sLanguage=null) 110{ 111 112 $qtypes = Question::typeList($sLanguage); 113 114 if ($ReturnType == "array") { 115 return $qtypes; 116 } 117 118 119 if ($ReturnType == "group") { 120 $newqType = []; 121 foreach ($qtypes as $qkey => $qtype) { 122 $newqType[$qtype['group']][$qkey] = $qtype; 123 } 124 125 126 $qtypeselecter = ""; 127 foreach ($newqType as $group => $members) { 128 $qtypeselecter .= '<optgroup label="'.$group.'">'; 129 foreach ($members as $TypeCode => $TypeProperties) { 130 $qtypeselecter .= "<option value='$TypeCode'"; 131 if ($SelectedCode == $TypeCode) { 132 $qtypeselecter .= " selected='selected'"; 133 } 134 $qtypeselecter .= ">{$TypeProperties['description']}</option>\n"; 135 } 136 $qtypeselecter .= '</optgroup>'; 137 } 138 139 return $qtypeselecter; 140 }; 141 $qtypeselecter = ""; 142 foreach ($qtypes as $TypeCode => $TypeProperties) { 143 $qtypeselecter .= "<option value='$TypeCode'"; 144 if ($SelectedCode == $TypeCode) { 145 $qtypeselecter .= " selected='selected'"; 146 } 147 $qtypeselecter .= ">{$TypeProperties['description']}</option>\n"; 148 } 149 150 151 return $qtypeselecter; 152} 153 154/** 155* isStandardTemplate returns true if a template is a standard template 156* This function does not check if a template actually exists 157* 158* @param mixed $sTemplateName template name to look for 159* @return bool True if standard template, otherwise false 160*/ 161function isStandardTemplate($sTemplateName) 162{ 163 return Template::isStandardTemplate($sTemplateName); 164} 165 166/** 167* getSurveyList() Queries the database (survey table) for a list of existing surveys 168* 169* @param boolean $bReturnArray If set to true an array instead of an HTML option list is given back 170* @return string|array This string is returned containing <option></option> formatted list of existing surveys 171* 172*/ 173function getSurveyList($bReturnArray = false) 174{ 175 static $cached = null; 176 $bCheckIntegrity = false; 177 $timeadjust = getGlobalSetting('timeadjust'); 178 App()->setLanguage((isset(Yii::app()->session['adminlang']) ? Yii::app()->session['adminlang'] : 'en')); 179 $surveynames = array(); 180 181 if (is_null($cached)) { 182 $surveyidresult = Survey::model() 183 ->permission(Yii::app()->user->getId()) 184 ->with('languagesettings') 185 ->findAll(); 186 foreach ($surveyidresult as $result) { 187 $surveynames[] = array_merge($result->attributes, $result->languagesettings[$result->language]->attributes); 188 } 189 190 usort($surveynames, function($a, $b) 191 { 192 return strcmp($a['surveyls_title'], $b['surveyls_title']); 193 }); 194 $cached = $surveynames; 195 } else { 196 $surveynames = $cached; 197 } 198 $surveyselecter = ""; 199 if ($bReturnArray === true) { 200 return $surveynames; 201 } 202 $activesurveys = ''; 203 $inactivesurveys = ''; 204 $expiredsurveys = ''; 205 foreach ($surveynames as $sv) { 206 207 $surveylstitle = flattenText($sv['surveyls_title']); 208 if (strlen($surveylstitle) > 70) { 209 $surveylstitle = htmlspecialchars(mb_strcut(html_entity_decode($surveylstitle, ENT_QUOTES, 'UTF-8'), 0, 70, 'UTF-8'))."..."; 210 } 211 212 if ($sv['active'] != 'Y') { 213 $inactivesurveys .= "<option "; 214 if (Yii::app()->user->getId() == $sv['owner_id']) { 215 $inactivesurveys .= " class='mysurvey emphasis'"; 216 } 217 $inactivesurveys .= " value='{$sv['sid']}'>{$surveylstitle}</option>\n"; 218 } elseif ($sv['expires'] != '' && $sv['expires'] < dateShift((string) date("Y-m-d H:i:s"), "Y-m-d H:i:s", $timeadjust)) { 219 $expiredsurveys .= "<option "; 220 if (Yii::app()->user->getId() == $sv['owner_id']) { 221 $expiredsurveys .= " class='mysurvey emphasis'"; 222 } 223 $expiredsurveys .= " value='{$sv['sid']}'>{$surveylstitle}</option>\n"; 224 } else { 225 $activesurveys .= "<option "; 226 if (Yii::app()->user->getId() == $sv['owner_id']) { 227 $activesurveys .= " class='mysurvey emphasis'"; 228 } 229 $activesurveys .= " value='{$sv['sid']}'>{$surveylstitle}</option>\n"; 230 } 231 } // End Foreach 232 233 //Only show each activesurvey group if there are some 234 if ($activesurveys != '') { 235 $surveyselecter .= "<optgroup label='".gT("Active")."' class='activesurveyselect'>\n"; 236 $surveyselecter .= $activesurveys."</optgroup>"; 237 } 238 if ($expiredsurveys != '') { 239 $surveyselecter .= "<optgroup label='".gT("Expired")."' class='expiredsurveyselect'>\n"; 240 $surveyselecter .= $expiredsurveys."</optgroup>"; 241 } 242 if ($inactivesurveys != '') { 243 $surveyselecter .= "<optgroup label='".gT("Inactive")."' class='inactivesurveyselect'>\n"; 244 $surveyselecter .= $inactivesurveys."</optgroup>"; 245 } 246 $surveyselecter = "<option selected='selected' value=''>".gT("Please choose...")."</option>\n".$surveyselecter; 247 return $surveyselecter; 248} 249 250function getTemplateList() 251{ 252 return Template::getTemplateList(); 253} 254 255 256/** 257* getGidPrevious() returns the Gid of the group prior to the current active group 258* 259* @param integer $surveyid 260* @param integer $gid 261* 262* @return integer|string The GID of the previous group or blank string if no group 263*/ 264function getGidPrevious($surveyid, $gid) 265{ 266 $surveyid = (int) $surveyid; 267 $s_lang = Survey::model()->findByPk($surveyid)->language; 268 $qresult = QuestionGroup::model()->findAllByAttributes(array('sid' => $surveyid, 'language' => $s_lang), array('order'=>'group_order')); 269 270 $i = 0; 271 $iPrev = -1; 272 foreach ($qresult as $qrow) { 273 $qrow = $qrow->attributes; 274 if ($gid == $qrow['gid']) {$iPrev = $i - 1; } 275 $i += 1; 276 } 277 278 if ($iPrev >= 0) {$GidPrev = $qresult[$iPrev]->gid; } else {$GidPrev = ""; } 279 return $GidPrev; 280} 281 282 283/** 284* getGidNext() returns the Gid of the group next to the current active group 285* 286* @param integer $surveyid 287* @param integer $gid 288* 289* @return integer|string The Gid of the next group or blank string if no group 290*/ 291function getGidNext($surveyid, $gid) 292{ 293 $surveyid = (int) $surveyid; 294 $s_lang = Survey::model()->findByPk($surveyid)->language; 295 296 $qresult = QuestionGroup::model()->findAllByAttributes(array('sid' => $surveyid, 'language' => $s_lang), array('order'=>'group_order')); 297 298 $i = 0; 299 $iNext = 0; 300 301 foreach ($qresult as $qrow) { 302 $qrow = $qrow->attributes; 303 if ($gid == $qrow['gid']) { 304 $iNext = $i + 1; 305 } 306 $i += 1; 307 } 308 309 if ($iNext < count($qresult)) { 310 $GidNext = $qresult[$iNext]->gid; 311 } else { 312 $GidNext = ""; 313 } 314 return $GidNext; 315} 316 317 318/** 319 * convertGETtoPOST a function to create a post Request from get parameters 320 * !!! This functions result has to be wrappen in singlequotes! 321 * 322 * @param String $url | The complete url with all parameters 323 * @return String | The onclick action for the element 324 */ 325function convertGETtoPOST($url) 326{ 327 // This function must be deprecated and replaced by $.post 328 $url = preg_replace('/&/i', '&', $url); 329 $stack = explode('?', $url); 330 $calledscript = array_shift($stack); 331 $query = array_shift($stack); 332 $aqueryitems = explode('&', $query); 333 $postArray = []; 334 $getArray = []; 335 foreach ($aqueryitems as $queryitem) { 336 $stack = explode('=', $queryitem); 337 $paramname = array_shift($stack); 338 $value = array_shift($stack); 339 if(in_array($paramname,array(Yii::app()->getComponent('urlManager')->routeVar))) { 340 $getArray[$paramname] = $value; 341 } else { 342 $postArray[$paramname] = $value; 343 } 344 } 345 if(!empty($getArray)) { 346 $calledscript = $calledscript."?".implode('&', array_map( 347 function ($v, $k) { 348 return $k.'='.$v; 349 }, 350 $getArray, 351 array_keys($getArray) 352 )); 353 } 354 $callscript = "window.LS.sendPost(\"".$calledscript."\",\"\",".json_encode($postArray).");"; 355 return $callscript; 356} 357 358 359/** 360* This function calculates how much space is actually used by all files uploaded 361* using the File Upload question type 362* 363* @returns integer Actual space used in MB 364*/ 365function calculateTotalFileUploadUsage() 366{ 367 global $uploaddir; 368 $sQuery = 'select sid from {{surveys}}'; 369 $oResult = dbExecuteAssoc($sQuery); //checked 370 $aRows = $oResult->readAll(); 371 $iTotalSize = 0.0; 372 foreach ($aRows as $aRow) { 373 $sFilesPath = $uploaddir.'/surveys/'.$aRow['sid'].'/files'; 374 if (file_exists($sFilesPath)) { 375 $iTotalSize += (float) getDirectorySize($sFilesPath); 376 } 377 } 378 return (float) $iTotalSize / 1024 / 1024; 379} 380 381/** 382 * @param string $directory 383 * @return int 384 */ 385function getDirectorySize($directory) 386{ 387 $size = 0; 388 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)) as $file) { 389 $size += $file->getSize(); 390 } 391 return $size; 392} 393 394 395 396/** 397 * Queries the database for the maximum sortorder of a group and returns the next higher one. 398 * 399 * @param integer $surveyid 400 * @return int 401 */ 402function getMaxGroupOrder($surveyid) 403{ 404 $queryResult = QuestionGroup::model()->find(array( 405 'condition' => 'sid = :sid', 406 'params' => array(':sid' => $surveyid), 407 'order' => 'group_order desc', 408 'limit' => '1' 409 )); 410 411 $current_max = !is_null($queryResult) ? $queryResult->group_order : ""; 412 413 if ($current_max !== "") { 414 $current_max += 1; 415 return $current_max; 416 } else { 417 return 0; 418 } 419} 420 421 422/** 423* getGroupOrder($surveyid,$gid) queries the database for the sortorder of a group. 424* 425* @param mixed $surveyid 426* @param mixed $gid 427* @return mixed 428*/ 429function getGroupOrder($surveyid, $gid) 430{ 431 $s_lang = Survey::model()->findByPk($surveyid)->language; 432 $grporder_result = QuestionGroup::model()->findByAttributes(array('sid' => $surveyid, 'gid' => $gid, 'language' => $s_lang)); //Checked 433 $grporder_row = $grporder_result->attributes; 434 $group_order = $grporder_row['group_order']; 435 if ($group_order == "") { 436 return "0"; 437 } else { 438 return $group_order; 439 } 440} 441 442/** 443* Queries the database for the maximum sort order of a question. 444* 445* @param integer $gid 446* @param integer|null $surveyid 447* @return integer 448*/ 449function getMaxQuestionOrder($gid, $surveyid) 450{ 451 $gid = (int) $gid; 452 $s_lang = Survey::model()->findByPk($surveyid)->language; 453 $max_sql = "SELECT max( question_order ) AS max FROM {{questions}} WHERE gid='{$gid}' AND language='{$s_lang}'"; 454 $max_result = Yii::app()->db->createCommand($max_sql)->query(); //Checked 455 $maxrow = $max_result->read(); 456 $current_max = $maxrow['max']; 457 if ($current_max == "") { 458 return 0; 459 } else { 460 return (int) $current_max; 461 } 462} 463 464/** 465* getQuestionClass() returns a class name for a given question type to allow custom styling for each question type. 466* 467* @param string $input containing unique character representing each question type. 468* @return string containing the class name for a given question type. 469*/ 470function getQuestionClass($input) 471{ 472 Question::getQuestionClass($input); 473}; 474 475/** 476* setupColumns() defines all the html tags to be wrapped around 477* various list type answers. 478* 479* @param integer $columns - the number of columns, usually supplied by $dcols 480* @param integer $answer_count - the number of answers to a question, usually supplied by $anscount 481* @param string $wrapperclass - a global class for the wrapper 482* @param string $itemclass - a class for the item 483* @return array with all the various opening and closing tags to generate a set of columns. 484* 485* It returns an array with the following items: 486* $wrapper['whole-start'] = Opening wrapper for the whole list 487* $wrapper['whole-end'] = closing wrapper for the whole list 488* $wrapper['col-devide'] = normal column devider 489* $wrapper['col-devide-last'] = the last column devider (to allow 490* for different styling of the last 491* column 492* $wrapper['item-start'] = opening wrapper tag for individual 493* option 494* $wrapper['item-start-other'] = opening wrapper tag for other 495* option 496* $wrapper['item-start-noanswer'] = opening wrapper tag for no answer 497* option 498* $wrapper['item-end'] = closing wrapper tag for individual 499* option 500* $wrapper['maxrows'] = maximum number of rows in each 501* column 502* $wrapper['cols'] = Number of columns to be inserted 503* (and checked against) 504* 505* 506* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 507* Columns are a problem. 508* Really there is no perfect solution to columns at the moment. 509* 510* - Using Tables is problematic semanticly. 511* - Using inline or float to create columns, causes the answers 512* flows horizontally, not vertically which is not ideal visually. 513* - Using CSS3 columns is also a problem because of browser support 514* and also because if you have answeres split across two or more 515* lines, and those answeres happen to fall at the bottom of a 516* column, the answer might be split across columns as well as 517* lines. 518* - Using nested unordered list with the first level of <LI>s 519* floated is the same as using tables and so is bad semantically 520* for the same reason tables are bad. 521* - Breaking the unordered lists into consecutive floated unordered 522* lists is not great semantically but probably not as bad as 523* using tables. 524* 525* Because I haven't been able to decide which option is the least 526* bad, I have handed over that responsibility to the admin who sets 527* LimeSurvey up on their server. 528* 529* There are four options: 530* 'css' using one of the various CSS only methods for 531* rendering columns. 532* (Check the CSS file for your chosen template to see 533* how columns are defined.) 534* 'ul' using multiple floated unordered lists. (DEFAULT) 535* 'table' using conventional tables based layout. 536* NULL blocks the use of columns 537* 538* 'ul' is the default because it's the best possible compromise 539* between semantic markup and visual layout. 540*/ 541function setupColumns($columns, $answer_count, $wrapperclass = "", $itemclass = "") 542{ 543 544 $column_style = Yii::app()->getConfig('column_style'); 545 if (!in_array($column_style, array('css', 'ul', 'table')) && !is_null($column_style)) { 546 $column_style = 'ul'; 547 }; 548 if (!is_null($column_style) && $columns != 1) { 549// Add a global class for all column 550 $wrapperclass .= " colstyle-{$column_style}"; 551 } 552 if ($columns < 2) { 553 $column_style = null; 554 $columns = 1; 555 } 556 557 if (($columns > $answer_count) && $answer_count > 0) { 558 $columns = $answer_count; 559 }; 560 561 562 $class_first = ' class="'.$wrapperclass.'"'; 563 if ($columns > 1 && !is_null($column_style)) { 564 if ($column_style == 'ul') { 565 $ul = '-ul'; 566 } else { 567 $ul = ''; 568 } 569 $class_first = ' class="'.$wrapperclass.' cols-'.$columns.$ul.' first"'; 570 $class = ' class="'.$wrapperclass.' cols-'.$columns.$ul.'"'; 571 $class_last_ul = ' class="'.$wrapperclass.' cols-'.$columns.$ul.' last"'; 572 $class_last_table = ' class="'.$wrapperclass.' cols-'.$columns.' last"'; 573 } else { 574 $class = ' class="'.$wrapperclass.'"'; 575 $class_last_ul = ' class="'.$wrapperclass.'"'; 576 $class_last_table = ' class="'.$wrapperclass.'"'; 577 }; 578 579 $wrapper = array( 580 'whole-start' => "\n<ul$class_first>\n" 581 ,'whole-end' => "</ul>\n" 582 ,'col-devide' => '' 583 ,'col-devide-last' => '' 584 ,'item-start' => "\t<li class=\"{$itemclass}\">\n" 585 ,'item-start-other' => "\t<li class=\"{$itemclass} other other-item\">\n" 586 ,'item-start-noanswer' => "\t<li class=\"{$itemclass} noanswer-item\">\n" 587 ,'item-end' => "\t</li>\n" 588 ,'maxrows' => ceil($answer_count / $columns) //Always rounds up to nearest whole number 589 ,'cols' => $columns 590 ); 591 592 switch ($column_style) { 593 case 'ul': if ($columns > 1) { 594 $wrapper['col-devide'] = "\n</ul>\n\n<ul$class>\n"; 595 $wrapper['col-devide-last'] = "\n</ul>\n\n<ul$class_last_ul>\n"; 596 } 597 break; 598 599 case 'table': $table_cols = ''; 600 for ($cols = $columns; $cols > 0; --$cols) { 601 switch ($cols) { 602 case $columns: $table_cols .= "\t<col$class_first />\n"; 603 break; 604 case 1: $table_cols .= "\t<col$class_last_table />\n"; 605 break; 606 default: $table_cols .= "\t<col$class />\n"; 607 }; 608 }; 609 610 if ($columns > 1) { 611 $wrapper['col-devide'] = "\t</ul>\n</td>\n\n<td>\n\t<ul>\n"; 612 $wrapper['col-devide-last'] = "\t</ul>\n</td>\n\n<td class=\"last\">\n\t<ul>\n"; 613 }; 614 $wrapper['whole-start'] = "\n<table$class>\n$table_cols\n\t<tbody>\n<tr>\n<td>\n\t<ul>\n"; 615 $wrapper['whole-end'] = "\t</ul>\n</td>\n</tr>\n\t</tbody>\n</table>\n"; 616 $wrapper['item-start'] = "<li class=\"{$itemclass}\">\n"; 617 $wrapper['item-end'] = "</li class=\"{$itemclass}\">\n"; 618 }; 619 620 return $wrapper; 621}; 622 623function alternation($alternate = '', $type = 'col') 624{ 625 /** 626 * alternation() Returns a class identifyer for alternating between 627 * two options. Used to style alternate elements differently. creates 628 * or alternates between the odd string and the even string used in 629 * as column and row classes for array type questions. 630 * 631 * @param string $alternate = '' (empty) (default) , 'array2' , 'array1' , 'odd' , 'even' 632 * @param string $type = 'col' (default) or 'row' 633 * 634 * @return string representing either the first alternation or the opposite alternation to the one supplied.. 635 */ 636 /* 637 // The following allows type to be left blank for row in subsequent 638 // function calls. 639 // It has been left out because 'row' must be defined the first time 640 // alternation() is called. Since it is only ever written once for each 641 // while statement within a function, 'row' is always defined. 642 if(!empty($alternate) && $type != 'row') 643 { if($alternate == ('array2' || 'array1')) 644 { 645 $type = 'row'; 646 }; 647 }; 648 // It has been left in case it becomes useful but probably should be 649 // removed. 650 */ 651 if ($type == 'row') { 652// Row is sub question OR Y Axis subquestion : it must be column for array by column 653 $odd = 'ls-odd'; 654 $even = 'ls-even'; 655 } else { 656// cols is answers part OR X axis subquestion : it must the row in array by column 657 $odd = 'ls-col-odd'; 658 $even = 'ls-col-even'; 659 }; 660 if ($alternate == $odd) { 661 $alternate = $even; 662 } else { 663 $alternate = $odd; 664 }; 665 return $alternate; 666} 667 668 669/** 670* longestString() returns the length of the longest string past to it. 671* @peram string $new_string 672* @peram integer $longest_length length of the (previously) longest string passed to it. 673* @param integer $longest_length 674* @return integer representing the length of the longest string passed (updated if $new_string was longer than $longest_length) 675* 676* usage should look like this: $longest_length = longestString( $new_string , $longest_length ); 677* 678*/ 679function longestString($new_string, $longest_length) 680{ 681 if ($longest_length < strlen(trim(strip_tags($new_string)))) { 682 $longest_length = strlen(trim(strip_tags($new_string))); 683 }; 684 return $longest_length; 685}; 686 687 688 689 690/** 691* getGroupList() queries the database for a list of all groups matching the current survey sid 692* 693* 694* @param string $gid - the currently selected gid/group 695* @param integer $surveyid 696* 697* @return string string is returned containing <option></option> formatted list of groups to current survey 698*/ 699function getGroupList($gid, $surveyid) 700{ 701 702 $groupselecter = ""; 703 $gid = sanitize_int($gid); 704 $surveyid = sanitize_int($surveyid); 705 if (!$surveyid) {$surveyid = returnGlobal('sid', true); } 706 $s_lang = Survey::model()->findByPk($surveyid)->language; 707 708 $gidquery = "SELECT gid, group_name FROM ".Yii::app()->db->quoteTableName('{{groups}}')." WHERE sid='{$surveyid}' AND language='{$s_lang}' ORDER BY group_order"; 709 $gidresult = Yii::app()->db->createCommand($gidquery)->query(); //Checked 710 foreach ($gidresult->readAll() as $gv) { 711 $groupselecter .= "<option"; 712 if ($gv['gid'] == $gid) {$groupselecter .= " selected='selected'"; $gvexist = 1; } 713 $groupselecter .= " value='".Yii::app()->getConfig('scriptname')."?sid=$surveyid&gid=".$gv['gid']."'>".htmlspecialchars($gv['group_name'])."</option>\n"; 714 } 715 if ($groupselecter) { 716 if (!isset($gvexist)) {$groupselecter = "<option selected='selected'>".gT("Please choose...")."</option>\n".$groupselecter; } else {$groupselecter .= "<option value='".Yii::app()->getConfig('scriptname')."?sid=$surveyid&gid='>".gT("None")."</option>\n"; } 717 } 718 return $groupselecter; 719} 720 721 722//FIXME rename and/or document this 723function getGroupList3($gid, $surveyid) 724{ 725 // 726 $gid = sanitize_int($gid); 727 $surveyid = sanitize_int($surveyid); 728 729 if (!$surveyid) {$surveyid = returnGlobal('sid', true); } 730 $groupselecter = ""; 731 $s_lang = Survey::model()->findByPk($surveyid)->language; 732 733 734 //$gidquery = "SELECT gid, group_name FROM ".db_table_name('groups')." WHERE sid=$surveyid AND language='{$s_lang}' ORDER BY group_order"; 735 736 $gidresult = QuestionGroup::model()->findAllByAttributes(array('sid' => $surveyid, 'language' => $s_lang), array('order'=>'group_order')); 737 738 foreach ($gidresult as $gv) { 739 $gv = $gv->attributes; 740 $groupselecter .= "<option"; 741 if ($gv['gid'] == $gid) {$groupselecter .= " selected='selected'"; } 742 $groupselecter .= " value='".$gv['gid']."'>".htmlspecialchars($gv['group_name'])." (ID:".$gv['gid'].")</option>\n"; 743 } 744 745 746 return $groupselecter; 747} 748 749/** 750 * put your comment there... 751 * 752 * @param mixed $gid 753 * @param mixed $language 754 * @return string 755 */ 756function getGroupListLang($gid, $language, $surveyid) 757{ 758 $groupselecter = ""; 759 if (!$surveyid) {$surveyid = returnGlobal('sid', true); } 760 761 $gidresult = QuestionGroup::model()->findAll(array('condition'=>'sid=:surveyid AND language=:language', 762 'order'=>'group_order', 763 'params'=>array(':surveyid'=>$surveyid, ':language'=>$language))); //Checked) 764 foreach ($gidresult as $gv) { 765 $gv = $gv->attributes; 766 $groupselecter .= "<option"; 767 if ($gv['gid'] == $gid) {$groupselecter .= " selected='selected'"; $gvexist = 1; } 768 $link = Yii::app()->getController()->createUrl("/admin/questiongroups/sa/view/surveyid/".$surveyid."/gid/".$gv['gid']); 769 $groupselecter .= " value='{$link}'>"; 770 if (strip_tags($gv['group_name'])) { 771 $groupselecter .= htmlspecialchars(strip_tags($gv['group_name'])); 772 } else { 773 $groupselecter .= htmlspecialchars($gv['group_name']); 774 } 775 $groupselecter .= "</option>\n"; 776 } 777 if ($groupselecter) { 778 $link = Yii::app()->getController()->createUrl("/admin/survey/sa/view/surveyid/".$surveyid); 779 if (!isset($gvexist)) {$groupselecter = "<option selected='selected'>".gT("Please choose...")."</option>\n".$groupselecter; } else {$groupselecter .= "<option value='{$link}'>".gT("None")."</option>\n"; } 780 } 781 return $groupselecter; 782} 783 784 785function getUserList($outputformat = 'fullinfoarray') 786{ 787 if (!empty(Yii::app()->session['loginID'])) { 788 $myuid = sanitize_int(Yii::app()->session['loginID']); 789 } 790 $usercontrolSameGroupPolicy = Yii::app()->getConfig('usercontrolSameGroupPolicy'); 791 if (!Permission::model()->hasGlobalPermission('superadmin', 'read') && isset($usercontrolSameGroupPolicy) && 792 $usercontrolSameGroupPolicy == true) { 793 if (isset($myuid)) { 794 $sDatabaseType = Yii::app()->db->getDriverName(); 795 if ($sDatabaseType == 'mssql' || $sDatabaseType == "sqlsrv" || $sDatabaseType == "dblib") { 796 $sSelectFields = 'users_name,uid,email,full_name,parent_id,CAST(password as varchar) as password'; 797 } else { 798 $sSelectFields = 'users_name,uid,email,full_name,parent_id,password'; 799 } 800 801 // List users from same group as me + all my childs 802 // a subselect is used here because MSSQL does not like to group by text 803 // also Postgres does like this one better 804 $uquery = " SELECT {$sSelectFields} from {{users}} where uid in ( 805 SELECT uid from {{user_in_groups}} where ugid in ( 806 SELECT ugid from {{user_in_groups}} where uid={$myuid} 807 ) 808 ) 809 UNION 810 SELECT {$sSelectFields} from {{users}} v where v.parent_id={$myuid} 811 UNION 812 SELECT {$sSelectFields} from {{users}} v where uid={$myuid}"; 813 814 } else { 815 return array(); // Or die maybe 816 } 817 818 } else { 819 $uquery = "SELECT * FROM {{users}} ORDER BY uid"; 820 } 821 822 $uresult = Yii::app()->db->createCommand($uquery)->query()->readAll(); //Checked 823 824 if (count($uresult) == 0 && !empty($myuid)) { 825//user is not in a group and usercontrolSameGroupPolicy is activated - at least show his own userinfo 826 $uquery = "SELECT u.* FROM {{users}} AS u WHERE u.uid=".$myuid; 827 $uresult = Yii::app()->db->createCommand($uquery)->query()->readAll(); //Checked 828 } 829 830 $userlist = array(); 831 $userlist[0] = "Reserved for logged in user"; 832 foreach ($uresult as $srow) { 833 if ($outputformat != 'onlyuidarray') { 834 if ($srow['uid'] != Yii::app()->session['loginID']) { 835 $userlist[] = array("user"=>$srow['users_name'], "uid"=>$srow['uid'], "email"=>$srow['email'], "password"=>$srow['password'], "full_name"=>$srow['full_name'], "parent_id"=>$srow['parent_id']); 836 } else { 837 $userlist[0] = array("user"=>$srow['users_name'], "uid"=>$srow['uid'], "email"=>$srow['email'], "password"=>$srow['password'], "full_name"=>$srow['full_name'], "parent_id"=>$srow['parent_id']); 838 } 839 } else { 840 if ($srow['uid'] != Yii::app()->session['loginID']) { 841 $userlist[] = $srow['uid']; 842 } else { 843 $userlist[0] = $srow['uid']; 844 } 845 } 846 847 } 848 return $userlist; 849} 850 851 852/** 853* Gets all survey infos in one big array including the language specific settings 854* 855* @param integer $surveyid The survey ID 856* @param string $languagecode The language code - if not given the base language of the particular survey is used 857* @return array|bool Returns array with survey info or false, if survey does not exist 858*/ 859function getSurveyInfo($surveyid, $languagecode = '') 860{ 861 static $staticSurveyInfo = array(); // Use some static 862 $surveyid = sanitize_int($surveyid); 863 $languagecode = sanitize_languagecode($languagecode); 864 $thissurvey = false; 865 $oSurvey = Survey::model()->findByPk($surveyid); 866 // Do job only if this survey exist 867 if (!$oSurvey) { 868 return false; 869 } 870 // if no language code is set then get the base language one 871 if ((!isset($languagecode) || $languagecode == '')) { 872 $languagecode = Survey::model()->findByPk($surveyid)->language; 873 } 874 875 if (isset($staticSurveyInfo[$surveyid][$languagecode])) { 876 $thissurvey = $staticSurveyInfo[$surveyid][$languagecode]; 877 } else { 878 $result = SurveyLanguageSetting::model()->with('survey')->findByPk(array('surveyls_survey_id' => $surveyid, 'surveyls_language' => $languagecode)); 879 if (is_null($result)) { 880 // When additional language was added, but not saved it does not exists 881 // We should revert to the base language then 882 $languagecode = Survey::model()->findByPk($surveyid)->language; 883 $result = SurveyLanguageSetting::model()->with('survey')->findByPk(array('surveyls_survey_id' => $surveyid, 'surveyls_language' => $languagecode)); 884 } 885 if ($result) { 886 $thissurvey = array_merge($result->survey->attributes, $result->attributes); 887 $thissurvey['name'] = $thissurvey['surveyls_title']; 888 $thissurvey['description'] = $thissurvey['surveyls_description']; 889 $thissurvey['welcome'] = $thissurvey['surveyls_welcometext']; 890 $thissurvey['datasecurity_notice_label'] = $thissurvey['surveyls_policy_notice_label']; 891 $thissurvey['datasecurity_error'] = $thissurvey['surveyls_policy_error']; 892 $thissurvey['datasecurity_notice'] = $thissurvey['surveyls_policy_notice']; 893 $thissurvey['templatedir'] = $thissurvey['template']; 894 $thissurvey['adminname'] = $thissurvey['admin']; 895 $thissurvey['tablename'] = $oSurvey->responsesTableName; 896 $thissurvey['urldescrip'] = $thissurvey['surveyls_urldescription']; 897 $thissurvey['url'] = $thissurvey['surveyls_url']; 898 $thissurvey['expiry'] = $thissurvey['expires']; 899 $thissurvey['email_invite_subj'] = $thissurvey['surveyls_email_invite_subj']; 900 $thissurvey['email_invite'] = $thissurvey['surveyls_email_invite']; 901 $thissurvey['email_remind_subj'] = $thissurvey['surveyls_email_remind_subj']; 902 $thissurvey['email_remind'] = $thissurvey['surveyls_email_remind']; 903 $thissurvey['email_confirm_subj'] = $thissurvey['surveyls_email_confirm_subj']; 904 $thissurvey['email_confirm'] = $thissurvey['surveyls_email_confirm']; 905 $thissurvey['email_register_subj'] = $thissurvey['surveyls_email_register_subj']; 906 $thissurvey['email_register'] = $thissurvey['surveyls_email_register']; 907 $thissurvey['attributedescriptions'] = $result->survey->tokenAttributes; 908 $thissurvey['attributecaptions'] = $result->attributeCaptions; 909 if (!isset($thissurvey['adminname'])) {$thissurvey['adminname'] = Yii::app()->getConfig('siteadminemail'); } 910 if (!isset($thissurvey['adminemail'])) {$thissurvey['adminemail'] = Yii::app()->getConfig('siteadminname'); } 911 if (!isset($thissurvey['urldescrip']) || $thissurvey['urldescrip'] == '') {$thissurvey['urldescrip'] = $thissurvey['surveyls_url']; } 912 913 $thissurvey['owner_username'] = $result->survey->ownerUserName; 914 915 $staticSurveyInfo[$surveyid][$languagecode] = $thissurvey; 916 } 917 918 } 919 $thissurvey['oSurvey'] = $oSurvey; 920 return $thissurvey; 921} 922 923/** 924* Returns the default email template texts as array 925* 926* @param mixed $sLanguage Required language translationb object 927* @param string $mode Escape mode for the translation function 928* @return array 929 * // TODO move to template model 930*/ 931function templateDefaultTexts($sLanguage, $mode = 'html', $sNewlines = 'text') 932{ 933 934 $aDefaultTexts = LsDefaultDataSets::getTemplateDefaultTexts($mode, $sLanguage); 935 936 if ($sNewlines == 'html') { 937 $aDefaultTexts = array_map('nl2br', $aDefaultTexts); 938 } 939 940 return $aDefaultTexts; 941} 942 943/** 944* Compares two elements from an array (passed by the usort function) 945* and returns -1, 0 or 1 depending on the result of the comparison of 946* the sort order of the group_order and question_order field 947* 948* @param mixed $a 949* @param mixed $b 950* @return int 951*/ 952function groupOrderThenQuestionOrder($a, $b) 953{ 954 if (isset($a['group_order']) && isset($b['group_order'])) { 955 $GroupResult = strnatcasecmp($a['group_order'], $b['group_order']); 956 } else { 957 $GroupResult = ""; 958 } 959 if ($GroupResult == 0) { 960 $TitleResult = strnatcasecmp($a["question_order"], $b["question_order"]); 961 return $TitleResult; 962 } 963 return $GroupResult; 964} 965 966 967//FIXME insert UestionGroup model to here 968/** 969 * Shifts the sortorder for questions, creating extra spaces at the start of the group 970 * This is an alias for updateQuestionOrder() 971 * 972 * @param integer $sid SID is not needed anymore, but is left here for backward compatibility 973 * @param integer $gid 974 * @param integer $shiftvalue 975 * 976 * @return void 977 */ 978function shiftOrderQuestions($sid, $gid, $shiftvalue) 979{ 980 $gid = (int) $gid; 981 $shiftvalue = (int) $shiftvalue; 982 983 Question::model()->updateQuestionOrder($gid, $shiftvalue); 984} 985 986/** 987 * Rewrites the sortorder for groups 988 * 989 * @param integer $surveyid 990 * 991 * @return void 992 */ 993function fixSortOrderGroups($surveyid) 994{ 995 $baselang = Survey::model()->findByPk($surveyid)->language; 996 QuestionGroup::model()->updateGroupOrder($surveyid, $baselang); 997} 998 999/** 1000 * @param integer $iSurveyID 1001 * @param integer $qid 1002 * @param integer $newgid 1003 */ 1004function fixMovedQuestionConditions($qid, $oldgid, $newgid, $iSurveyID = null) //Function rewrites the cfieldname for a question after group change 1005{ 1006 if (!isset($iSurveyID)) { 1007 $iSurveyID = Yii::app()->getConfig('sid'); 1008 } 1009 $qid = (int) $qid; 1010 $oldgid = (int) $oldgid; 1011 $newgid = (int) $newgid; 1012 Condition::model()->updateCFieldName($iSurveyID, $qid, $oldgid, $newgid); 1013 // TMSW Condition->Relevance: Call LEM->ConvertConditionsToRelevance() when done 1014} 1015 1016 1017/** 1018 * This function returns POST/REQUEST vars, for some vars like SID and others they are also sanitized 1019 * TODO: extends Yii:getParam 1020 * 1021 * @param string $stringname 1022 * @param boolean $bRestrictToString 1023 * @return array|bool|mixed|int|null 1024 */ 1025function returnGlobal($stringname, $bRestrictToString = false) 1026{ 1027 $urlParam = Yii::app()->request->getParam($stringname); 1028 $aCookies = Yii::app()->request->getCookies(); 1029 if (is_null($urlParam) && $stringname != 'sid') { 1030 if (isset($aCookies[$stringname])) { 1031 $urlParam = $aCookies[$stringname]; 1032 } 1033 } 1034 $bUrlParamIsArray = is_array($urlParam); // Needed to array map or if $bRestrictToString 1035 if (!is_null($urlParam) && $stringname != '' && (!$bUrlParamIsArray || !$bRestrictToString)) { 1036 if ($stringname == 'sid' || $stringname == "gid" || $stringname == "oldqid" || 1037 $stringname == "qid" || $stringname == "tid" || 1038 $stringname == "lid" || $stringname == "ugid" || 1039 $stringname == "thisstep" || $stringname == "scenario" || 1040 $stringname == "cqid" || $stringname == "cid" || 1041 $stringname == "qaid" || $stringname == "scid") { 1042 if ($bUrlParamIsArray) { 1043 return array_map("sanitize_int", $urlParam); 1044 } else { 1045 return sanitize_int($urlParam); 1046 } 1047 } elseif ($stringname == "lang" || $stringname == "adminlang") { 1048 if ($bUrlParamIsArray) { 1049 return array_map("sanitize_languagecode", $urlParam); 1050 } else { 1051 return sanitize_languagecode($urlParam); 1052 } 1053 } elseif ($stringname == "htmleditormode" || 1054 $stringname == "subaction" || 1055 $stringname == "questionselectormode" || 1056 $stringname == "templateeditormode" 1057 ) { 1058 if ($bUrlParamIsArray) { 1059 return array_map("sanitize_paranoid_string", $urlParam); 1060 } else { 1061 return sanitize_paranoid_string($urlParam); 1062 } 1063 } elseif ($stringname == "cquestions") { 1064 if ($bUrlParamIsArray) { 1065 return array_map("sanitize_cquestions", $urlParam); 1066 } else { 1067 return sanitize_cquestions($urlParam); 1068 } 1069 } 1070 return $urlParam; 1071 } else { 1072 return null; 1073 } 1074} 1075 1076 1077function sendCacheHeaders() 1078{ 1079 if (!headers_sent()) { 1080 if (Yii::app()->getConfig('x_frame_options', 'allow') == 'sameorigin') { 1081 header('X-Frame-Options: SAMEORIGIN'); 1082 } 1083 header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); // this line lets IE7 run LimeSurvey in an iframe 1084 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past 1085 header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); // always modified 1086 header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 1087 header('Content-Type: text/html; charset=utf-8'); 1088 } 1089} 1090 1091/** 1092* @param integer $iSurveyID The Survey ID 1093* @param string $sFieldCode Field code of the particular field 1094* @param string $sValue The stored response value 1095* @param string $sLanguage Initialized limesurvey_lang object for the resulting response data 1096* @return string 1097*/ 1098function getExtendedAnswer($iSurveyID, $sFieldCode, $sValue, $sLanguage) 1099{ 1100 1101 if ($sValue == null || $sValue == '') { 1102 return ''; 1103 } 1104 $survey = Survey::model()->findByPk($iSurveyID); 1105 //Fieldcode used to determine question, $sValue used to match against answer code 1106 //Returns NULL if question type does not suit 1107 if (strpos($sFieldCode, "{$iSurveyID}X") === 0) { 1108 //Only check if it looks like a real fieldcode 1109 $fieldmap = createFieldMap($survey, 'short', false, false, $sLanguage); 1110 if (isset($fieldmap[$sFieldCode])) { 1111 $fields = $fieldmap[$sFieldCode]; 1112 } else { 1113 return ''; 1114 } 1115 1116 // If it is a comment field there is nothing to convert here 1117 if ($fields['aid'] == 'comment') { 1118 return $sValue; 1119 } 1120 1121 //Find out the question type 1122 $this_type = $fields['type']; 1123 switch ($this_type) { 1124 case 'D': 1125 if (trim($sValue) != '') { 1126 $qidattributes = QuestionAttribute::model()->getQuestionAttributes($fields['qid']); 1127 $dateformatdetails = getDateFormatDataForQID($qidattributes, $iSurveyID); 1128 $sValue = convertDateTimeFormat($sValue, "Y-m-d H:i:s", $dateformatdetails['phpdate']); 1129 } 1130 break; 1131 case 'K': 1132 case 'N': 1133 // Fix the value : Value is stored as decimal in SQL 1134 if (trim($sValue) != '') { 1135 if($sValue[0] === ".") { 1136 // issue #15685 mssql SAVE 0.01 AS .0100000000, set it at 0.0100000000 1137 $sValue = "0".$sValue; 1138 } 1139 if (strpos($sValue, ".") !== false) { 1140 $sValue = rtrim(rtrim($sValue, "0"), "."); 1141 } 1142 } 1143 break; 1144 case "L": 1145 case "!": 1146 case "O": 1147 case "^": 1148 case "I": 1149 case "R": 1150 $result = Answer::model()->getAnswerFromCode($fields['qid'], $sValue, $sLanguage); 1151 foreach ($result as $row) { 1152 $this_answer = $row['answer']; 1153 } // while 1154 if ($sValue == "-oth-") { 1155 $this_answer = gT("Other", null, $sLanguage); 1156 } 1157 break; 1158 case "M": 1159 case "J": 1160 case "P": 1161 switch ($sValue) { 1162 case "Y": $this_answer = gT("Yes", null, $sLanguage); break; 1163 } 1164 break; 1165 case "Y": 1166 switch ($sValue) { 1167 case "Y": $this_answer = gT("Yes", null, $sLanguage); break; 1168 case "N": $this_answer = gT("No", null, $sLanguage); break; 1169 default: $this_answer = gT("No answer", null, $sLanguage); 1170 } 1171 break; 1172 case "G": 1173 switch ($sValue) { 1174 case "M": $this_answer = gT("Male", null, $sLanguage); break; 1175 case "F": $this_answer = gT("Female", null, $sLanguage); break; 1176 default: $this_answer = gT("No answer", null, $sLanguage); 1177 } 1178 break; 1179 case "C": 1180 switch ($sValue) { 1181 case "Y": $this_answer = gT("Yes", null, $sLanguage); break; 1182 case "N": $this_answer = gT("No", null, $sLanguage); break; 1183 case "U": $this_answer = gT("Uncertain", null, $sLanguage); break; 1184 } 1185 break; 1186 case "E": 1187 switch ($sValue) { 1188 case "I": $this_answer = gT("Increase", null, $sLanguage); break; 1189 case "D": $this_answer = gT("Decrease", null, $sLanguage); break; 1190 case "S": $this_answer = gT("Same", null, $sLanguage); break; 1191 } 1192 break; 1193 case "F": 1194 case "H": 1195 case "1": 1196 if (isset($fields['scale_id'])) { 1197 $iScaleID = $fields['scale_id']; 1198 } else { 1199 $iScaleID = 0; 1200 } 1201 $result = Answer::model()->getAnswerFromCode($fields['qid'], $sValue, $sLanguage, $iScaleID); 1202 foreach ($result as $row) { 1203 $this_answer = $row['answer']; 1204 } // while 1205 if ($sValue == "-oth-") { 1206 $this_answer = gT("Other", null, $sLanguage); 1207 } 1208 break; 1209 case "|": //File upload 1210 if (substr($sFieldCode, -9) != 'filecount') { 1211 //Show the filename, size, title and comment -- no link! 1212 $files = json_decode($sValue, true); 1213 $sValue = ''; 1214 if (is_array($files)) { 1215 foreach ($files as $file) { 1216 if (!isset($file['title'])) { 1217 $file['title'] = ''; 1218 } 1219 if (!isset($file['comment'])) { 1220 $file['comment'] = ''; 1221 } 1222 $sValue .= rawurldecode($file['name']). 1223 ' ('.round($file['size']).'KB) '. 1224 strip_tags($file['title']); 1225 if (trim(strip_tags($file['comment'])) != "") { 1226 $sValue .= ' - '.strip_tags($file['comment']); 1227 } 1228 1229 } 1230 } 1231 } 1232 break; 1233 default: 1234 ; 1235 } // switch 1236 } 1237 switch ($sFieldCode) { 1238 case 'submitdate': 1239 case 'startdate': 1240 case 'datestamp': 1241 if (trim($sValue) != '') { 1242 $dateformatdetails = getDateFormatDataForQID(null, $iSurveyID); 1243 $sValue = convertDateTimeFormat($sValue, "Y-m-d H:i:s", $dateformatdetails['phpdate'].' H:i:s'); 1244 } 1245 break; 1246 } 1247 if (isset($this_answer)) { 1248 return $this_answer." [$sValue]"; 1249 } else { 1250 return $sValue; 1251 } 1252} 1253 1254/** 1255* Validate an email address - also supports IDN email addresses 1256* @returns True/false for valid/invalid 1257* 1258* @param mixed $sEmailAddress Email address to check 1259*/ 1260function validateEmailAddress($sEmailAddress) 1261{ 1262 require_once(APPPATH.'third_party/idna-convert/idna_convert.class.php'); 1263 $oIdnConverter = new idna_convert(); 1264 $sEmailAddress = $oIdnConverter->encode($sEmailAddress); 1265 $bResult = filter_var($sEmailAddress, FILTER_VALIDATE_EMAIL); 1266 if ($bResult !== false) { 1267 return true; 1268 } 1269 return false; 1270} 1271 1272/** 1273* Validate an list of email addresses - either as array or as semicolon-limited text 1274* @return string List with valid email addresses - invalid email addresses are filtered - false if none of the email addresses are valid 1275* 1276* @param string $aEmailAddressList Email address to check 1277* @returns array 1278*/ 1279function validateEmailAddresses($aEmailAddressList) 1280{ 1281 $aOutList = []; 1282 if (!is_array($aEmailAddressList)) { 1283 $aEmailAddressList = explode(';', $aEmailAddressList); 1284 } 1285 1286 foreach ($aEmailAddressList as $sEmailAddress) { 1287 $sEmailAddress = trim($sEmailAddress); 1288 if (validateEmailAddress($sEmailAddress)) { 1289 $aOutList[] = $sEmailAddress; 1290 } 1291 } 1292 return $aOutList; 1293} 1294 1295/** 1296 * This functions generates a a summary containing the SGQA for questions of a survey, enriched with options per question 1297 * It can be used for the generation of statistics. Derived from Statistics_userController 1298 * @param int $iSurveyID Id of the Survey in question 1299 * @param array $aFilters an array which is the result of a query in Questions model 1300 * @param string $sLanguage 1301 * @return array The summary 1302 */ 1303function createCompleteSGQA($iSurveyID, $aFilters, $sLanguage) 1304{ 1305 $allfields = []; 1306 foreach ($aFilters as $flt) { 1307 Yii::app()->loadHelper("surveytranslator"); 1308 $myfield = "{$iSurveyID}X{$flt['gid']}X{$flt['qid']}"; 1309 $oSurvey = Survey::model()->findByPk($iSurveyID); 1310 $aAdditionalLanguages = array_filter(explode(" ", $oSurvey->additional_languages)); 1311 if (is_null($sLanguage) || !in_array($sLanguage, $aAdditionalLanguages)) { 1312 $sLanguage = $oSurvey->language; 1313 } 1314 switch ($flt['type']) { 1315 case "K": // Multiple Numerical 1316 case "Q": // Multiple Short Text 1317 //get answers 1318 $result = Question::model()->getQuestionsForStatistics('title as code, question as answer', "parent_qid=$flt[qid] AND language = '{$sLanguage}'", 'question_order'); 1319 1320 //go through all the (multiple) answers 1321 foreach ($result as $row) { 1322 $myfield2 = $flt['type'].$myfield.reset($row); 1323 $allfields[] = $myfield2; 1324 } 1325 break; 1326 case "A": // ARRAY OF 5 POINT CHOICE QUESTIONS 1327 case "B": // ARRAY OF 10 POINT CHOICE QUESTIONS 1328 case "C": // ARRAY OF YES\No\gT("Uncertain") QUESTIONS 1329 case "E": // ARRAY OF Increase/Same/Decrease QUESTIONS 1330 case "F": // FlEXIBLE ARRAY 1331 case "H": // ARRAY (By Column) 1332 //get answers 1333 $result = Question::model()->getQuestionsForStatistics('title, question', "parent_qid=$flt[qid] AND language = '{$sLanguage}'", 'question_order'); 1334 1335 //go through all the (multiple) answers 1336 foreach ($result as $row) { 1337 $myfield2 = $myfield.reset($row); 1338 $allfields[] = $myfield2; 1339 } 1340 break; 1341 // all "free text" types (T, U, S) get the same prefix ("T") 1342 case "T": // Long free text 1343 case "U": // Huge free text 1344 case "S": // Short free text 1345 $myfield = "T$myfield"; 1346 $allfields[] = $myfield; 1347 break; 1348 case ";": //ARRAY (Multi Flex) (Text) 1349 case ":": //ARRAY (Multi Flex) (Numbers) 1350 $result = Question::model()->getQuestionsForStatistics('title, question', "parent_qid=$flt[qid] AND language = '{$sLanguage}' AND scale_id = 0", 'question_order'); 1351 1352 foreach ($result as $row) { 1353 $fresult = Question::model()->getQuestionsForStatistics('title, question', "parent_qid=$flt[qid] AND language = '{$sLanguage}' AND scale_id = 1", 'question_order'); 1354 foreach ($fresult as $frow) { 1355 $myfield2 = $myfield.reset($row)."_".$frow['title']; 1356 $allfields[] = $myfield2; 1357 } 1358 } 1359 break; 1360 case "R": //RANKING 1361 //get some answers 1362 $result = Answer::model()->getQuestionsForStatistics('code, answer', "qid=$flt[qid] AND language = '{$sLanguage}'", 'sortorder, answer'); 1363 //get number of answers 1364 //loop through all answers. if there are 3 items to rate there will be 3 statistics 1365 $i = 0; 1366 foreach ($result as $row) { 1367 $i++; 1368 $myfield2 = "R".$myfield.$i."-".strlen($i); 1369 $allfields[] = $myfield2; 1370 } 1371 1372 break; 1373 //Boilerplate questions are only used to put some text between other questions -> no analysis needed 1374 case "X": //This is a boilerplate question and it has no business in this script 1375 break; 1376 case "1": // MULTI SCALE 1377 //get answers 1378 $result = Question::model()->getQuestionsForStatistics('title, question', "parent_qid=$flt[qid] AND language = '{$sLanguage}'", 'question_order'); 1379 //loop through answers 1380 foreach ($result as $row) { 1381 //----------------- LABEL 1 --------------------- 1382 $myfield2 = $myfield.reset($row)."#0"; 1383 $allfields[] = $myfield2; 1384 //----------------- LABEL 2 --------------------- 1385 $myfield2 = $myfield.reset($row)."#1"; 1386 $allfields[] = $myfield2; 1387 } //end WHILE -> loop through all answers 1388 break; 1389 1390 case "P": //P - Multiple choice with comments 1391 case "M": //M - Multiple choice 1392 case "N": //N - Numerical input 1393 case "D": //D - Date 1394 $myfield2 = $flt['type'].$myfield; 1395 $allfields[] = $myfield2; 1396 break; 1397 default: //Default settings 1398 $allfields[] = $myfield; 1399 break; 1400 1401 } //end switch 1402 } 1403 1404 return $allfields; 1405 1406} 1407 1408 1409 1410 1411 1412/** 1413* This function generates an array containing the fieldcode, and matching data in the same order as the activate script 1414* 1415* @param Survey $survey 1416* @param string $style 'short' (default) or 'full' - full creates extra information like default values 1417* @param boolean $force_refresh - Forces to really refresh the array, not just take the session copy 1418* @param bool|int $questionid Limit to a certain qid only (for question preview) - default is false 1419* @param string $sLanguage The language to use 1420* @param array $aDuplicateQIDs 1421* @return array 1422*/ 1423function createFieldMap($survey, $style = 'short', $force_refresh = false, $questionid = false, $sLanguage = '', &$aDuplicateQIDs = array()) 1424{ 1425 1426 $sLanguage = sanitize_languagecode($sLanguage); 1427 $surveyid = $survey->sid; 1428 //checks to see if fieldmap has already been built for this page. 1429 if (isset(Yii::app()->session['fieldmap-'.$surveyid.$sLanguage]) && !$force_refresh && $questionid === false) { 1430 return Yii::app()->session['fieldmap-'.$surveyid.$sLanguage]; 1431 } 1432 /* Check if $sLanguage is a survey valid language (else $fieldmap is empty) */ 1433 if ($sLanguage == '' || !in_array($sLanguage, $survey->allLanguages)) { 1434 $sLanguage = $survey->language; 1435 } 1436 $fieldmap = []; 1437 $fieldmap["id"] = array("fieldname"=>"id", 'sid'=>$surveyid, 'type'=>"id", "gid"=>"", "qid"=>"", "aid"=>""); 1438 if ($style == "full") { 1439 $fieldmap["id"]['title'] = ""; 1440 $fieldmap["id"]['question'] = gT("Response ID"); 1441 $fieldmap["id"]['group_name'] = ""; 1442 } 1443 1444 $fieldmap["submitdate"] = array("fieldname"=>"submitdate", 'type'=>"submitdate", 'sid'=>$surveyid, "gid"=>"", "qid"=>"", "aid"=>""); 1445 if ($style == "full") { 1446 $fieldmap["submitdate"]['title'] = ""; 1447 $fieldmap["submitdate"]['question'] = gT("Date submitted"); 1448 $fieldmap["submitdate"]['group_name'] = ""; 1449 } 1450 1451 $fieldmap["lastpage"] = array("fieldname"=>"lastpage", 'sid'=>$surveyid, 'type'=>"lastpage", "gid"=>"", "qid"=>"", "aid"=>""); 1452 if ($style == "full") { 1453 $fieldmap["lastpage"]['title'] = ""; 1454 $fieldmap["lastpage"]['question'] = gT("Last page"); 1455 $fieldmap["lastpage"]['group_name'] = ""; 1456 } 1457 1458 $fieldmap["startlanguage"] = array("fieldname"=>"startlanguage", 'sid'=>$surveyid, 'type'=>"startlanguage", "gid"=>"", "qid"=>"", "aid"=>""); 1459 if ($style == "full") { 1460 $fieldmap["startlanguage"]['title'] = ""; 1461 $fieldmap["startlanguage"]['question'] = gT("Start language"); 1462 $fieldmap["startlanguage"]['group_name'] = ""; 1463 } 1464 1465 $fieldmap['seed'] = array('fieldname' => 'seed', 'sid' => $surveyid, 'type' => 'seed', 'gid' => '', 'qid' => '', 'aid' => ''); 1466 if ($style == 'full') { 1467 $fieldmap["seed"]['title'] = ""; 1468 $fieldmap["seed"]['question'] = gT("Seed"); 1469 $fieldmap["seed"]['group_name'] = ""; 1470 } 1471 1472 //Check for any additional fields for this survey and create necessary fields (token and datestamp and ipaddr) 1473 $prow = $survey->getAttributes(); //Checked 1474 1475 if ($prow['anonymized'] == "N" && $survey->hasTokensTable) { 1476 $fieldmap["token"] = array("fieldname"=>"token", 'sid'=>$surveyid, 'type'=>"token", "gid"=>"", "qid"=>"", "aid"=>""); 1477 if ($style == "full") { 1478 $fieldmap["token"]['title'] = ""; 1479 $fieldmap["token"]['question'] = gT("Token"); 1480 $fieldmap["token"]['group_name'] = ""; 1481 } 1482 } 1483 if ($prow['datestamp'] == "Y") { 1484 $fieldmap["startdate"] = array("fieldname"=>"startdate", 1485 'type'=>"startdate", 1486 'sid'=>$surveyid, 1487 "gid"=>"", 1488 "qid"=>"", 1489 "aid"=>""); 1490 if ($style == "full") { 1491 $fieldmap["startdate"]['title'] = ""; 1492 $fieldmap["startdate"]['question'] = gT("Date started"); 1493 $fieldmap["startdate"]['group_name'] = ""; 1494 } 1495 1496 $fieldmap["datestamp"] = array("fieldname"=>"datestamp", 1497 'type'=>"datestamp", 1498 'sid'=>$surveyid, 1499 "gid"=>"", 1500 "qid"=>"", 1501 "aid"=>""); 1502 if ($style == "full") { 1503 $fieldmap["datestamp"]['title'] = ""; 1504 $fieldmap["datestamp"]['question'] = gT("Date last action"); 1505 $fieldmap["datestamp"]['group_name'] = ""; 1506 } 1507 1508 } 1509 if ($prow['ipaddr'] == "Y") { 1510 $fieldmap["ipaddr"] = array("fieldname"=>"ipaddr", 1511 'type'=>"ipaddress", 1512 'sid'=>$surveyid, 1513 "gid"=>"", 1514 "qid"=>"", 1515 "aid"=>""); 1516 if ($style == "full") { 1517 $fieldmap["ipaddr"]['title'] = ""; 1518 $fieldmap["ipaddr"]['question'] = gT("IP address"); 1519 $fieldmap["ipaddr"]['group_name'] = ""; 1520 } 1521 } 1522 // Add 'refurl' to fieldmap. 1523 if ($prow['refurl'] == "Y") { 1524 $fieldmap["refurl"] = array("fieldname"=>"refurl", 'type'=>"url", 'sid'=>$surveyid, "gid"=>"", "qid"=>"", "aid"=>""); 1525 if ($style == "full") { 1526 $fieldmap["refurl"]['title'] = ""; 1527 $fieldmap["refurl"]['question'] = gT("Referrer URL"); 1528 $fieldmap["refurl"]['group_name'] = ""; 1529 } 1530 } 1531 1532 $sOldLanguage = App()->language; 1533 App()->setLanguage($sLanguage); 1534 // Collect all default values once so don't need separate query for each question with defaults 1535 // First collect language specific defaults 1536 $defaultsQuery = "SELECT a.qid, a.sqid, a.scale_id, a.specialtype, a.defaultvalue" 1537 . " FROM {{defaultvalues}} as a, {{questions}} as b" 1538 . " WHERE a.qid = b.qid" 1539 . " AND a.language = b.language" 1540 . " AND a.language = '{$sLanguage}'" 1541 . " AND b.same_default=0" 1542 . " AND b.sid = ".$surveyid; 1543 $defaultResults = Yii::app()->db->createCommand($defaultsQuery)->queryAll(); 1544 1545 $defaultValues = array(); // indexed by question then subquestion 1546 foreach ($defaultResults as $dv) { 1547 if ($dv['specialtype'] != '') { 1548 $sq = $dv['specialtype']; 1549 } else { 1550 $sq = $dv['sqid']; 1551 } 1552 $defaultValues[$dv['qid'].'~'.$sq] = $dv['defaultvalue']; 1553 } 1554 1555 // Now overwrite language-specific defaults (if any) base language values for each question that uses same_defaults=1 1556 $baseLanguage = $survey->language; 1557 $defaultsQuery = "SELECT a.qid, a.sqid, a.scale_id, a.specialtype, a.defaultvalue" 1558 . " FROM {{defaultvalues}} as a, {{questions}} as b" 1559 . " WHERE a.qid = b.qid" 1560 . " AND a.language = b.language" 1561 . " AND a.language = '{$baseLanguage}'" 1562 . " AND b.same_default=1" 1563 . " AND b.sid = ".$surveyid; 1564 $defaultResults = Yii::app()->db->createCommand($defaultsQuery)->queryAll(); 1565 1566 foreach ($defaultResults as $dv) { 1567 if ($dv['specialtype'] != '') { 1568 $sq = $dv['specialtype']; 1569 } else { 1570 $sq = $dv['sqid']; 1571 } 1572 $defaultValues[$dv['qid'].'~'.$sq] = $dv['defaultvalue']; 1573 } 1574 $qtypes = getQuestionTypeList('', 'array'); 1575 1576 // Main query 1577 $aquery = "SELECT * " 1578 ." FROM {{questions}} as questions, ".Yii::app()->db->quoteTableName('{{groups}}')." as question_groups" 1579 ." WHERE questions.gid=question_groups.gid AND " 1580 ." questions.sid=$surveyid AND " 1581 ." questions.language='{$sLanguage}' AND " 1582 ." questions.parent_qid=0 AND " 1583 ." question_groups.language='{$sLanguage}' "; 1584 if ($questionid !== false) { 1585 $aquery .= " and questions.qid={$questionid} "; 1586 } 1587 $aquery .= " ORDER BY group_order, question_order"; 1588 /** @var Question[] $questions */ 1589 $questions = Yii::app()->db->createCommand($aquery)->queryAll(); 1590 $questionSeq = -1; // this is incremental question sequence across all groups 1591 $groupSeq = -1; 1592 $_groupOrder = -1; 1593 1594 foreach ($questions as $arow) { 1595//With each question, create the appropriate field(s)) 1596 ++$questionSeq; 1597 1598 // fix fact taht group_order may have gaps 1599 if ($_groupOrder != $arow['group_order']) { 1600 $_groupOrder = $arow['group_order']; 1601 ++$groupSeq; 1602 } 1603 // Condition indicators are obsolete with EM. However, they are so tightly coupled into LS code that easider to just set values to 'N' for now and refactor later. 1604 $conditions = 'N'; 1605 $usedinconditions = 'N'; 1606 1607 // Field identifier 1608 // GXQXSXA 1609 // G=Group Q=Question S=Subquestion A=Answer Option 1610 // If S or A don't exist then set it to 0 1611 // Implicit (subqestion intermal to a question type ) or explicit qubquestions/answer count starts at 1 1612 1613 // Types "L", "!", "O", "D", "G", "N", "X", "Y", "5", "S", "T", "U" 1614 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}"; 1615 1616 if ($qtypes[$arow['type']]['subquestions'] == 0 && $arow['type'] != "R" && $arow['type'] != "|") { 1617 if (isset($fieldmap[$fieldname])) { 1618 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1619 } 1620 1621 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>"{$arow['type']}", 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>""); 1622 1623 if ($style == "full") { 1624 $fieldmap[$fieldname]['title'] = $arow['title']; 1625 $fieldmap[$fieldname]['question'] = $arow['question']; 1626 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1627 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1628 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1629 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1630 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1631 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1632 if (isset($defaultValues[$arow['qid'].'~0'])) { 1633 $fieldmap[$fieldname]['defaultvalue'] = $defaultValues[$arow['qid'].'~0']; 1634 } 1635 } 1636 switch ($arow['type']) { 1637 case "L": //RADIO LIST 1638 case "!": //DROPDOWN LIST 1639 if ($arow['other'] == "Y") { 1640 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}other"; 1641 if (isset($fieldmap[$fieldname])) { 1642 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1643 } 1644 1645 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1646 'type'=>$arow['type'], 1647 'sid'=>$surveyid, 1648 "gid"=>$arow['gid'], 1649 "qid"=>$arow['qid'], 1650 "aid"=>"other"); 1651 // dgk bug fix line above. aid should be set to "other" for export to append to the field name in the header line. 1652 if ($style == "full") { 1653 $fieldmap[$fieldname]['title'] = $arow['title']; 1654 $fieldmap[$fieldname]['question'] = $arow['question']; 1655 $fieldmap[$fieldname]['subquestion'] = gT("Other"); 1656 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1657 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1658 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1659 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1660 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1661 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1662 if (isset($defaultValues[$arow['qid'].'~other'])) { 1663 $fieldmap[$fieldname]['defaultvalue'] = $defaultValues[$arow['qid'].'~other']; 1664 } 1665 } 1666 } 1667 break; 1668 case "O": //DROPDOWN LIST WITH COMMENT 1669 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}comment"; 1670 if (isset($fieldmap[$fieldname])) { 1671 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1672 } 1673 1674 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1675 'type'=>$arow['type'], 1676 'sid'=>$surveyid, 1677 "gid"=>$arow['gid'], 1678 "qid"=>$arow['qid'], 1679 "aid"=>"comment"); 1680 // dgk bug fix line below. aid should be set to "comment" for export to append to the field name in the header line. Also needed set the type element correctly. 1681 if ($style == "full") { 1682 $fieldmap[$fieldname]['title'] = $arow['title']; 1683 $fieldmap[$fieldname]['question'] = $arow['question']; 1684 $fieldmap[$fieldname]['subquestion'] = gT("Comment"); 1685 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1686 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1687 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1688 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1689 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1690 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1691 } 1692 break; 1693 } 1694 } 1695 // For Multi flexi question types 1696 elseif ($qtypes[$arow['type']]['subquestions'] == 2 && $qtypes[$arow['type']]['answerscales'] == 0) { 1697 //MULTI FLEXI 1698 $abrows = getSubQuestions($surveyid, $arow['qid'], $sLanguage); 1699 //Now first process scale=1 1700 $answerset = array(); 1701 $answerList = array(); 1702 foreach ($abrows as $key=>$abrow) { 1703 if ($abrow['scale_id'] == 1) { 1704 $answerset[] = $abrow; 1705 $answerList[] = array( 1706 'code'=>$abrow['title'], 1707 'answer'=>$abrow['question'], 1708 ); 1709 unset($abrows[$key]); 1710 } 1711 } 1712 reset($abrows); 1713 foreach ($abrows as $abrow) { 1714 foreach ($answerset as $answer) { 1715 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}{$abrow['title']}_{$answer['title']}"; 1716 if (isset($fieldmap[$fieldname])) { 1717 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1718 } 1719 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1720 'type'=>$arow['type'], 1721 'sid'=>$surveyid, 1722 "gid"=>$arow['gid'], 1723 "qid"=>$arow['qid'], 1724 "aid"=>$abrow['title']."_".$answer['title'], 1725 "sqid"=>$abrow['qid']); 1726 if ($style == "full") { 1727 $fieldmap[$fieldname]['title'] = $arow['title']; 1728 $fieldmap[$fieldname]['question'] = $arow['question']; 1729 $fieldmap[$fieldname]['subquestion1'] = $abrow['question']; 1730 $fieldmap[$fieldname]['subquestion2'] = $answer['question']; 1731 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1732 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1733 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1734 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1735 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1736 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1737 $fieldmap[$fieldname]['preg'] = $arow['preg']; 1738 $fieldmap[$fieldname]['answerList'] = $answerList; 1739 $fieldmap[$fieldname]['SQrelevance'] = $abrow['relevance']; 1740 } 1741 } 1742 } 1743 unset($answerset); 1744 } elseif ($arow['type'] == "1") { 1745 $abrows = getSubQuestions($surveyid, $arow['qid'], $sLanguage); 1746 foreach ($abrows as $abrow) { 1747 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}{$abrow['title']}#0"; 1748 if (isset($fieldmap[$fieldname])) { 1749 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1750 } 1751 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>$abrow['title'], "scale_id"=>0); 1752 if ($style == "full") { 1753 $fieldmap[$fieldname]['title'] = $arow['title']; 1754 $fieldmap[$fieldname]['question'] = $arow['question']; 1755 $fieldmap[$fieldname]['subquestion'] = $abrow['question']; 1756 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1757 $fieldmap[$fieldname]['scale'] = gT('Scale 1'); 1758 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1759 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1760 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1761 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1762 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1763 $fieldmap[$fieldname]['SQrelevance'] = $abrow['relevance']; 1764 } 1765 1766 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}{$abrow['title']}#1"; 1767 if (isset($fieldmap[$fieldname])) { 1768 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1769 } 1770 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>$abrow['title'], "scale_id"=>1); 1771 if ($style == "full") { 1772 $fieldmap[$fieldname]['title'] = $arow['title']; 1773 $fieldmap[$fieldname]['question'] = $arow['question']; 1774 $fieldmap[$fieldname]['subquestion'] = $abrow['question']; 1775 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1776 $fieldmap[$fieldname]['scale'] = gT('Scale 2'); 1777 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1778 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1779 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1780 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1781 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1782 // TODO SQrelevance for different scales? $fieldmap[$fieldname]['SQrelevance']=$abrow['relevance']; 1783 } 1784 } 1785 } elseif ($arow['type'] == "R") { 1786 // Sub question by answer number OR attribute 1787 $answersCount = intval(Answer::model()->countByAttributes(array('qid' => $arow['qid'], 'language' => $sLanguage))); 1788 $maxDbAnswer = QuestionAttribute::model()->find("qid = :qid AND attribute = 'max_subquestions'", array(':qid' => $arow['qid'])); 1789 $columnsCount = (!$maxDbAnswer || intval($maxDbAnswer->value) < 1) ? $answersCount : intval($maxDbAnswer->value); 1790 $columnsCount = min($columnsCount,$answersCount); // Can not be upper than current answers #14899 1791 for ($i = 1; $i <= $columnsCount; $i++) { 1792 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}$i"; 1793 if (isset($fieldmap[$fieldname])) { 1794 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1795 } 1796 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>$i); 1797 if ($style == "full") { 1798 $fieldmap[$fieldname]['title'] = $arow['title']; 1799 $fieldmap[$fieldname]['question'] = $arow['question']; 1800 $fieldmap[$fieldname]['subquestion'] = sprintf(gT('Rank %s'), $i); 1801 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1802 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1803 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1804 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1805 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1806 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1807 } 1808 } 1809 } elseif ($arow['type'] == "|") { 1810 $qidattributes = QuestionAttribute::model()->getQuestionAttributes($arow['qid']); 1811 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}"; 1812 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1813 'type'=>$arow['type'], 1814 'sid'=>$surveyid, 1815 "gid"=>$arow['gid'], 1816 "qid"=>$arow['qid'], 1817 "aid"=>'' 1818 ); 1819 if ($style == "full") { 1820 $fieldmap[$fieldname]['title'] = $arow['title']; 1821 $fieldmap[$fieldname]['question'] = $arow['question']; 1822 $fieldmap[$fieldname]['max_files'] = $qidattributes['max_num_of_files']; 1823 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1824 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1825 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1826 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1827 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1828 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1829 } 1830 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}"."_filecount"; 1831 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1832 'type'=>$arow['type'], 1833 'sid'=>$surveyid, 1834 "gid"=>$arow['gid'], 1835 "qid"=>$arow['qid'], 1836 "aid"=>"filecount" 1837 ); 1838 if ($style == "full") { 1839 $fieldmap[$fieldname]['title'] = $arow['title']; 1840 $fieldmap[$fieldname]['question'] = "filecount - ".$arow['question']; 1841 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1842 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1843 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1844 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1845 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1846 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1847 } 1848 } else { 1849// Question types with subquestions and one answer per subquestion (M/A/B/C/E/F/H/P) 1850 //MULTI ENTRY 1851 $abrows = getSubQuestions($surveyid, $arow['qid'], $sLanguage); 1852 foreach ($abrows as $abrow) { 1853 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}{$abrow['title']}"; 1854 1855 if (isset($fieldmap[$fieldname])) { 1856 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1857 } 1858 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 1859 'type'=>$arow['type'], 1860 'sid'=>$surveyid, 1861 'gid'=>$arow['gid'], 1862 'qid'=>$arow['qid'], 1863 'aid'=>$abrow['title'], 1864 'sqid'=>$abrow['qid']); 1865 if ($style == "full") { 1866 $fieldmap[$fieldname]['title'] = $arow['title']; 1867 $fieldmap[$fieldname]['question'] = $arow['question']; 1868 $fieldmap[$fieldname]['subquestion'] = $abrow['question']; 1869 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1870 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1871 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1872 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1873 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1874 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1875 $fieldmap[$fieldname]['preg'] = $arow['preg']; 1876 // get SQrelevance from DB 1877 $fieldmap[$fieldname]['SQrelevance'] = $abrow['relevance']; 1878 if (isset($defaultValues[$arow['qid'].'~'.$abrow['qid']])) { 1879 $fieldmap[$fieldname]['defaultvalue'] = $defaultValues[$arow['qid'].'~'.$abrow['qid']]; 1880 } 1881 } 1882 if ($arow['type'] == "P") { 1883 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}{$abrow['title']}comment"; 1884 if (isset($fieldmap[$fieldname])) { 1885 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1886 } 1887 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>$abrow['title']."comment"); 1888 if ($style == "full") { 1889 $fieldmap[$fieldname]['title'] = $arow['title']; 1890 $fieldmap[$fieldname]['question'] = $arow['question']; 1891 $fieldmap[$fieldname]['subquestion1'] = gT('Comment'); 1892 $fieldmap[$fieldname]['subquestion'] =$abrow['question']; 1893 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1894 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1895 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1896 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1897 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1898 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1899 } 1900 } 1901 } 1902 if ($arow['other'] == "Y" && ($arow['type'] == "M" || $arow['type'] == "P")) { 1903 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}other"; 1904 if (isset($fieldmap[$fieldname])) { 1905 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1906 } 1907 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>"other"); 1908 if ($style == "full") { 1909 $fieldmap[$fieldname]['title'] = $arow['title']; 1910 $fieldmap[$fieldname]['question'] = $arow['question']; 1911 $fieldmap[$fieldname]['subquestion'] = gT('Other'); 1912 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1913 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1914 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1915 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1916 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1917 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1918 $fieldmap[$fieldname]['other'] = $arow['other']; 1919 } 1920 if ($arow['type'] == "P") { 1921 $fieldname = "{$arow['sid']}X{$arow['gid']}X{$arow['qid']}othercomment"; 1922 if (isset($fieldmap[$fieldname])) { 1923 $aDuplicateQIDs[$arow['qid']] = array('fieldname'=>$fieldname, 'question'=>$arow['question'], 'gid'=>$arow['gid']); 1924 } 1925 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>$arow['type'], 'sid'=>$surveyid, "gid"=>$arow['gid'], "qid"=>$arow['qid'], "aid"=>"othercomment"); 1926 if ($style == "full") { 1927 $fieldmap[$fieldname]['title'] = $arow['title']; 1928 $fieldmap[$fieldname]['question'] = $arow['question']; 1929 $fieldmap[$fieldname]['subquestion'] = gT('Other comment'); 1930 $fieldmap[$fieldname]['group_name'] = $arow['group_name']; 1931 $fieldmap[$fieldname]['mandatory'] = $arow['mandatory']; 1932 $fieldmap[$fieldname]['hasconditions'] = $conditions; 1933 $fieldmap[$fieldname]['usedinconditions'] = $usedinconditions; 1934 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1935 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1936 $fieldmap[$fieldname]['other'] = $arow['other']; 1937 } 1938 } 1939 } 1940 } 1941 if (isset($fieldmap[$fieldname])) { 1942 //set question relevance (uses last SQ's relevance field for question relevance) 1943 $fieldmap[$fieldname]['relevance'] = $arow['relevance']; 1944 $fieldmap[$fieldname]['grelevance'] = $arow['grelevance']; 1945 $fieldmap[$fieldname]['questionSeq'] = $questionSeq; 1946 $fieldmap[$fieldname]['groupSeq'] = $groupSeq; 1947 $fieldmap[$fieldname]['preg'] = $arow['preg']; 1948 $fieldmap[$fieldname]['other'] = $arow['other']; 1949 $fieldmap[$fieldname]['help'] = $arow['help']; 1950 1951 // Set typeName 1952 } else { 1953 --$questionSeq; // didn't generate a valid $fieldmap entry, so decrement the question counter to ensure they are sequential 1954 } 1955 1956 if (isset($fieldmap[$fieldname]['typename'])) { 1957 $fieldmap[$fieldname]['typename'] = $typename[$fieldname] = $arow['typename']; 1958 } 1959 } 1960 App()->setLanguage($sOldLanguage); 1961 1962 if ($questionid === false) { 1963 // If the fieldmap was randomized, the master will contain the proper order. Copy that fieldmap with the new language settings. 1964 if (isset(Yii::app()->session['survey_'.$surveyid]['fieldmap-'.$surveyid.'-randMaster'])) { 1965 $masterFieldmap = Yii::app()->session['survey_'.$surveyid]['fieldmap-'.$surveyid.'-randMaster']; 1966 $mfieldmap = Yii::app()->session['survey_'.$surveyid][$masterFieldmap]; 1967 1968 foreach ($mfieldmap as $fieldname => $mf) { 1969 if (isset($fieldmap[$fieldname])) { 1970 // This array holds the keys of translatable attributes 1971 $translatable = array_flip(array('question', 'subquestion', 'subquestion1', 'subquestion2', 'group_name', 'answerList', 'defaultValue', 'help')); 1972 // We take all translatable attributes from the new fieldmap 1973 $newText = array_intersect_key($fieldmap[$fieldname], $translatable); 1974 // And merge them with the other values from the random fieldmap like questionSeq, groupSeq etc. 1975 $mf = $newText + $mf; 1976 } 1977 $mfieldmap[$fieldname] = $mf; 1978 } 1979 $fieldmap = $mfieldmap; 1980 } 1981 1982 Yii::app()->session['fieldmap-'.$surveyid.$sLanguage] = $fieldmap; 1983 } 1984 return $fieldmap; 1985} 1986 1987/** 1988* Returns true if the given survey has a File Upload Question Type 1989* @param integer $iSurveyID 1990* @return bool 1991*/ 1992function hasFileUploadQuestion($iSurveyID) 1993{ 1994 $iCount = Question::model()->count("sid=:surveyid AND parent_qid=0 AND type='|'", array(':surveyid' => $iSurveyID)); 1995 return $iCount > 0; 1996} 1997 1998/** 1999* This function generates an array containing the fieldcode, and matching data in the same order as the activate script 2000* 2001* @param string $surveyid The Survey ID 2002* @param string $style 'short' (default) or 'full' - full creates extra information like default values 2003* @param boolean $force_refresh - Forces to really refresh the array, not just take the session copy 2004* @param int $questionid Limit to a certain qid only (for question preview) - default is false 2005* @param string $sQuestionLanguage The language to use 2006* @return array 2007*/ 2008function createTimingsFieldMap($surveyid, $style = 'full', $force_refresh = false, $questionid = false, $sQuestionLanguage = null) 2009{ 2010 2011 static $timingsFieldMap; 2012 2013 $sLanguage = sanitize_languagecode($sQuestionLanguage); 2014 $surveyid = sanitize_int($surveyid); 2015 $survey = Survey::model()->findByPk($surveyid); 2016 2017 $sOldLanguage = App()->language; 2018 App()->setLanguage($sLanguage); 2019 2020 //checks to see if fieldmap has already been built for this page. 2021 if (isset($timingsFieldMap[$surveyid][$style][$sLanguage]) && $force_refresh === false) { 2022 return $timingsFieldMap[$surveyid][$style][$sLanguage]; 2023 } 2024 2025 //do something 2026 $fields = createFieldMap($survey, $style, $force_refresh, $questionid, $sQuestionLanguage); 2027 $fieldmap = []; 2028 $fieldmap['interviewtime'] = array('fieldname'=>'interviewtime', 'type'=>'interview_time', 'sid'=>$surveyid, 'gid'=>'', 'qid'=>'', 'aid'=>'', 'question'=>gT('Total time'), 'title'=>'interviewtime'); 2029 foreach ($fields as $field) { 2030 if (!empty($field['gid'])) { 2031 // field for time spent on page 2032 $fieldname = "{$field['sid']}X{$field['gid']}time"; 2033 if (!isset($fieldmap[$fieldname])) { 2034 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>"page_time", 'sid'=>$surveyid, "gid"=>$field['gid'], "group_name"=>$field['group_name'], "qid"=>'', 'aid'=>'', 'title'=>'groupTime'.$field['gid'], 'question'=>gT('Group time').": ".$field['group_name']); 2035 } 2036 2037 // field for time spent on answering a question 2038 $fieldname = "{$field['sid']}X{$field['gid']}X{$field['qid']}time"; 2039 if (!isset($fieldmap[$fieldname])) { 2040 $fieldmap[$fieldname] = array("fieldname"=>$fieldname, 'type'=>"answer_time", 'sid'=>$surveyid, "gid"=>$field['gid'], "group_name"=>$field['group_name'], "qid"=>$field['qid'], 'aid'=>'', "title"=>$field['title'].'Time', "question"=>gT('Question time').": ".$field['title']); 2041 } 2042 } 2043 } 2044 2045 $timingsFieldMap[$surveyid][$style][$sLanguage] = $fieldmap; 2046 App()->setLanguage($sOldLanguage); 2047 return $timingsFieldMap[$surveyid][$style][$sLanguage]; 2048} 2049 2050/** 2051 * 2052 * @param mixed $needle 2053 * @param mixed $haystack 2054 * @param string $keyname 2055 * @param integer $maxanswers 2056 * @return array 2057 */ 2058function arraySearchByKey($needle, $haystack, $keyname, $maxanswers = "") 2059{ 2060 $output = array(); 2061 foreach ($haystack as $hay) { 2062 if (array_key_exists($keyname, $hay)) { 2063 if ($hay[$keyname] == $needle) { 2064 if ($maxanswers == 1) { 2065 return $hay; 2066 } else { 2067 $output[] = $hay; 2068 } 2069 } 2070 } 2071 } 2072 return $output; 2073} 2074 2075/** 2076* This function returns a count of the number of saved responses to a survey 2077* 2078* @param mixed $surveyid Survey ID 2079*/ 2080function getSavedCount($surveyid) 2081{ 2082 $surveyid = (int) $surveyid; 2083 2084 return SavedControl::model()->getCountOfAll($surveyid); 2085} 2086 2087 2088function buildLabelSetCheckSumArray() 2089{ 2090 // BUILD CHECKSUMS FOR ALL EXISTING LABEL SETS 2091 2092 /**$query = "SELECT lid 2093 FROM ".db_table_name('labelsets')." 2094 ORDER BY lid"; */ 2095 $result = LabelSet::model()->getLID(); //($query) or safeDie("safe_died collecting labelset ids<br />$query<br />"); //Checked) 2096 $csarray = array(); 2097 foreach ($result as $row) { 2098 $thisset = ""; 2099 $query2 = "SELECT code, title, sortorder, language, assessment_value 2100 FROM {{labels}} 2101 WHERE lid={$row['lid']} 2102 ORDER BY language, sortorder, code"; 2103 $result2 = Yii::app()->db->createCommand($query2)->query(); 2104 foreach ($result2->readAll() as $row2) { 2105 $thisset .= implode('.', $row2); 2106 } // while 2107 $csarray[$row['lid']] = hash('sha256', $thisset); 2108 } 2109 2110 return $csarray; 2111} 2112 2113 2114 2115/** 2116* 2117* Returns the questionAttribtue value set or '' if not set 2118* @author: lemeur 2119* @param $questionAttributeArray 2120* @param string $attributeName 2121* @param $language string Optional: The language if the particualr attributes is localizable 2122* @return string 2123*/ 2124function getQuestionAttributeValue($questionAttributeArray, $attributeName, $language = '') 2125{ 2126 if ($language == '' && isset($questionAttributeArray[$attributeName])) { 2127 return $questionAttributeArray[$attributeName]; 2128 } elseif ($language != '' && isset($questionAttributeArray[$attributeName][$language])) { 2129 return $questionAttributeArray[$attributeName][$language]; 2130 } else { 2131 return ''; 2132 } 2133} 2134 2135 2136function categorySort($a, $b) 2137{ 2138 $result = strnatcasecmp($a['category'], $b['category']); 2139 if ($result == 0) { 2140 $result = $a['sortorder'] - $b['sortorder']; 2141 } 2142 return $result; 2143} 2144 2145 2146 2147 2148// make a string safe to include in an HTML 'value' attribute. 2149function HTMLEscape($str) 2150{ 2151 // escape newline characters, too, in case we put a value from 2152 // a TEXTAREA into an <input type="hidden"> value attribute. 2153 return str_replace(array("\x0A", "\x0D"), array(" ", " "), 2154 htmlspecialchars($str, ENT_QUOTES)); 2155} 2156 2157/** 2158* This function strips UTF-8 control characters from strings, except tabs, CR and LF 2159* - it is intended to be used before any response data is saved to the response table 2160* 2161* @param mixed $sValue A string to be sanitized 2162* @return string A sanitized string, otherwise the unmodified original variable 2163*/ 2164function stripCtrlChars($sValue) 2165{ 2166 if (is_string($sValue)) { 2167 $sValue = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', '', $sValue); 2168 } 2169 return $sValue; 2170} 2171 2172// make a string safe to include in a JavaScript String parameter. 2173function javascriptEscape($str, $strip_tags = false, $htmldecode = false) 2174{ 2175 2176 if ($htmldecode == true) { 2177 $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8'); 2178 } 2179 if ($strip_tags == true) { 2180 $str = strip_tags($str); 2181 } 2182 return str_replace(array('\'', '"', "\n", "\r"), 2183 array("\\'", '\u0022', "\\n", '\r'), 2184 $str); 2185} 2186// make a string safe to include in a json String parameter. 2187function jsonEscape($str, $strip_tags = false, $htmldecode = false) 2188{ 2189 2190 if ($htmldecode == true) { 2191 $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8'); 2192 } 2193 if ($strip_tags == true) { 2194 $str = strip_tags($str); 2195 } 2196 return str_replace(array('"','\''), array("'","'"), $str); 2197} 2198 2199/** 2200* This function mails a text $body to the recipient $to. 2201* You can use more than one recipient when using a semicolon separated string with recipients. 2202* 2203* @param string $body Body text of the email in plain text or HTML 2204* @param mixed $subject Email subject 2205* @param mixed $to Array with several email addresses or single string with one email address 2206* @param mixed $from 2207* @param mixed $sitename 2208* @param boolean $ishtml 2209* @param mixed $bouncemail 2210* @param mixed $attachments 2211* @return bool If successful returns true 2212*/ 2213function SendEmailMessage($body, $subject, $to, $from, $sitename, $ishtml = false, $bouncemail = null, $attachments = null, $customheaders = "") 2214{ 2215 global $maildebug, $maildebugbody; 2216 require_once(APPPATH.'/third_party/html2text/src/Html2Text.php'); 2217 2218 $emailmethod = Yii::app()->getConfig('emailmethod'); 2219 $emailsmtphost = Yii::app()->getConfig("emailsmtphost"); 2220 $emailsmtpuser = Yii::app()->getConfig("emailsmtpuser"); 2221 $emailsmtppassword = Yii::app()->getConfig("emailsmtppassword"); 2222 $emailsmtpdebug = Yii::app()->getConfig("emailsmtpdebug"); 2223 $emailsmtpssl = Yii::app()->getConfig("emailsmtpssl"); 2224 $defaultlang = Yii::app()->getConfig("defaultlang"); 2225 $emailcharset = Yii::app()->getConfig("emailcharset"); 2226 2227 if ($emailcharset != 'utf-8') { 2228 $body = mb_convert_encoding($body, $emailcharset, 'utf-8'); 2229 $subject = mb_convert_encoding($subject, $emailcharset, 'utf-8'); 2230 $sitename = mb_convert_encoding($sitename, $emailcharset, 'utf-8'); 2231 } 2232 2233 if (!is_array($to)) { 2234 $to = array($to); 2235 } 2236 2237 2238 2239 if (!is_array($customheaders) && $customheaders == '') { 2240 $customheaders = array(); 2241 } 2242 if (Yii::app()->getConfig('demoMode')) { 2243 $maildebug = gT('Email was not sent because demo-mode is activated.'); 2244 $maildebugbody = ''; 2245 return false; 2246 } 2247 2248 if (is_null($bouncemail)) { 2249 $sender = $from; 2250 } else { 2251 $sender = $bouncemail; 2252 } 2253 2254 2255 require_once(APPPATH.'/third_party/phpmailer/load_phpmailer.php'); 2256 $mail = new PHPMailer\PHPMailer\PHPMailer; 2257 $mail->SMTPAutoTLS = false; 2258 if (!$mail->SetLanguage($defaultlang, APPPATH.'/third_party/phpmailer/language/')) { 2259 $mail->SetLanguage('en', APPPATH.'/third_party/phpmailer/language/'); 2260 } 2261 $mail->CharSet = $emailcharset; 2262 if (isset($emailsmtpssl) && trim($emailsmtpssl) !== '' && $emailsmtpssl !== 0) { 2263 if ($emailsmtpssl === 1) {$mail->SMTPSecure = "ssl"; } else {$mail->SMTPSecure = $emailsmtpssl; } 2264 } 2265 2266 $fromname = ''; 2267 $fromemail = $from; 2268 if (strpos($from, '<')) { 2269 $fromemail = substr($from, strpos($from, '<') + 1, strpos($from, '>') - 1 - strpos($from, '<')); 2270 $fromname = trim(substr($from, 0, strpos($from, '<') - 1)); 2271 } 2272 2273 $senderemail = $sender; 2274 if (strpos($sender, '<')) { 2275 $senderemail = substr($sender, strpos($sender, '<') + 1, strpos($sender, '>') - 1 - strpos($sender, '<')); 2276 } 2277 2278 switch ($emailmethod) { 2279 case "qmail": 2280 $mail->IsQmail(); 2281 break; 2282 case "smtp": 2283 $mail->IsSMTP(); 2284 if ($emailsmtpdebug > 0) { 2285 $mail->SMTPDebug = $emailsmtpdebug; 2286 } 2287 if (strpos($emailsmtphost, ':') > 0) { 2288 $mail->Host = substr($emailsmtphost, 0, strpos($emailsmtphost, ':')); 2289 $mail->Port = (int) substr($emailsmtphost, strpos($emailsmtphost, ':') + 1); 2290 } else { 2291 $mail->Host = $emailsmtphost; 2292 } 2293 $mail->Username = $emailsmtpuser; 2294 $mail->Password = $emailsmtppassword; 2295 if (trim($emailsmtpuser) != "") { 2296 $mail->SMTPAuth = true; 2297 } 2298 break; 2299 case "sendmail": 2300 $mail->IsSendmail(); 2301 break; 2302 default: 2303 $mail->IsMail(); 2304 } 2305 2306 $mail->SetFrom($fromemail, $fromname); 2307 $mail->Sender = $senderemail; // Sets Return-Path for error notifications 2308 foreach ($to as $singletoemail) { 2309 if (strpos($singletoemail, '<')) { 2310 $toemail = substr($singletoemail, strpos($singletoemail, '<') + 1, strpos($singletoemail, '>') - 1 - strpos($singletoemail, '<')); 2311 $toname = trim(substr($singletoemail, 0, strpos($singletoemail, '<') - 1)); 2312 $mail->AddAddress($toemail, $toname); 2313 } else { 2314 $mail->AddAddress($singletoemail); 2315 } 2316 } 2317 if (is_array($customheaders)) { 2318 foreach ($customheaders as $key=>$val) { 2319 $mail->AddCustomHeader($val); 2320 } 2321 } 2322 $mail->AddCustomHeader("X-Surveymailer: $sitename Emailer (LimeSurvey.org)"); 2323 if ($ishtml) { 2324 $mail->IsHTML(true); 2325 if (strpos($body, "<html>") === false) { 2326 $body = "<html>".$body."</html>"; 2327 } 2328 $mail->msgHTML($body, App()->getConfig("publicdir")); // This allow embedded image if we remove the servername from image 2329 $html = new \Html2Text\Html2Text($body); 2330 $mail->AltBody = $html->getText(); 2331 } else { 2332 $mail->IsHTML(false); 2333 $mail->Body = $body; 2334 } 2335 // Add attachments if they are there. 2336 if (is_array($attachments)) { 2337 foreach ($attachments as $attachment) { 2338 // Attachment is either an array with filename and attachment name. 2339 if (is_array($attachment)) { 2340 $mail->AddAttachment($attachment[0], $attachment[1]); 2341 } else { 2342// Or a string with the filename. 2343 $mail->AddAttachment($attachment); 2344 } 2345 } 2346 } 2347 $mail->Subject = $subject; 2348 2349 if ($emailsmtpdebug > 0) { 2350 ob_start(); 2351 } 2352 $sent = $mail->Send(); 2353 $maildebug = $mail->ErrorInfo; 2354 if ($emailsmtpdebug > 0) { 2355 $maildebug .= '<br><strong>'.gT('SMTP debug output:').'</strong><pre>'.\CHtml::encode(ob_get_contents()).'</pre>'; 2356 ob_end_clean(); 2357 } 2358 $maildebugbody = $mail->Body; 2359 //if(!$sent) var_dump($maildebug); 2360 return $sent; 2361} 2362 2363 2364/** 2365* This functions removes all HTML tags, Javascript, CRs, linefeeds and other strange chars from a given text 2366* 2367* @param string $sTextToFlatten Text you want to clean 2368* @param boolean $bKeepSpan set to true for keep span, used for expression manager. Default: false 2369* @param boolean $bDecodeHTMLEntities If set to true then all HTML entities will be decoded to the specified charset. Default: false 2370* @param string $sCharset Charset to decode to if $decodeHTMLEntities is set to true. Default: UTF-8 2371* @param string $bStripNewLines strip new lines if true, if false replace all new line by \r\n. Default: true 2372* 2373* @return string Cleaned text 2374*/ 2375function flattenText($sTextToFlatten, $bKeepSpan = false, $bDecodeHTMLEntities = false, $sCharset = 'UTF-8', $bStripNewLines = true) 2376{ 2377 $sNicetext = stripJavaScript($sTextToFlatten); 2378 // When stripping tags, add a space before closing tags so that strings with embedded HTML tables don't get concatenated 2379 $sNicetext = str_replace(array('</td', '</th'), array(' </td', ' </th'), $sNicetext); 2380 if ($bKeepSpan) { 2381 // Keep <span> so can show EM syntax-highlighting; add space before tags so that word-wrapping not destroyed when remove tags. 2382 $sNicetext = strip_tags($sNicetext, '<span><table><tr><td><th>'); 2383 } else { 2384 $sNicetext = strip_tags($sNicetext); 2385 } 2386 // ~\R~u : see "What \R matches" and "Newline sequences" in http://www.pcre.org/pcre.txt - only available since PCRE 7.0 2387 if ($bStripNewLines) { 2388// strip new lines 2389 if (version_compare(substr(PCRE_VERSION, 0, strpos(PCRE_VERSION, ' ')), '7.0') > -1) { 2390 $sNicetext = preg_replace(array('~\R~u'), array(' '), $sNicetext); 2391 } else { 2392 // Poor man's replacement for line feeds 2393 $sNicetext = str_replace(array("\r\n", "\n", "\r"), array(' ', ' ', ' '), $sNicetext); 2394 } 2395 } elseif (version_compare(substr(PCRE_VERSION, 0, strpos(PCRE_VERSION, ' ')), '7.0') > -1) { 2396 // unify newlines to \r\n 2397 $sNicetext = preg_replace(array('~\R~u'), array("\r\n"), $sNicetext); 2398 } 2399 if ($bDecodeHTMLEntities === true) { 2400 $sNicetext = str_replace(' ', ' ', $sNicetext); // html_entity_decode does not convert to spaces 2401 $sNicetext = html_entity_decode($sNicetext, ENT_QUOTES, $sCharset); 2402 } 2403 $sNicetext = trim($sNicetext); 2404 return $sNicetext; 2405} 2406 2407 2408/** 2409* getArrayFilterExcludesCascadesForGroup() queries the database and produces a list of array_filter_exclude questions and targets with in the same group 2410* @return array a keyed nested array, keyed by the qid of the question, containing cascade information 2411*/ 2412function getArrayFilterExcludesCascadesForGroup($surveyid, $gid = "", $output = "qid") 2413{ 2414 $surveyid = sanitize_int($surveyid); 2415 $survey = Survey::model()->findByPk($surveyid); 2416 2417 $gid = sanitize_int($gid); 2418 2419 2420 $cascaded = array(); 2421 $sources = array(); 2422 $qidtotitle = array(); 2423 $fieldmap = createFieldMap($survey, 'full', false, false, $survey->language); 2424 2425 if ($gid != "") { 2426 $qrows = arraySearchByKey($gid, $fieldmap, 'gid'); 2427 } else { 2428 $qrows = $fieldmap; 2429 } 2430 $grows = array(); //Create an empty array in case query not return any rows 2431 // Store each result as an array with in the $grows array 2432 foreach ($qrows as $qrow) { 2433 if (isset($qrow['gid']) && !empty($qrow['gid'])) { 2434 $grows[$qrow['qid']] = array('qid' => $qrow['qid'], 'type' => $qrow['type'], 'mandatory' => $qrow['mandatory'], 'title' => $qrow['title'], 'gid' => $qrow['gid']); 2435 } 2436 } 2437 foreach ($grows as $qrow) { 2438 // Cycle through questions to see if any have list_filter attributes 2439 $qidtotitle[$qrow['qid']] = $qrow['title']; 2440 $qresult = QuestionAttribute::model()->getQuestionAttributes($qrow['qid']); 2441 if (isset($qresult['array_filter_exclude'])) { 2442 // We Found a array_filter attribute 2443 $val = $qresult['array_filter_exclude']; // Get the Value of the Attribute ( should be a previous question's title in same group ) 2444 foreach ($grows as $avalue) { 2445 // Cycle through all the other questions in this group until we find the source question for this array_filter 2446 if ($avalue['title'] == $val) { 2447 /* This question ($avalue) is the question that provides the source information we use 2448 * to determine which answers show up in the question we're looking at, which is $qrow['qid'] 2449 * So, in other words, we're currently working on question $qrow['qid'], trying to find out more 2450 * information about question $avalue['qid'], because that's the source */ 2451 $sources[$qrow['qid']] = $avalue['qid']; /* This question ($qrow['qid']) relies on answers in $avalue['qid'] */ 2452 if (isset($cascades)) {unset($cascades); } 2453 $cascades = array(); /* Create an empty array */ 2454 2455 /* At this stage, we know for sure that this question relies on one other question for the filter */ 2456 /* But this function wants to send back information about questions that rely on multiple other questions for the filter */ 2457 /* So we don't want to do anything yet */ 2458 2459 /* What we need to do now, is check whether the question this one relies on, also relies on another */ 2460 2461 /* The question we are now checking is $avalue['qid'] */ 2462 $keepgoing = 1; 2463 $questiontocheck = $avalue['qid']; 2464 /* If there is a key in the $sources array that is equal to $avalue['qid'] then we want to add that 2465 * to the $cascades array */ 2466 while ($keepgoing > 0) { 2467 if (!empty($sources[$questiontocheck])) { 2468 $cascades[] = $sources[$questiontocheck]; 2469 /* Now we need to move down the chain */ 2470 /* We want to check the $sources[$questiontocheck] question */ 2471 $questiontocheck = $sources[$questiontocheck]; 2472 } else { 2473 /* Since it was empty, there must not be any more questions down the cascade */ 2474 $keepgoing = 0; 2475 } 2476 } 2477 /* Now add all that info */ 2478 if (count($cascades) > 0) { 2479 $cascaded[$qrow['qid']] = $cascades; 2480 } 2481 } 2482 } 2483 } 2484 } 2485 $cascade2 = array(); 2486 if ($output == "title") { 2487 foreach ($cascaded as $key=>$cascade) { 2488 foreach ($cascade as $item) { 2489 $cascade2[$key][] = $qidtotitle[$item]; 2490 } 2491 } 2492 $cascaded = $cascade2; 2493 } 2494 return $cascaded; 2495} 2496 2497function createPassword() 2498{ 2499 $aCharacters = "ABCDEGHJIKLMNOPQURSTUVWXYZabcdefhjmnpqrstuvwxyz23456789"; 2500 $iPasswordLength = 12; 2501 $sPassword = ''; 2502 for ($i = 0; $i < $iPasswordLength; $i++) { 2503 $sPassword .= $aCharacters[(int) floor(rand(0, strlen($aCharacters) - 1))]; 2504 } 2505 return $sPassword; 2506} 2507 2508// TODO input Survey Object 2509function languageDropdown($surveyid, $selected) 2510{ 2511 $slangs = Survey::model()->findByPk($surveyid)->additionalLanguages; 2512 $baselang = Survey::model()->findByPk($surveyid)->language; 2513 array_unshift($slangs, $baselang); 2514 $html = "<select class='listboxquestions' name='langselect' onchange=\"window.open(this.options[this.selectedIndex].value, '_top')\">\n"; 2515 2516 foreach ($slangs as $lang) { 2517 $link = Yii::app()->homeUrl.("/admin/dataentry/sa/view/surveyid/".$surveyid."/lang/".$lang); 2518 if ($lang == $selected) { 2519 $html .= "\t<option value='{$link}' selected='selected'>".getLanguageNameFromCode($lang, false)."</option>\n"; 2520 } 2521 if ($lang != $selected) { 2522 $html .= "\t<option value='{$link}'>".getLanguageNameFromCode($lang, false)."</option>\n"; 2523 } 2524 } 2525 $html .= "</select>"; 2526 return $html; 2527} 2528 2529// TODO input Survey Object 2530/** 2531 * Creates a <select> HTML element for language selection for this survey 2532 * 2533 * @param int $surveyid 2534 * @param string $selected The selected language 2535 * @return string 2536 */ 2537function languageDropdownClean($surveyid, $selected) 2538{ 2539 $slangs = Survey::model()->findByPk($surveyid)->additionalLanguages; 2540 $baselang = Survey::model()->findByPk($surveyid)->language; 2541 array_unshift($slangs, $baselang); 2542 $html = "<select class='form-control listboxquestions' id='language' name='language'>\n"; 2543 foreach ($slangs as $lang) { 2544 if ($lang == $selected) { 2545 $html .= "\t<option value='$lang' selected='selected'>".getLanguageNameFromCode($lang, false)."</option>\n"; 2546 } 2547 if ($lang != $selected) { 2548 $html .= "\t<option value='$lang'>".getLanguageNameFromCode($lang, false)."</option>\n"; 2549 } 2550 } 2551 $html .= "</select>"; 2552 return $html; 2553} 2554 2555/** 2556* This function removes a directory recursively 2557* 2558* @param string $dirname 2559* @return bool 2560*/ 2561function rmdirr($dirname) 2562{ 2563 // Sanity check 2564 if (!file_exists($dirname)) { 2565 return false; 2566 } 2567 2568 // Simple delete for a file 2569 if (is_file($dirname) || is_link($dirname)) { 2570 return @unlink($dirname); 2571 } 2572 2573 // Loop through the folder 2574 $dir = dir($dirname); 2575 while (false !== $entry = $dir->read()) { 2576 // Skip pointers 2577 if ($entry == '.' || $entry == '..') { 2578 continue; 2579 } 2580 2581 // Recurse 2582 rmdirr($dirname.DIRECTORY_SEPARATOR.$entry); 2583 } 2584 2585 // Clean up 2586 $dir->close(); 2587 return @rmdir($dirname); 2588} 2589 2590/** 2591* This function removes surrounding and masking quotes from the CSV field 2592* 2593* @param mixed $field 2594* @return mixed 2595*/ 2596function CSVUnquote($field) 2597{ 2598 //print $field.":"; 2599 $field = preg_replace("/^\040*\"/", "", $field); 2600 $field = preg_replace("/\"\040*$/", "", $field); 2601 $field = str_replace('""', '"', $field); 2602 //print $field."\n"; 2603 return $field; 2604} 2605 2606/** 2607* This function return actual completion state 2608* 2609* @return string|boolean (complete|incomplete|all) or false 2610*/ 2611function incompleteAnsFilterState() 2612{ 2613 $letsfilter = returnGlobal('completionstate'); //read get/post completionstate 2614 2615 // first let's initialize the incompleteanswers session variable 2616 if ($letsfilter != '') { 2617// use the read value if not empty 2618 Yii::app()->session['incompleteanswers'] = $letsfilter; 2619 } elseif (empty(Yii::app()->session['incompleteanswers'])) { 2620// sets default variable value from config file 2621 Yii::app()->session['incompleteanswers'] = Yii::app()->getConfig('filterout_incomplete_answers'); 2622 } 2623 2624 if (Yii::app()->session['incompleteanswers'] == 'complete' || Yii::app()->session['incompleteanswers'] == 'all' || Yii::app()->session['incompleteanswers'] == 'incomplete') { 2625 return Yii::app()->session['incompleteanswers']; 2626 } else { 2627// last resort is to prevent filtering 2628 return false; 2629 } 2630} 2631 2632 2633/** 2634* isCaptchaEnabled($screen, $usecaptchamode) 2635* @param string $screen - the screen name for which to test captcha activation 2636* 2637* @return boolean|null - returns true if captcha must be enabled 2638**/ 2639function isCaptchaEnabled($screen, $captchamode = '') 2640{ 2641 if (!extension_loaded('gd')) { 2642 return false; 2643 } 2644 switch ($screen) { 2645 case 'registrationscreen': 2646 if ($captchamode == 'A' || 2647 $captchamode == 'B' || 2648 $captchamode == 'D' || 2649 $captchamode == 'R') { 2650 return true; 2651 } 2652 return false; 2653 case 'surveyaccessscreen': 2654 if ($captchamode == 'A' || 2655 $captchamode == 'B' || 2656 $captchamode == 'C' || 2657 $captchamode == 'X') { 2658 return true; 2659 } 2660 return false; 2661 case 'saveandloadscreen': 2662 if ($captchamode == 'A' || 2663 $captchamode == 'C' || 2664 $captchamode == 'D' || 2665 $captchamode == 'S') { 2666 return true; 2667 } 2668 return false; 2669 default: 2670 return true; 2671 } 2672} 2673 2674 2675/** 2676* Check if a table does exist in the database 2677* 2678* @param string $sTableName Table name to check for (without dbprefix!)) 2679* @return boolean True or false if table exists or not 2680*/ 2681function tableExists($sTableName) 2682{ 2683 $sTableName = Yii::app()->db->tablePrefix.str_replace(array('{', '}'), array('', ''), $sTableName); 2684 return in_array($sTableName, Yii::app()->db->schema->getTableNames()); 2685} 2686 2687// Returns false if the survey is anonymous, 2688// and a survey participants table exists: in this case the completed field of a token 2689// will contain 'Y' instead of the submitted date to ensure privacy 2690// Returns true otherwise 2691function isTokenCompletedDatestamped($thesurvey) 2692{ 2693 if ($thesurvey['anonymized'] == 'Y' && tableExists('tokens_'.$thesurvey['sid'])) { 2694 return false; 2695 } else { 2696 return true; 2697 } 2698} 2699 2700/** 2701* example usage 2702* $date = "2006-12-31 21:00"; 2703* $shift "+6 hours"; // could be days, weeks... see function strtotime() for usage 2704* 2705* echo sql_date_shift($date, "Y-m-d H:i:s", $shift); 2706* 2707* will output: 2007-01-01 03:00:00 2708* 2709* @param string $date 2710* @param string $dformat 2711* @param mixed $shift 2712* @return string 2713*/ 2714function dateShift($date, $dformat, $shift) 2715{ 2716 return date($dformat, strtotime($shift, strtotime($date))); 2717} 2718 2719 2720// getBounceEmail: returns email used to receive error notifications 2721function getBounceEmail($surveyid) 2722{ 2723 $surveyInfo = getSurveyInfo($surveyid); 2724 2725 if ($surveyInfo['bounce_email'] == '') { 2726 return null; // will be converted to from in MailText 2727 } else { 2728 return $surveyInfo['bounce_email']; 2729 } 2730} 2731 2732// getEmailFormat: returns email format for the survey 2733// returns 'text' or 'html' 2734function getEmailFormat($surveyid) 2735{ 2736 $surveyInfo = getSurveyInfo($surveyid); 2737 if ($surveyInfo['htmlemail'] == 'Y') { 2738 return 'html'; 2739 } else { 2740 return 'text'; 2741 } 2742 2743} 2744 2745// Check if user has manage rights for a template 2746function hasTemplateManageRights($userid, $sThemeFolder) 2747{ 2748 $userid = (int) $userid; 2749 $sThemeFolder = sanitize_paranoid_string($sThemeFolder); 2750 if ($sThemeFolder === false) { 2751 return false; 2752 } 2753 return Permission::model()->hasTemplatePermission($sThemeFolder, 'read', $userid); 2754} 2755 2756 2757/** 2758* Translate links which are in any answer/question/survey/email template/label set to their new counterpart 2759* 2760* @param string $sType 'survey' or 'label' 2761* @param mixed $iOldSurveyID 2762* @param mixed $iNewSurveyID 2763* @param mixed $sString 2764* @return string 2765*/ 2766function translateLinks($sType, $iOldSurveyID, $iNewSurveyID, $sString) 2767{ 2768 $iOldSurveyID = (int) $iOldSurveyID; 2769 $iNewSurveyID = (int) $iNewSurveyID; // To avoid injection of a /e regex modifier without having to check all execution paths 2770 if ($sType == 'survey') { 2771 $sPattern = '(http(s)?:\/\/)?(([a-z0-9\/\.])*(?=(\/upload))\/upload\/surveys\/'.$iOldSurveyID.'\/)'; 2772 $sReplace = Yii::app()->getConfig("publicurl")."upload/surveys/{$iNewSurveyID}/"; 2773 return preg_replace('/'.$sPattern.'/u', $sReplace, $sString); 2774 } elseif ($sType == 'label') { 2775 $sPattern = '(http(s)?:\/\/)?(([a-z0-9\/\.])*(?=(\/upload))\/upload\/labels\/'.$iOldSurveyID.'\/)'; 2776 $sReplace = Yii::app()->getConfig("publicurl")."upload/labels/{$iNewSurveyID}/"; 2777 return preg_replace("/".$sPattern."/u", $sReplace, $sString); 2778 } else // unknown type 2779 { 2780 return $sString; 2781 } 2782} 2783 2784/** 2785 * This function creates the old fieldnames for survey import 2786 * 2787 * @param mixed $iOldSID The old survey id 2788 * @param integer $iNewSID The new survey id 2789 * @param array $aGIDReplacements An array with group ids (oldgid=>newgid) 2790 * @param array $aQIDReplacements An array with question ids (oldqid=>newqid) 2791 * @return array|bool 2792 */ 2793function reverseTranslateFieldNames($iOldSID, $iNewSID, $aGIDReplacements, $aQIDReplacements) 2794{ 2795 $aGIDReplacements = array_flip($aGIDReplacements); 2796 $aQIDReplacements = array_flip($aQIDReplacements); 2797 2798 /** @var Survey $oNewSurvey */ 2799 $oNewSurvey = Survey::model()->findByPk($iNewSID); 2800 2801 if ($iOldSID == $iNewSID) { 2802 $forceRefresh = true; // otherwise grabs the cached copy and throws undefined index exceptions 2803 } else { 2804 $forceRefresh = false; 2805 } 2806 $aFieldMap = createFieldMap($oNewSurvey, 'short', $forceRefresh, false, $oNewSurvey->language); 2807 2808 $aFieldMappings = array(); 2809 foreach ($aFieldMap as $sFieldname=>$aFieldinfo) { 2810 if ($aFieldinfo['qid'] != null) { 2811 $aFieldMappings[$sFieldname] = $iOldSID.'X'.$aGIDReplacements[$aFieldinfo['gid']].'X'.$aQIDReplacements[$aFieldinfo['qid']].$aFieldinfo['aid']; 2812 if ($aFieldinfo['type'] == '1') { 2813 $aFieldMappings[$sFieldname] = $aFieldMappings[$sFieldname].'#'.$aFieldinfo['scale_id']; 2814 } 2815 // now also add a shortened field mapping which is needed for certain kind of condition mappings 2816 $aFieldMappings[$iNewSID.'X'.$aFieldinfo['gid'].'X'.$aFieldinfo['qid']] = $iOldSID.'X'.$aGIDReplacements[$aFieldinfo['gid']].'X'.$aQIDReplacements[$aFieldinfo['qid']]; 2817 // Shortened field mapping for timings table 2818 $aFieldMappings[$iNewSID.'X'.$aFieldinfo['gid']] = $iOldSID.'X'.$aGIDReplacements[$aFieldinfo['gid']]; 2819 } 2820 } 2821 return array_flip($aFieldMappings); 2822} 2823 2824/** 2825 * put your comment there... 2826 * 2827 * @param integer $id 2828 * @param string $type 2829 * @return bool 2830 */ 2831function hasResources($id, $type = 'survey') 2832{ 2833 $dirname = Yii::app()->getConfig("uploaddir"); 2834 2835 if ($type == 'survey') { 2836 $dirname .= "/surveys/$id"; 2837 } elseif ($type == 'label') { 2838 $dirname .= "/labels/$id"; 2839 } else { 2840 return false; 2841 } 2842 2843 if (is_dir($dirname) && $dh = opendir($dirname)) { 2844 while (($entry = readdir($dh)) !== false) { 2845 if ($entry !== '.' && $entry !== '..') { 2846 return true; 2847 } 2848 } 2849 closedir($dh); 2850 } else { 2851 return false; 2852 } 2853 2854 return false; 2855} 2856 2857/** 2858 * Creates a random sequence of characters 2859 * 2860 * @param integer $length Length of resulting string 2861 * @param string $pattern To define which characters should be in the resulting string 2862 * @return string 2863 */ 2864function randomChars($length, $pattern = "23456789abcdefghijkmnpqrstuvwxyz") 2865{ 2866 $patternlength = strlen($pattern) - 1; 2867 $key = ''; 2868 for ($i = 0; $i < $length; $i++) { 2869 $key .= $pattern[mt_rand(0, $patternlength)]; 2870 } 2871 return $key; 2872} 2873 2874/** 2875* used to translate simple text to html (replacing \n with <br /> 2876* 2877* @param mixed $mytext 2878* @param mixed $ishtml 2879* @return mixed 2880*/ 2881function conditionalNewlineToBreak($mytext, $ishtml, $encoded = '') 2882{ 2883 if ($ishtml === true) { 2884 // $mytext has been processed by gT with html mode 2885 // and thus \n has already been translated to 2886 if ($encoded == '') { 2887 $mytext = str_replace(' ', '<br />', $mytext); 2888 } 2889 return str_replace("\n", '<br />', $mytext); 2890 } else { 2891 return $mytext; 2892 } 2893} 2894 2895 2896function breakToNewline($data) 2897{ 2898 return preg_replace('!<br.*>!iU', "\n", $data); 2899} 2900 2901/** 2902* Provides a safe way to end the application 2903* 2904* @param mixed $sText 2905* @returns boolean Fake return so Scrutinizes shuts up 2906*/ 2907function safeDie($sText) 2908{ 2909 //Only allowed tag: <br /> 2910 $textarray = explode('<br />', $sText); 2911 $textarray = array_map('htmlspecialchars', $textarray); 2912 die(implode('<br />', $textarray)); 2913 return false; // do not remove 2914} 2915 2916/** 2917 * @param string $str 2918 */ 2919function fixCKeditorText($str) 2920{ 2921 $str = str_replace('<br type="_moz" />', '', $str); 2922 if ($str == "<br />" || $str == " " || $str == " ") { 2923 $str = ""; 2924 } 2925 if (preg_match("/^[\s]+$/", $str)) { 2926 $str = ''; 2927 } 2928 if ($str == "\n") { 2929 $str = ""; 2930 } 2931 if (trim($str) == " " || trim($str) == '') { 2932// chrome adds a single element to empty fckeditor fields 2933 $str = ""; 2934 } 2935 2936 return $str; 2937} 2938 2939 2940/** 2941 * This is a helper function for getAttributeFieldNames 2942 * 2943 * @param mixed $fieldname 2944 * @return bool 2945 */ 2946function filterForAttributes($fieldname) 2947{ 2948 if (strpos($fieldname, 'attribute_') === false) { 2949 return false; 2950 } else { 2951 return true; 2952 } 2953 } 2954 2955/** 2956* Retrieves the attribute field names from the related survey participants table 2957* 2958* @param mixed $iSurveyID The survey ID 2959* @return array The fieldnames 2960*/ 2961function getAttributeFieldNames($iSurveyID) 2962{ 2963 $survey = Survey::model()->findByPk($iSurveyID); 2964 if (!$survey->hasTokensTable || !$table = Yii::app()->db->schema->getTable($survey->tokensTableName)) { 2965 return Array(); 2966 } 2967 2968 return array_filter(array_keys($table->columns), 'filterForAttributes'); 2969 2970} 2971 2972/** 2973 * Returns the full list of attribute token fields including the properties for each field 2974 * Use this instead of plain Survey::model()->findByPk($iSurveyID)->tokenAttributes calls because Survey::model()->findByPk($iSurveyID)->tokenAttributes may contain old descriptions where the fields does not physically exist 2975 * 2976 * @param integer $iSurveyID The Survey ID 2977 * @return array 2978 */ 2979function getParticipantAttributes($iSurveyID) 2980{ 2981 $survey = Survey::model()->findByPk($iSurveyID); 2982 if (!$survey->hasTokensTable || !Yii::app()->db->schema->getTable($survey->tokensTableName)) { 2983 return Array(); 2984 } 2985 return getTokenFieldsAndNames($iSurveyID, true); 2986} 2987 2988 2989 2990 2991 2992/** 2993* Retrieves the attribute names from the related survey participants table 2994* 2995* @param mixed $surveyid The survey ID 2996* @param boolean $bOnlyAttributes Set this to true if you only want the fieldnames of the additional attribue fields - defaults to false 2997* @return array The fieldnames as key and names as value in an Array 2998*/ 2999function getTokenFieldsAndNames($surveyid, $bOnlyAttributes = false) 3000{ 3001 3002 3003 $aBasicTokenFields = array('firstname'=>array( 3004 'description'=>gT('First name'), 3005 'mandatory'=>'N', 3006 'showregister'=>'Y' 3007 ), 3008 'lastname'=>array( 3009 'description'=>gT('Last name'), 3010 'mandatory'=>'N', 3011 'showregister'=>'Y' 3012 ), 3013 'email'=>array( 3014 'description'=>gT('Email address'), 3015 'mandatory'=>'N', 3016 'showregister'=>'Y' 3017 ), 3018 'emailstatus'=>array( 3019 'description'=>gT("Email status"), 3020 'mandatory'=>'N', 3021 'showregister'=>'N' 3022 ), 3023 'token'=>array( 3024 'description'=>gT('Token'), 3025 'mandatory'=>'N', 3026 'showregister'=>'Y' 3027 ), 3028 'language'=>array( 3029 'description'=>gT('Language code'), 3030 'mandatory'=>'N', 3031 'showregister'=>'Y' 3032 ), 3033 'sent'=>array( 3034 'description'=>gT('Invitation sent date'), 3035 'mandatory'=>'N', 3036 'showregister'=>'Y' 3037 ), 3038 'remindersent'=>array( 3039 'description'=>gT('Last reminder sent date'), 3040 'mandatory'=>'N', 3041 'showregister'=>'Y' 3042 ), 3043 'remindercount'=>array( 3044 'description'=>gT('Total numbers of sent reminders'), 3045 'mandatory'=>'N', 3046 'showregister'=>'Y' 3047 ), 3048 'usesleft'=>array( 3049 'description'=>gT('Uses left'), 3050 'mandatory'=>'N', 3051 'showregister'=>'Y' 3052 ), 3053 ); 3054 3055 $aExtraTokenFields = getAttributeFieldNames($surveyid); 3056 $aSavedExtraTokenFields = Survey::model()->findByPk($surveyid)->tokenAttributes; 3057 3058 // Drop all fields that are in the saved field description but not in the table definition 3059 $aSavedExtraTokenFields = array_intersect_key($aSavedExtraTokenFields, array_flip($aExtraTokenFields)); 3060 3061 // Now add all fields that are in the table but not in the field description 3062 foreach ($aExtraTokenFields as $sField) { 3063 if (!isset($aSavedExtraTokenFields[$sField])) { 3064 $aSavedExtraTokenFields[$sField] = array( 3065 'description'=>$sField, 3066 'mandatory'=>'N', 3067 'showregister'=>'N', 3068 'cpdbmap'=>'' 3069 ); 3070 } elseif (empty($aSavedExtraTokenFields[$sField]['description'])) { 3071 $aSavedExtraTokenFields[$sField]['description'] = $sField; 3072 } 3073 } 3074 if ($bOnlyAttributes) { 3075 return $aSavedExtraTokenFields; 3076 } else { 3077 return array_merge($aBasicTokenFields, $aSavedExtraTokenFields); 3078 } 3079} 3080 3081 3082/** 3083* This function strips any content between and including <javascript> tags 3084* 3085* @param string $sContent String to clean 3086* @return string Cleaned string 3087*/ 3088function stripJavaScript($sContent) 3089{ 3090 $text = preg_replace('@<script[^>]*?>.*?</script>@si', '', $sContent); 3091 // TODO : Adding the onload/onhover etc ... or remove this false security function 3092 return $text; 3093} 3094 3095/** 3096* This function converts emebedded Javascript to Text 3097* 3098* @param string $sContent String to clean 3099* @return string Cleaned string 3100*/ 3101function showJavaScript($sContent) 3102{ 3103 $text = preg_replace_callback('@<script[^>]*?>.*?</script>@si', 3104 function($matches) { 3105 return htmlspecialchars($matches[0]); 3106 }, $sContent); 3107 return $text; 3108} 3109 3110/** 3111* This function cleans files from the temporary directory being older than 1 day 3112* @todo Make the days configurable 3113*/ 3114function cleanTempDirectory() 3115{ 3116 $dir = Yii::app()->getConfig('tempdir').DIRECTORY_SEPARATOR; 3117 $dp = opendir($dir) or safeDie('Could not open temporary directory'); 3118 while ($file = readdir($dp)) { 3119 if (is_file($dir.$file) && (filemtime($dir.$file)) < (strtotime('-1 days')) && $file != 'index.html' && $file != '.gitignore' && $file != 'readme.txt') { 3120 /** @scrutinizer ignore-unhandled */ @unlink($dir.$file); 3121 } 3122 } 3123 $dir = Yii::app()->getConfig('tempdir').DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR; 3124 $dp = opendir($dir) or safeDie('Could not open temporary upload directory'); 3125 while ($file = readdir($dp)) { 3126 if (is_file($dir.$file) && (filemtime($dir.$file)) < (strtotime('-1 days')) && $file != 'index.html' && $file != '.gitignore' && $file != 'readme.txt') { 3127 /** @scrutinizer ignore-unhandled */ @unlink($dir.$file); 3128 } 3129 } 3130 closedir($dp); 3131} 3132 3133function useFirebug() 3134{ 3135 if (FIREBUG == true) { 3136 App()->getClientScript()->registerScriptFile('http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'); 3137 }; 3138}; 3139 3140/** 3141* This is a convenience function for the coversion of datetime values 3142* 3143* @param mixed $value 3144* @param string $fromdateformat 3145* @param mixed $todateformat 3146* @return string 3147*/ 3148function convertDateTimeFormat($value, $fromdateformat, $todateformat) 3149{ 3150 $date = DateTime::createFromFormat($fromdateformat, $value); 3151 if ($date) { 3152 return $date->format($todateformat); 3153 } else { 3154 $date = new DateTime($value); 3155 return $date->format($todateformat); 3156 } 3157} 3158 3159/** 3160* This is a convenience function to convert any date, in any date format, to the global setting date format 3161* Check if the time shoul be rendered also 3162* 3163* @param string $sDate 3164* @param boolean $withTime 3165* @return string 3166*/ 3167function convertToGlobalSettingFormat($sDate, $withTime = false) 3168{ 3169 3170 $sDateformatdata = getDateFormatData(Yii::app()->session['dateformat']); // We get the Global Setting date format 3171 $usedDatetime = ($withTime === true ? $sDateformatdata['phpdate']." H:i" : $sDateformatdata['phpdate']); //return also hours and minutes if asked for 3172 try { 3173 // Workaround for bug in older PHP version (confirmed for 5.5.9) 3174 // The bug is causing invalid dates to create an internal server error which cannot not be caught by try.. catch 3175 if (@strtotime($sDate) === false) { 3176 throw new Exception("Failed to parse date string ({$sDate})"); 3177 } 3178 $oDate = new DateTime($sDate); // We generate the Date object (PHP will deal with the format of the string) 3179 $sDate = $oDate->format($usedDatetime); // We apply it to the Date object to generate a string date 3180 return $sDate; // We return the string date 3181 } catch (Exception $e) { 3182 $oDate = new DateTime('1/1/1980 00:00'); // We generate the Date object (PHP will deal with the format of the string) 3183 $sDate = $oDate->format($usedDatetime); // We apply it to the Date object to generate a string date 3184 return $sDate; // We return the string date 3185 3186 } 3187} 3188 3189/** 3190* This function removes the UTF-8 Byte Order Mark from a string 3191* 3192* @param string $str 3193* @return string 3194*/ 3195function removeBOM($str = "") 3196{ 3197 if (substr($str, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) { 3198 $str = substr($str, 3); 3199 } 3200 return $str; 3201} 3202 3203 3204/** 3205 * This function returns the complete directory path to a given template name 3206 * 3207 * @param mixed $sTemplateName 3208 * @return string 3209 */ 3210function getTemplatePath($sTemplateName = '') 3211{ 3212 return Template::getTemplatePath($sTemplateName); 3213} 3214 3215/** 3216 * This function returns the complete URL path to a given template name 3217 * 3218 * @param mixed $sTemplateName 3219 * @return string 3220 */ 3221function getTemplateURL($sTemplateName) 3222{ 3223 return Template::getTemplateURL($sTemplateName); 3224} 3225 3226/** 3227 * Return an array of subquestions for a given sid/qid 3228 * 3229 * @param int $sid 3230 * @param int $qid 3231 * @param string $sLanguage Language of the subquestion text 3232 * @return array 3233 */ 3234function getSubQuestions($sid, $qid, $sLanguage) 3235{ 3236 3237 static $subquestions; 3238 3239 if (!isset($subquestions[$sid])) { 3240 $subquestions[$sid] = array(); 3241 } 3242 if (!isset($subquestions[$sid][$sLanguage])) { 3243 3244 $query = "SELECT sq.*, q.other FROM {{questions}} as sq, {{questions}} as q" 3245 ." WHERE sq.parent_qid=q.qid AND q.sid=".$sid 3246 ." AND sq.language='".$sLanguage."' " 3247 ." AND q.language='".$sLanguage."' " 3248 ." ORDER BY sq.parent_qid, q.question_order,sq.scale_id , sq.question_order"; 3249 3250 $query = Yii::app()->db->createCommand($query)->query(); 3251 3252 $resultset = array(); 3253 //while ($row=$result->FetchRow()) 3254 foreach ($query->readAll() as $row) { 3255 $resultset[$row['parent_qid']][] = $row; 3256 } 3257 $subquestions[$sid][$sLanguage] = $resultset; 3258 } 3259 if (isset($subquestions[$sid][$sLanguage][$qid])) { 3260 return $subquestions[$sid][$sLanguage][$qid]; 3261 } 3262 return array(); 3263} 3264 3265/** 3266* Wrapper function to retrieve an xmlwriter object and do error handling if it is not compiled 3267* into PHP 3268*/ 3269function getXMLWriter() 3270{ 3271 if (!extension_loaded('xmlwriter')) { 3272 safeDie('XMLWriter class not compiled into PHP, please contact your system administrator'); 3273 } 3274 return new XMLWriter(); 3275} 3276 3277/** 3278* SSLRedirect() generates a redirect URL for the appropriate SSL mode then applies it. 3279* (Was redirect() before CodeIgniter port.) 3280* 3281* @param string $enforceSSLMode string 's' or '' (empty). 3282*/ 3283function SSLRedirect($enforceSSLMode) 3284{ 3285 $url = 'http'.$enforceSSLMode.'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 3286 if (!headers_sent()) { 3287// If headers not sent yet... then do php redirect 3288 //ob_clean(); 3289 header('Location: '.$url); 3290 //ob_flush(); 3291 Yii::app()->end(); 3292 }; 3293}; 3294 3295/** 3296* enforceSSLMode() $force_ssl is on or off, it checks if the current 3297* request is to HTTPS (or not). If $force_ssl is on, and the 3298* request is not to HTTPS, it redirects the request to the HTTPS 3299* version of the URL, if the request is to HTTPS, it rewrites all 3300* the URL variables so they also point to HTTPS. 3301*/ 3302function enforceSSLMode() 3303{ 3304 $bForceSSL = ''; // off 3305 $bSSLActive = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != "off") || 3306 (isset($_SERVER['HTTP_FORWARDED_PROTO']) && $_SERVER['HTTP_FORWARDED_PROTO'] == "https") || 3307 (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https")); 3308 if (Yii::app()->getConfig('ssl_emergency_override') !== true) { 3309 $bForceSSL = strtolower(getGlobalSetting('force_ssl')); 3310 } 3311 if ($bForceSSL == 'on' && !$bSSLActive) { 3312 SSLRedirect('s'); 3313 } 3314 3315}; 3316 3317 3318/** 3319 * Creates an array with details on a particular response for display purposes 3320 * Used in Print answers, Detailed response view and Detailed admin notification email 3321 * 3322 * @param mixed $iSurveyID 3323 * @param mixed $iResponseID 3324 * @param mixed $sLanguageCode 3325 * @param boolean $bHonorConditions Apply conditions 3326 * @return array 3327 */ 3328function getFullResponseTable($iSurveyID, $iResponseID, $sLanguageCode, $bHonorConditions = true) 3329{ 3330 $survey = Survey::model()->findByPk($iSurveyID); 3331 $aFieldMap = createFieldMap($survey, 'full', false, false, $sLanguageCode); 3332 3333 //Get response data 3334 $idrow = SurveyDynamic::model($iSurveyID)->findByAttributes(array('id'=>$iResponseID)); 3335 3336 // Create array of non-null values - those are the relevant ones 3337 $aRelevantFields = array(); 3338 3339 foreach ($aFieldMap as $sKey=>$fname) { 3340 if (LimeExpressionManager::QuestionIsRelevant($fname['qid']) || $bHonorConditions === false) { 3341 $aRelevantFields[$sKey] = $fname; 3342 } 3343 } 3344 3345 $aResultTable = array(); 3346 $oldgid = 0; 3347 $oldqid = 0; 3348 foreach ($aRelevantFields as $sKey=>$fname) { 3349 if (!empty($fname['qid'])) { 3350 $attributes = QuestionAttribute::model()->getQuestionAttributes($fname['qid']); 3351 if (getQuestionAttributeValue($attributes, 'hidden') == 1) { 3352 continue; 3353 } 3354 } 3355 $question = $fname['question']; 3356 $subquestion = ''; 3357 if (isset($fname['gid']) && !empty($fname['gid'])) { 3358 //Check to see if gid is the same as before. if not show group name 3359 if ($oldgid !== $fname['gid']) { 3360 $oldgid = $fname['gid']; 3361 if (LimeExpressionManager::GroupIsRelevant($fname['gid']) || $bHonorConditions === false) { 3362 $aResultTable['gid_'.$fname['gid']] = array($fname['group_name'], QuestionGroup::model()->getGroupDescription($fname['gid'], $sLanguageCode)); 3363 } 3364 } 3365 } 3366 if (!empty($fname['qid'])) { 3367 if ($oldqid !== $fname['qid']) { 3368 $oldqid = $fname['qid']; 3369 if (isset($fname['subquestion']) || isset($fname['subquestion1']) || isset($fname['subquestion2'])) { 3370 $aResultTable['qid_'.$fname['sid'].'X'.$fname['gid'].'X'.$fname['qid']] = array($fname['question'], '', ''); 3371 } else { 3372 $answer = getExtendedAnswer($iSurveyID, $fname['fieldname'], $idrow[$fname['fieldname']], $sLanguageCode); 3373 $aResultTable[$fname['fieldname']] = array($question, '', $answer); 3374 continue; 3375 } 3376 } 3377 } else { 3378 $answer = getExtendedAnswer($iSurveyID, $fname['fieldname'], $idrow[$fname['fieldname']], $sLanguageCode); 3379 $aResultTable[$fname['fieldname']] = array($question, '', $answer); 3380 continue; 3381 } 3382 if (isset($fname['subquestion'])) { 3383 $subquestion = "[{$fname['subquestion']}]"; 3384 } 3385 3386 if (isset($fname['subquestion1'])) { 3387 $subquestion = "[{$fname['subquestion1']}]"; 3388 } 3389 3390 if (isset($fname['subquestion2'])) { 3391 $subquestion .= "[{$fname['subquestion2']}]"; 3392 } 3393 3394 $answer = getExtendedAnswer($iSurveyID, $fname['fieldname'], $idrow[$fname['fieldname']], $sLanguageCode); 3395 $aResultTable[$fname['fieldname']] = array($question, $subquestion, $answer); 3396 } 3397 return $aResultTable; 3398} 3399 3400/** 3401 * Check if $str is an integer, or string representation of an integer 3402 * 3403 * @param string $mStr 3404 * @return bool|int 3405 */ 3406function isNumericInt($mStr) 3407{ 3408 if (is_int($mStr)) { 3409 return true; 3410 } elseif (is_string($mStr)) { 3411 return preg_match("/^[0-9]+$/", $mStr); 3412 } 3413 return false; 3414} 3415 3416/** 3417* Implode and sort content array for very long arrays 3418* 3419* @param string $sDelimeter 3420* @param array $aArray 3421* @return string String showing array content 3422*/ 3423function short_implode($sDelimeter, $sHyphen, $aArray) 3424{ 3425 if (sizeof($aArray) < Yii::app()->getConfig('minlengthshortimplode')) { 3426 sort($aArray); 3427 return implode($sDelimeter, $aArray); 3428 } else { 3429 sort($aArray); 3430 $iIndexA = 0; 3431 $sResult = null; 3432 while ($iIndexA < sizeof($aArray)) { 3433 if ($iIndexA == 0) { 3434 $sResult = $aArray[$iIndexA]; 3435 } else { 3436 if (strlen($sResult) > Yii::app()->getConfig('maxstringlengthshortimplode') - strlen($sDelimeter) - 3) { 3437 return $sResult.$sDelimeter.'...'; 3438 } else { 3439 $sResult = $sResult.$sDelimeter.$aArray[$iIndexA]; 3440 } 3441 } 3442 $iIndexB = $iIndexA + 1; 3443 if ($iIndexB < sizeof($aArray)) { 3444 while ($iIndexB < sizeof($aArray) && $aArray[$iIndexB] - 1 == $aArray[$iIndexB - 1]) { 3445 $iIndexB++; 3446 } 3447 if ($iIndexA < $iIndexB - 1) { 3448 $sResult = $sResult.$sHyphen.$aArray[$iIndexB - 1]; 3449 } 3450 } 3451 $iIndexA = $iIndexB; 3452 } 3453 return $sResult; 3454 } 3455} 3456 3457/** 3458* Include Keypad headers 3459*/ 3460function includeKeypad() 3461{ 3462 App()->getClientScript()->registerScriptFile(Yii::app()->getConfig('third_party').'jquery-keypad/jquery.plugin.min.js'); 3463 App()->getClientScript()->registerScriptFile(Yii::app()->getConfig('third_party').'jquery-keypad/jquery.keypad.min.js'); 3464 $localefile = Yii::app()->getConfig('rootdir').'/third_party/jquery-keypad/jquery.keypad-'.App()->language.'.js'; 3465 if (App()->language != 'en' && file_exists($localefile)) { 3466 Yii::app()->getClientScript()->registerScriptFile(Yii::app()->getConfig('third_party').'jquery-keypad/jquery.keypad-'.App()->language.'.js'); 3467 } 3468 Yii::app()->getClientScript()->registerCssFile(Yii::app()->getConfig('third_party')."jquery-keypad/jquery.keypad.alt.css"); 3469} 3470 3471 3472/** 3473* This function replaces the old insertans tags with new ones across a survey 3474* 3475* @param string $newsid Old SID 3476* @param string $oldsid New SID 3477* @param mixed $fieldnames Array array('oldfieldname'=>'newfieldname') 3478*/ 3479function translateInsertansTags($newsid, $oldsid, $fieldnames) 3480{ 3481 uksort($fieldnames, function($a, $b) {return strlen($a) < strlen($b); }); 3482 3483 Yii::app()->loadHelper('database'); 3484 $newsid = (int) $newsid; 3485 $oldsid = (int) $oldsid; 3486 3487 # translate 'surveyls_urldescription' and 'surveyls_url' INSERTANS tags in surveyls 3488 $sql = "SELECT surveyls_survey_id, surveyls_language, surveyls_urldescription, surveyls_url from {{surveys_languagesettings}} 3489 WHERE surveyls_survey_id=".$newsid." AND (surveyls_urldescription LIKE '%{$oldsid}X%' OR surveyls_url LIKE '%{$oldsid}X%')"; 3490 $result = dbExecuteAssoc($sql) or safeDie("Can't read groups table in translateInsertansTags"); // Checked 3491 3492 //while ($qentry = $res->FetchRow()) 3493 foreach ($result->readAll() as $qentry) { 3494 $urldescription = $qentry['surveyls_urldescription']; 3495 $endurl = $qentry['surveyls_url']; 3496 $language = $qentry['surveyls_language']; 3497 3498 foreach ($fieldnames as $sOldFieldname=>$sNewFieldname) { 3499 $pattern = $sOldFieldname; 3500 $replacement = $sNewFieldname; 3501 $urldescription = preg_replace('/'.$pattern.'/', $replacement, $urldescription); 3502 $endurl = preg_replace('/'.$pattern.'/', $replacement, $endurl); 3503 } 3504 3505 if (strcmp($urldescription, $qentry['surveyls_urldescription']) != 0 || 3506 (strcmp($endurl, $qentry['surveyls_url']) != 0)) { 3507 3508 // Update Field 3509 3510 $data = array( 3511 'surveyls_urldescription' => $urldescription, 3512 'surveyls_url' => $endurl 3513 ); 3514 3515 $where = array( 3516 'surveyls_survey_id' => $newsid, 3517 'surveyls_language' => $language 3518 ); 3519 3520 SurveyLanguageSetting::model()->updateRecord($data, $where); 3521 3522 } // Enf if modified 3523 } // end while qentry 3524 3525 # translate 'quotals_urldescrip' and 'quotals_url' INSERTANS tags in quota_languagesettings 3526 $sql = "SELECT quotals_id, quotals_urldescrip, quotals_url from {{quota_languagesettings}} qls, {{quota}} q 3527 WHERE sid=".$newsid." AND q.id=qls.quotals_quota_id AND (quotals_urldescrip LIKE '%{$oldsid}X%' OR quotals_url LIKE '%{$oldsid}X%')"; 3528 $result = dbExecuteAssoc($sql) or safeDie("Can't read quota table in transInsertAns"); // Checked 3529 3530 foreach ($result->readAll() as $qentry) { 3531 $urldescription = $qentry['quotals_urldescrip']; 3532 $endurl = $qentry['quotals_url']; 3533 3534 foreach ($fieldnames as $sOldFieldname=>$sNewFieldname) { 3535 $pattern = $sOldFieldname; 3536 $replacement = $sNewFieldname; 3537 $urldescription = preg_replace('/'.$pattern.'/', $replacement, $urldescription); 3538 $endurl = preg_replace('/'.$pattern.'/', $replacement, $endurl); 3539 } 3540 3541 if (strcmp($urldescription, $qentry['quotals_urldescrip']) != 0 || (strcmp($endurl, $qentry['quotals_url']) != 0)) { 3542 // Update Field 3543 $sqlupdate = "UPDATE {{quota_languagesettings}} SET quotals_urldescrip='".$urldescription."', quotals_url='".$endurl."' WHERE quotals_id={$qentry['quotals_id']}"; 3544 dbExecuteAssoc($sqlupdate) or safeDie("Couldn't update INSERTANS in quota_languagesettings<br />$sqlupdate<br />"); //Checked 3545 } // Enf if modified 3546 } // end while qentry 3547 3548 # translate 'description' INSERTANS tags in groups 3549 $quotedGroups = Yii::app()->db->quoteTableName('{{groups}}'); 3550 $sql = "SELECT gid, language, group_name, description from $quotedGroups 3551 WHERE sid=".$newsid." AND description LIKE '%{$oldsid}X%' OR group_name LIKE '%{$oldsid}X%'"; 3552 $res = dbExecuteAssoc($sql) or safeDie("Can't read groups table in transInsertAns"); // Checked 3553 3554 //while ($qentry = $res->FetchRow()) 3555 foreach ($res->readAll() as $qentry) { 3556 $gpname = $qentry['group_name']; 3557 $description = $qentry['description']; 3558 $gid = $qentry['gid']; 3559 $language = $qentry['language']; 3560 3561 foreach ($fieldnames as $sOldFieldname=>$sNewFieldname) { 3562 $pattern = $sOldFieldname; 3563 $replacement = $sNewFieldname; 3564 $gpname = preg_replace('/'.$pattern.'/', $replacement, $gpname); 3565 $description = preg_replace('/'.$pattern.'/', $replacement, $description); 3566 } 3567 3568 if (strcmp($description, $qentry['description']) != 0 || strcmp($gpname, $qentry['group_name']) != 0) { 3569 // Update Fields 3570 $where = array( 3571 'gid' => $gid, 3572 'language' => $language 3573 ); 3574 $oGroup = QuestionGroup::model()->findByAttributes($where); 3575 $oGroup->description = $description; 3576 $oGroup->group_name = $gpname; 3577 $oGroup->save(); 3578 3579 } // Enf if modified 3580 } // end while qentry 3581 3582 # translate 'question' and 'help' INSERTANS tags in questions 3583 $sql = "SELECT qid, language, question, help from {{questions}} 3584 WHERE sid=".$newsid." AND (question LIKE '%{$oldsid}X%' OR help LIKE '%{$oldsid}X%')"; 3585 $result = dbExecuteAssoc($sql) or safeDie("Can't read question table in transInsertAns "); // Checked 3586 3587 //while ($qentry = $res->FetchRow()) 3588 $aResultData = $result->readAll(); 3589 foreach ($aResultData as $qentry) { 3590 $question = $qentry['question']; 3591 $help = $qentry['help']; 3592 $qid = $qentry['qid']; 3593 $language = $qentry['language']; 3594 3595 foreach ($fieldnames as $sOldFieldname=>$sNewFieldname) { 3596 $pattern = $sOldFieldname; 3597 $replacement = $sNewFieldname; 3598 $question = preg_replace('/'.$pattern.'/', $replacement, $question); 3599 $help = preg_replace('/'.$pattern.'/', $replacement, $help); 3600 } 3601 3602 if (strcmp($question, $qentry['question']) != 0 || 3603 strcmp($help, $qentry['help']) != 0) { 3604 // Update Field 3605 3606 $data = array( 3607 'question' => $question, 3608 'help' => $help 3609 ); 3610 3611 $where = array( 3612 'qid' => $qid, 3613 'language' => $language 3614 ); 3615 3616 Question::model()->updateByPk($where, $data); 3617 3618 } // Enf if modified 3619 } // end while qentry 3620 3621 # translate 'answer' INSERTANS tags in answers 3622 $result = Answer::model()->oldNewInsertansTags($newsid, $oldsid); 3623 3624 //while ($qentry = $res->FetchRow()) 3625 foreach ($result as $qentry) { 3626 $answer = $qentry['answer']; 3627 $code = $qentry['code']; 3628 $qid = $qentry['qid']; 3629 $language = $qentry['language']; 3630 3631 foreach ($fieldnames as $sOldFieldname=>$sNewFieldname) { 3632 $pattern = $sOldFieldname; 3633 $replacement = $sNewFieldname; 3634 $answer = preg_replace('/'.$pattern.'/', $replacement, $answer); 3635 } 3636 3637 if (strcmp($answer, $qentry['answer']) != 0) { 3638 // Update Field 3639 3640 $data = array( 3641 'answer' => $answer, 3642 'qid' => $qid 3643 ); 3644 3645 $where = array( 3646 'code' => $code, 3647 'language' => $language 3648 ); 3649 3650 Answer::model()->updateRecord($data, $where); 3651 3652 } // Enf if modified 3653 } // end while qentry 3654} 3655 3656/** 3657* Replaces EM variable codes in a current survey with a new one 3658* 3659* @param integer $iSurveyID The survey ID 3660* @param mixed $aCodeMap The codemap array (old_code=>new_code) 3661*/ 3662function replaceExpressionCodes($iSurveyID, $aCodeMap) 3663{ 3664 $arQuestions = Question::model()->findAll("sid=:sid", array(':sid'=>$iSurveyID)); 3665 foreach ($arQuestions as $arQuestion) { 3666 $bModified = false; 3667 foreach ($aCodeMap as $sOldCode=>$sNewCode) { 3668 // Don't search/replace old codes that are too short or were numeric (because they would not have been usable in EM expressions anyway) 3669 if (strlen($sOldCode) > 1 && !is_numeric($sOldCode)) { 3670 $sOldCode = preg_quote($sOldCode, '~'); 3671 $arQuestion->relevance=preg_replace("/\b{$sOldCode}/",$sNewCode,$arQuestion->relevance,-1,$iCount); 3672 $bModified = $bModified || $iCount; 3673 $arQuestion->question = preg_replace("~{[^}]*\K{$sOldCode}(?=[^}]*?})~", $sNewCode, $arQuestion->question, -1, $iCount); 3674 $bModified = $bModified || $iCount; 3675 } 3676 } 3677 if ($bModified) { 3678 $arQuestion->save(); 3679 } 3680 } 3681 $arGroups = QuestionGroup::model()->findAll("sid=:sid", array(':sid'=>$iSurveyID)); 3682 foreach ($arGroups as $arGroup) { 3683 $bModified = false; 3684 foreach ($aCodeMap as $sOldCode=>$sNewCode) { 3685 $sOldCode = preg_quote($sOldCode, '~'); 3686 $arGroup->grelevance=preg_replace("~{[^}]*\K{$sOldCode}(?=[^}]*?})~",$sNewCode,$arGroup->grelevance,-1,$iCount); 3687 $bModified = $bModified || $iCount; 3688 $arGroup->description = preg_replace("~{[^}]*\K{$sOldCode}(?=[^}]*?})~", $sNewCode, $arGroup->description, -1, $iCount); 3689 $bModified = $bModified || $iCount; 3690 } 3691 if ($bModified) { 3692 $arGroup->save(); 3693 } 3694 } 3695} 3696 3697 3698/** 3699* cleanLanguagesFromSurvey() removes any languages from survey tables that are not in the passed list 3700* @param string $sid - the currently selected survey 3701* @param string $availlangs - space separated list of additional languages in survey 3702* @return bool - always returns true 3703*/ 3704function cleanLanguagesFromSurvey($sid, $availlangs) 3705{ 3706 3707 Yii::app()->loadHelper('database'); 3708 // 3709 $sid = sanitize_int($sid); 3710 $baselang = Survey::model()->findByPk($sid)->language; 3711 $aLanguages = []; 3712 if (!empty($availlangs) && $availlangs != " ") { 3713 $availlangs = sanitize_languagecodeS($availlangs); 3714 $aLanguages = explode(" ", $availlangs); 3715 if ($aLanguages[count($aLanguages) - 1] == "") { 3716 array_pop($aLanguages); 3717 } 3718 } 3719 3720 $sqllang = "language <> '".$baselang."' "; 3721 3722 if (!empty($availlangs) && $availlangs != " ") { 3723 foreach ($aLanguages as $lang) { 3724 $sqllang .= "AND language <> '".$lang."' "; 3725 } 3726 } 3727 3728 // Remove From Answer Table 3729 $query = "SELECT qid FROM {{questions}} WHERE sid='{$sid}' AND $sqllang"; 3730 $qidresult = dbExecuteAssoc($query); 3731 3732 foreach ($qidresult->readAll() as $qrow) { 3733 3734 $myqid = $qrow['qid']; 3735 $query = "DELETE FROM {{answers}} WHERE qid='$myqid' AND $sqllang"; 3736 dbExecuteAssoc($query); 3737 } 3738 3739 // Remove From Questions Table 3740 $query = "DELETE FROM {{questions}} WHERE sid='{$sid}' AND $sqllang"; 3741 dbExecuteAssoc($query); 3742 3743 // Remove From QuestionGroup Table 3744 $query = "DELETE FROM ".Yii::app()->db->quoteTableName('{{groups}}')." WHERE sid='{$sid}' AND $sqllang"; 3745 dbExecuteAssoc($query); 3746 3747 return true; 3748} 3749 3750/** 3751* fixLanguageConsistency() fixes missing groups, questions, answers, quotas & assessments for languages on a survey 3752* @param string $sid - the currently selected survey 3753* @param string $availlangs - space separated list of additional languages in survey - if empty all additional languages of a survey are checked against the base language 3754* @return bool - always returns true 3755*/ 3756function fixLanguageConsistency($sid, $availlangs = '') 3757{ 3758 $sid = sanitize_int($sid); 3759 3760 3761 if (trim($availlangs) != '') { 3762 $availlangs = sanitize_languagecodeS($availlangs); 3763 $langs = explode(" ", $availlangs); 3764 if ($langs[count($langs) - 1] == "") { 3765 array_pop($langs); 3766 } 3767 } else { 3768 $langs = Survey::model()->findByPk($sid)->additionalLanguages; 3769 } 3770 if (count($langs) == 0) { 3771 return true; // Survey only has one language 3772 } 3773 $baselang = Survey::model()->findByPk($sid)->language; 3774 $query = "SELECT * FROM ".Yii::app()->db->quoteTableName('{{groups}}')." WHERE sid='{$sid}' AND language='{$baselang}' ORDER BY group_order"; 3775 $result = Yii::app()->db->createCommand($query)->query(); 3776 foreach ($result->readAll() as $group) { 3777 foreach ($langs as $lang) { 3778 3779 $query = "SELECT count(gid) FROM ".Yii::app()->db->quoteTableName('{{groups}}')." WHERE sid='{$sid}' AND gid='{$group['gid']}' AND language='{$lang}'"; 3780 $gresult = Yii::app()->db->createCommand($query)->queryScalar(); 3781 if ($gresult < 1) { 3782 $data = array( 3783 'gid' => $group['gid'], 3784 'sid' => $group['sid'], 3785 'group_name' => $group['group_name'], 3786 'group_order' => $group['group_order'], 3787 'description' => $group['description'], 3788 'randomization_group' => $group['randomization_group'], 3789 'grelevance' => $group['grelevance'], 3790 'language' => $lang 3791 3792 ); 3793 switchMSSQLIdentityInsert('groups', true); 3794 Yii::app()->db->createCommand()->insert('{{groups}}', $data); 3795 switchMSSQLIdentityInsert('groups', false); 3796 } 3797 } 3798 reset($langs); 3799 } 3800 3801 // Fix subquestions where the scale id is different between languages 3802 // First find all affected question IDs 3803 $query = "SELECT q.qid FROM {{questions}} q JOIN {{questions}} r ON q.qid=r.qid WHERE q.parent_qid<>0 AND q.scale_id<>r.scale_id GROUP BY q.qid"; 3804 $result = Yii::app()->db->createCommand($query)->queryColumn(); 3805 foreach ($result as $questionID) { 3806 // Get the question in base language 3807 $oQuestion=Question::model()->findByAttributes(array('qid'=>$questionID,'language'=>$baselang)); 3808 // Update the scale ID according to language 3809 Yii::app()->db->createCommand()->update('{{questions}}',array('scale_id'=>$oQuestion->scale_id),'qid='.$questionID); 3810 } 3811 3812 $quests = array(); 3813 $query = "SELECT * FROM {{questions}} WHERE sid='{$sid}' AND language='{$baselang}' ORDER BY question_order"; 3814 $result = Yii::app()->db->createCommand($query)->query()->readAll(); 3815 if (count($result) > 0) { 3816 foreach ($result as $question) { 3817 array_push($quests, $question['qid']); 3818 foreach ($langs as $lang) { 3819 $query = "SELECT count(qid) FROM {{questions}} WHERE sid='{$sid}' AND qid='{$question['qid']}' AND language='{$lang}' AND scale_id={$question['scale_id']}"; 3820 $gresult = Yii::app()->db->createCommand($query)->queryScalar(); 3821 if ($gresult < 1) { 3822 switchMSSQLIdentityInsert('questions', true); 3823 $data = array( 3824 'qid' => $question['qid'], 3825 'sid' => $question['sid'], 3826 'gid' => $question['gid'], 3827 'type' => $question['type'], 3828 'title' => $question['title'], 3829 'question' => $question['question'], 3830 'preg' => $question['preg'], 3831 'help' => $question['help'], 3832 'other' => $question['other'], 3833 'mandatory' => $question['mandatory'], 3834 'question_order' => $question['question_order'], 3835 'language' => $lang, 3836 'scale_id' => $question['scale_id'], 3837 'parent_qid' => $question['parent_qid'], 3838 'relevance' => $question['relevance'] 3839 ); 3840 Yii::app()->db->createCommand()->insert('{{questions}}', $data); 3841 } 3842 } 3843 reset($langs); 3844 } 3845 3846 $sqlans = ""; 3847 foreach ($quests as $quest) { 3848 $sqlans .= " OR qid = '".$quest."' "; 3849 } 3850 $query = "SELECT * FROM {{answers}} WHERE language='{$baselang}' and (".trim($sqlans, ' OR').") ORDER BY qid, code"; 3851 $result = Yii::app()->db->createCommand($query)->query(); 3852 foreach ($result->readAll() as $answer) { 3853 foreach ($langs as $lang) { 3854 $query = "SELECT count(qid) FROM {{answers}} WHERE code='{$answer['code']}' AND qid='{$answer['qid']}' AND language='{$lang}' AND scale_id={$answer['scale_id']}"; 3855 $gresult = Yii::app()->db->createCommand($query)->queryScalar(); 3856 if ($gresult < 1) { 3857 $data = array( 3858 'qid' => $answer['qid'], 3859 'code' => $answer['code'], 3860 'answer' => $answer['answer'], 3861 'scale_id' => $answer['scale_id'], 3862 'sortorder' => $answer['sortorder'], 3863 'language' => $lang, 3864 'assessment_value' => $answer['assessment_value'] 3865 ); 3866 Yii::app()->db->createCommand()->insert('{{answers}}', $data); 3867 } 3868 } 3869 reset($langs); 3870 } 3871 } 3872 /* Remove invalid question : can break survey */ 3873 Survey::model()->findByPk($sid)->fixInvalidQuestions(); 3874 3875 $query = "SELECT * FROM {{assessments}} WHERE sid='{$sid}' AND language='{$baselang}'"; 3876 $result = Yii::app()->db->createCommand($query)->query(); 3877 foreach ($result->readAll() as $assessment) { 3878 foreach ($langs as $lang) { 3879 $query = "SELECT count(id) FROM {{assessments}} WHERE sid='{$sid}' AND id='{$assessment['id']}' AND language='{$lang}'"; 3880 $gresult = Yii::app()->db->createCommand($query)->queryScalar(); 3881 if ($gresult < 1) { 3882 $data = array( 3883 'id' => $assessment['id'], 3884 'sid' => $assessment['sid'], 3885 'scope' => $assessment['scope'], 3886 'gid' => $assessment['gid'], 3887 'name' => $assessment['name'], 3888 'minimum' => $assessment['minimum'], 3889 'maximum' => $assessment['maximum'], 3890 'message' => $assessment['message'], 3891 'language' => $lang 3892 ); 3893 Yii::app()->db->createCommand()->insert('{{assessments}}', $data); 3894 } 3895 } 3896 reset($langs); 3897 } 3898 3899 3900 $query = "SELECT * FROM {{quota_languagesettings}} join {{quota}} q on quotals_quota_id=q.id WHERE q.sid='{$sid}' AND quotals_language='{$baselang}'"; 3901 $result = Yii::app()->db->createCommand($query)->query(); 3902 foreach ($result->readAll() as $qls) { 3903 foreach ($langs as $lang) { 3904 $query = "SELECT count(quotals_id) FROM {{quota_languagesettings}} WHERE quotals_quota_id='{$qls['quotals_quota_id']}' AND quotals_language='{$lang}'"; 3905 $gresult = Yii::app()->db->createCommand($query)->queryScalar(); 3906 if ($gresult < 1) { 3907 $data = array( 3908 'quotals_quota_id' => $qls['quotals_quota_id'], 3909 'quotals_name' => $qls['quotals_name'], 3910 'quotals_message' => $qls['quotals_message'], 3911 'quotals_url' => $qls['quotals_url'], 3912 'quotals_urldescrip' => $qls['quotals_urldescrip'], 3913 'quotals_language' => $lang 3914 ); 3915 Yii::app()->db->createCommand()->insert('{{quota_languagesettings}}', $data); 3916 } 3917 } 3918 reset($langs); 3919 } 3920 3921 return true; 3922} 3923 3924/** 3925* This function switches identity insert on/off for the MSSQL database 3926* 3927* @param string $table table name (without prefix) 3928* @param boolean $state Set to true to activate ID insert, or false to deactivate 3929*/ 3930function switchMSSQLIdentityInsert($table, $state) 3931{ 3932 if (in_array(Yii::app()->db->getDriverName(), array('mssql', 'sqlsrv', 'dblib'))) { 3933 if ($state === true) { 3934 // This needs to be done directly on the PDO object because when using CdbCommand or similar it won't have any effect 3935 Yii::app()->db->pdoInstance->exec('SET IDENTITY_INSERT '.Yii::app()->db->tablePrefix.$table.' ON'); 3936 } else { 3937 // This needs to be done directly on the PDO object because when using CdbCommand or similar it won't have any effect 3938 Yii::app()->db->pdoInstance->exec('SET IDENTITY_INSERT '.Yii::app()->db->tablePrefix.$table.' OFF'); 3939 } 3940 } 3941} 3942 3943/** 3944 * Retrieves the last Insert ID realiable for cross-DB applications 3945 * 3946 * @param string $sTableName Needed for Postgres and MSSQL 3947 * @return string 3948 */ 3949function getLastInsertID($sTableName) 3950{ 3951 $sDBDriver = Yii::app()->db->getDriverName(); 3952 if ($sDBDriver == 'mysql' || $sDBDriver == 'mysqli') { 3953 return Yii::app()->db->getLastInsertID(); 3954 } else { 3955 return Yii::app()->db->getCommandBuilder()->getLastInsertID($sTableName); 3956 } 3957} 3958 3959// TMSW Condition->Relevance: This function is not needed? Optionally replace this with call to EM to get similar info 3960/** 3961* getGroupDepsForConditions() get Dependencies between groups caused by conditions 3962* @param string $sid - the currently selected survey 3963* @param string $depgid - (optionnal) get only the dependencies applying to the group with gid depgid 3964* @param string $targgid - (optionnal) get only the dependencies for groups dependents on group targgid 3965* @param string $indexby - (optionnal) "by-depgid" for result indexed with $res[$depgid][$targgid] 3966* "by-targgid" for result indexed with $res[$targgid][$depgid] 3967* @return array - returns an array describing the conditions or NULL if no dependecy is found 3968* 3969* Example outupt assumin $index-by="by-depgid": 3970*Array 3971*( 3972* [125] => Array // Group Id 125 is dependent on 3973* ( 3974* [123] => Array // Group Id 123 3975* ( 3976* [depgpname] => G3 // GID-125 has name G3 3977* [targetgpname] => G1 // GID-123 has name G1 3978* [conditions] => Array 3979* ( 3980* [189] => Array // Because Question Id 189 3981* ( 3982* [0] => 9 // Have condition 9 set 3983* [1] => 10 // and condition 10 set 3984* [2] => 14 // and condition 14 set 3985* ) 3986* 3987* ) 3988* 3989* ) 3990* 3991* [124] => Array // GID 125 is also dependent on GID 124 3992* ( 3993* [depgpname] => G3 3994* [targetgpname] => G2 3995* [conditions] => Array 3996* ( 3997* [189] => Array // Because Question Id 189 have conditions set 3998* ( 3999* [0] => 11 4000* ) 4001* 4002* [215] => Array // And because Question Id 215 have conditions set 4003* ( 4004* [0] => 12 4005* ) 4006* 4007* ) 4008* 4009* ) 4010* 4011* ) 4012* 4013*) 4014* 4015* Usage example: 4016* * Get all group dependencies for SID $sid indexed by depgid: 4017* $result=getGroupDepsForConditions($sid); 4018* * Get all group dependencies for GID $gid in survey $sid indexed by depgid: 4019* $result=getGroupDepsForConditions($sid,$gid); 4020* * Get all group dependents on group $gid in survey $sid indexed by targgid: 4021* $result=getGroupDepsForConditions($sid,"all",$gid,"by-targgid"); 4022*/ 4023function getGroupDepsForConditions($sid, $depgid = "all", $targgid = "all", $indexby = "by-depgid") 4024{ 4025 $sid = sanitize_int($sid); 4026 $condarray = Array(); 4027 $sqldepgid = ""; 4028 $sqltarggid = ""; 4029 if ($depgid != "all") { $depgid = sanitize_int($depgid); $sqldepgid = "AND tq.gid=$depgid"; } 4030 if ($targgid != "all") {$targgid = sanitize_int($targgid); $sqltarggid = "AND tq2.gid=$targgid"; } 4031 4032 $baselang = Survey::model()->findByPk($sid)->language; 4033 $condquery = "SELECT tg.gid as depgid, tg.group_name as depgpname, " 4034 . "tg2.gid as targgid, tg2.group_name as targgpname, tq.qid as depqid, tc.cid FROM " 4035 . "{{conditions}} AS tc, " 4036 . "{{questions}} AS tq, " 4037 . "{{questions}} AS tq2, " 4038 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg ," 4039 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg2 " 4040 . "WHERE tq.language='{$baselang}' AND tq2.language='{$baselang}' AND tg.language='{$baselang}' AND tg2.language='{$baselang}' AND tc.qid = tq.qid AND tq.sid=$sid " 4041 . "AND tq.gid = tg.gid AND tg2.gid = tq2.gid " 4042 . "AND tq2.qid=tc.cqid AND tq.gid != tg2.gid $sqldepgid $sqltarggid"; 4043 $condresult = Yii::app()->db->createCommand($condquery)->query()->readAll(); 4044 4045 if (count($condresult) > 0) { 4046 foreach ($condresult as $condrow) { 4047 4048 switch ($indexby) { 4049 case "by-depgid": 4050 $depgid = $condrow['depgid']; 4051 $targetgid = $condrow['targgid']; 4052 $depqid = $condrow['depqid']; 4053 $cid = $condrow['cid']; 4054 $condarray[$depgid][$targetgid]['depgpname'] = $condrow['depgpname']; 4055 $condarray[$depgid][$targetgid]['targetgpname'] = $condrow['targgpname']; 4056 $condarray[$depgid][$targetgid]['conditions'][$depqid][] = $cid; 4057 break; 4058 4059 case "by-targgid": 4060 $depgid = $condrow['depgid']; 4061 $targetgid = $condrow['targgid']; 4062 $depqid = $condrow['depqid']; 4063 $cid = $condrow['cid']; 4064 $condarray[$targetgid][$depgid]['depgpname'] = $condrow['depgpname']; 4065 $condarray[$targetgid][$depgid]['targetgpname'] = $condrow['targgpname']; 4066 $condarray[$targetgid][$depgid]['conditions'][$depqid][] = $cid; 4067 break; 4068 } 4069 } 4070 return $condarray; 4071 } 4072 return null; 4073} 4074 4075// TMSW Condition->Relevance: This function is not needed? Optionally replace this with call to EM to get similar info 4076/** 4077* getQuestDepsForConditions() get Dependencies between groups caused by conditions 4078* @param string $sid - the currently selected survey 4079* @param string $gid - (optionnal) only search dependecies inside the Group Id $gid 4080* @param string $depqid - (optionnal) get only the dependencies applying to the question with qid depqid 4081* @param string $targqid - (optionnal) get only the dependencies for questions dependents on question Id targqid 4082* @param string $indexby - (optionnal) "by-depqid" for result indexed with $res[$depqid][$targqid] 4083* "by-targqid" for result indexed with $res[$targqid][$depqid] 4084* @return array - returns an array describing the conditions or NULL if no dependecy is found 4085* 4086* Example outupt assumin $index-by="by-depqid": 4087*Array 4088*( 4089* [184] => Array // Question Id 184 4090* ( 4091* [183] => Array // Depends on Question Id 183 4092* ( 4093* [0] => 5 // Because of condition Id 5 4094* ) 4095* 4096* ) 4097* 4098*) 4099* 4100* Usage example: 4101* * Get all questions dependencies for Survey $sid and group $gid indexed by depqid: 4102* $result=getQuestDepsForConditions($sid,$gid); 4103* * Get all questions dependencies for question $qid in survey/group $sid/$gid indexed by depqid: 4104* $result=getGroupDepsForConditions($sid,$gid,$qid); 4105* * Get all questions dependents on question $qid in survey/group $sid/$gid indexed by targqid: 4106* $result=getGroupDepsForConditions($sid,$gid,"all",$qid,"by-targgid"); 4107*/ 4108function getQuestDepsForConditions($sid, $gid = "all", $depqid = "all", $targqid = "all", $indexby = "by-depqid", $searchscope = "samegroup") 4109{ 4110 4111 $condarray = Array(); 4112 4113 $baselang = Survey::model()->findByPk($sid)->language; 4114 $sqlgid = ""; 4115 $sqldepqid = ""; 4116 $sqltargqid = ""; 4117 $sqlsearchscope = ""; 4118 if ($gid != "all") {$gid = sanitize_int($gid); $sqlgid = "AND tq.gid=$gid"; } 4119 if ($depqid != "all") {$depqid = sanitize_int($depqid); $sqldepqid = "AND tq.qid=$depqid"; } 4120 if ($targqid != "all") {$targqid = sanitize_int($targqid); $sqltargqid = "AND tq2.qid=$targqid"; } 4121 if ($searchscope == "samegroup") {$sqlsearchscope = "AND tq2.gid=tq.gid"; } 4122 4123 $condquery = "SELECT tq.qid as depqid, tq2.qid as targqid, tc.cid 4124 FROM {{conditions}} AS tc, {{questions}} AS tq, {{questions}} AS tq2 4125 WHERE tq.language='{$baselang}' AND tq2.language='{$baselang}' AND tc.qid = tq.qid AND tq.sid='$sid' 4126 AND tq2.qid=tc.cqid $sqlsearchscope $sqlgid $sqldepqid $sqltargqid"; 4127 $condresult = Yii::app()->db->createCommand($condquery)->query()->readAll(); 4128 if (count($condresult) > 0) { 4129 foreach ($condresult as $condrow) { 4130 $depqid = $condrow['depqid']; 4131 $targetqid = $condrow['targqid']; 4132 $condid = $condrow['cid']; 4133 switch ($indexby) { 4134 case "by-depqid": 4135 $condarray[$depqid][$targetqid][] = $condid; 4136 break; 4137 4138 case "by-targqid": 4139 $condarray[$targetqid][$depqid][] = $condid; 4140 break; 4141 } 4142 } 4143 return $condarray; 4144 } 4145 return null; 4146} 4147 4148// TMSW Condition->Relevance: This function is not needed - could replace with a message from EM output. 4149/** 4150* checkMoveQuestionConstraintsForConditions() 4151* @param string $sid - the currently selected survey 4152* @param string $qid - qid of the question you want to check possible moves 4153* @param string $newgid - (optionnal) get only constraints when trying to move to this particular GroupId 4154* otherwise, get all moves constraints for this question 4155* 4156* @return array - returns an array describing the conditions 4157* Array 4158* ( 4159* ['notAbove'] = null | Array 4160* ( 4161* Array ( gid1, group_order1, qid1, cid1 ) 4162* ) 4163* ['notBelow'] = null | Array 4164* ( 4165* Array ( gid2, group_order2, qid2, cid2 ) 4166* ) 4167* ) 4168* 4169* This should be read as: 4170* - this question can't be move above group gid1 in position group_order1 because of the condition cid1 on question qid1 4171* - this question can't be move below group gid2 in position group_order2 because of the condition cid2 on question qid2 4172* 4173*/ 4174function checkMoveQuestionConstraintsForConditions($sid, $qid, $newgid = "all") 4175{ 4176 4177 $resarray = Array(); 4178 $resarray['notAbove'] = null; // defaults to no constraint 4179 $resarray['notBelow'] = null; // defaults to no constraint 4180 $sid = sanitize_int($sid); 4181 $qid = sanitize_int($qid); 4182 4183 if ($newgid != "all") { 4184 $newgid = sanitize_int($newgid); 4185 $newgorder = getGroupOrder($sid, $newgid); 4186 } else { 4187 $newgorder = ''; // Not used in this case 4188 } 4189 4190 $baselang = Survey::model()->findByPk($sid)->language; 4191 4192 // First look for 'my dependencies': questions on which I have set conditions 4193 $condquery = "SELECT tq.qid as depqid, tq.gid as depgid, tg.group_order as depgorder, " 4194 . "tq2.qid as targqid, tq2.gid as targgid, tg2.group_order as targgorder, " 4195 . "tc.cid FROM " 4196 . "{{conditions}} AS tc, " 4197 . "{{questions}} AS tq, " 4198 . "{{questions}} AS tq2, " 4199 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg ," 4200 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg2 " 4201 . "WHERE tq.language='{$baselang}' AND tq2.language='{$baselang}' AND tc.qid = tq.qid AND tq.sid=$sid " 4202 . "AND tq2.qid=tc.cqid AND tg.gid=tq.gid AND tg2.gid=tq2.gid AND tq.qid=$qid ORDER BY tg2.group_order DESC"; 4203 4204 $condresult = Yii::app()->db->createCommand($condquery)->query(); 4205 4206 foreach ($condresult->readAll() as $condrow) { 4207 // This Question can go up to the minimum GID on the 1st row 4208 $depqid = $condrow['depqid']; 4209 $targetgid = $condrow['targgid']; 4210 $targetgorder = $condrow['targgorder']; 4211 $condid = $condrow['cid']; 4212 if ($newgid != "all") { 4213 // Get only constraints when trying to move to this group 4214 if ($newgorder < $targetgorder) { 4215 $resarray['notAbove'][] = Array($targetgid, $targetgorder, $depqid, $condid); 4216 } 4217 } else { 4218 // get all moves constraints 4219 $resarray['notAbove'][] = Array($targetgid, $targetgorder, $depqid, $condid); 4220 } 4221 } 4222 4223 // Secondly look for 'questions dependent on me': questions that have conditions on my answers 4224 $condquery = "SELECT tq.qid as depqid, tq.gid as depgid, tg.group_order as depgorder, " 4225 . "tq2.qid as targqid, tq2.gid as targgid, tg2.group_order as targgorder, " 4226 . "tc.cid FROM {{conditions}} AS tc, " 4227 . "{{questions}} AS tq, " 4228 . "{{questions}} AS tq2, " 4229 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg ," 4230 . Yii::app()->db->quoteTableName('{{groups}}')." AS tg2 " 4231 . "WHERE tq.language='{$baselang}' AND tq2.language='{$baselang}' AND tc.qid = tq.qid AND tq.sid=$sid " 4232 . "AND tq2.qid=tc.cqid AND tg.gid=tq.gid AND tg2.gid=tq2.gid AND tq2.qid=$qid ORDER BY tg.group_order"; 4233 4234 $condresult = Yii::app()->db->createCommand($condquery)->query(); 4235 4236 foreach ($condresult->readAll() as $condrow) { 4237 // This Question can go down to the maximum GID on the 1st row 4238 $depqid = $condrow['depqid']; 4239 $depgid = $condrow['depgid']; 4240 $depgorder = $condrow['depgorder']; 4241 $condid = $condrow['cid']; 4242 if ($newgid != "all") { 4243 // Get only constraints when trying to move to this group 4244 if ($newgorder > $depgorder) { 4245 $resarray['notBelow'][] = Array($depgid, $depgorder, $depqid, $condid); 4246 } 4247 } else { 4248 // get all moves constraints 4249 $resarray['notBelow'][] = Array($depgid, $depgorder, $depqid, $condid); 4250 } 4251 } 4252 return $resarray; 4253} 4254 4255/** 4256* Get a list of all user groups 4257* @returns array 4258*/ 4259function getUserGroupList() 4260{ 4261 $sQuery = "SELECT distinct a.ugid, a.name, a.owner_id FROM {{user_groups}} AS a LEFT JOIN {{user_in_groups}} AS b ON a.ugid = b.ugid WHERE 1=1 "; 4262 if (!Permission::model()->hasGlobalPermission('superadmin', 'read')) { 4263 $sQuery .= "AND uid = ".Yii::app()->session['loginID']; 4264 } 4265 $sQuery .= " ORDER BY name"; 4266 4267 $sresult = Yii::app()->db->createCommand($sQuery)->query(); //Checked 4268 if (!$sresult) {return "Database Error"; } 4269 $aGroupNames = []; 4270 foreach ($sresult->readAll() as $row) { 4271 $aGroupNames[] = $row; 4272 } 4273 $simplegidarray = array(); 4274 if (isset($aGroupNames)) { 4275 foreach ($aGroupNames as $gn) { 4276 $simplegidarray[] = $gn['ugid']; 4277 } 4278 } 4279 return $simplegidarray; 4280} 4281 4282// TODO use Yii model forms 4283function getGroupUserList($ugid) 4284{ 4285 Yii::app()->loadHelper('database'); 4286 4287 4288 $ugid = sanitize_int($ugid); 4289 $surveyidquery = "SELECT a.uid, a.users_name, a.full_name FROM {{users}} AS a LEFT JOIN (SELECT uid AS id FROM {{user_in_groups}} WHERE ugid = {$ugid}) AS b ON a.uid = b.id WHERE id IS NULL ORDER BY a.users_name"; 4290 4291 $surveyidresult = dbExecuteAssoc($surveyidquery); //Checked 4292 if (!$surveyidresult) {return "Database Error"; } 4293 $surveyselecter = ""; 4294 $aSurveyNames = []; 4295 foreach ($surveyidresult->readAll() as $row) { 4296 $aSurveyNames[] = $row; 4297 } 4298 //$surveynames = $surveyidresult->GetRows(); 4299 if (isset($aSurveyNames)) { 4300 foreach ($aSurveyNames as $sv) { 4301 $surveyselecter .= "<option"; 4302 $surveyselecter .= " value='{$sv['uid']}'>".\CHtml::encode($sv['users_name'])." (".\CHtml::encode($sv['full_name']).")</option>\n"; 4303 } 4304 } 4305 $surveyselecter = "<option value='-1' selected='selected'>".gT("Please choose...")."</option>\n".$surveyselecter; 4306 return $surveyselecter; 4307} 4308 4309/** 4310* Run an arbitrary sequence of semicolon-delimited SQL commands 4311* 4312* Assumes that the input text (file or string) consists of 4313* a number of SQL statements ENDING WITH SEMICOLONS. The 4314* semicolons MUST be the last character in a line. 4315* Lines that are blank or that start with "#" or "--" (postgres) are ignored. 4316* Only tested with mysql dump files (mysqldump -p -d limesurvey) 4317* Function kindly borrowed by Moodle 4318* @param string $sqlfile The path where a file with sql commands can be found on the server. 4319* @param string $sqlstring If no path is supplied then a string with semicolon delimited sql 4320* commands can be supplied in this argument. 4321* @return bool Returns true if database was modified successfully. 4322*/ 4323function modifyDatabase($sqlfile = '', $sqlstring = '') 4324{ 4325 Yii::app()->loadHelper('database'); 4326 4327 4328 global $siteadminemail; 4329 global $siteadminname; 4330 global $codeString; 4331 global $modifyoutput; 4332 4333 $success = true; // Let's be optimistic 4334 $modifyoutput = ''; 4335 $lines = []; 4336 if (!empty($sqlfile)) { 4337 if (!is_readable($sqlfile)) { 4338 $success = false; 4339 echo '<p>Tried to modify database, but "'.$sqlfile.'" doesn\'t exist!</p>'; 4340 return $success; 4341 } else { 4342 $lines = file($sqlfile); 4343 } 4344 } else { 4345 $sqlstring = trim($sqlstring); 4346 if ($sqlstring[strlen($sqlstring) - 1] != ";") { 4347 $sqlstring .= ";"; // add it in if it's not there. 4348 } 4349 $lines[] = $sqlstring; 4350 } 4351 4352 $command = ''; 4353 4354 foreach ($lines as $line) { 4355 $line = rtrim($line); 4356 $length = strlen($line); 4357 4358 if ($length and $line[0] <> '#' and substr($line, 0, 2) <> '--') { 4359 if (substr($line, $length - 1, 1) == ';') { 4360 $line = substr($line, 0, $length - 1); // strip ; 4361 $command .= $line; 4362 $command = str_replace('prefix_', Yii::app()->db->tablePrefix, $command); // Table prefixes 4363 $command = str_replace('$defaultuser', Yii::app()->getConfig('defaultuser'), $command); 4364 $command = str_replace('$defaultpass', hash('sha256', Yii::app()->getConfig('defaultpass')), $command); 4365 $command = str_replace('$siteadminname', $siteadminname, $command); 4366 $command = str_replace('$siteadminemail', $siteadminemail, $command); 4367 $command = str_replace('$defaultlang', Yii::app()->getConfig('defaultlang'), $command); 4368 $command = str_replace('$databasetabletype', Yii::app()->db->getDriverName(), $command); 4369 4370 try 4371 { Yii::app()->db->createCommand($command)->query(); //Checked 4372 $command = htmlspecialchars($command); 4373 $modifyoutput .= ". "; 4374 } catch (CDbException $e) { 4375 $command = htmlspecialchars($command); 4376 $modifyoutput .= "<br />".sprintf(gT("SQL command failed: %s"), "<span style='font-size:10px;'>".$command."</span>", "<span style='color:#ee0000;font-size:10px;'></span><br/>"); 4377 $success = false; 4378 } 4379 4380 $command = ''; 4381 } else { 4382 $command .= $line; 4383 } 4384 } 4385 } 4386 4387 return $success; 4388 4389} 4390 4391/** 4392* Returns labelsets for given language(s), or for all if null 4393* 4394* @param string $languages 4395* @return array 4396*/ 4397function getLabelSets($languages = null) 4398{ 4399 $aLanguages = array(); 4400 if (!empty($languages)) { 4401 $languages = sanitize_languagecodeS($languages); 4402 $aLanguages = explode(' ', trim($languages)); 4403 } 4404 4405 $criteria = new CDbCriteria; 4406 $criteria->order = "label_name"; 4407 foreach ($aLanguages as $k => $item) { 4408 $criteria->params[':lang_like1_'.$k] = "% $item %"; 4409 $criteria->params[':lang_'.$k] = $item; 4410 $criteria->params[':lang_like2_'.$k] = "% $item"; 4411 $criteria->params[':lang_like3_'.$k] = "$item %"; 4412 $criteria->addCondition(" 4413 ((languages like :lang_like1_$k) or 4414 (languages = :lang_$k) or 4415 (languages like :lang_like2_$k) or 4416 (languages like :lang_like3_$k))"); 4417 } 4418 4419 $result = LabelSet::model()->findAll($criteria); 4420 $labelsets = array(); 4421 foreach ($result as $row) { 4422 $labelsets[] = array($row->lid, $row->label_name); 4423 } 4424 return $labelsets; 4425} 4426 4427/** 4428 * get the header 4429 * @param bool $meta : not used in any call (2016-10-18) 4430 * @return string 4431 */ 4432function getHeader($meta = false) 4433{ 4434 /* Todo : move this to layout/public.html */ 4435 global $surveyid; 4436 Yii::app()->loadHelper('surveytranslator'); 4437 4438 // Set Langage // TODO remove one of the Yii::app()->session see bug #5901 4439 if (Yii::app()->session['survey_'.$surveyid]['s_lang']) { 4440 $languagecode = Yii::app()->session['survey_'.$surveyid]['s_lang']; 4441 } elseif (isset($surveyid) && $surveyid && Survey::model()->findByPk($surveyid)) { 4442 $languagecode = Survey::model()->findByPk($surveyid)->language; 4443 } else { 4444 $languagecode = Yii::app()->getConfig('defaultlang'); 4445 } 4446 $header = "<!DOCTYPE html>\n"; 4447 $class = "no-js $languagecode"; 4448 $header .= "<html lang=\"{$languagecode}\""; 4449 4450 if (getLanguageRTL($languagecode)) { 4451 $header .= " dir=\"rtl\" "; 4452 $class .= " dir-rtl"; 4453 } else { 4454 $header .= " dir=\"ltr\" "; 4455 $class .= " dir-ltr"; 4456 } 4457 $header .= " class=\"{$class}\">\n"; 4458 $header .= "\t<head>\n"; 4459 Yii::app()->clientScript->registerScriptFile(Yii::app()->getConfig("generalscripts").'nojs.js', CClientScript::POS_HEAD); 4460 if ($meta) { 4461 $header .= $meta; 4462 } 4463 return $header; 4464} 4465 4466 4467function doHeader() 4468{ 4469 echo getHeader(); 4470} 4471 4472/** 4473* This function returns the header for the printable survey 4474* @return String 4475* 4476*/ 4477function getPrintableHeader() 4478{ 4479 global $rooturl, $homeurl; 4480 $headelements = App()->getController()->renderPartial('/survey/system/print_survey/header', array(), true, true); 4481 return $headelements; 4482} 4483 4484/** 4485 * This function returns the Footer as result string 4486 * If you want to echo the Footer use doFooter()! 4487 * @return string 4488 */ 4489function getFooter() 4490{ 4491 return "\n\n\t</body>\n</html>\n"; 4492} 4493 4494function doFooter() 4495{ 4496 echo getFooter(); 4497} 4498 4499 4500 4501/** 4502* Retrieve a HTML <OPTION> list of survey admin users 4503* 4504* @param boolean $bIncludeOwner If the survey owner should be included 4505* @param boolean $bIncludeSuperAdmins If Super admins should be included 4506* @param int $surveyid 4507* @return string 4508*/ 4509function getSurveyUserList($bIncludeSuperAdmins = true, $surveyid) 4510{ 4511 4512 $surveyid = (int) $surveyid; 4513 4514 $sSurveyIDQuery = "SELECT a.uid, a.users_name, a.full_name FROM {{users}} AS a 4515 LEFT OUTER JOIN (SELECT uid AS id FROM {{permissions}} WHERE entity_id = {$surveyid} and entity='survey') AS b ON a.uid = b.id 4516 WHERE id IS NULL "; 4517 if (!$bIncludeSuperAdmins) { 4518 // @todo: Adjust for new permission system - not urgent since it it just display 4519 // $sSurveyIDQuery.='and superadmin=0 '; 4520 } 4521 $sSurveyIDQuery .= 'ORDER BY a.users_name'; 4522 $oSurveyIDResult = Yii::app()->db->createCommand($sSurveyIDQuery)->query(); //Checked 4523 $aSurveyIDResult = $oSurveyIDResult->readAll(); 4524 4525 $surveyselecter = ""; 4526 $authorizedUsersList = []; 4527 4528 if (Yii::app()->getConfig('usercontrolSameGroupPolicy') == true) { 4529 $authorizedUsersList = getUserList('onlyuidarray'); 4530 } 4531 4532 $svexist = false; 4533 foreach ($aSurveyIDResult as $sv) { 4534 if (Yii::app()->getConfig('usercontrolSameGroupPolicy') == false || 4535 in_array($sv['uid'], $authorizedUsersList)) { 4536 $surveyselecter .= "<option"; 4537 $surveyselecter .= " value='{$sv['uid']}'>".\CHtml::encode($sv['users_name'])." ".\CHtml::encode($sv['full_name'])."</option>\n"; 4538 $svexist = true; 4539 } 4540 } 4541 4542 if ($svexist) { 4543 $surveyselecter = "<option value='-1' selected='selected'>".gT("Please choose...")."</option>\n".$surveyselecter; 4544 } else { 4545 $surveyselecter = "<option value='-1'>".gT("None")."</option>\n".$surveyselecter; 4546 } 4547 4548 return $surveyselecter; 4549} 4550 4551/** 4552 * Return HTML <option> list of user groups 4553 * @param string $outputformat 4554 * @param int $surveyid 4555 * @return string|array 4556 */ 4557function getSurveyUserGroupList($outputformat = 'htmloptions', $surveyid) 4558{ 4559 4560 $surveyid = sanitize_int($surveyid); 4561 4562 $surveyidquery = "SELECT a.ugid, a.name, MAX(d.ugid) AS da 4563 FROM {{user_groups}} AS a 4564 LEFT JOIN ( 4565 SELECT b.ugid 4566 FROM {{user_in_groups}} AS b 4567 LEFT JOIN (SELECT * FROM {{permissions}} 4568 WHERE entity_id = {$surveyid} and entity='survey') AS c ON b.uid = c.uid WHERE c.uid IS NULL 4569 ) AS d ON a.ugid = d.ugid GROUP BY a.ugid, a.name HAVING MAX(d.ugid) IS NOT NULL"; 4570 $surveyidresult = Yii::app()->db->createCommand($surveyidquery)->query(); //Checked 4571 $aResult = $surveyidresult->readAll(); 4572 4573 $authorizedGroupsList = []; 4574 if (Yii::app()->getConfig('usercontrolSameGroupPolicy') == true) { 4575 $authorizedGroupsList = getUserGroupList(); 4576 } 4577 4578 $svexist = false; 4579 $surveyselecter = ""; 4580 $simpleugidarray = []; 4581 foreach ($aResult as $sv) { 4582 if (Yii::app()->getConfig('usercontrolSameGroupPolicy') == false || 4583 in_array($sv['ugid'], $authorizedGroupsList)) { 4584 $surveyselecter .= "<option"; 4585 $surveyselecter .= " value='{$sv['ugid']}'>{$sv['name']}</option>\n"; 4586 $simpleugidarray[] = $sv['ugid']; 4587 $svexist = true; 4588 } 4589 } 4590 4591 if ($svexist) { 4592 $surveyselecter = "<option value='-1' selected='selected'>".gT("Please choose...")."</option>\n".$surveyselecter; 4593 } else { 4594 $surveyselecter = "<option value='-1'>".gT("None")."</option>\n".$surveyselecter; 4595 } 4596 4597 if ($outputformat == 'simpleugidarray') { 4598 return $simpleugidarray; 4599 } else { 4600 return $surveyselecter; 4601 } 4602} 4603 4604 4605 4606/** 4607* This function fixes the group ID and type on all subquestions 4608* Optimized for minimum memory usage even on huge databases 4609*/ 4610function fixSubquestions() 4611{ 4612 $surveyidresult = Yii::app()->db->createCommand() 4613 ->select('sq.qid, q.gid , q.type ') 4614 ->from('{{questions}} sq') 4615 ->join('{{questions}} q', 'sq.parent_qid=q.qid') 4616 ->where('sq.parent_qid>0 AND (sq.gid!=q.gid or sq.type!=q.type)') 4617 ->limit(10000) 4618 ->query(); 4619 $aRecords = $surveyidresult->readAll(); 4620 while (count($aRecords) > 0) { 4621 foreach ($aRecords as $sv) { 4622 Yii::app()->db->createCommand("update {{questions}} set type='{$sv['type']}', gid={$sv['gid']} where qid={$sv['qid']}")->execute(); 4623 } 4624 $surveyidresult = Yii::app()->db->createCommand() 4625 ->select('sq.qid, q.gid , q.type ') 4626 ->from('{{questions}} sq') 4627 ->join('{{questions}} q', 'sq.parent_qid=q.qid') 4628 ->where('sq.parent_qid>0 AND (sq.gid!=q.gid or sq.type!=q.type)') 4629 ->limit(10000) 4630 ->query(); 4631 $aRecords = $surveyidresult->readAll(); 4632 } 4633 4634} 4635 4636/** 4637* Must use ls_json_encode to json_encode content, otherwise LimeExpressionManager will think that the associative arrays are expressions and try to parse them. 4638*/ 4639function ls_json_encode($content) 4640{ 4641 $ans = json_encode($content); 4642 $ans = str_replace(array('{', '}'), array('{ ', ' }'), $ans); 4643 return $ans; 4644} 4645 4646/** 4647 * Decode a json string, sometimes needs stripslashes 4648 * 4649 * @param string $jsonString 4650 * @return mixed 4651 */ 4652function json_decode_ls($jsonString) 4653{ 4654 $decoded = json_decode($jsonString, true); 4655 4656 if (is_null($decoded) && !empty($jsonString)) { 4657 // probably we need stipslahes 4658 $decoded = json_decode(stripslashes($jsonString), true); 4659 } 4660 4661 return $decoded; 4662} 4663 4664/** 4665 * Return accepted codingsArray for importing files 4666 * 4667 * Used in vvimport 4668 * TODO : use in token and 4669 * @return array 4670 */ 4671function aEncodingsArray() 4672{ 4673 $aEncodings = array( 4674 "armscii8" => gT("ARMSCII-8 Armenian"), 4675 "ascii" => gT("US ASCII"), 4676 "big5" => gT("Big5 Traditional Chinese"), 4677 "binary" => gT("Binary pseudo charset"), 4678 "cp1250" => gT("Windows Central European (Windows-1250)"), 4679 "cp1251" => gT("Windows Cyrillic (Windows-1251)"), 4680 "cp1256" => gT("Windows Arabic (Windows-1256)"), 4681 "cp1257" => gT("Windows Baltic (Windows-1257)"), 4682 "cp850" => gT("DOS West European (cp850)"), 4683 "cp852" => gT("DOS Central European (cp852)"), 4684 "cp866" => gT("DOS Cyrillic (cp866)"), 4685 "cp932" => gT("Windows-31J - SJIS for Windows Japanese (cp932)"), 4686 "dec8" => gT("DEC West European"), 4687 "eucjpms" => gT("UJIS for Windows Japanese"), 4688 "euckr" => gT("EUC-KR Korean"), 4689 "gb2312" => gT("GB2312 Simplified Chinese"), 4690 "gbk" => gT("GBK Simplified Chinese"), 4691 "geostd8" => gT("GEOSTD8 Georgian"), 4692 "greek" => gT("ISO 8859-7 Greek"), 4693 "hebrew" => gT("ISO 8859-8 Hebrew"), 4694 "hp8" => gT("HP West European"), 4695 "keybcs2" => gT("DOS Kamenicky Czech-Slovak (cp895)"), 4696 "koi8r" => gT("KOI8-R Relcom Russian"), 4697 "koi8u" => gT("KOI8-U Ukrainian"), 4698 "latin1" => gT("ISO 8859-1 West European (latin1)"), 4699 "latin2" => gT("ISO 8859-2 Central European (latin2)"), 4700 "latin5" => gT("ISO 8859-9 Turkish (latin5)"), 4701 "latin7" => gT("ISO 8859-13 Baltic (latin7)"), 4702 "macce" => gT("Mac Central European"), 4703 "macroman" => gT("Mac West European"), 4704 "sjis" => gT("Shift-JIS Japanese"), 4705 "swe7" => gT("7bit Swedish"), 4706 "tis620" => gT("TIS620 Thai"), 4707 "ucs2" => gT("UCS-2 Unicode"), 4708 "ujis" => gT("EUC-JP Japanese"), 4709 "utf8" => gT("UTF-8 Unicode"), 4710 ); 4711 // Sort list of encodings 4712 asort($aEncodings); 4713 $aEncodings = array("auto" => gT("(Automatic)")) + $aEncodings; 4714 return $aEncodings; 4715 } 4716 4717 4718/** 4719* Ellipsize String 4720* 4721* This public static function will strip tags from a string, split it at its max_length and ellipsize 4722* 4723* @param string $sString string to ellipsize 4724* @param integer $iMaxLength max length of string 4725* @param integer $fPosition int (1|0) or float, .5, .2, etc for position to split 4726* @param string $sEllipsis ellipsis ; Default '...' 4727* @return string ellipsized string 4728*/ 4729function ellipsize($sString, $iMaxLength, $fPosition = 1, $sEllipsis = '…') 4730{ 4731 // Strip tags 4732 $sString = trim(strip_tags($sString)); 4733 // Is the string long enough to ellipsize? 4734 if (mb_strlen($sString, 'UTF-8') <= $iMaxLength + 3) { 4735 return $sString; 4736 } 4737 4738 $iStrLen = mb_strlen($sString, 'UTF-8'); 4739 $sBegin = mb_substr($sString, 0, (int) floor($iMaxLength * $fPosition), 'UTF-8'); 4740 $sEnd = mb_substr($sString, $iStrLen - ($iMaxLength - mb_strlen($sBegin, 'UTF-8')), $iStrLen, 'UTF-8'); 4741 return $sBegin.$sEllipsis.$sEnd; 4742} 4743 4744/** 4745* This function tries to returns the 'real' IP address under all configurations 4746* Do not rely security-wise on the detected IP address as except for REMOTE_ADDR all fields could be manipulated by the web client 4747*/ 4748function getIPAddress() 4749{ 4750 $sIPAddress = '127.0.0.1'; 4751 if (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)!==false) { 4752 //check IP address from share internet 4753 $sIPAddress = $_SERVER['HTTP_CLIENT_IP']; 4754 } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP)!==false) { 4755 //Check IP address passed from proxy 4756 $sIPAddress = $_SERVER['HTTP_X_FORWARDED_FOR']; 4757 } elseif (!empty($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)!==false) { 4758 $sIPAddress = $_SERVER['REMOTE_ADDR']; 4759 } 4760 return $sIPAddress; 4761} 4762 4763 4764/** 4765* This function tries to find out a valid language code for the language of the browser used 4766* If it cannot find it it will return the default language from global settings 4767* 4768*/ 4769function getBrowserLanguage() 4770{ 4771 $sLanguage = Yii::app()->getRequest()->getPreferredLanguage(); 4772 Yii::app()->loadHelper("surveytranslator"); 4773 $aLanguages = getLanguageData(); 4774 if (!isset($aLanguages[$sLanguage])) { 4775 $sLanguage = str_replace('_', '-', $sLanguage); 4776 if (strpos($sLanguage, '-') !== false) { 4777 $aLanguage = explode('-', $sLanguage); 4778 $aLanguage[1] = strtoupper($aLanguage[1]); 4779 $sLanguage = implode('-', $aLanguage); 4780 } 4781 if (!isset($aLanguages[$sLanguage])) { 4782 $sLanguage = substr($sLanguage, 0, strpos($sLanguage, '-')); 4783 if (!isset($aLanguages[$sLanguage])) { 4784 $sLanguage = Yii::app()->getConfig('defaultlang'); 4785 } 4786 } 4787 } 4788 return $sLanguage; 4789} 4790 4791function array_diff_assoc_recursive($array1, $array2) 4792{ 4793 $difference = array(); 4794 foreach ($array1 as $key => $value) { 4795 if (is_array($value)) { 4796 if (!isset($array2[$key]) || !is_array($array2[$key])) { 4797 $difference[$key] = $value; 4798 } else { 4799 $new_diff = array_diff_assoc_recursive($value, $array2[$key]); 4800 if (!empty($new_diff)) { 4801 $difference[$key] = $new_diff; 4802 } 4803 } 4804 } else if (!array_key_exists($key, $array2) || $array2[$key] !== $value) { 4805 $difference[$key] = $value; 4806 } 4807 } 4808 return $difference; 4809} 4810 4811/** 4812 * Calculate folder size 4813 * NB: If this function is changed, please notify LimeSurvey GmbH. 4814 * An exact copy of this function is used to calculate storage 4815 * limit on LimeSurvey Pro hosting. 4816 * @param string $dir Folder 4817 * @return integer Size in bytes. 4818 */ 4819function folderSize($dir) 4820{ 4821 $size = 0; 4822 foreach (glob(rtrim($dir, '/').'/*', GLOB_NOSORT) as $each) { 4823 if (is_file($each)) { 4824 // NB: stat() can be used to calculate disk usage (instead 4825 // of file size - it's not the same thing). 4826 //$stat = stat($each); 4827 //$tmpsize = $stat[11] * $stat[12] / 8; 4828 //$size += $tmpsize; 4829 $size += filesize($each); 4830 } else { 4831 $size += folderSize($each); 4832 } 4833 } 4834 return $size; 4835} 4836 4837/** 4838 * Format size in human readable format. 4839 * @param int $bytes 4840 * @param int $decimals 4841 * @return string 4842 */ 4843function humanFilesize($bytes, $decimals = 2) 4844{ 4845 $sz = 'BKMGTP'; 4846 //$factor = floor((strlen($bytes) - 1) / 3); 4847 $factor = 2; 4848 $string = sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)).@$sz[$factor]; 4849 $aLangData = getLanguageData(); 4850 $radix = getRadixPointData($aLangData[Yii::app()->session['adminlang']]['radixpoint']); 4851 return str_replace('.', $radix['separator'], $string); 4852} 4853 4854/** 4855* This function transforms the php.ini notation for numbers (like '2M') to an integer (2*1024*1024 in this case) 4856* 4857* @param string $sSize 4858* @return integer The value in bytes 4859*/ 4860function convertPHPSizeToBytes($sSize) 4861{ 4862 // 4863 $sSuffix = strtoupper(substr($sSize, -1)); 4864 if (!in_array($sSuffix, array('P', 'T', 'G', 'M', 'K'))) { 4865 return (int) $sSize; 4866 } 4867 $iValue = substr($sSize, 0, -1); 4868 switch ($sSuffix) { 4869 case 'P': 4870 $iValue *= 1024; 4871 // Fallthrough intended 4872 case 'T': 4873 $iValue *= 1024; 4874 // Fallthrough intended 4875 case 'G': 4876 $iValue *= 1024; 4877 // Fallthrough intended 4878 case 'M': 4879 $iValue *= 1024; 4880 // Fallthrough intended 4881 case 'K': 4882 $iValue *= 1024; 4883 break; 4884 } 4885 return (int) $iValue; 4886} 4887 4888function getMaximumFileUploadSize() 4889{ 4890 return min(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize'))); 4891} 4892 4893/** 4894 * Decodes token attribute data because due to bugs in the past it can be written in JSON or be serialized - future format should be JSON as serialized data can be exploited 4895 * 4896 * @param string $oTokenAttributeData The original token attributes as stored in the database 4897 * @return array|mixed 4898 */ 4899function decodeTokenAttributes($oTokenAttributeData) 4900{ 4901 if (trim($oTokenAttributeData) == '') { 4902 return array(); 4903 } 4904 if (substr($oTokenAttributeData, 0, 1) != '{' && substr($oTokenAttributeData, 0, 1) != '[') { 4905 $sSerialType = getSerialClass($oTokenAttributeData); 4906 if ($sSerialType == 'array') { 4907// Safe to decode 4908 $aReturnData = @unserialize($oTokenAttributeData); 4909 } else { 4910// Something else, might be unsafe 4911 return array(); 4912 } 4913 } else { 4914 $aReturnData = @json_decode($oTokenAttributeData, true); 4915 } 4916 if ($aReturnData === false || $aReturnData === null) { 4917 return array(); 4918 } 4919 return $aReturnData; 4920} 4921 4922/** 4923 * @param string $sSerial 4924 * @return string|null 4925 */ 4926function getSerialClass($sSerial) 4927{ 4928 $aTypes = array('s' => 'string', 'a' => 'array', 'b' => 'bool', 'i' => 'int', 'd' => 'float', 'N;' => 'NULL'); 4929 4930 $aParts = explode(':', $sSerial, 4); 4931 return isset($aTypes[$aParts[0]]) ? $aTypes[$aParts[0]] : (isset($aParts[2]) ? trim($aParts[2], '"') : null); 4932} 4933 4934/** 4935* Force Yii to create a new CSRF token by removing the old one 4936* 4937*/ 4938function regenerateCSRFToken() 4939{ 4940 // Expire the CSRF cookie 4941 $cookie = new CHttpCookie('YII_CSRF_TOKEN', ''); 4942 $cookie->expire = time() - 3600; 4943 Yii::app()->request->cookies['YII_CSRF_TOKEN'] = $cookie; 4944} 4945 4946/** 4947* A function to remove ../ or ./ from paths to prevent directory traversal 4948* 4949* @param mixed $path 4950*/ 4951function get_absolute_path($path) 4952{ 4953 $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); 4954 $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); 4955 $absolutes = array(); 4956 foreach ($parts as $part) { 4957 if ('.' == $part) { 4958 continue; 4959 } 4960 if ('..' == $part) { 4961 array_pop($absolutes); 4962 } else { 4963 $absolutes[] = $part; 4964 } 4965 } 4966 return implode(DIRECTORY_SEPARATOR, $absolutes); 4967} 4968 4969/** 4970* Check if string is JSON array 4971* 4972* @param string $str 4973* @return bool 4974*/ 4975function isJson($str) { 4976 $json = json_decode($str); 4977 return $json && $str != $json; 4978} 4979 4980/** 4981* Check if array is associative 4982* 4983* @param array $array 4984* @return bool 4985*/ 4986function isAssociativeArray($array){ 4987 foreach ($array as $key => $value) { 4988 if (is_string($key)) { 4989 return true; 4990 } 4991 } 4992 return false; 4993} 4994 4995 4996/** 4997* Create a directory in tmp dir using a random string 4998* 4999* @param string $dir the temp directory (if empty will use the one from configuration) 5000* @param string $prefix wanted prefix for the directory 5001* @param int $mode wanted file mode for this directory 5002* @return string the path of the created directory 5003*/ 5004function createRandomTempDir($dir=null, $prefix = '', $mode = 0700) 5005{ 5006 5007 $sDir = (empty($dir)) ? Yii::app()->getConfig('tempdir') : get_absolute_path ($dir); 5008 5009 if (substr($sDir, -1) != DIRECTORY_SEPARATOR) { 5010 $sDir .= DIRECTORY_SEPARATOR; 5011 } 5012 5013 do { 5014 $sRandomString = getRandomString(); 5015 $path = $sDir.$prefix.$sRandomString; 5016 } 5017 while (!mkdir($path, $mode)); 5018 5019 return $path; 5020} 5021 5022/** 5023 * Generate a random string, using openssl if available, else using md5 5024 * @param int $length wanted lenght of the random string (only for openssl mode) 5025 * @return string 5026 */ 5027function getRandomString($length=32){ 5028 5029 if ( function_exists('openssl_random_pseudo_bytes') ) { 5030 $token = ""; 5031 $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 5032 $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz"; 5033 $codeAlphabet.= "0123456789"; 5034 for($i=0;$i<$length;$i++){ 5035 $token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))]; 5036 } 5037 }else{ 5038 $token = md5(uniqid(rand(), true)); 5039 } 5040 return $token; 5041} 5042 5043/** 5044 * Get a random number between two values using openssl_random_pseudo_bytes 5045 * @param int $min 5046 * @param int $max 5047 * @return string 5048 */ 5049function crypto_rand_secure($min, $max) { 5050 $range = $max - $min; 5051 if ($range < 0) return $min; // not so random... 5052 $log = log($range, 2); 5053 $bytes = (int) ($log / 8) + 1; // length in bytes 5054 $bits = (int) $log + 1; // length in bits 5055 $filter = (int) (1 << $bits) - 1; // set all lower bits to 1 5056 do { 5057 $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))); 5058 $rnd = $rnd & $filter; // discard irrelevant bits 5059 } while ($rnd >= $range); 5060 return $min + $rnd; 5061} 5062 5063/** 5064 * Test if a given zip file is Zip Bomb 5065 * see comment here : http://php.net/manual/en/function.zip-entry-filesize.php 5066 * @param string $zip_filename 5067 * @return int 5068 */ 5069function isZipBomb($zip_filename) 5070{ 5071 return ( get_zip_originalsize($zip_filename) > Yii::app()->getConfig('maximum_unzipped_size') ); 5072} 5073 5074/** 5075 * Get the original size of a zip archive to prevent Zip Bombing 5076 * see comment here : http://php.net/manual/en/function.zip-entry-filesize.php 5077 * @param string $filename 5078 * @return int 5079 */ 5080function get_zip_originalsize($filename) { 5081 5082 if ( function_exists ('zip_entry_filesize') ){ 5083 $size = 0; 5084 $resource = zip_open($filename); 5085 5086 if ( ! is_int($resource) ) { 5087 while ($dir_resource = zip_read($resource)) { 5088 $size += zip_entry_filesize($dir_resource); 5089 } 5090 zip_close($resource); 5091 } 5092 5093 return $size; 5094 }else{ 5095 if ( YII_DEBUG ){ 5096 Yii::app()->setFlashMessage("Warning! The PHP Zip extension is not installed on this server. You're not protected from ZIP bomb attacks.", 'error'); 5097 } 5098 } 5099 5100 return -1; 5101} 5102 5103/** 5104 * PHP7 has created a little nasty bomb with count throwing erroros on uncountables 5105 * This is to "fix" this problem 5106 * 5107 * @param mixed $element 5108 * @return integer counted element 5109 * @author 5110 */ 5111function safecount($element) 5112{ 5113 $isCountable = is_array($element) || $element instanceof Countable; 5114 if($isCountable) { 5115 return count($element); 5116 } 5117 return 0; 5118} 5119