<?php

if (!defined('BASEPATH')) {
    exit('No direct script access allowed');
}
/*
* LimeSurvey
* Copyright (C) 2007-2026 The LimeSurvey Project Team
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

// TODO: Why needed?
require_once(Yii::app()->basePath . '/libraries/MersenneTwister.php');

use LimeSurvey\PluginManager\PluginEvent;
use LimeSurvey\Models\Services\SurveyAccessModeService;

function loadanswers()
{
    Yii::trace('start', 'survey.loadanswers');
    global $surveyid;
    global $thisstep;
    global $clienttoken;


    $scid = Yii::app()->request->getQuery('scid');
    if (Yii::app()->request->getParam('loadall') === "reload") {
        $sLoadName = Yii::app()->request->getParam('loadname');
        $sLoadPass = Yii::app()->request->getParam('loadpass');
        $oCriteria = new CDbCriteria();
        if (isset($scid)) {
            //Would only come from email : we don't need it ....
            $oCriteria->addCondition("saved_control.scid=:scid");
            $aParams[':scid'] = $scid;
        }
        $oCriteria->addCondition("saved_control.identifier=:identifier");
        $aParams[':identifier'] = $sLoadName;
        $oCriteria->params = $aParams;
        /* relations saved_control is needed: force */
        $oResponses = SurveyDynamic::model($surveyid)->with('saved_control')->find($oCriteria);
    } elseif (isset($_SESSION['responses_' . $surveyid]['srid'])) {
        /* relations saved_control is not needed : only lazy load */
        $oResponses = SurveyDynamic::model($surveyid)->findByPk($_SESSION['responses_' . $surveyid]['srid']);
    } else {
        return false;
    }

    if (!$oResponses) {
        return false;
    }

    /* If we came from reload : check access_code */
    if (!empty($sLoadName) && !empty($oResponses->saved_control)) {
        $saved_control = $oResponses->saved_control;
        $access_code = $oResponses->saved_control->access_code;
        $md5_code = md5((string) $sLoadPass);
        $sha256_code = hash('sha256', (string) $sLoadPass);
        if ($md5_code === $access_code || $sha256_code === $access_code || password_verify((string) $sLoadPass, (string) $access_code)) {
            // If survey come from reload (GET or POST); some value need to be found on saved_control, not on survey
            if (Yii::app()->request->getParam('loadall') === "reload") {
                // We don't need to control if we have one, because we do the test before
                $_SESSION['responses_' . $surveyid]['scid'] = $saved_control->scid;
                $_SESSION['responses_' . $surveyid]['step'] = ($saved_control->saved_thisstep > 1) ? $saved_control->saved_thisstep : 1;
                $thisstep = $_SESSION['responses_' . $surveyid]['step'] - 1; // deprecated ?
                $_SESSION['responses_' . $surveyid]['srid'] = $saved_control->srid; // Seems OK without
                $_SESSION['responses_' . $surveyid]['refurl'] = $saved_control->refurl;
            }
        } else {
            return false;
        }
    }

    /* Decrypt loaded responses */
    $oResponses->decrypt();
    // Get if survey is been answered
    $submitdate = $oResponses->submitdate;
    $aRow = $oResponses->attributes;
    foreach ($aRow as $column => $value) {
        if ($column === "token") {
            $clienttoken = $value;
            $token = $value;
        } elseif ($column === 'lastpage' && !isset($_SESSION['responses_' . $surveyid]['step'])) {
            if (is_null($submitdate) || $submitdate === "N") {
                $_SESSION['responses_' . $surveyid]['step'] = ($value > 1 ? $value : 1);
                $thisstep = $_SESSION['responses_' . $surveyid]['step'] - 1;
            } else {
                $_SESSION['responses_' . $surveyid]['maxstep'] = $_SESSION['responses_' . $surveyid]['totalsteps'];
            }
        } elseif ($column === "datestamp") {
            $_SESSION['responses_' . $surveyid]['datestamp'] = $value;
        }
        if ($column === "startdate") {
            $_SESSION['responses_' . $surveyid]['startdate'] = $value;
        } else {
            //Only make session variables for those in insertarray[] or in fieldmap
            if (in_array($column, $_SESSION['responses_' . $surveyid]['insertarray']) && isset($_SESSION['responses_' . $surveyid]['fieldmap'][$column])) {
                /* Fix for numeric value */
                if (in_array($_SESSION['responses_' . $surveyid]['fieldmap'][$column]['type'], [Question::QT_N_NUMERICAL, Question::QT_K_MULTIPLE_NUMERICAL])) {
                    /* Value need to be a string if it's null to evaluate conditions. This is especially important for the deletenonvalue feature, otherwise we would erase any answer with condition such as EQUALS-NO-ANSWER */
                    $value = is_null($value) ? "" : trim($value);
                    /* If value is set : it came from DB as decimal, must fix for some validation (mantis #19435) */
                    if ($value != '') {
                        if ($value[0] === ".") {
                            $value = "0" . $value;
                        }
                        if (strpos($value, ".") !== false) {
                            $value = rtrim(rtrim($value, "0"), ".");
                        }
                    }
                }
                if ($_SESSION['responses_' . $surveyid]['fieldmap'][$column]['type'] == Question::QT_D_DATE) {
                    /* Value need to be a string if it's null to evaluate conditions. This is especially important for the deletenonvalue feature, otherwise we would erase any answer with condition such as EQUALS-NO-ANSWER */
                    $value = is_null($value) ? "" : trim($value);
                }
                $_SESSION['responses_' . $surveyid][$column] = $value;
            }
        }
    }
    $_SESSION['responses_' . $surveyid]['LEMtokenResume'] = true;
    return true;
}


/**
 * This function creates the language selector for a particular survey
*
* @param string  $sSelectedLanguage : lang to be selected (forced)
*
* @return array|false               : array of data if more than one language, else false
*/
function getLanguageChangerDatas($sSelectedLanguage = "")
{
    $surveyid = Yii::app()->getConfig('surveyID');
    Yii::app()->loadHelper("surveytranslator");

    $aSurveyLangs = Survey::model()->findByPk($surveyid)->getAllLanguages();

    // return datas only of there are more than one lanagage
    if (count($aSurveyLangs) > 1) {
        $aAllLanguages = getLanguageData(true);
        $aSurveyLangs  = array_intersect_key($aAllLanguages, array_flip($aSurveyLangs)); // Sort languages by their locale name
        $sClass        = "ls-language-changer-item";
        $sAction       = Yii::app()->request->getParam('action', ''); // Different behaviour if preview

        $routeParams   = array(
            "sid" => $surveyid,
        );

        // retrieve the route of url in preview mode
        if (substr((string) $sAction, 0, 7) == 'preview') {
            $routeParams["action"] = $sAction;
            if (intval(Yii::app()->request->getParam('gid', 0))) {
                $routeParams['gid'] = intval(Yii::app()->request->getParam('gid', 0));
            }

            if ($sAction == 'previewquestion' && intval(Yii::app()->request->getParam('gid', 0)) && intval(Yii::app()->request->getParam('qid', 0))) {
                $routeParams['qid'] = intval(Yii::app()->request->getParam('qid', 0));
            }

            if (!is_null(Yii::app()->request->getParam('token'))) {
                $routeParams['token'] = Yii::app()->request->getParam('token');
            }

            // @todo : add other params (for prefilling by URL in preview mode)
            $sClass     .= " ls-no-js-hidden ls-previewmode-language-dropdown ";
            $sTargetURL  = Yii::app()->getController()->createUrl("survey/index", $routeParams);
        } else {
            $sTargetURL = null;
        }

        $aListLang = array();
        foreach ($aSurveyLangs as $sLangCode => $aSurveyLang) {
            $aListLang[$sLangCode] = html_entity_decode((string) $aSurveyLang['nativedescription'], ENT_COMPAT, 'UTF-8') . ' - ' . $aSurveyLang['description'];
        }

        $sSelected = ($sSelectedLanguage) ? $sSelectedLanguage : App()->language;

        $languageChangerDatas = array(
            'sSelected' => $sSelected,
            'aListLang' => $aListLang,
            'sClass'    => $sClass,
            'targetUrl' => $sTargetURL,
        );

        return $languageChangerDatas;
    } else {
        return false;
    }
}

/**
 * This function creates the language selector for the public survey index page
 *
 * @param string $sSelectedLanguage The language in which all information is shown
 * @return array|bool
 */
function getLanguageChangerDatasPublicList($sSelectedLanguage)
{
    $aLanguages = getLanguageDataRestricted(true); // Order by native
    if (count($aLanguages) > 1) {
        $sClass = "ls-language-changer-item";
        foreach ($aLanguages as $sLangCode => $aLanguage) {
                    $aListLang[$sLangCode] = html_entity_decode((string) $aLanguage['nativedescription'], ENT_COMPAT, 'UTF-8') . ' - ' . $aLanguage['description'];
        }
        $sSelected = $sSelectedLanguage;

        $languageChangerDatas = array(
            'sSelected' => $sSelected,
            'aListLang' => $aListLang,
            'sClass'    => $sClass,
        );

        return $languageChangerDatas;
    } else {
        return false;
    }
}

/**
 * Construct flash message container
 * Used in templatereplace to replace {FLASHMESSAGE} in startpage.tstpl
 *
 * @return string
 */
function makeFlashMessage()
{
    global $surveyid;
    $html = "";

    $language = Yii::app()->getLanguage();
    $originalPrefix = Yii::app()->user->getStateKeyPrefix();
    // Bug in Yii? Getting the state-key prefix changes the locale, so set the language manually after.
    Yii::app()->setLanguage($language);
    Yii::app()->user->setStateKeyPrefix('frontend' . $surveyid);

    $mapYiiToBootstrapClass = array(
        'error' => 'danger',
        'success' => 'success',
        'notice' => 'info'
        // no warning in Yii?
    );

    foreach (Yii::app()->user->getFlashes() as $key => $message) {
        $html .= "<div class='alert alert-" . $mapYiiToBootstrapClass[$key] . " alert-dismissible flash-" . $key . "'>" . $message . "</div>\n";
    }

    Yii::app()->user->setStateKeyPrefix($originalPrefix);

    return $html;
}

