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('/&amp;/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&amp;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&amp;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("&#10;", "&#13;"),
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("&apos;","&apos;"), $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('&nbsp;', ' ', $sNicetext); // html_entity_decode does not convert &nbsp; 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 &#10;
2886        if ($encoded == '') {
2887            $mytext = str_replace('&#10;', '<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 == "&nbsp;") {
2923        $str = "";
2924    }
2925    if (preg_match("/^[\s]+$/", $str)) {
2926        $str = '';
2927    }
2928    if ($str == "\n") {
2929        $str = "";
2930    }
2931    if (trim($str) == "&nbsp;" || trim($str) == '') {
2932// chrome adds a single &nbsp; 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 = '&hellip;')
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