/**
* checkUploadedFileValidity used in SurveyRuntimeHelper
*/
function checkUploadedFileValidity($surveyid, $move, $backok = null)
{
    global $thisstep;

    $survey = Survey::model()->findByPk($surveyid);
    if (!isset($backok) || $backok != "Y") {
        $fieldmap = createFieldMap($survey, 'full', false, false, $_SESSION['responses_' . $surveyid]['s_lang']);

        if (!empty(App()->getRequest()->getPost('fieldnames'))) {
            $fields = explode("|", (string) $_POST['fieldnames']);

            foreach ($fields as $field) {
                if (array_key_exists($field, $fieldmap) && $fieldmap[$field]['type'] == Question::QT_VERTICAL_FILE_UPLOAD && !strrpos((string) $fieldmap[$field]['fieldname'], "_Cfilecount")) {
                    $validation = QuestionAttribute::model()->getQuestionAttributes($fieldmap[$field]['qid']);

                    $filecount = 0;

                    $json = App()->getRequest()->getPost($field);
                    $phparray = json_decode(urldecode((string) $json));
                    // if name is blank, its basic, hence check
                    // else, its ajax, don't check, bypass it.
                    if (!empty($phparray)) {
                        if (!empty($phparray[0]->size)) {
                            // ajax
                            $filecount = count($phparray);
                        } else {
                            // basic
                            for ($i = 1; $i <= $validation['max_num_of_files']; $i++) {
                                if (!isset($_FILES[$field . "_file_" . $i]) || $_FILES[$field . "_file_" . $i]['name'] == '') {
                                    continue;
                                }

                                $filecount++;

                                $file = $_FILES[$field . "_file_" . $i];

                                // File size validation
                                if ($file['size'] > $validation['max_filesize'] * 1000) {
                                    $filenotvalidated = array();
                                    $filenotvalidated[$field . "_file_" . $i] = sprintf(gT("Sorry, the uploaded file (%s) is larger than the allowed filesize of %s KB."), $file['size'], $validation['max_filesize']);
                                    $append = true;
                                }

                                // File extension validation
                                $pathinfo = pathinfo(basename((string) $file['name']));
                                $ext = $pathinfo['extension'];

                                $validExtensions = explode(",", (string) $validation['allowed_filetypes']);
                                if (!(in_array($ext, $validExtensions))) {
                                    if (isset($append) && $append) {
                                        $filenotvalidated[$field . "_file_" . $i] .= sprintf(gT("Sorry, only %s extensions are allowed!"), $validation['allowed_filetypes']);
                                        unset($append);
                                    } else {
                                        $filenotvalidated = array();
                                        $filenotvalidated[$field . "_file_" . $i] .= sprintf(gT("Sorry, only %s extensions are allowed!"), $validation['allowed_filetypes']);
                                    }
                                }
                            }
                        }
                    } else {
                        $filecount = 0;
                    }

                    if (isset($validation['min_num_of_files']) && $filecount < $validation['min_num_of_files'] && LimeExpressionManager::QuestionIsRelevant($fieldmap[$field]['qid'])) {
                        $filenotvalidated = array();
                        $filenotvalidated[$field] = gT("The minimum number of files has not been uploaded.");
                    }
                }
            }
        }
        if (isset($filenotvalidated)) {
            if (isset($move) && $move == "moveprev") {
                $_SESSION['responses_' . $surveyid]['step'] = $thisstep;
            }
            if (isset($move) && $move == "movenext") {
                $_SESSION['responses_' . $surveyid]['step'] = $thisstep;
            }
            return $filenotvalidated;
        }
    }
    if (!isset($filenotvalidated)) {
        return false;
    } else {
        return $filenotvalidated;
    }
}

/**
* Takes two single element arrays and adds second to end of first if value exists
* Why not use array_merge($array1,array_filter($array2);
*/
function addtoarray_single($array1, $array2)
{
    //
    if (is_array($array2)) {
        foreach ($array2 as $ar) {
            if ($ar && $ar !== null) {
                $array1[] = $ar;
            }
        }
    }
    return $array1;
}

/**
* Marks a tokens as completed and sends a confirmation email to the participiant.
* If $quotaexit is set to true then the user exited the survey due to a quota
* restriction and the according token is only marked as 'Q'
*
* @param boolean $quotaexit
*/
function submittokens($quotaexit = false)
{
    $surveyid = Yii::app()->getConfig('surveyID');
    if (isset($_SESSION['responses_' . $surveyid]['s_lang'])) {
        $thissurvey = getSurveyInfo($surveyid, $_SESSION['responses_' . $surveyid]['s_lang']);
    } else {
        $thissurvey = getSurveyInfo($surveyid);
    }
    $clienttoken = $_SESSION['responses_' . $surveyid]['token'] ?? '';

    $tokenused = ($_SESSION['responses_' . $surveyid]['tokenused'] ?? false);

    if (($clienttoken === '') && (!$tokenused) && ($thissurvey['access_mode'] !== SurveyAccessModeService::$ACCESS_TYPE_CLOSED)) {
        return; //optional
    }

    // Shift the date due to global timezone setting
    $today = gmdate("Y-m-d H:i");

    // check how many uses the token has left
    $token = Token::model($surveyid)->findByAttributes(array('token' => $clienttoken ? $clienttoken : $tokenused));
    if (!$token) {
        throw new CHttpException(403, gT("Invalid access code"));
    }
    $token->scenario = 'FinalSubmit'; // Do not XSS filter token data

    if ($quotaexit == true) {
        $token->completed = 'Q';
        if (!$tokenused) {
            $token->usesleft--;
        }
    } else {
        if ($token->usesleft <= 1) {
            // Finish the token
            if (isTokenCompletedDatestamped($thissurvey)) {
                $token->completed = $today;
            } else {
                $token->completed = 'Y';
            }
            if (isset($token->participant_id)) {
                $slquery = SurveyLink::model()->find('participant_id = :pid AND survey_id = :sid AND token_id = :tid', array(':pid' => $token->participant_id, ':sid' => $surveyid, ':tid' => $token->tid));
                if ($slquery) {
                    if (isTokenCompletedDatestamped($thissurvey)) {
                        $slquery->date_completed = $today;
                    } else {
                        // Update the survey_links table if necessary, to protect anonymity, use the date_created field date
                        $slquery->date_completed = $slquery->date_created;
                    }
                    $slquery->save();
                }
            }
        }
        $token->usesleft--;
    }
    $token->decrypt();
    $token->encryptSave();

    if ($quotaexit == false) {
        $token->decrypt();
        if ($token && trim(strip_tags((string) $thissurvey['email_confirm'])) != "" && $thissurvey['sendconfirmation'] == "Y") {
            $sToAddress = LimeMailer::validateAddresses($token->email);
            if ($sToAddress) {
                /* Force a replacement to fill coreReplacement like {SURVEYRESOURCESURL} for example */
                $reData = array('thissurvey' => $thissurvey);
                templatereplace(
                    "{SID}",
                    array(), /* No tempvars update */
                    $reData /* Be sure to use current survey */
                );
                $mail = new \LimeMailer();
                $mail->setSurvey($surveyid);
                $mail->setToken($token->token);
                $mail->setTypeWithRaw('confirm', Yii::app()->getLanguage());
                $mail->replaceTokenAttributes = true;
                $mail->addUrlsPlaceholders(array('SURVEY'));
                $mail->sendMessage();
            }
        }
    }
}

/**
 * Send a submit notification to the email address specified in the notifications tab in the survey settings
 * @param array $emails Emailnotifications that should be sent ['responseTo' => [['failedEmailId' => 'failedEmailId1', 'responseid' => 'responseid1', 'recipient' => 'recipient1', 'language' => 'language1'], [...]], 'notificationTo' => [[..., ..., ...][...]]]
 * @param bool $return whether the function should return values
 * @param int $surveyid survey ID of currently used survey
 * @throws \PHPMailer\PHPMailer\Exception
 * @throws CException
 */
function sendSubmitNotifications($surveyid, array $emails = [], bool $return = false)
{
    // @todo: Remove globals
    global $thissurvey;
    $bIsHTML = ($thissurvey['htmlemail'] === 'Y'); // Needed for ANSWERTABLE
    $debug = App()->getConfig('debug');

    //LimeMailer instance
    $mailer = \LimeMailer::getInstance(\LimeMailer::ResetComplete);
    $mailer->setSurvey($surveyid);
    $mailer->aUrlsPlaceholders = ['VIEWRESPONSE','EDITRESPONSE','STATISTICS'];

    //emails to be sent and return values
    $aEmailNotificationTo = $emails['admin_notification'] ?? [];
    $aEmailResponseTo = $emails['admin_responses'] ?? [];
    $failedEmailCount = 0;
    $successfullEmailCount = 0;

    //replacementVars for LEM
    $aReplacementVars = array();
    $aReplacementVars['STATISTICSURL'] = App()->getController()->createAbsoluteUrl("/admin/statistics/sa/index/surveyid/{$surveyid}");
    $aReplacementVars['ANSWERTABLE'] = '';

    if (!isset($_SESSION['responses_' . $surveyid]['srid'])) {
        $responseId = null; /* Maybe just return ? */
    } else {
        //replacementVars for LEM requiring a response id
        $responseId = $_SESSION['responses_' . $surveyid]['srid'];
        $aReplacementVars['EDITRESPONSEURL'] = App()->getController()->createAbsoluteUrl("/admin/dataentry/sa/editdata/subaction/edit/surveyid/{$surveyid}/id/{$responseId}");
        $aReplacementVars['VIEWRESPONSEURL'] = App()->getController()->createAbsoluteUrl("responses/view/", ['surveyId' => $surveyid, 'id' => $responseId]);
    }

    // set email language
    $emailLanguage = null;
    if (isset($_SESSION['responses_' . $surveyid]['s_lang'])) {
        $emailLanguage = $_SESSION['responses_' . $surveyid]['s_lang'];
    }

    // create array of recipients for emailnotifications
    if (!empty($thissurvey['emailnotificationto']) && empty($emails)) {
        $recipients = LimeExpressionManager::ProcessStepString($thissurvey['emailnotificationto'], array('ADMINEMAIL' => $thissurvey['adminemail']), 3, true);
        $aEmailNotificationTo = array_merge($aEmailNotificationTo, LimeMailer::validateAddresses($recipients));
    }
    // // create array of recipients for emailresponses
    if (!empty($thissurvey['emailresponseto']) && empty($emails)) {
        $recipients = LimeExpressionManager::ProcessStepString($thissurvey['emailresponseto'], array('ADMINEMAIL' => $thissurvey['adminemail']), 3, true);
        $aEmailResponseTo = array_merge($aEmailResponseTo, LimeMailer::validateAddresses($recipients));
    }

    if (count($aEmailNotificationTo) || count($aEmailResponseTo)) {
        /* Force a replacement to fill coreReplacement like {SURVEYRESOURCESURL} for example */
        $reData = array('thissurvey' => $thissurvey);
        templatereplace(
            "{SID}",
            array(), /* No tempvars update (except old Replacement like */
            $reData /* Be sure to use current survey */
        );
    }

    // update replacement fields before we handle email sending
    LimeExpressionManager::updateReplacementFields($aReplacementVars);

    // admin_notification (Basic admin notification)
    if (count($aEmailNotificationTo) > 0) {
        $mailer = LimeMailer::getInstance();
        $mailer->setTypeWithRaw('admin_notification', $emailLanguage);
        foreach ($aEmailNotificationTo as $sRecipient) {
            /** set mailer params for @see FailedEmailController::actionResend() */
            if (!empty($emails)) {
                $failedNotificationId = $sRecipient['id'];
                $responseId = $sRecipient['responseId'];
                $notificationRecipient = $sRecipient['recipient'];
                $emailLanguage = $sRecipient['language'];
                $aReplacementVars['ANSWERTABLE'] = getResponseTableReplacement($surveyid, $responseId, $emailLanguage, $bIsHTML);
                LimeExpressionManager::updateReplacementFields($aReplacementVars);
                $mailer->setTypeWithRaw('admin_notification', $emailLanguage);
                $mailer->setTo($notificationRecipient);
                $mailerSuccess = $mailer->resend(json_decode((string) $sRecipient['resendVars'], true));
            } else {
                $failedNotificationId = null;
                $notificationRecipient = $sRecipient;
                $aReplacementVars['ANSWERTABLE'] = getResponseTableReplacement($surveyid, $responseId, $emailLanguage, $bIsHTML);
                LimeExpressionManager::updateReplacementFields($aReplacementVars);
                $mailer->setTo($notificationRecipient);
                $mailerSuccess = $mailer->SendMessage();
            }
            if (!$mailerSuccess) {
                $failedEmailCount++;
                saveFailedEmail($failedNotificationId, $notificationRecipient, $surveyid, $responseId, 'admin_notification', $emailLanguage, $mailer);
                if (empty($emails) && $debug > 0 && Permission::model()->hasSurveyPermission($surveyid, 'surveysettings', 'update')) {
                    /* Find a better way to show email error … */
                    echo CHtml::tag(
                        "div",
                        ['class' => 'alert alert-danger'],
                        sprintf(gT("Basic admin notification could not be sent because of error: %s"), $mailer->getError())
                    );
                }
            } else {
                $successfullEmailCount++;
                //preserve failedEmail if it exists
                failedEmailSuccess($failedNotificationId);
            }
        }
    }

    // admin_notification (Detailed admin notification)
    if (count($aEmailResponseTo) > 0) {
        // there was no token used so lets remove the token field from insertarray
        if (isset($_SESSION['responses_' . $surveyid]['insertarray'][0])) {
            if (!isset($_SESSION['responses_' . $surveyid]['token']) && $_SESSION['responses_' . $surveyid]['insertarray'][0] === 'token') {
                unset($_SESSION['responses_' . $surveyid]['insertarray'][0]);
            }
        }
        $mailer = \LimeMailer::getInstance();
        $mailer->setTypeWithRaw('admin_responses', $emailLanguage);
        foreach ($aEmailResponseTo as $sRecipient) {
            /** set mailer params for @see FailedEmailController::actionResend() */
            if (!empty($emails)) {
                $failedNotificationId = $sRecipient['id'];
                $responseId = $sRecipient['responseId'];
                $responseRecipient = $sRecipient['recipient'];
                $emailLanguage = $sRecipient['language'];
                $aReplacementVars['ANSWERTABLE'] = getResponseTableReplacement($surveyid, $responseId, $emailLanguage, $bIsHTML);
                LimeExpressionManager::updateReplacementFields($aReplacementVars);
                $mailer->setTypeWithRaw('admin_responses', $emailLanguage);
                $mailer->setTo($responseRecipient);
                $mailerSuccess = $mailer->resend(json_decode((string) $sRecipient['resendVars'], true));
            } else {
                $failedNotificationId = null;
                $responseRecipient = $sRecipient;
                $aReplacementVars['ANSWERTABLE'] = getResponseTableReplacement($surveyid, $responseId, $emailLanguage, $bIsHTML);
                LimeExpressionManager::updateReplacementFields($aReplacementVars);
                $mailer->setTo($responseRecipient);
                $mailerSuccess = $mailer->SendMessage();
            }
            if (!$mailerSuccess) {
                $failedEmailCount++;
                saveFailedEmail($failedNotificationId, $responseRecipient, $surveyid, $responseId, 'admin_responses', $emailLanguage, $mailer);
                if (empty($emails) && $debug > 0 && Permission::model()->hasSurveyPermission($surveyid, 'surveysettings', 'update')) {
                    /* Find a better way to show email error … */
                    echo CHtml::tag(
                        "div",
                        ['class' => 'alert alert-danger'],
                        sprintf(gT("Detailed admin notification could not be sent because of error: %s"), $mailer->getError())
                    );
                }
            } else {
                $successfullEmailCount++;
                failedEmailSuccess($failedNotificationId);
            }
        }
    }
    if ($return) {
        return [
            'successfullEmailCount' => $successfullEmailCount,
            'failedEmailCount'      => $failedEmailCount,
        ];
    }
}

/**
 * create ANSWERTABLE replacement field content
 * @param $surveyid
 * @param $responseId
 * @param $emailLanguage
 * @param $bIsHTML
 * @return string
 * @throws CException
 */
function getResponseTableReplacement($surveyid, $responseId, $emailLanguage, $bIsHTML): string
{
    $aFullResponseTable = getFullResponseTable($surveyid, $responseId, $emailLanguage);
    $ResultTableHTML = "<table class='printouttable' >\n";
    $ResultTableText = "\n\n";
    Yii::import('application.helpers.viewHelper');
    foreach ($aFullResponseTable as $sFieldname => $fname) {
        if (substr($sFieldname, 0, 4) === 'gid_') {
            $questionText = viewHelper::flatEllipsizeText($fname[0], true, 0);
            $ResultTableHTML .= "\t<tr class='printanswersgroup'><td colspan='2'>" . $questionText . "</td></tr>\n";
            $ResultTableText .= "\n" . $questionText . "\n\n";
        } elseif (substr($sFieldname, 0, 4) === 'qid_') {
            $questionText = viewHelper::flatEllipsizeText($fname[0], true, 0);
            $ResultTableHTML .= "\t<tr class='printanswersquestionhead'><td  colspan='2'>" . $questionText . "</td></tr>\n";
            $ResultTableText .= "\n" . $questionText . "\n";
        } else {
            $questionText = viewHelper::flatEllipsizeText($fname[0], true, 0) . " " .  viewHelper::flatEllipsizeText($fname[1], true, 0);
            $ResultTableHTML .= "\t<tr class='printanswersquestion'><td>" . $questionText . "</td><td class='printanswersanswertext'>" . CHtml::encode($fname[2]) . "</td></tr>\n";
            $ResultTableText .= "     " . $questionText . ": {$fname[2]}\n";
        }
    }
    $ResultTableHTML .= "</table>\n";
    $ResultTableText .= "\n\n";

    if ($bIsHTML) {
        return $ResultTableHTML;
    }
    return $ResultTableText;
}

/**
 * Saves a failed email whenever processing and sensing an email fails or overwrites a found entry with updated values
 *
 * @param int|null $id Id of failed email
 * @param string|null $recipient
 * @param int $surveyId
 * @param int $responseId
 * @param string|null $emailType
 * @param string|null $language
 * @param LimeMailer $mailer
 * @return bool
 */
function saveFailedEmail(?int $id, ?string $recipient, int $surveyId, int $responseId, string $emailType, ?string $language, LimeMailer $mailer): bool
{
    $errorMessage = $mailer->getError();
    $resendVars = json_encode($mailer->getResendEmailVars());
    if (isset($id)) {
        $failedEmail = FailedEmail::model()->findByPk($id);
        if (isset($failedEmail)) {
            $failedEmail->surveyid = $surveyId;
            $failedEmail->error_message = $errorMessage;
            $failedEmail->status = 'SEND FAILED';
            $failedEmail->updated = date('Y-m-d H:i:s');
        }
    }

    if (!isset($failedEmail)) {
        $failedEmail = new FailedEmail();
        $failedEmail->recipient = $recipient;
        $failedEmail->surveyid = $surveyId;
        $failedEmail->responseid = $responseId;
        $failedEmail->email_type = $emailType;
        $failedEmail->language = $language;
        $failedEmail->error_message = $errorMessage;
        $failedEmail->created = date('Y-m-d H:i:s');
        $failedEmail->status = 'SEND FAILED';
        $failedEmail->updated = date('Y-m-d H:i:s');
        $failedEmail->resend_vars = $resendVars;
    }

    try {
        return $failedEmail->save(false);
    } catch (\CDbException $e) {
        // If there's an exception, it might be due to the error message, so we log it
        // and try to save again with a generic error message
        Yii::log("Error saving failed email: " . $e->getMessage(), CLogger::LEVEL_ERROR);
        Yii::log("Original error message: " . $errorMessage, CLogger::LEVEL_ERROR);
        $failedEmail->error_message = gT("Original error message could not be saved.");
        return $failedEmail->save(false);
    }
}

function failedEmailSuccess($id)
{
    $model = new FailedEmail();
    $failedEmail = $model->findByPk($id);
    if (isset($failedEmail)) {
        $failedEmail->status = 'SEND SUCCESS';
        $failedEmail->updated = date('Y-m-d H:i:s');
        return $failedEmail->save();
    }
    return false;
}

/**
 * submitfailed : used in em_manager_helper.php
 *
 * "Unexpected error"
 *
 * Will send e-mail to adminemail if defined.
 *
 * @param string $errormsg
 * @param string $query  Will be included in sent email
 * @return string Error message
 */
function submitfailed($errormsg = '', $query = null)
{
    global $debug;
    global $thissurvey;
    global $surveyid;

    $completed = "<p><span class='ri-error-warning-fill'></span>&nbsp;<strong>"
    . gT("Responses were not saved") . "</strong></p>"
    . "<p>"
    . gT("An unexpected error has occurred and your responses cannot be saved.")
    . "</p>";
    if ($thissurvey['adminemail']) {
        $completed .= "<p>";
        $completed .= gT("Your responses have not been lost and have been emailed to the survey administrator and will be entered into our database at a later point.");
        $completed .= "</p>";
        if (Yii::app()->getConfig('debug') > 0 && !empty($errormsg)) {
            $completed .= 'Error message: ' . htmlspecialchars($errormsg) . '<br />';
        }
        $email = sprintf(gT("An error occurred saving a response to survey %s", "unescaped"), $thissurvey['name'] . " - $surveyid\n\n");
        $email .= gT("DATA TO BE ENTERED", "unescaped") . ":\n";
        foreach ($_SESSION['responses_' . $surveyid]['insertarray'] as $value) {
            if (isset($_SESSION['responses_' . $surveyid][$value])) {
                $email .= "$value: {$_SESSION['responses_'.$surveyid][$value]}\n";
            } else {
                $email .= "$value: N/A\n";
            }
        }
        if (!empty($query)) {
            $email .= "\n" . gT("SQL CODE THAT FAILED", "unescaped") . ":\n"
            . $query . "\n\n";
        }
        if (!empty($errormsg)) {
            $email .= gT("Error message", "unescaped") . ":\n"
               . $errormsg . "\n\n";
        }

        $mailer = new \LimeMailer();
        $mailer->emailType = "errorsavingresults";
        $mailer->Subject = gT("Error saving results", "unescaped");
        $mailer->Body = $email;
        $mailer->setSurvey($surveyid);
        $mailer->addAddress($thissurvey['adminemail']);
        $mailer->sendMessage();
    } else {
        $completed .= "<a href='javascript:location.reload()'>" . gT("Try to submit again") . "</a>\n";
    }
    return $completed;
}

/**
 * This function builds all the required session variables when a survey is first started and
 * it loads any answer defaults from command line or from the table defaultvalues
 * It is called from the related format script (group.php, question.php, survey.php)
 * if the survey has just started.
 *
 * @param int $surveyid
 * @param boolean $preview Defaults to false
 * @return void
 */
function buildsurveysession($surveyid, $preview = false)
{
    /// Yii::trace('start', 'survey.buildsurveysession');
    global $clienttoken;
    global $tokensexist;

    $survey = Survey::model()->findByPk($surveyid);

    $preview                          = ($preview) ? $preview : Yii::app()->getConfig('previewmode');
    $sLangCode                        = App()->language;
    $thissurvey                       = getSurveyInfo($surveyid, $sLangCode);
    $oTemplate                        = Template::model()->getInstance('', $surveyid);
    App()->getController()->sTemplate = $oTemplate->sTemplateName; // It's going to be hard to be sure this is used ....
    $sTemplateViewPath                = $oTemplate->viewPath;


    // Reset all the session variables and start again
    resetAllSessionVariables($surveyid);

    // NOTE: All of this is already done in survey controller.
    // We keep it here only for Travis Tested that are still not using Selenium
    // As soon as the tests are rewrote to use selenium, those lines can be removed
    $lang = $_SESSION['responses_' . $surveyid]['s_lang'] ?? '';
    if (empty($lang)) {
        // Multi lingual support order : by REQUEST, if not by Token->language else by survey default language

        /** @psalm-suppress UndefinedVariable TODO: $oTokenEntry is never defined */
        if (returnGlobal('lang', true)) {
            $language_to_set = returnGlobal('lang', true);
        } elseif (isset($oTokenEntry) && $oTokenEntry) {
            // If survey have token : we have a $oTokenEntry
            // Can use $oTokenEntry = Token::model($surveyid)->findByAttributes(array('token'=>$clienttoken)); if we move on another function : this par don't validate the token validity
            $language_to_set = $oTokenEntry->language;
        } else {
            $language_to_set = $thissurvey['language'];
        }
            // Always SetSurveyLanguage : surveys controller SetSurveyLanguage too, if different : broke survey (#09769)
           SetSurveyLanguage($surveyid, $language_to_set);
    }


    UpdateGroupList($surveyid, $_SESSION['responses_' . $surveyid]['s_lang']);

    $totalquestions = $survey->countTotalQuestions;
    $totalVisibleQuestions = $survey->getCountTotalQuestions(false);

    $iTotalGroupsWithoutQuestions = QuestionGroup::model()->getTotalGroupsWithoutQuestions($surveyid);

    $_SESSION['responses_' . $surveyid]['totalquestions'] = $totalquestions;
    $_SESSION['responses_' . $surveyid]['totalVisibleQuestions'] = $totalVisibleQuestions;

    // 2. SESSION VARIABLE: totalsteps
    setTotalSteps($surveyid, $thissurvey, $totalquestions, $totalVisibleQuestions);

    // Break out and crash if there are no questions!
    if (($totalquestions == 0 || $iTotalGroupsWithoutQuestions > 0) && !$preview) {
        breakOutAndCrash($sTemplateViewPath, $totalquestions, $iTotalGroupsWithoutQuestions, $thissurvey);
    }

    //3. SESSION VARIABLE - insertarray
    //An array containing information about used to insert the data into the db at the submit stage
    //4. SESSION VARIABLE - fieldarray
    //See rem at end..

    if ($tokensexist == 1 && $clienttoken) {
        $_SESSION['responses_' . $surveyid]['token'] = $clienttoken;
    }

    if ($thissurvey['anonymized'] == "N") {
        $_SESSION['responses_' . $surveyid]['insertarray'][] = "token";
    }

    $fieldmap = $_SESSION['responses_' . $surveyid]['fieldmap'] = createFieldMap($survey, 'full', true, false, $_SESSION['responses_' . $surveyid]['s_lang']);

    // first call to initFieldArray
    initFieldArray($surveyid, $fieldmap);

    // Prefill questions/answers from command line params
    prefillFromCommandLine($surveyid);

    if (isset($_SESSION['responses_' . $surveyid]['fieldarray'])) {
        $_SESSION['responses_' . $surveyid]['fieldarray'] = array_values($_SESSION['responses_' . $surveyid]['fieldarray']);
    }

    //Check if a passthru label and value have been included in the query url
    checkPassthruLabel($surveyid, $preview, $fieldmap);

    Yii::trace('end', 'survey.buildsurveysession');
}

/**
 * Check if a passthru label and value have been included in the query url
 * @param int $surveyid
 * @param boolean $preview
 * @return void
 */
function checkPassthruLabel($surveyid, $preview, $fieldmap)
{
    $oResult = SurveyURLParameter::model()->getParametersForSurvey($surveyid);
    foreach ($oResult->readAll() as $aRow) {
        if (isset($_GET[$aRow['parameter']]) && !$preview) {
            $_SESSION['responses_' . $surveyid]['urlparams'][$aRow['parameter']] = $_GET[$aRow['parameter']];
            if ($aRow['targetqid'] != '') {
                foreach ($fieldmap as $sFieldname => $aField) {
                    if ($aRow['targetsqid'] != '') {
                        if ($aField['qid'] == $aRow['targetqid'] && $aField['sqid'] == $aRow['targetsqid']) {
                            $_SESSION['responses_' . $surveyid]['startingValues'][$sFieldname] = $_GET[$aRow['parameter']];
                            $_SESSION['responses_' . $surveyid]['startingValues'][$aRow['parameter']] = $_GET[$aRow['parameter']];
                        }
                    } else {
                        if ($aField['qid'] == $aRow['targetqid']) {
                            $_SESSION['responses_' . $surveyid]['startingValues'][$sFieldname] = $_GET[$aRow['parameter']];
                            $_SESSION['responses_' . $surveyid]['startingValues'][$aRow['parameter']] = $_GET[$aRow['parameter']];
                        }
                    }
                }
            }
        }
    }
}

/**
 * Prefill startvalues from command line param
 * @param integer $surveyid
 * @return void
 */
function prefillFromCommandLine($surveyid)
{
    // This keys will never be prefilled
    $reservedGetValues = array(
        'token',
        'sid',
        'gid',
        'qid',
        'lang',
        'newtest',
        'action',
        'seed'
    );

    if (!isset($_SESSION['responses_' . $surveyid]['startingValues'])) {
        $startingValues = array();
    } else {
        $startingValues = $_SESSION['responses_' . $surveyid]['startingValues'];
    }
    $request = Yii::app()->getRequest();
    if (in_array($request->getRequestType(), ['GET', 'POST'])) {
        $getValues = array_diff_key($request->getQueryParams(), array_combine($reservedGetValues, $reservedGetValues));
        if (!empty($getValues)) {
            $qcode2sgqa = array();
            Yii::import('application.helpers.viewHelper');
            foreach ($_SESSION['responses_' . $surveyid]['fieldmap'] as $sgqa => $details) {
                $qcode2sgqa[viewHelper::getFieldCode($details, array('LEMcompat' => true))] = $sgqa;
            }
            foreach ($getValues as $k => $v) {
                if (isset($_SESSION['responses_' . $surveyid]['fieldmap'][$k])) {
                    // Qqa prefilling
                    $startingValues[$k] = $v;
                } elseif (array_key_exists($k, $qcode2sgqa)) {
                    // EM code prefilling
                    $startingValues[$qcode2sgqa[$k]] = $v;
                }
            }
        }
    }
    $_SESSION['responses_' . $surveyid]['startingValues'] = $startingValues;
}

/**
 * @param array $fieldmap
 * @param integer $surveyid
 * @return void
 */
function initFieldArray($surveyid, array $fieldmap)
{
    // Reset field array if called more than once (should not happen)
    $_SESSION['responses_' . $surveyid]['fieldarray'] = array();

    foreach ($fieldmap as $key => $field) {
        if (isset($field['qid']) && $field['qid'] != '') {
            $_SESSION['responses_' . $surveyid]['fieldnamesInfo'][$field['fieldname']]   = 'Q' . $field['qid'];
            $_SESSION['responses_' . $surveyid]['insertarray'][]                         = $field['fieldname'];
            //fieldarray ARRAY CONTENTS -
            //            [0]=questions.qid,
            //            [1]=fieldname,
            //            [2]=questions.title,
            //            [3]=questions.question
            //            [4]=questions.type,
            //            [5]=questions.gid,
            //            [6]=questions.mandatory,
            //            [7]=conditionsexist,
            //            [8]=usedinconditions
            //            [8]=usedinconditions
            //            [9]=used in group.php for question count
            //            [10]=new group id for question in randomization group (GroupbyGroup Mode)

            if (!isset($_SESSION['responses_' . $surveyid]['fieldarray']['Q' . $field['qid']])) {
                //JUST IN CASE : PRECAUTION!
                //following variables are set only if $style=="full" in createFieldMap() in common_helper.
                //so, if $style = "short", set some default values here!
                if (isset($field['title'])) {
                                    $title = $field['title'];
                } else {
                                    $title = "";
                }

                if (isset($field['question'])) {
                                    $question = $field['question'];
                } else {
                                    $question = "";
                }

                if (isset($field['mandatory'])) {
                                    $mandatory = $field['mandatory'];
                } else {
                                    $mandatory = 'N';
                }

                if (isset($field['hasconditions'])) {
                                    $hasconditions = $field['hasconditions'];
                } else {
                                    $hasconditions = 'N';
                }

                if (isset($field['usedinconditions'])) {
                                    $usedinconditions = $field['usedinconditions'];
                } else {
                                    $usedinconditions = 'N';
                }

                $_SESSION['responses_' . $surveyid]['fieldarray']['Q' . $field['qid']] = array($field['qid'],
                'Q' . $field['qid'],
                $title,
                $question,
                $field['type'],
                $field['gid'],
                $mandatory,
                $hasconditions,
                $usedinconditions);
            }

            if (isset($field['random_gid'])) {
                $_SESSION['responses_' . $surveyid]['fieldarray']['Q' . $field['qid']][10] = $field['random_gid'];
            }
        }
    }
}


/**
 * Apply randomizationGroup and randomizationQuestion to session fieldmap
 * @param int $surveyid
 * @param boolean $preview
 * @return void
 */
function randomizationGroupsAndQuestions($surveyid, $preview = false, $fieldmap = array())
{
    // Initialize the randomizer. Seed will be stored in response.
    // TODO: rewrite this THE YII WAY !!!! (application/vendor + internal config for namespace + aliases; etc)
    ls\mersenne\setSeed($surveyid);

    $fieldmap = (empty($fieldmap)) ? $_SESSION['responses_' . $surveyid]['fieldmap'] : $fieldmap;

    [$fieldmap, $randomized1] = randomizationGroup($surveyid, $fieldmap, $preview); // Randomization groups for groups
    [$fieldmap, $randomized2] = randomizationQuestion($surveyid, $fieldmap, $preview); // Randomization groups for questions

    $randomized = $randomized1 || $randomized2;
    ;
    $_SESSION['responses_' . $surveyid]['randomized'] = $randomized;

    if ($randomized === true) {
        $fieldmap = finalizeRandomization($fieldmap);

        $_SESSION['responses_' . $surveyid]['fieldmap-' . $surveyid . $_SESSION['responses_' . $surveyid]['s_lang']] = $fieldmap;
        $_SESSION['responses_' . $surveyid]['fieldmap-' . $surveyid . '-randMaster']                            = 'fieldmap-' . $surveyid . $_SESSION['responses_' . $surveyid]['s_lang'];
    }

    $_SESSION['responses_' . $surveyid]['fieldmap'] = $fieldmap;

    return $fieldmap;
}

/**
 * Randomization group for groups
 * @param int $surveyid
 * @param array $fieldmap
 * @param boolean $preview
 * @return array ($fieldmap, $randomized)
 */
function randomizationGroup($surveyid, array $fieldmap, $preview)
{
    // Randomization groups for groups
    $aRandomGroups   = array();
    $aGIDCompleteMap = array();

    // First find all groups and their groups IDS
    $criteria = new CDbCriteria();
    $criteria->addColumnCondition(array('sid' => $surveyid));
    $criteria->addCondition("randomization_group != ''");

    $oData = QuestionGroup::model()->findAll($criteria);

    foreach ($oData as $aGroup) {
        $aRandomGroups[$aGroup['randomization_group']][] = $aGroup['gid'];
    }

    // Shuffle each group and create a map for old GID => new GID
    foreach ($aRandomGroups as $sGroupName => $aGIDs) {
        $aShuffledIDs    = $aGIDs;
        $aShuffledIDs    = ls\mersenne\shuffle($aShuffledIDs);
        $aGIDCompleteMap = $aGIDCompleteMap + array_combine($aGIDs, $aShuffledIDs);
    }

    $_SESSION['responses_' . $surveyid]['groupReMap'] = $aGIDCompleteMap;

    $randomized = false; // So we can trigger reorder once for group and question randomization

    // Now adjust the grouplist
    if (count($aRandomGroups) > 0 && !$preview) {
        $randomized = true; // So we can trigger reorder once for group and question randomization

        // Now adjust the grouplist
        Yii::import('application.helpers.frontend_helper', true); // make sure frontend helper is loaded ???? We are inside frontend_helper..... TODO: check if it can be removed
        UpdateGroupList($surveyid, $_SESSION['responses_' . $surveyid]['s_lang']);
        // ... and the fieldmap

        // First create a fieldmap with GID as key
        foreach ($fieldmap as $aField) {
            if (isset($aField['gid'])) {
                $GroupFieldMap[$aField['gid']][] = $aField;
            } else {
                $GroupFieldMap['other'][] = $aField;
            }
        }

        // swap it
        foreach ($GroupFieldMap as $iOldGid => $fields) {
            $iNewGid = $iOldGid;
            if (isset($aGIDCompleteMap[$iOldGid])) {
                $iNewGid = $aGIDCompleteMap[$iOldGid];
            }
            $newGroupFieldMap[$iNewGid] = $GroupFieldMap[$iNewGid];
        }

        $GroupFieldMap = $newGroupFieldMap;
        // and convert it back to a fieldmap
        unset($fieldmap);

        foreach ($GroupFieldMap as $aGroupFields) {
            foreach ($aGroupFields as $aField) {
                if (isset($aField['fieldname'])) {
                    $fieldmap[$aField['fieldname']] = $aField; // isset() because of the shuffled flag above
                }
            }
        }
    }

    return array($fieldmap, $randomized);
}

/**
 * Randomization group for questions
 * @param int $surveyid
 * @param array $fieldmap
 * @param boolean $preview
 * @return array ($fieldmap, $randomized)
 */
function randomizationQuestion($surveyid, array $fieldmap, $preview)
{
    $randomized   = false;
    $randomGroups = array();

    // Find all defined randomization groups through question attribute values
    if (in_array(Yii::app()->db->getDriverName(), array('mssql', 'sqlsrv', 'dblib'))) {
        //Previous query: $rgquery = "SELECT attr.qid, CAST(value as varchar(255)) as value FROM {{question_attributes}} as attr right join {{questions}} as quests on attr.qid=quests.qid WHERE attribute='random_group' and CAST(value as varchar(255)) <> '' and sid=$surveyid GROUP BY attr.qid, CAST(value as varchar(255))";
        $rgresult = Question::model()->with('questionattributes')->together()->findAll("attribute='random_group' and CAST(value as varchar(255)) <>'' and sid={$surveyid}");
    } else {
        //Previous query: $rgquery = "SELECT attr.qid, value FROM {{question_attributes}} as attr right join {{questions}} as quests on attr.qid=quests.qid WHERE attribute='random_group' and value <> '' and sid=$surveyid GROUP BY attr.qid, value";
        $rgresult = Question::model()->with('questionattributes')->together()->findAll("attribute='random_group' and value <>'' and sid={$surveyid}");
    }
    foreach ($rgresult as $rgrow) {
        $randomGroups[$rgrow->questionattributes['random_group']->value][] = $rgrow['qid']; // Get the question IDs for each randomization group
    }

    // If we have randomization groups set, then lets cycle through each group and
    // replace questions in the group with a randomly chosen one from the same group
    if (count($randomGroups) > 0 && !$preview) {
        $randomized    = true; // So we can trigger reorder once for group and question randomization
        $copyFieldMap  = array();
        $oldQuestOrder = array();
        $newQuestOrder = array();
        $randGroupNames = array();

        foreach ($randomGroups as $key => $value) {
            $oldQuestOrder[$key] = $randomGroups[$key];
            $newQuestOrder[$key] = $oldQuestOrder[$key];
            // We shuffle the question list to get a random key->qid which will be used to swap from the old key
            $newQuestOrder[$key] = ls\mersenne\shuffle($newQuestOrder[$key]);
            $randGroupNames[] = $key;
        }

        // Loop through the fieldmap and swap each question as they come up
        foreach ($fieldmap as $fieldkey => $fieldval) {
            $found = 0;

            foreach ($randomGroups as $gkey => $gval) {
                // We found a qid that is in the randomization group
                if (isset($fieldval['qid']) && in_array($fieldval['qid'], $oldQuestOrder[$gkey])) {
                    // Get the swapped question
                    $idx = array_search($fieldval['qid'], $oldQuestOrder[$gkey]);

                    foreach ($fieldmap as $key => $field) {
                        if (isset($field['qid']) && $field['qid'] == $newQuestOrder[$gkey][$idx]) {
                            $field['random_gid'] = $fieldval['gid']; // It is possible to swap to another group
                            $copyFieldMap[$key]  = $field;
                        }
                    }
                    $found = 1;
                    break;
                } else {
                    $found = 2;
                }
            }

            if ($found == 2) {
                $copyFieldMap[$fieldkey] = $fieldval;
            }
            reset($randomGroups);
        }
        $fieldmap = $copyFieldMap;
    }

    return array($fieldmap, $randomized);
}

/**
 * Stuff?
 * @param array $fieldmap
 * @return array Fieldmap
 */
function finalizeRandomization($fieldmap)
{
    // reset the sequencing counts
    $gseq = -1;
    $_gid = -1;
    $qseq = -1;
    $_qid = -1;
    $copyFieldMap = array();

    foreach ($fieldmap as $key => $val) {
        if ($val['gid'] != '') {
            if (isset($val['random_gid'])) {
                $gid = $val['random_gid'];
            } else {
                $gid = $val['gid'];
            }

            if ($gid != $_gid) {
                $_gid = $gid;
                ++$gseq;
            }
        }

        if ($val['qid'] != '' && $val['qid'] != $_qid) {
            $_qid = $val['qid'];
            ++$qseq;
        }

        if ($val['gid'] != '' && $val['qid'] != '') {
            $val['groupSeq']    = $gseq;
            $val['questionSeq'] = $qseq;
        }

        $copyFieldMap[$key] = $val;
    }
    return $copyFieldMap;
}

/**
 * Test if token is valid
 * @param array $subscenarios
 * @param array $thissurvey
 * @param array $aEnterTokenData
 * @param string $clienttoken
 * @return string[] ($renderToken, $FlashError)
 */
function testIfTokenIsValid(array $subscenarios, array $thissurvey, array $aEnterTokenData, $clienttoken)
{
    $FlashError = '';
    if (FailedLoginAttempt::model()->isLockedOut(FailedLoginAttempt::TYPE_TOKEN)) {
        $FlashError = sprintf(gT('You have exceeded the number of maximum access code validation attempts. Please wait %d minutes before trying again.'), App()->getConfig('timeOutParticipants') / 60);
        $renderToken = 'main';
    } else {
        if (!$subscenarios['tokenValid']) {
            //Check if there is a clienttoken set
            if ((!isset($clienttoken) || $clienttoken == "")) {
                if (isset($thissurvey) && $thissurvey['allowregister'] == "Y") {
                    $renderToken = 'register';
                } else {
                    $renderToken = 'main';
                }
            } else {
                //token was wrong
                $errorMsg    = gT("The access code you have provided is either not valid, or has already been used.");
                $FlashError .= $errorMsg;
                $renderToken = 'main';
                FailedLoginAttempt::model()->addAttempt(FailedLoginAttempt::TYPE_TOKEN);
            }
        } else {
            $aEnterTokenData['visibleToken'] = $clienttoken;
            $aEnterTokenData['token'] = $clienttoken;
            $renderToken = 'correct';
            // Intentionally don't reset FailedLoginAttempt for this IP.
            // FailedLoginAttempt::model()->deleteAttempts();
        }
    }

    return array($renderToken, $FlashError, $aEnterTokenData);
}

/**
 * Returns which way should be rendered
 * @param string $renderToken
 * @param string $renderCaptcha
 * @return string
 */
function getRenderWay($renderToken, $renderCaptcha)
{
    $renderWay = "";
    if ($renderToken !== $renderCaptcha) {
        if ($renderToken === "register") {
            $renderWay = "register";
        }
        if ($renderCaptcha === "correct" || $renderToken === "correct") {
            $renderWay = "main";
        }
        if ($renderCaptcha === "") {
            $renderWay = $renderToken;
        } elseif ($renderToken === "") {
            $renderWay = $renderCaptcha;
        }
    } else {
        $renderWay = $renderToken;
    }
    return $renderWay;
}

/**
 * Render token, captcha or register form
 * @param string $renderWay
 * @param array $scenarios
 * @param string $sTemplateViewPath
 * @param array $aEnterTokenData
 * @param int $surveyid
 * @return void
 */
function renderRenderWayForm($renderWay, array $scenarios, $sTemplateViewPath, $aEnterTokenData, $surveyid, $aSurveyInfo = null)
{
    switch ($renderWay) {
        case "main": //Token required, maybe Captcha required
            // Datas for the form
            $aForm                    = array();
            $aForm['sType']           = ($scenarios['tokenRequired']) ? 'token' : 'captcha';
            $aForm['token']           = array_key_exists('token', $aEnterTokenData) ? $aEnterTokenData['token'] : null;
            $aForm['aEnterErrors']    = $aEnterTokenData['aEnterErrors'];
            $aForm['bCaptchaEnabled'] = $aEnterTokenData['bCaptchaEnabled'] ?? '';
            if ($aForm['bCaptchaEnabled']) {
                Yii::app()->getController()->createAction('captcha');
            }
            $oSurvey = Survey::model()->findByPk($surveyid);
            if (empty($aSurveyInfo)) {
                $aSurveyInfo  =  getsurveyinfo($surveyid, App()->getLanguage());
            }
            // Rendering layout_user_forms.twig
            $thissurvey                     = $oSurvey->attributes;
            $thissurvey["aForm"]            = $aForm;
            $thissurvey['surveyUrl']        = App()->createUrl("/survey/index", array_merge(["sid" => $surveyid], getForwardParameters(Yii::app()->getRequest())));
            $thissurvey['include_content']  = 'userforms';

            Yii::app()->clientScript->registerScriptFile(Yii::app()->getConfig("generalscripts") . 'nojs.js', CClientScript::POS_HEAD);

            // Language selector
            if ($aSurveyInfo['alanguageChanger']['show']) {
                $aSurveyInfo['alanguageChanger']['datas']['targetUrl'] = $thissurvey['surveyUrl'];
            }
            $thissurvey['alanguageChanger'] = $aSurveyInfo['alanguageChanger'];

            $aData['aSurveyInfo'] = $thissurvey;

            $aData['aSurveyInfo'] = array_merge($aSurveyInfo, $aData['aSurveyInfo']);

            Yii::app()->twigRenderer->renderTemplateFromFile("layout_user_forms.twig", $aData, false);
            break;

        case "register": //Register new user
            // Add the event and test if done
            Yii::app()->runController("register/index/sid/{$surveyid}");
            Yii::app()->end();
            break;

        case "correct": //Nothing to hold back, render survey
        default:
            break;
    }
}

/**
 * Resets all session variables for this survey
 * @param int $surveyid
 * @return void
 */
function resetAllSessionVariables($surveyid)
{
    Yii:app()->session->regenerateID(true);
    unset($_SESSION['responses_' . $surveyid]['grouplist']);
    unset($_SESSION['responses_' . $surveyid]['fieldarray']);
    unset($_SESSION['responses_' . $surveyid]['insertarray']);
    unset($_SESSION['responses_' . $surveyid]['fieldnamesInfo']);
    unset($_SESSION['responses_' . $surveyid]['fieldmap-' . $surveyid . '-randMaster']);
    unset($_SESSION['responses_' . $surveyid]['groupReMap']);
    $_SESSION['responses_' . $surveyid]['fieldnamesInfo'] = array();
}

/**
 * The number of "pages" that will be presented in this survey
 * The number of pages to be presented will differ depending on the survey format
 * Set totalsteps in session
 * @param int $surveyid
 * @param array $thissurvey
 * @param integer $totalquestions
 * @return void
 */
function setTotalSteps($surveyid, array $thissurvey, $totalquestions, $totalVisibleQuestions)
{
    switch ($thissurvey['format']) {
        case "A":
            $_SESSION['responses_' . $surveyid]['totalsteps'] = 1;
            break;

        case "G":
            if (isset($_SESSION['responses_' . $surveyid]['grouplist'])) {
                $_SESSION['responses_' . $surveyid]['totalsteps'] = count($_SESSION['responses_' . $surveyid]['grouplist']);
            }
            break;

        case "S":
            $_SESSION['responses_' . $surveyid]['totalsteps'] = $totalquestions;
            $_SESSION['responses_' . $surveyid]['totalVisibleSteps'] = $totalVisibleQuestions;
    }
}

/**
 * @todo Rename
 * @todo Move HTML to view
 * @param string $sTemplateViewPath
 * @param int $totalquestions
 * @param int $iTotalGroupsWithoutQuestions
 * @param array $thissurvey
 * @return void
 */
function breakOutAndCrash($sTemplateViewPath, $totalquestions, $iTotalGroupsWithoutQuestions, array $thissurvey)
{

    $sTitle = gT("This survey cannot be tested or completed for the following reason(s):");
    $sMessage = '';

    if ($totalquestions == 0) {
        $sMessage = gT("There are no questions in this survey.");
    }

    if ($iTotalGroupsWithoutQuestions > 0) {
        $sMessage = gT("There are empty question groups in this survey - please create at least one question within a question group.");
    }

    renderError($sTitle, $sMessage, $thissurvey, $sTemplateViewPath);
}

/**
 * @param string $sTemplateViewPath
 */
function renderError($sTitle, $sMessage, $thissurvey, $sTemplateViewPath)
{
    // Template settings
    $surveyid = $thissurvey['sid'];
    //$oTemplate         = Template::model()->getInstance('', $surveyid);
    //$oTemplate->registerAssets();

    $aError = array();
    $aError['title']      = (!empty(trim((string) $sTitle))) ? $sTitle : gT("This survey cannot be tested or completed for the following reason(s):");
    $aError['message']    = $sMessage;
    $thissurvey['aError'] = $aError;

    Yii::app()->twigRenderer->renderTemplateFromFile("layout_errors.twig", array('oSurvey' => Survey::model()->findByPk($surveyid), 'aSurveyInfo' => $thissurvey), false);
}

/**
 * TODO: call this function from surveyRuntimeHelper
 * TODO: remove surveymover()
 */
function getNavigatorDatas()
{
    global $surveyid, $thissurvey;

    $aNavigator = array();
    $aNavigator['show'] = true;

    $sMoveNext          = "movenext";
    $sMovePrev          = "";
    $iSessionStep       = $_SESSION['responses_' . $surveyid]['step'] ?? false;
    $iSessionMaxStep    = $_SESSION['responses_' . $surveyid]['maxstep'] ?? false;
    $iSessionTotalSteps = $_SESSION['responses_' . $surveyid]['totalsteps'] ?? false;

    // Count down
    $aNavigator['disabled'] = '';
    if ($thissurvey['navigationdelay'] > 0 && ($iSessionMaxStep !== false && $iSessionMaxStep == $iSessionStep)) {
        $aNavigator['disabled'] = " disabled";
        App()->getClientScript()->registerScriptFile(Yii::app()->getConfig('generalscripts') . "/navigator-countdown.js");
        App()->getClientScript()->registerScript('navigator_countdown', "navigator_countdown(" . $thissurvey['navigationdelay'] . ");\n", LSYii_ClientScript::POS_POSTSCRIPT);
    }

    // Previous ?
    if (
        $thissurvey['format'] != Question::QT_A_ARRAY_5_POINT && ($thissurvey['allowprev'] != "N")
        && $iSessionStep
        && !($iSessionStep == 1 && $thissurvey['showwelcome'] == 'N')
        && !Yii::app()->getConfig('previewmode')
    ) {
        $sMovePrev = "moveprev";
    }

    // Submit ?
    if (
        $iSessionStep && ($iSessionStep == $iSessionTotalSteps)
        || $thissurvey['format'] == 'A'
    ) {
        $sMoveNext = "movesubmit";
    }

    // todo Remove Next if needed (example quota show previous only: maybe other, but actually don't use surveymover)
    if (Yii::app()->getConfig('previewmode')) {
        $sMoveNext = "";
    }


    $aNavigator['aMovePrev']['show']  = ($sMovePrev != '');
    $aNavigator['aMoveNext']['show']  = ($sMoveNext != '');
    $aNavigator['aMoveNext']['value'] = $sMoveNext;


    // SAVE BUTTON
    if ($thissurvey['allowsave'] == "Y") {
        App()->getClientScript()->registerScript("activateActionLink", "activateActionLink();\n", LSYii_ClientScript::POS_POSTSCRIPT);

        // Fill some test here, more clear ....
        $bAnonymized                = $thissurvey["anonymized"] == 'Y';
        $bTokenanswerspersistence   = $thissurvey['tokenanswerspersistence'] == 'Y' && tableExists('tokens_' . $surveyid);
        $bAlreadySaved              = isset($_SESSION['responses_' . $surveyid]['scid']);
        $iSessionStep               = ($_SESSION['responses_' . $surveyid]['step'] ?? false);
        $iSessionMaxStep            = ($_SESSION['responses_' . $surveyid]['maxstep'] ?? false);

        // Find out if the user has any saved data
        if ($thissurvey['format'] == 'A') {
            if ((!$bTokenanswerspersistence || $bAnonymized) && !$bAlreadySaved) {
                $aNavigator['load']['show'] = true;
            }
            $aNavigator['save']['show'] = true;
        } elseif (!$iSessionStep) {
            //Welcome page, show load (but not save)
            if ((!$bTokenanswerspersistence || $bAnonymized) && !$bAlreadySaved) {
                $aNavigator['load']['show'] = true;
            }

            if ($thissurvey['showwelcome'] == "N") {
                $aNavigator['save']['show'] = true;
            }
        } elseif ($iSessionMaxStep == 1 && $thissurvey['showwelcome'] == "N") {
            //First page, show LOAD and SAVE
            if ((!$bTokenanswerspersistence || $bAnonymized) && !$bAlreadySaved) {
                $aNavigator['load']['show'] = true;
            }

            $aNavigator['save']['show'] = true;
        } elseif (getMove() != "movelast") {
            // Not on last page or submitted survey
            $aNavigator['save']['show'] = true;
        }
    }

    return $aNavigator;
}

/**
 * Calculate assessment scores
 *
 * @param integer $surveyid
 * @param boolean $onlyCurrent : only current ( ASSESSMENT_CURRENT_TOTAL )
 * @return array
 */
function doAssessment($surveyid, $onlyCurrent = true)
{
    /* Default : show nothing */
    $assessment = array(
        'show' => false,
        'total' => array(
            'show' => false,
        ),
        'subtotal' => array(
            'show' => false,
        ),
        'total_score' => "", // Current total is set to 0 if assessments == "Y", empty string if not.
        'subtotal_score' => array(), // Score by group, used only on endpage currently
    );
    $oSurvey = Survey::model()->findByPk($surveyid);
    if ($oSurvey->assessments != "Y") {
        return array(
            'show' => false,
            'datas' => $assessment,
            'currentotal' => '',
        );
    }
    $currentLanguage = App()->getLanguage();
    if (!isset($_SESSION['responses_' . $surveyid]['s_lang'])) {
        /* Then not inside survey … can surely return directly */
        return array(
            'show' => false,
            'datas' => $assessment,
            'currentotal' => '',
        );
    }
    /* Always count and count only one time … */
    $fieldmap = createFieldMap($oSurvey, "full", false, false, $currentLanguage);
    $total    = 0;
    $groups   = array();
    foreach ($fieldmap as $field) {
        // Init Assessment Value
        $assessmentValue = null;
        $assessmentValue = null;
        if (in_array($field['type'], array('1', 'F', 'H', 'W', 'Z', 'L', '!', 'M', 'O', 'P'))) {
            $fieldmap[$field['fieldname']]['assessment_value'] = 0;
            if (!empty($_SESSION['responses_' . $surveyid][$field['fieldname']])) {
                //Multiflexi choice  - result is the assessment attribute value
                if (($field['type'] == "M") || ($field['type'] == "P")) {
                    if ($_SESSION['responses_' . $surveyid][$field['fieldname']] == "Y") {
                        $aAttributes     = QuestionAttribute::model()->getQuestionAttributes($field['qid']);
                        $assessmentValue = (int) $aAttributes['assessment_value'];
                    }
                } else {
                    // Single choice question
                    $oAssessementAnswer = Answer::model()->find(array(
                        'select' => 'code,assessment_value',
                        'condition' => 'qid = :qid and code = :code',
                        'params' => array(":qid" => $field['qid'], ":code" => $_SESSION['responses_' . $surveyid][$field['fieldname']])
                    ));
                    if ($oAssessementAnswer) {
                        $assessmentValue    = $oAssessementAnswer->assessment_value;
                    }
                }
                $fieldmap[$field['fieldname']]['assessment_value'] = $assessmentValue;
            }
            $groups[] = $field['gid'];
        }
        // If this is a question (and not a survey field, like ID), save assessment value
        if ($field['qid'] > 0) {
            /**
             * Allow Plugin to update assessment value
             */
            // Prepare Event Info
            $event = new PluginEvent('afterSurveyQuestionAssessment');
            $event->set('surveyId', $surveyid);
            $event->set('lang', $currentLanguage);
            $event->set('gid', $field['gid']);
            $event->set('qid', $field['qid']);

            if (array_key_exists('sqid', $field)) {
                $event->set('sqid', $field['sqid']);
            }

            if (array_key_exists('aid', $field)) {
                $event->set('aid', $field['aid']);
            }

            $event->set('assessmentValue', $assessmentValue);
            if (isset($_SESSION['responses_' . $surveyid][$field['fieldname']])) {
                $event->set('response', $_SESSION['responses_' . $surveyid][$field['fieldname']]);
            }
            // Dispatch Event and Get new assessment value
            App()->getPluginManager()->dispatchEvent($event);
            $updatedAssessmentValue = $event->get('assessmentValue', $assessmentValue);

            /**
             * Save assessment value on the response
             */
            $fieldmap[$field['fieldname']]['assessment_value'] = $updatedAssessmentValue;
            $total = $total + $updatedAssessmentValue;
        }
    }
    $assessment['total_score'] = $total;
    if ($onlyCurrent) {
        return array(
            'show' => false,
            'datas' => $assessment,
            'currentotal' => $total,
        );
    }
    /* count by group */
    $groups = array_unique($groups);
    $subtotal = array();
    foreach ($groups as $group) {
        $grouptotal = 0;
        foreach ($fieldmap as $field) {
            if ($field['gid'] == $group && isset($field['assessment_value'])) {
                if (isset($_SESSION['responses_' . $surveyid][$field['fieldname']])) {
                    $grouptotal = $grouptotal + $field['assessment_value'];
                }
            }
        }
        $subtotal[$group] = $grouptotal;
    }

    /* Get current assessment (can be only for last page …) */
    $aoAssessements = Assessment::model()->findAll(array(
        'condition' => "sid = :sid and language = :language",
        'order' => 'scope,id', // No real order in assessment, here : group first (why ?) and by creation
        'params' => array(':sid' => $surveyid,':language' => $currentLanguage)
    ));
    if (!empty($aoAssessements)) {
        foreach ($aoAssessements as $oAssessement) {
            if ($oAssessement->scope == "G") {
                /* send only current valid assessments */
                if ($oAssessement->minimum <= $subtotal[$oAssessement->gid] && $subtotal[$oAssessement->gid] <= $oAssessement->maximum) {
                    $assessment['group'][$oAssessement->gid][] = array(
                        "name"    => $oAssessement->name,
                        "min"     => $oAssessement->minimum,
                        "max"     => $oAssessement->maximum,
                        "message" => $oAssessement->message
                    );
                }
            } else {
                /* send only current valid assessments */
                if ($oAssessement->minimum <= $total && $total <= $oAssessement->maximum) {
                    $assessment['total']['show'] = true;
                    $assessment['total'][] = array(
                        "name"    => $oAssessement->name,
                        "min"     => $oAssessement->minimum,
                        "max"     => $oAssessement->maximum,
                        "message" => $oAssessement->message
                    );
                }
            }
        }
    }
    if (!empty($subtotal) && !empty($assessment['group'])) {
        $assessment['subtotal']['show']  = true;
        $assessment['subtotal']['datas'] = $subtotal;
    }
        $assessment['subtotal_score'] = $subtotal;
        $assessment['total_score']    = $total;

        return array(
            'show' => ($assessment['subtotal']['show'] || $assessment['total']['show']),
            'datas' => $assessment,
            'currentotal' => $total,
        );
}


/**
* Update SESSION VARIABLE: grouplist
* A list of groups in this survey, ordered by group name.
* @param string $language
* @param integer $surveyid
*/
function UpdateGroupList($surveyid, $language)
{

    unset($_SESSION['responses_' . $surveyid]['grouplist']);

    $result = QuestionGroup::model()->findAllByAttributes(['sid' => $surveyid], ['order' => 'group_order ASC']);
    $groupList = array();
    foreach ($result as $row) {
        $group = array(
            'gid'         => $row['gid'],
            'group_name'  => $row->questiongroupl10ns[$language]->group_name,
            'description' =>  $row->questiongroupl10ns[$language]->description);
        $groupList[] = $group;
        $gidList[$row['gid']] = $group;
    }

    if (!Yii::app()->getConfig('previewmode') && isset($_SESSION['responses_' . $surveyid]['groupReMap']) && count($_SESSION['responses_' . $surveyid]['groupReMap']) > 0) {
        // Now adjust the grouplist
        $groupRemap    = $_SESSION['responses_' . $surveyid]['groupReMap'];
        $groupListCopy = $groupList;

        foreach ($groupList as $gseq => $info) {
            $gid = $info['gid'];
            if (isset($groupRemap[$gid])) {
                $gid = $groupRemap[$gid];
            }
            $groupListCopy[$gseq] = $gidList[$gid];
        }
        $groupList = $groupListCopy;
    }
        $_SESSION['responses_' . $surveyid]['grouplist'] = $groupList;
}

/**
* FieldArray contains all necessary information regarding the questions
* This function is needed to update it in case the survey is switched to another language
* @todo: Make 'fieldarray' obsolete by replacing with EM session info
*/
function updateFieldArray()
{
    global $surveyid;


    if (isset($_SESSION['responses_' . $surveyid]['fieldarray'])) {
        foreach ($_SESSION['responses_' . $surveyid]['fieldarray'] as $key => $value) {
            $questionarray = &$_SESSION['responses_' . $surveyid]['fieldarray'][$key];
            $arQuestion = Question::model()->findByPk($questionarray[0]);
            if (!empty($arQuestion)) {
                $questionarray[2] = $arQuestion->title;
                $questionarray[3] = $arQuestion->questionl10ns[$_SESSION['responses_' . $surveyid]['s_lang']]->question;
            }
            unset($questionarray);
        }
    }
}

/**
 * encodeEmail : encode admin email in public part
 *
 * @param mixed $mail
 * @param mixed $text
 * @param mixed $class
 * @param mixed $params
 * @return mixed|string
 */
function encodeEmail($mail, $text = "", $class = "", $params = array())
{
    $encmail = "";
    for ($i = 0; $i < strlen((string) $mail); $i++) {
        $encMod = rand(0, 2);
        switch ($encMod) {
            case 0: // None
                $encmail .= substr((string) $mail, $i, 1);
                break;
            case 1: // Decimal
                $encmail .= "&#" . ord(substr((string) $mail, $i, 1)) . ';';
                break;
            case 2: // Hexadecimal
                $encmail .= "&#x" . dechex(ord(substr((string) $mail, $i, 1))) . ';';
                break;
        }
    }

    if (!$text) {
        $text = $encmail;
    }
    return $text;
}

/**
* GetReferringUrl() returns the referring URL
* @return string
*/
function getReferringUrl()
{
    // read it from server variable
    if (isset($_SERVER["HTTP_REFERER"])) {
        if (!Yii::app()->getConfig('strip_query_from_referer_url')) {
            return $_SERVER["HTTP_REFERER"];
        } else {
            $aRefurl = explode("?", (string) $_SERVER["HTTP_REFERER"]);
            return $aRefurl[0];
        }
    } else {
        return null;
    }
}

/**
* Shows the welcome page, used in group by group and question by question mode
* @param mixed $thissurvey unused, reset to aSurveyInfo at start of function
* @param array $aSurveyInfo
*/
function display_first_page($thissurvey, $aSurveyInfo)
{
    global $token, $surveyid;

    $thissurvey                 = $aSurveyInfo;
    $thissurvey['aNavigator']   = getNavigatorDatas();
    LimeExpressionManager::StartProcessingPage();
    LimeExpressionManager::StartProcessingGroup(-1, $thissurvey['anonymized'] != "N", $surveyid); // start on welcome page

    // WHY HERE ?????
    $_SESSION['responses_' . $surveyid]['LEMpostKey'] = mt_rand();

    $loadsecurity = returnGlobal('loadsecurity', true);

    $thissurvey['EM']['ScriptsAndHiddenInputs']  = \CHtml::hiddenField('sid', $surveyid, array('id' => 'sid'));
    $thissurvey['EM']['ScriptsAndHiddenInputs'] .= \CHtml::hiddenField('lastgroupname', '_WELCOME_SCREEN_', array('id' => 'lastgroupname')); //This is to ensure consistency with mandatory checks, and new group test
    $thissurvey['EM']['ScriptsAndHiddenInputs'] .= \CHtml::hiddenField('LEMpostKey', $_SESSION['responses_' . $surveyid]['LEMpostKey'], array('id' => 'LEMpostKey'));
    $thissurvey['EM']['ScriptsAndHiddenInputs'] .= \CHtml::hiddenField('thisstep', 0, array('id' => 'thisstep'));
    if (!empty($_SESSION['responses_' . $surveyid]['token']) && $thissurvey['anonymized'] != "Y") {
        $thissurvey['EM']['ScriptsAndHiddenInputs'] .= \CHtml::hiddenField('token', $_SESSION['responses_' . $surveyid]['token'], array('id' => 'token'));
    }

    if (!empty($loadsecurity)) {
        $thissurvey['EM']['ScriptsAndHiddenInputs'] .= \CHtml::hiddenField('loadsecurity', $loadsecurity, array('id' => 'loadsecurity'));
    }

    $thissurvey['EM']['ScriptsAndHiddenInputs'] .= LimeExpressionManager::GetRelevanceAndTailoringJavaScript();

    Yii::app()->clientScript->registerScriptFile(Yii::app()->getConfig("generalscripts") . 'nojs.js', CClientScript::POS_HEAD);
    LimeExpressionManager::FinishProcessingPage();

    $thissurvey['surveyUrl'] = Yii::app()->getController()->createUrl("survey/index", array("sid" => $surveyid)); // For form action (will remove newtest)
    $thissurvey['attr']['welcomecontainer'] = $thissurvey['attr']['surveyname'] = $thissurvey['attr']['description'] = $thissurvey['attr']['welcome'] = $thissurvey['attr']['questioncount'] = '';

    $thissurvey['include_content'] = 'firstpage';

    Yii::app()->twigRenderer->renderTemplateFromFile("layout_global.twig", array('oSurvey' => Survey::model()->findByPk($surveyid), 'aSurveyInfo' => $thissurvey), false);
}

/**
* killSurveySession : reset $_SESSION part for the survey
* @param int $iSurveyID
*/
function killSurveySession($iSurveyID)
{
    // Unset the session
    unset($_SESSION['responses_' . $iSurveyID]);
    // Force EM to refresh
    LimeExpressionManager::SetDirtyFlag();

    //  unsetting LEMsingleton from session so new survey execution would start with new LEM instance
    //  SetDirtyFlag() method doesn't reset LEM properly
    //  this solution fixes bug: https://bugs.limesurvey.org/view.php?id=10162
    unset($_SESSION["LEMsingleton"]);
}

/**
* Resets all question timers by expiring the related cookie - this needs to be called before any output is done
* @todo Deprecated - Question timers no longer use cookies
*/
function resetTimers()
{
    $cookie = new CHttpCookie('limesurvey_timers', '');
    $cookie->expire = time() - 3600;
    Yii::app()->request->cookies['limesurvey_timers'] = $cookie;
}

/**
 * Removes all question timers for this survey from local storage
 *
 * @return void
 */
function resetQuestionTimers($surveyid)
{
    Yii::app()->clientScript->registerScript(
        'resetQuestionTimers',
        //'LSvar.bResetQuestionTimers=true;',
        "resetQuestionTimers({$surveyid})",
        LSYii_ClientScript::POS_BEGIN
    );
}

/**
* Set the public survey language
* Control if language exist in this survey, else set to survey default language
* if $surveyid <= 0 : set the language to default site language
* @param int $surveyid
* @param string $sLanguage
*/
function SetSurveyLanguage($surveyid, $sLanguage)
{
    $surveyid         = sanitize_int($surveyid);
    $default_language = Yii::app()->getConfig('defaultlang');

    if (isset($surveyid) && $surveyid > 0) {
        $default_survey_language     = Survey::model()->findByPk($surveyid)->language;
        $additional_survey_languages = Survey::model()->findByPk($surveyid)->getAdditionalLanguages();

        if (
            empty($sLanguage)                                                   //check if there
            || (!in_array($sLanguage, $additional_survey_languages))            //Is the language in the survey-language array
            || ($default_survey_language == $sLanguage)                         //Is the $default_language the chosen language?
        ) {
            // Language not supported, fall back to survey's default language
            $_SESSION['responses_' . $surveyid]['s_lang'] = $default_survey_language;
        } else {
            $_SESSION['responses_' . $surveyid]['s_lang'] = $sLanguage;
        }

        App()->setLanguage($_SESSION['responses_' . $surveyid]['s_lang']);
        Yii::app()->loadHelper('surveytranslator');
        LimeExpressionManager::SetEMLanguage($_SESSION['responses_' . $surveyid]['s_lang']);
    } else {
        if (!$sLanguage) {
            $sLanguage = $default_language;
        }

        $_SESSION['responses_' . $surveyid]['s_lang'] = $sLanguage;
        App()->setLanguage($_SESSION['responses_' . $surveyid]['s_lang']);
    }
}

/**
* getMove get move button clicked
* @return string
**/
function getMove()
{
    $aAcceptedMove = array('default', 'movenext', 'movesubmit', 'moveprev', 'saveall', 'loadall', 'clearall', 'changelang');
    // We can control is save and load are OK : todo fix according to survey settings
    // Maybe allow $aAcceptedMove in Plugin
    $move = Yii::app()->request->getParam('move');
    /* @deprecated since we use button and not input with different value. */
    foreach ($aAcceptedMove as $sAccepteMove) {
        if (Yii::app()->request->getParam($sAccepteMove)) {
                    $move = $sAccepteMove;
        }
    }
    /* default move (user don't click on a button, but use enter in a input:text or a select */
    if ($move == 'default') {
        $surveyid = Yii::app()->getConfig('surveyID');
        $thissurvey = getsurveyinfo($surveyid);
        $iSessionStep = $_SESSION['responses_' . $surveyid]['step'] ?? false;
        $iSessionTotalSteps = $_SESSION['responses_' . $surveyid]['totalsteps'] ?? false;
        if ($iSessionStep && ($iSessionStep == $iSessionTotalSteps) || $thissurvey['format'] == 'A') {
            $move = "movesubmit";
        } else {
            $move = "movenext";
        }
    }
    return $move;
}

/**
 * For later use, don't remove.
 * @return array<string>
 */
function cookieConsentLocalization()
{
    return array(
        gT('By continuing this survey you approve the data protection policy of the service provider.'),
        gT('OK'),
        gT('View policy'),
        gT('Please be patient until you are forwarded to the final URL.')
    );
}

/**
 * Returns an array of URL parameters that can be forwarded
 *
 * @param LSHttpRequest $request the HTTP request
 *
 * @return array<string,mixed>
 */
function getForwardParameters($request)
{
    $reservedGetValues = array(
        'token',
        'sid',
        'gid',
        'qid',
        'lang',
        'newtest',
        'action',
        'seed',
        'index.php',
    );

    $parameters = [];
    if (in_array($request->getRequestType(), ['GET', 'POST'])) {
        $parameters = array_diff_key($request->getQueryParams(), array_flip($reservedGetValues));
    }
    return $parameters;
}
