<?php

/*
* LimeSurvey
* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

use LimeSurvey\Helpers\questionHelper;

/**
 * This function imports a LimeSurvey .lsg question group XML file
 *
 * @param string  $sFullFilePath The full filepath of the uploaded file
 * @param integer $iNewSID       The new survey id - the page will always be added after the last page in the survey
 * @param boolean $bTranslateLinksFields
 *
 * @return mixed
 */
function XMLImportGroup($sFullFilePath, $iNewSID, $bTranslateLinksFields)
{
    $sBaseLanguage         = Survey::model()->findByPk($iNewSID)->language;
    if (\PHP_VERSION_ID < 80000) {
        $bOldEntityLoaderState = libxml_disable_entity_loader(true); // @see: http://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection
    }

    $sXMLdata              = file_get_contents($sFullFilePath);
    $xml                   = simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET);


    if ($xml === false || $xml->LimeSurveyDocType != 'Group') {
        throw new Exception('This is not a valid LimeSurvey group structure XML file.');
    }

    $iDBVersion = (int) $xml->DBVersion;
    $aQIDReplacements = array();
    $aQuestionCodeReplacements = array();
    $results['defaultvalues'] = 0;
    $results['answers'] = 0;
    $results['question_attributes'] = 0;
    $results['subquestions'] = 0;
    $results['conditions'] = 0;
    $results['groups'] = 0;
    $results['importwarnings'] = [];

    $importlanguages = array();
    foreach ($xml->languages->language as $language) {
        $importlanguages[] = (string) $language;
    }

    if (!in_array($sBaseLanguage, $importlanguages)) {
        $results['fatalerror'] = gT("The languages of the imported group file must at least include the base language of this survey.");
        return $results;
    }

    // Import group table ===================================================================================
    $iGroupOrder = Yii::app()->db->createCommand()->select('MAX(group_order)')->from('{{groups}}')->where('sid=:sid', array(':sid' => $iNewSID))->queryScalar();
    if ($iGroupOrder === false) {
        $iNewGroupOrder = 0;
    } else {
        $iNewGroupOrder = $iGroupOrder + 1;
    }

    foreach ($xml->groups->rows->row as $row) {
        $insertdata = array();
        foreach ($row as $key => $value) {
            $insertdata[(string) $key] = (string) $value;
        }
        $iOldSID = $insertdata['sid'];
        $insertdata['sid'] = $iNewSID;
        $insertdata['group_order'] = $iNewGroupOrder;
        $oldgid = $insertdata['gid'];
        unset($insertdata['gid']); // save the old qid
        $aDataL10n = array();

        if (!isset($xml->group_l10ns->rows->row)) {
            $aDataL10n['group_name'] = $insertdata['group_name'];
            $aDataL10n['description'] = $insertdata['description'];
            $aDataL10n['language'] = $insertdata['language'];
            unset($insertdata['group_name']);
            unset($insertdata['description']);
            unset($insertdata['language']);
        }
        if (!isset($aGIDReplacements[$oldgid])) {
            $questionGroup = new QuestionGroup();
            $questionGroup->sid = $insertdata['sid'];
            $questionGroup->group_order = $insertdata['group_order'];
            $questionGroup->randomization_group = $insertdata['randomization_group'];
            $questionGroup->grelevance = $insertdata['grelevance'];
            if (!$questionGroup->save()) {
                throw new Exception(gT("Error") . ": Failed to insert data [3]<br />");
            }

            $newgid = $questionGroup->gid;

            $aGIDReplacements[$oldgid] = $newgid; // add old and new qid to the mapping array
            $results['groups']++;
        }
        if (!empty($aDataL10n)) {
            $aDataL10n['gid'] = $aGIDReplacements[$oldgid];
            $oQuestionGroupL10n = new QuestionGroupL10n();
            $oQuestionGroupL10n->setAttributes($aDataL10n, false);
            $oQuestionGroupL10n->save();
        }
    }

    if (isset($xml->group_l10ns->rows->row)) {
        foreach ($xml->group_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            $insertdata['group_name'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['group_name']);
            $insertdata['description'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['description']);
            if (isset($aGIDReplacements[$insertdata['gid']])) {
                $insertdata['gid'] = $aGIDReplacements[$insertdata['gid']];
            } else {
                continue; //Skip invalid group ID
            }
            $oQuestionGroupL10n = new QuestionGroupL10n();
            $oQuestionGroupL10n->setAttributes($insertdata, false);
            $oQuestionGroupL10n->save();
        }
    }

    // Import questions table ===================================================================================

    // We have to run the question table data two times - first to find all main questions
    // then for subquestions (because we need to determine the new qids for the main questions first)

    /** @var Question[] */
    $importedQuestions = [];
    $results['questions'] = 0;
    if (isset($xml->questions)) {
        foreach ($xml->questions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (!isset($aGIDReplacements[$insertdata['gid']])) {
                // Skip questions with invalid group id
                continue;
            }
            if (!isset($insertdata['mandatory']) || trim($insertdata['mandatory']) == '') {
                $insertdata['mandatory'] = 'N';
            }
            $iOldSID = $insertdata['sid'];
            $insertdata['sid'] = $iNewSID;
            $insertdata['gid'] = $aGIDReplacements[$insertdata['gid']];
            $iOldQID = $insertdata['qid']; // save the old qid
            unset($insertdata['qid']);
            if (!isset($xml->question_l10ns->rows->row)) {
                if ($bTranslateLinksFields) {
                    $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
                    $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
                }
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
                unset($insertdata['question']);
                unset($insertdata['help']);
                unset($insertdata['language']);
            }

            if (!$bTranslateLinksFields) {
                $sScenario = 'archiveimport';
            } else {
                $sScenario = 'import';
            }

            $oQuestion = new Question($sScenario);
            $oQuestion->setAttributes($insertdata, false);

            if (!isset($aQIDReplacements[$iOldQID])) {
                // Try to fix question title for valid question code enforcement
                if (!$oQuestion->validate(array('title'))) {
                    $sOldTitle = $oQuestion->title;
                    $sNewTitle = preg_replace("/[^A-Za-z0-9]/", '', $sOldTitle);
                    if (is_numeric(substr($sNewTitle, 0, 1))) {
                        $sNewTitle = 'q' . $sNewTitle;
                    }

                    $oQuestion->title = $sNewTitle;
                }

                $attempts = 0;
                // Try to fix question title for unique question code enforcement
                $index = 0;
                $rand = mt_rand(0, 1024);
                while (!$oQuestion->validate(array('title'))) {
                    $sNewTitle = 'r' . $rand . 'q' . $index;
                    $index++;
                    $oQuestion->title = $sNewTitle;
                    $attempts++;
                    if ($attempts > 10) {
                        throw new Exception(gT("Error") . ": Failed to resolve question code problems after 10 attempts.<br />");
                    }
                }
                if (!$oQuestion->save()) {
                    throw new Exception(gT("Error while saving: ") . print_r($oQuestion->errors, true));
                }
                $aQIDReplacements[$iOldQID] = $oQuestion->qid;
                $results['questions']++;
                $importedQuestions[$aQIDReplacements[$iOldQID]] = $oQuestion;
            }

            if (isset($oQuestionL10n)) {
                $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
                $oQuestionL10n->save();
                unset($oQuestionL10n);
            }
            // Set a warning if question title was updated
            if (isset($sNewTitle) && isset($sOldTitle)) {
                $results['importwarnings'][] = sprintf(gT("Question code %s was updated to %s."), $sOldTitle, $sNewTitle);
                $aQuestionCodeReplacements[$sOldTitle] = $sNewTitle;
                unset($sNewTitle);
                unset($sOldTitle);
            }
        }
    }

    // Import subquestions -------------------------------------------------------
    if (isset($xml->subquestions)) {
        foreach ($xml->subquestions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }

            if ($insertdata['gid'] == 0) {
                continue;
            }
            if (!isset($insertdata['mandatory']) || trim($insertdata['mandatory']) == '') {
                $insertdata['mandatory'] = 'N';
            }
            $iOldSID = $insertdata['sid'];
            $insertdata['sid'] = $iNewSID;
            $insertdata['gid'] = $aGIDReplacements[(int) $insertdata['gid']];
            $iOldQID = (int) $insertdata['qid'];
            unset($insertdata['qid']); // save the old qid
            $insertdata['parent_qid'] = $aQIDReplacements[(int) $insertdata['parent_qid']]; // remap the parent_qid
            if (!isset($insertdata['help'])) {
                $insertdata['help'] = '';
            }            // now translate any links
            if (!isset($xml->question_l10ns->rows->row)) {
                if ($bTranslateLinksFields) {
                    $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
                    $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
                }
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
                unset($insertdata['question']);
                unset($insertdata['help']);
                unset($insertdata['language']);
            }
            if (!$bTranslateLinksFields) {
                $sScenario = 'archiveimport';
            } else {
                $sScenario = 'import';
            }

            $oQuestion = new Question($sScenario);
            $oQuestion->setAttributes($insertdata, false);

            if (!isset($aQIDReplacements[$iOldQID])) {
                switchMSSQLIdentityInsert('questions', false);
                // Try to fix question title for valid question code enforcement
                if (!$oQuestion->validate(array('title'))) {
                    $sOldTitle = $oQuestion->title;
                    $sNewTitle = preg_replace("/[^A-Za-z0-9]/", '', $sOldTitle);
                    if (is_numeric(substr($sNewTitle, 0, 1))) {
                        $sNewTitle = 'sq' . $sNewTitle;
                    }

                    $oQuestion->title = $sNewTitle;
                }

                $attempts = 0;
                // Try to fix question title for unique question code enforcement
                while (!$oQuestion->validate(array('title'))) {
                    if (!isset($index)) {
                        $index = 0;
                        $rand = mt_rand(0, 1024);
                    } else {
                        $index++;
                    }

                    $sNewTitle = 'r' . $rand . 'sq' . $index;
                    $oQuestion->title = $sNewTitle;
                    $attempts++;

                    if ($attempts > 10) {
                        throw new Exception(gT("Error") . ": Failed to resolve question code problems after 10 attempts.<br />");
                    }
                }
                if (!$oQuestion->save()) {
                    throw new Exception(gT("Error while saving: ") . print_r($oQuestion->errors, true));
                }
                $aQIDReplacements[$iOldQID] = $oQuestion->qid;
                ;
                $results['questions']++;
            }

            if (isset($oQuestionL10n)) {
                $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
                $oQuestionL10n->save();
                unset($oQuestionL10n);
            }

            // Set a warning if question title was updated
            if (isset($sNewTitle) && isset($sOldTitle)) {
                $results['importwarnings'][] = sprintf(gT("Title of subquestion %s was updated to %s."), $sOldTitle, $sNewTitle); // Maybe add the question title ?
                $aQuestionCodeReplacements[$sOldTitle] = $sNewTitle;
                unset($sNewTitle);
                unset($sOldTitle);
            }
        }
    }


    //  Import question_l10ns
    if (isset($xml->question_l10ns->rows->row)) {
        foreach ($xml->question_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
            $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[$insertdata['qid']];
            } else {
                continue; //Skip invalid group ID
            }
            $oQuestionL10n = new QuestionL10n();
            $oQuestionL10n->setAttributes($insertdata, false);
            $oQuestionL10n->save();
        }
    }

    // Import answers ------------------------------------------------------------
    if (isset($xml->answers)) {
        foreach ($xml->answers->rows->row as $row) {
            $insertdata = array();

            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (isset($xml->answer_l10ns->rows->row)) {
                $iOldAID = $insertdata['aid'];
                unset($insertdata['aid']);
            }
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                continue;
            }

            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the parent_qid

            if (!isset($xml->answer_l10ns->rows->row)) {
                $oAnswerL10n = new AnswerL10n();
                $oAnswerL10n->answer = $insertdata['answer'];
                $oAnswerL10n->language = $insertdata['language'];
                unset($insertdata['answer']);
                unset($insertdata['language']);
            }

            $oAnswer = new Answer();
            $oAnswer->setAttributes($insertdata, false);
            if ($oAnswer->save() && isset($xml->answer_l10ns->rows->row)) {
                $aAIDReplacements[$iOldAID] = $oAnswer->aid;
            }
            $results['answers']++;
            if (isset($oAnswerL10n)) {
                $oAnswer = Answer::model()->findByAttributes(['qid' => $insertdata['qid'], 'code' => $insertdata['code'], 'scale_id' => $insertdata['scale_id']]);
                $oAnswerL10n->aid = $oAnswer->aid;
                $oAnswerL10n->save();
                unset($oAnswerL10n);
            }
        }
    }

    //  Import answer_l10ns
    if (isset($xml->answer_l10ns->rows->row)) {
        foreach ($xml->answer_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            if (isset($aAIDReplacements[$insertdata['aid']])) {
                $insertdata['aid'] = $aAIDReplacements[$insertdata['aid']];
            } else {
                continue; //Skip invalid answer ID
            }
            $oAnswerL10n = new AnswerL10n();
            $oAnswerL10n->setAttributes($insertdata, false);
            $oAnswerL10n->save();
        }
    }


    // Import questionattributes --------------------------------------------------------------
    if (isset($xml->question_attributes)) {
        $aAllAttributes = questionHelper::getAttributesDefinitions();

        /** @var array<integer,array<string,mixed>> List of "answer order" related attributes, grouped by qid */
        $answerOrderAttributes = [];
        foreach ($xml->question_attributes->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['qaid']);
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                // Skip questions with invalid group id
                continue;
            }
            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the parent_qid

            // Question theme was previously stored as a question attribute ('question_template'), but now it
            // is a normal attribute of the Question model. So we must check if the imported question has the
            // 'question_template' attribute and use it for overriding 'question_theme_name' instead of saving
            // as QuestionAttribute.
            if ($insertdata['attribute'] == 'question_template') {
                $oQuestion = Question::model()->findByPk($insertdata['qid']);
                if (!empty($oQuestion)) {
                    $oQuestion->question_theme_name = $insertdata['value'];
                    $oQuestion->save();
                }
                continue;
            }

            // Keep "answer order" related attributes in an array to process later (because we need to combine two attributes)
            if (
                $insertdata['attribute'] == 'alphasort'
                || (
                    $insertdata['attribute'] == 'random_order'
                    && in_array($importedQuestions[$insertdata['qid']]->type, ['!', 'L', 'O', 'R'])
                )
            ) {
                $answerOrderAttributes[$insertdata['qid']][$insertdata['attribute']] = $insertdata['value'];
                continue;
            }

            if (
                $iDBVersion < 156 && isset($aAllAttributes[$insertdata['attribute']]['i18n']) &&
                $aAllAttributes[$insertdata['attribute']]['i18n']
            ) {
                foreach ($importlanguages as $sLanguage) {
                    $insertdata['language'] = $sLanguage;
                    App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
                }
            } else {
                App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
            }
            $results['question_attributes']++;
        }

        // Process "answer order" attributes
        foreach ($answerOrderAttributes as $importedQid => $questionAttributes) {
            if (!empty($questionAttributes['random_order'])) {
                $insertdata = [
                    'qid' => $importedQid,
                    'attribute' => 'answer_order',
                    'value' => 'random',
                ];
                App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
                $results['question_attributes']++;
                continue;
            }
            if (!empty($questionAttributes['alphasort'])) {
                $insertdata = [
                    'qid' => $importedQid,
                    'attribute' => 'answer_order',
                    'value' => 'alphabetical',
                ];
                App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
                $results['question_attributes']++;
            }
        }
    }


    // Import defaultvalues ------------------------------------------------------
    importDefaultValues($xml, $importlanguages, $aQIDReplacements, $results);

    // Import conditions --------------------------------------------------------------
    if (isset($xml->conditions)) {
        foreach ($xml->conditions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            // replace the qid for the new one (if there is no new qid in the $aQIDReplacements array it mean that this condition is orphan -> error, skip this record)
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[$insertdata['qid']]; // remap the qid
            } else {
                // a problem with this answer record -> don't consider
                continue;
            }
            if (isset($aQIDReplacements[$insertdata['cqid']])) {
                $insertdata['cqid'] = $aQIDReplacements[$insertdata['cqid']]; // remap the qid
            } else {
                // a problem with this answer record -> don't consider
                continue;
            }

            list($oldcsid, $oldcgid, $oldqidanscode) = explode("X", $insertdata["cfieldname"], 3);

            if ($oldcgid != $oldgid) {
                // this means that the condition is in another group (so it should not have to be been exported -> skip it
                continue;
            }

            unset($insertdata["cid"]);

            // recreate the cfieldname with the new IDs
            if (preg_match("/^\+/", $oldcsid)) {
                $newcfieldname = '+' . $iNewSID . "X" . $newgid . "X" . $insertdata["cqid"] . substr($oldqidanscode, strlen($iOldQID));
            } else {
                $newcfieldname = $iNewSID . "X" . $newgid . "X" . $insertdata["cqid"] . substr($oldqidanscode, strlen($iOldQID));
            }

            $insertdata["cfieldname"] = $newcfieldname;
            if (trim($insertdata["method"]) == '') {
                $insertdata["method"] = '==';
            }

            // now translate any links
            Yii::app()->db->createCommand()->insert('{{conditions}}', $insertdata);
            $results['conditions']++;
        }
    }
    LimeExpressionManager::RevertUpgradeConditionsToRelevance($iNewSID);
    LimeExpressionManager::UpgradeConditionsToRelevance($iNewSID);

    if (count($aQuestionCodeReplacements)) {
        array_unshift(
            $results['importwarnings'],
            "<span class='warningtitle'>"
            . gT('Attention: Several question codes were updated. Please check these carefully as the update  may not be perfect with customized expressions.')
            . '</span>'
        );
    }

    $results['newgid'] = $newgid;
    $results['labelsets'] = 0;
    $results['labels'] = 0;

    if (\PHP_VERSION_ID < 80000) {
        libxml_disable_entity_loader($bOldEntityLoaderState); // Put back entity loader to its original state, to avoid contagion to other applications on the server
    }
    return $results;
}

/**
 * This function imports a LimeSurvey .lsq question XML file
 *
 * @param string $sFullFilePath The full filepath of the uploaded file
 * @param integer $iNewSID The new survey id
 * @param $iNewGID
 * @param bool[] $options
 * @return array
 * @throws CException
 */
function XMLImportQuestion($sFullFilePath, $iNewSID, $iNewGID, $options = array('autorename' => false,'translinkfields' => true))
{
    $sBaseLanguage = Survey::model()->findByPk($iNewSID)->language;
    $sXMLdata = file_get_contents($sFullFilePath);
    $xml = simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET);
    if ($xml->LimeSurveyDocType != 'Question') {
        throw new Exception('This is not a valid LimeSurvey question structure XML file.');
    }
    $iDBVersion = (int) $xml->DBVersion;
    $aQIDReplacements = array();

    $results['defaultvalues'] = 0;
    $results['answers'] = 0;
    $results['question_attributes'] = 0;
    $results['subquestions'] = 0;

    $importlanguages = array();
    foreach ($xml->languages->language as $language) {
        $importlanguages[] = (string) $language;
    }

    if (!in_array($sBaseLanguage, $importlanguages)) {
        $results['fatalerror'] = gT("The languages of the imported question file must at least include the base language of this survey.");
        return $results;
    }

    // Import questions table ===================================================================================

    // We have to run the question table data two times - first to find all main questions
    // then for subquestions (because we need to determine the new qids for the main questions first)

    $query = "SELECT MAX(question_order) AS maxqo FROM {{questions}} WHERE sid=$iNewSID AND gid=$iNewGID";
    $res = Yii::app()->db->createCommand($query)->query();
    $resrow = $res->read();
    $newquestionorder = $resrow['maxqo'] + 1;
    if (is_null($newquestionorder)) {
        $newquestionorder = 0;
    } else {
        $newquestionorder++;
    }


    $aLanguagesSupported = array();
    foreach ($xml->languages->language as $language) {
        $aLanguagesSupported[] = (string) $language;
    }

    foreach ($xml->questions->rows->row as $row) {
        $insertdata = array();
        foreach ($row as $key => $value) {
            $insertdata[(string) $key] = (string) $value;
        }

        $iOldSID = $insertdata['sid'];
        $insertdata['sid'] = $iNewSID;
        $insertdata['gid'] = $iNewGID;
        $insertdata['question_order'] = $newquestionorder;
        $iOldQID = $insertdata['qid']; // save the old qid
        unset($insertdata['qid']);

        // now translate any links
        if (!isset($xml->question_l10ns->rows->row)) {
            $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
            $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
            $oQuestionL10n = new QuestionL10n();
            $oQuestionL10n->question = $insertdata['question'];
            $oQuestionL10n->help = $insertdata['help'];
            $oQuestionL10n->language = $insertdata['language'];
            unset($insertdata['question']);
            unset($insertdata['help']);
            unset($insertdata['language']);
        }

        $oQuestion = new Question('import');
        $oQuestion->setAttributes($insertdata, false);

        if (!isset($aQIDReplacements[$iOldQID])) {
            if (!$oQuestion->validate(array('title')) && $options['autorename']) {
                if (isset($sNewTitle)) {
                    $oQuestion->title = $sNewTitle;
                } else {
                    $sOldTitle = $oQuestion->title;
                    $oQuestion->title = $sNewTitle = $oQuestion->getNewTitle();
                    if (!$sNewTitle) {
                        $results['fatalerror'] = CHtml::errorSummary(
                            $oQuestion,
                            gT("The question could not be imported for the following reasons:")
                        );
                        return $results;
                    }
                    $results['importwarnings'][] = sprintf(
                        gT("Question code %s was updated to %s."),
                        $sOldTitle,
                        $sNewTitle
                    );
                    unset($sNewTitle);
                    unset($sOldTitle);
                }
            }
            if (isset($insertdata['qid'])) {
                switchMSSQLIdentityInsert('questions', true);
            }

            if (!$oQuestion->save()) {
                $results['fatalerror'] = CHtml::errorSummary(
                    $oQuestion,
                    gT("The question could not be imported for the following reasons:")
                );
                return $results;
            }

            switchMSSQLIdentityInsert('questions', false);
            $aQIDReplacements[$iOldQID] = $oQuestion->qid;

            $newqid = $oQuestion->qid;
        }

        $results['questions'] = isset($results['questions']) ? $results['questions'] + 1 : 1;

        if (isset($oQuestionL10n)) {
            $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
            $oQuestionL10n->save();
            unset($oQuestionL10n);
        }
    }

    // Import subquestions -------------------------------------------------------
    if (isset($xml->subquestions)) {
        foreach ($xml->subquestions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }

            if (!isset($xml->question_l10ns->rows->row)) {
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
            }
            if ($insertdata['gid'] == 0) {
                continue;
            }
            if (!isset($insertdata['mandatory']) || trim($insertdata['mandatory']) == '') {
                $insertdata['mandatory'] = 'N';
            }
            $iOldSID = $insertdata['sid'];
            $insertdata['sid'] = $iNewSID;
            $insertdata['gid'] = $iNewGID;
            $iOldQID = (int) $insertdata['qid'];
            unset($insertdata['qid']); // save the old qid
            $insertdata['parent_qid'] = $aQIDReplacements[(int) $insertdata['parent_qid']]; // remap the parent_qid
            if (!isset($insertdata['help'])) {
                $insertdata['help'] = '';
            }            // now translate any links
            if (!isset($xml->question_l10ns->rows->row)) { //when does subquestions are stored in xml file in tag "question_l10ns"?
                if ($options['translinkfields']) {
                    $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
                    $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
                }
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
                unset($insertdata['question']);
                unset($insertdata['help']);
                unset($insertdata['language']);
            } elseif (isset($insertdata['question'])) {
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
            }
            if (!$options['autorename']) {
                $sScenario = 'archiveimport';
            } else {
                $sScenario = 'import';
            }

            $oQuestion = new Question($sScenario);
            $oQuestion->setAttributes($insertdata, false);

            if (!isset($aQIDReplacements[$iOldQID])) {
                // Try to fix question title for valid question code enforcement
                if (!$oQuestion->validate(array('title'))) {
                    $sOldTitle = $oQuestion->title;
                    $sNewTitle = preg_replace("/[^A-Za-z0-9]/", '', $sOldTitle);
                    if (is_numeric(substr($sNewTitle, 0, 1))) {
                        $sNewTitle = 'sq' . $sNewTitle;
                    }

                    $oQuestion->title = $sNewTitle;
                }

                $attempts = 0;
                // Try to fix question title for unique question code enforcement
                while (!$oQuestion->validate(array('title'))) {
                    if (!isset($index)) {
                        $index = 0;
                        $rand = mt_rand(0, 1024);
                    } else {
                        $index++;
                    }

                    $sNewTitle = 'r' . $rand . 'sq' . $index;
                    $oQuestion->title = $sNewTitle;
                    $attempts++;

                    if ($attempts > 10) {
                        throw new Exception(gT("Error") . ": Failed to resolve question code problems after 10 attempts.<br />");
                    }
                }
                if (!$oQuestion->save()) {
                    throw new Exception(gT("Error while saving: ") . print_r($oQuestion->errors, true));
                }

                $aQIDReplacements[$iOldQID] = $oQuestion->qid;

                $results['questions']++;
            }

            if (isset($oQuestionL10n)) {
                $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
                $oQuestionL10n->save();
                unset($oQuestionL10n);
            }

            // Set a warning if question title was updated
            if (isset($sNewTitle) && isset($sOldTitle)) {
                $results['importwarnings'][] = sprintf(gT("Title of subquestion %s was updated to %s."), $sOldTitle, $sNewTitle); // Maybe add the question title ?
                $aQuestionCodeReplacements[$sOldTitle] = $sNewTitle;
                unset($sNewTitle);
                unset($sOldTitle);
            }
        }
    }

    //  Import question_l10ns
    if (isset($xml->question_l10ns->rows->row)) {
        foreach ($xml->question_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
            $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[$insertdata['qid']];
            } else {
                continue; //Skip invalid group ID
            }
            $oQuestionL10n = new QuestionL10n();
            $oQuestionL10n->setAttributes($insertdata, false);
            $oQuestionL10n->save();
        }
    }

    // Import answers ------------------------------------------------------------
    if (isset($xml->answers)) {
        foreach ($xml->answers->rows->row as $row) {
            $insertdata = array();

            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (isset($xml->answer_l10ns->rows->row)) {
                $iOldAID = $insertdata['aid'];
                unset($insertdata['aid']);
            }
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                continue;
            }

            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the parent_qid

            if (!isset($xml->answer_l10ns->rows->row)) {
                // now translate any links
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
                if ($options['translinkfields']) {
                    $insertdata['answer'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['answer']);
                }
                $oAnswerL10n = new AnswerL10n();
                $oAnswerL10n->answer = $insertdata['answer'];
                $oAnswerL10n->language = $insertdata['language'];
                unset($insertdata['answer']);
                unset($insertdata['language']);
            }

            $oAnswer = new Answer();
            $oAnswer->setAttributes($insertdata, false);
            if ($oAnswer->save() && isset($xml->answer_l10ns->rows->row)) {
                $aAIDReplacements[$iOldAID] = $oAnswer->aid;
            }
            $results['answers']++;
            if (isset($oAnswerL10n)) {
                $oAnswer = Answer::model()->findByAttributes(
                    [
                        'qid' => $insertdata['qid'],
                        'code' => $insertdata['code'],
                        'scale_id' => $insertdata['scale_id']
                    ]
                );
                $oAnswerL10n->aid = $oAnswer->aid;
                $oAnswerL10n->save();
                unset($oAnswerL10n);
            }
        }
    }

    //  Import answer_l10ns
    if (isset($xml->answer_l10ns->rows->row)) {
        foreach ($xml->answer_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            if ($options['translinkfields']) {
                $insertdata['answer'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['answer']);
            }
            if (isset($aAIDReplacements[$insertdata['aid']])) {
                $insertdata['aid'] = $aAIDReplacements[$insertdata['aid']];
            } else {
                continue; //Skip invalid answer ID
            }
            $oAnswerL10n = new AnswerL10n();
            $oAnswerL10n->setAttributes($insertdata, false);
            $oAnswerL10n->save();
        }
    }

    // Import questionattributes --------------------------------------------------------------
    if (isset($xml->question_attributes)) {
        $aAllAttributes = questionHelper::getAttributesDefinitions();
        /** @var array<string,mixed> List of "answer order" related attributes */
        $answerOrderAttributes = [];
        foreach ($xml->question_attributes->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['qaid']);
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the parent_qid
            }

            // Question theme was previously stored as a question attribute ('question_template'), but now it
            // is a normal attribute of the Question model. So we must check if the imported question has the
            // 'question_template' attribute and use it for overriding 'question_theme_name' instead of saving
            // as QuestionAttribute.
            if ($insertdata['attribute'] == 'question_template') {
                $oQuestion = Question::model()->findByPk($insertdata['qid']);
                if (!empty($oQuestion)) {
                    $oQuestion->question_theme_name = $insertdata['value'];
                    $oQuestion->save();
                }
                continue;
            }

            // Keep "answer order" related attributes in an array to process later (because we need to combine two attributes)
            if (
                $insertdata['attribute'] == 'alphasort'
                || (
                    $insertdata['attribute'] == 'random_order'
                    && in_array($oQuestion->type, ['!', 'L', 'O', 'R'])
                )
            ) {
                $answerOrderAttributes[$insertdata['attribute']] = $insertdata['value'];
                continue;
            }

            if (
                $iDBVersion < 156 &&
                isset($aAllAttributes[$insertdata['attribute']]['i18n']) &&
                $aAllAttributes[$insertdata['attribute']]['i18n']
            ) {
                foreach ($importlanguages as $sLanguage) {
                    $insertdata['language'] = $sLanguage;
                    $attributes = new QuestionAttribute();
                    foreach ($insertdata as $k => $v) {
                        $attributes->$k = $v;
                    }

                    $attributes->save();
                }
            } else {
                $attributes = new QuestionAttribute();
                foreach ($insertdata as $k => $v) {
                    $attributes->$k = $v;
                }

                $attributes->save();
            }
            checkWrongQuestionAttributes($insertdata['qid']);
            $results['question_attributes']++;
        }
    }

    // Process "answer order" attributes
    if (!empty($answerOrderAttributes['random_order'])) {
        $insertdata = [
            'qid' => $newqid,
            'attribute' => 'answer_order',
            'value' => 'random',
        ];
        App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
        $results['question_attributes']++;
    } elseif (!empty($answerOrderAttributes['alphasort'])) {
        $insertdata = [
            'qid' => $newqid,
            'attribute' => 'answer_order',
            'value' => 'alphabetical',
        ];
        App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
        $results['question_attributes']++;
    }

    // Import defaultvalues ------------------------------------------------------
    importDefaultValues($xml, $aLanguagesSupported, $aQIDReplacements, $results);

    LimeExpressionManager::SetDirtyFlag(); // so refreshes syntax highlighting

    $results['newqid'] = $newqid;
    $results['questions'] = 1;
    $results['labelsets'] = 0;
    $results['labels'] = 0;
    return $results;
}

/**
* XMLImportLabelsets()
* Function resp[onsible to import a labelset from XML format.
* @param string $sFullFilePath
* @param mixed $options
* @return
*/
function XMLImportLabelsets($sFullFilePath, $options)
{
    $sXMLdata = (string) file_get_contents($sFullFilePath);
    $xml = simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET);
    if ($xml->LimeSurveyDocType != 'Label set') {
        throw new Exception('This is not a valid LimeSurvey label set structure XML file.');
    }
    $aLSIDReplacements = $results = [];
    $results['labelsets'] = 0;
    $results['labels'] = 0;
    $results['warnings'] = array();
    $aImportedLabelSetIDs = array();

    // Import label sets table ===================================================================================
    foreach ($xml->labelsets->rows->row as $row) {
        $insertdata = array();
        foreach ($row as $key => $value) {
            $insertdata[(string) $key] = (string) $value;
        }
        $iOldLabelSetID = $insertdata['lid'];
        unset($insertdata['lid']); // save the old qid

        // Insert the new question
        $arLabelset = new LabelSet();
        $arLabelset->setAttributes($insertdata);
        $arLabelset->save();
        $aLSIDReplacements[$iOldLabelSetID] = $arLabelset->lid; // add old and new lsid to the mapping array
        $results['labelsets']++;
        $aImportedLabelSetIDs[] = $arLabelset->lid;
    }

    // Import labels table ===================================================================================
    if (isset($xml->labels->rows->row)) {
        foreach ($xml->labels->rows->row as $row) {
            $insertdata = [];
            $insertdataLS = [];
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            $insertdata['lid'] = $aLSIDReplacements[$insertdata['lid']];
            if (!isset($xml->label_l10ns->rows->row)) {
                $insertdataLS['title'] = $insertdata['title'];
                $insertdataLS['language'] = $insertdata['language'];
                unset($insertdata['title']);
                unset($insertdata['language']);
            } else {
                $iOldLabelID = $insertdata['id'];
            }
            unset($insertdata['id']);

            if (!isset($xml->label_l10ns->rows->row)) {
                $findLabel = Label::model()->findByAttributes($insertdata);
                if (empty($findLabel)) {
                    $arLabel = new Label();
                    $arLabel->setAttributes($insertdata);
                    $arLabel->save();
                    $insertdataLS['label_id'] = $arLabel->id;
                } else {
                    $insertdataLS['label_id'] = $findLabel->id;
                }
                $arLabelL10n = new LabelL10n();
                $arLabelL10n->setAttributes($insertdataLS);
                $arLabelL10n->save();
            } else {
                $arLabel = new Label();
                $arLabel->setAttributes($insertdata);
                $arLabel->save();
                $aLIDReplacements[$iOldLabelID] = $arLabel->id;
            }

            $results['labels']++;
        }
    }

    // Import label_l10ns table ===================================================================================
    if (isset($xml->label_l10ns->rows->row)) {
        foreach ($xml->label_l10ns->rows->row as $row) {
            $insertdata = [];
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            $insertdata['label_id'] = $aLIDReplacements[$insertdata['label_id']];
            $arLabelL10n = new LabelL10n();
            $arLabelL10n->setAttributes($insertdata);
            $arLabelL10n->save();
        }
    }

    //CHECK FOR DUPLICATE LABELSETS

    if ($options['checkforduplicates'] == 'on') {
        $aLabelSetCheckSums = buildLabelSetCheckSumArray();
        $aCounts = array_count_values($aLabelSetCheckSums);
        foreach ($aImportedLabelSetIDs as $iLabelSetID) {
            if ($aCounts[$aLabelSetCheckSums[$iLabelSetID]] > 1) {
                LabelSet::model()->deleteLabelSet($iLabelSetID);
            }
        }

        //END CHECK FOR DUPLICATES
    }
    return $results;
}

/**
 * @param string $sFullFilePath
 * @param boolean $bTranslateLinksFields
 * @param string $sNewSurveyName
 * @param integer $DestSurveyID
 */
function importSurveyFile($sFullFilePath, $bTranslateLinksFields, $sNewSurveyName = null, $DestSurveyID = null)
{
    $aPathInfo = pathinfo($sFullFilePath);
    if (isset($aPathInfo['extension'])) {
        $sExtension = strtolower($aPathInfo['extension']);
    } else {
        $sExtension = "";
    }
    switch ($sExtension) {
        case 'lss':
            $aImportResults = XMLImportSurvey($sFullFilePath, null, $sNewSurveyName, $DestSurveyID, $bTranslateLinksFields);
            if (!empty($aImportResults['newsid'])) {
                $SurveyIntegrity = new LimeSurvey\Models\Services\SurveyIntegrity(Survey::model()->findByPk($aImportResults['newsid']));
                $SurveyIntegrity->fixSurveyIntegrity();
            }
            return $aImportResults;
        case 'txt':
        case 'tsv':
            $aImportResults = TSVImportSurvey($sFullFilePath);
            if ($aImportResults && $aImportResults['newsid']) {
                $SurveyIntegrity = new LimeSurvey\Models\Services\SurveyIntegrity(Survey::model()->findByPk($aImportResults['newsid']));
                $SurveyIntegrity->fixSurveyIntegrity();
            }
            return $aImportResults;
        case 'lsa':
            // Import a survey archive
            Yii::app()->loadLibrary('admin.pclzip');
            $pclzip = new PclZip(array('p_zipname' => $sFullFilePath));
            $aFiles = $pclzip->listContent();

            if ($pclzip->extract(PCLZIP_OPT_PATH, Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR, PCLZIP_OPT_BY_EREG, '/(lss|lsr|lsi|lst)$/') == 0) {
                unset($pclzip);
            }
            $aImportResults = [];

            if (!is_array($aFiles)) {
                $aImportResults['error'] = gT("This is not a valid LimeSurvey LSA file.");
                return $aImportResults;
            }
            // Step 1 - import the LSS file and activate the survey
            foreach ($aFiles as $aFile) {
                if (pathinfo($aFile['filename'], PATHINFO_EXTENSION) == 'lss') {
                    //Import the LSS file
                    $aImportResults = XMLImportSurvey(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename'], null, $sNewSurveyName, null, true, false);
                    if ($aImportResults && $aImportResults['newsid']) {
                        $SurveyIntegrity = new LimeSurvey\Models\Services\SurveyIntegrity(Survey::model()->findByPk($aImportResults['newsid']));
                        $SurveyIntegrity->fixSurveyIntegrity();
                    }
                    // Activate the survey
                    Yii::app()->loadHelper("admin/activate");
                    $survey = Survey::model()->findByPk($aImportResults['newsid']);
                    $surveyActivator = new SurveyActivator($survey);
                    $surveyActivator->activate();
                    unlink(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename']);
                    break;
                }
            }

            // Step 2 - import the responses file
            foreach ($aFiles as $aFile) {
                if (pathinfo($aFile['filename'], PATHINFO_EXTENSION) == 'lsr') {
                    //Import the LSS file
                    $aResponseImportResults = XMLImportResponses(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename'], $aImportResults['newsid'], $aImportResults['FieldReMap']);
                    $aImportResults = array_merge($aResponseImportResults, $aImportResults);
                    $aImportResults['importwarnings'] = array_merge($aImportResults['importwarnings'], $aImportResults['warnings']);
                    unlink(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename']);
                    break;
                }
            }

            // Step 3 - import the tokens file - if exists
            foreach ($aFiles as $aFile) {
                if (pathinfo($aFile['filename'], PATHINFO_EXTENSION) == 'lst') {
                    Yii::app()->loadHelper("admin/token");
                    $aTokenImportResults = [];
                    if (Token::createTable($aImportResults['newsid'])) {
                        $aTokenCreateResults = array('tokentablecreated' => true);
                        $aImportResults = array_merge($aTokenCreateResults, $aImportResults);
                        $aTokenImportResults = XMLImportTokens(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename'], $aImportResults['newsid']);
                    } else {
                        $aTokenImportResults['warnings'][] = gT("Unable to create survey participants table");
                    }

                    $aImportResults = array_merge_recursive($aTokenImportResults, $aImportResults);
                    $aImportResults['importwarnings'] = array_merge($aImportResults['importwarnings'], $aImportResults['warnings']);
                    unlink(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename']);
                    break;
                }
            }
            // Step 4 - import the timings file - if exists
            Yii::app()->db->schema->refresh();
            foreach ($aFiles as $aFile) {
                if (pathinfo($aFile['filename'], PATHINFO_EXTENSION) == 'lsi' && tableExists("survey_{$aImportResults['newsid']}_timings")) {
                    $aTimingsImportResults = XMLImportTimings(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename'], $aImportResults['newsid'], $aImportResults['FieldReMap']);
                    $aImportResults = array_merge($aTimingsImportResults, $aImportResults);
                    unlink(Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . $aFile['filename']);
                    break;
                }
            }
            return $aImportResults;
        default:
            // Unknow file , return null why not throw error ?
            return null;
    }
}

/**
* This function imports a LimeSurvey .lss survey XML file
*
* @param string $sFullFilePath  The full filepath of the uploaded file
* @param string $sXMLdata
* @todo Use transactions to prevent orphaned data and clean rollback on errors
*/
function XMLImportSurvey($sFullFilePath, $sXMLdata = null, $sNewSurveyName = null, $iDesiredSurveyId = null, $bTranslateInsertansTags = true, $bConvertInvalidQuestionCodes = true)
{
    $isCopying = ($sNewSurveyName != null);
    Yii::app()->loadHelper('database');
    $results = [];
    $aGIDReplacements = array();
    if ($sXMLdata === null) {
        $sXMLdata = (string) file_get_contents($sFullFilePath);
    }

    $xml = @simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET | LIBXML_PARSEHUGE);

    if (!$xml || $xml->LimeSurveyDocType != 'Survey') {
        $results['error'] = gT("This is not a valid LimeSurvey survey structure XML file.");
        return $results;
    }

    $iDBVersion = (int) $xml->DBVersion;
    $aQIDReplacements = array();
    $aQuestionCodeReplacements = array();
    $aQuotaReplacements = array();
    $results['defaultvalues'] = 0;
    $results['answers'] = 0;
    $results['surveys'] = 0;
    $results['questions'] = 0;
    $results['subquestions'] = 0;
    $results['question_attributes'] = 0;
    $results['groups'] = 0;
    $results['assessments'] = 0;
    $results['quota'] = 0;
    $results['quotals'] = 0;
    $results['quotamembers'] = 0;
    $results['plugin_settings'] = 0;
    $results['themes'] = 0;
    $results['survey_url_parameters'] = 0;
    $results['importwarnings'] = array();
    $results['theme_options_original_data'] = '';
    $results['theme_options_differences'] = array();
    $sTemplateName = '';

    /** @var bool Indicates if the email templates have attachments with untranslated URLs or not */
    $hasOldAttachments = false;

    $aLanguagesSupported = array();
    foreach ($xml->languages->language as $language) {
        $aLanguagesSupported[] = (string) $language;
    }

    $results['languages'] = count($aLanguagesSupported);

    // Import surveys table ====================================================

    foreach ($xml->surveys->rows->row as $row) {
        $insertdata = array();

        foreach ($row as $key => $value) {
            // Set survey group id to default if not a copy
            if ($key == 'gsid' & !$isCopying) {
                $value = 1;
            }
            if ($key == 'template') {
                $sTemplateName = (string)$value;
            }
            $insertdata[(string) $key] = (string) $value;
        }
        $iOldSID = $results['oldsid'] = $insertdata['sid'];
        // Fix#14609 wishSID overwrite sid
        if (!is_null($iDesiredSurveyId)) {
            $insertdata['sid'] = $iDesiredSurveyId;
        }

        if ($iDBVersion < 145) {
            if (isset($insertdata['private'])) {
                $insertdata['anonymized'] = $insertdata['private'];
            }
            unset($insertdata['private']);
            unset($insertdata['notification']);
        }

        //Make sure it is not set active
        $insertdata['active'] = 'N';
        //Set current user to be the owner
        $insertdata['owner_id'] = Yii::app()->session['loginID'];

        if (isset($insertdata['bouncetime']) && $insertdata['bouncetime'] == '') {
            $insertdata['bouncetime'] = null;
        }

        if (isset($insertdata['showXquestions'])) {
            $insertdata['showxquestions'] = $insertdata['showXquestions'];
            unset($insertdata['showXquestions']);
        }

        if (isset($insertdata['googleAnalyticsStyle'])) {
            $insertdata['googleanalyticsstyle'] = $insertdata['googleAnalyticsStyle'];
            unset($insertdata['googleAnalyticsStyle']);
        }

        if (isset($insertdata['googleAnalyticsAPIKey'])) {
            $insertdata['googleanalyticsapikey'] = $insertdata['googleAnalyticsAPIKey'];
            unset($insertdata['googleAnalyticsAPIKey']);
        }

        if (isset($insertdata['allowjumps'])) {
            $insertdata['questionindex'] = ($insertdata['allowjumps'] == "Y") ? 1 : 0;
            unset($insertdata['allowjumps']);
        }

        if (isset($insertdata['tokenlength']) && $insertdata['tokenlength'] > Token::MAX_LENGTH) {
            $insertdata['tokenlength'] = Token::MAX_LENGTH;
        }
        /* Remove unknow column */
        $aSurveyModelsColumns = Survey::model()->attributes;
        $aSurveyModelsColumns['wishSID'] = null; // Can not be imported
        $aBadData = array_diff_key($insertdata, $aSurveyModelsColumns);
        $insertdata = array_intersect_key($insertdata, $aSurveyModelsColumns);
        // Fill a optional array of error
        foreach ($aBadData as $key => $value) {
            $results['importwarnings'][] = sprintf(gT("This survey setting has not been imported: %s => %s"), $key, $value);
        }
        $newSurvey = Survey::model()->insertNewSurvey($insertdata);
        if ($newSurvey->sid) {
            $iNewSID = $results['newsid'] = $newSurvey->sid;
            $results['surveys']++;
            if (!empty($iDesiredSurveyId) && $iNewSID != $iDesiredSurveyId) {
                $results['importwarnings'][] = gT("The desired survey ID was already in use, therefore a random one was assigned.");
            }
        } else {
            $results['error'] = CHtml::errorSummary($newSurvey, gT("Unable to import survey."));
            return $results;
        }
    }

    // Single flag to indicate if the attachements format is wrong, to avoid showing the warning multiple times
    $wrongAttachmentsFormat = false;

    // Import survey languagesettings table ===================================================================================
    foreach ($xml->surveys_languagesettings->rows->row as $row) {
        $insertdata = array();
        foreach ($row as $key => $value) {
            $insertdata[(string) $key] = (string) $value;
        }

        if (!in_array($insertdata['surveyls_language'], $aLanguagesSupported)) {
            continue;
        }

        // Assign new survey ID
        $insertdata['surveyls_survey_id'] = $iNewSID;

        // Assign new survey name (if a copy)
        if ($isCopying) {
            $insertdata['surveyls_title'] = $sNewSurveyName;
        }

        if ($bTranslateInsertansTags) {
            $insertdata['surveyls_title'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_title']);
            if (isset($insertdata['surveyls_description'])) {
                $insertdata['surveyls_description'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_description']);
            }
            if (isset($insertdata['surveyls_welcometext'])) {
                $insertdata['surveyls_welcometext'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_welcometext']);
            }
            if (isset($insertdata['surveyls_urldescription'])) {
                $insertdata['surveyls_urldescription'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_urldescription']);
            }
            if (isset($insertdata['surveyls_email_invite'])) {
                $insertdata['surveyls_email_invite'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_email_invite']);
            }
            if (isset($insertdata['surveyls_email_remind'])) {
                $insertdata['surveyls_email_remind'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_email_remind']);
            }
            if (isset($insertdata['surveyls_email_register'])) {
                $insertdata['surveyls_email_register'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_email_register']);
            }
            if (isset($insertdata['surveyls_email_confirm'])) {
                $insertdata['surveyls_email_confirm'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['surveyls_email_confirm']);
            }
        }

        // Email attachments are with relative paths on the file, but are currently expected to be saved as absolute.
        // Transforming them from relative paths to absolute paths.
        if (!empty($insertdata['attachments'])) {
            // NOTE: Older LSS files have attachments as a serialized array, while newer ones have it as a JSON string.
            // Serialized attachments are not supported anymore.
            $attachments = json_decode($insertdata['attachments'], true);
            if (!empty($attachments) && is_array($attachments)) {
                $uploadDir = realpath(Yii::app()->getConfig('uploaddir'));
                foreach ($attachments as &$template) {
                    foreach ($template as &$attachment) {
                        if (!isAbsolutePath($attachment['url'])) {
                            $attachment['url'] = $uploadDir . DIRECTORY_SEPARATOR . $attachment['url'];
                        }
                        if ($bTranslateInsertansTags) {
                            $attachment['url'] = translateLinks('survey', $iOldSID, $iNewSID, $attachment['url'], true);
                        }
                    }
                    // If links are not translated and the email templates have attachments, we need to show a warning
                    if (!$bTranslateInsertansTags && !empty($template)) {
                        $hasOldAttachments = true;
                    }
                }
            } elseif (is_null($attachments)) {
                // JSON decode failed. Most probably the attachments were in the PHP serialization format.
                $wrongAttachmentsFormat = true;
            }
            $insertdata['attachments'] = serialize($attachments);
        }

        if (isset($insertdata['surveyls_attributecaptions']) && substr($insertdata['surveyls_attributecaptions'], 0, 1) != '{') {
            unset($insertdata['surveyls_attributecaptions']);
        }
        $aColumns = SurveyLanguageSetting::model()->attributes;
        $insertdata = array_intersect_key($insertdata, $aColumns);

        $surveyLanguageSetting = new SurveyLanguageSetting();
        $surveyLanguageSetting->setAttributes($insertdata, false);
        try {
            // Clear alias if it was already in use
            $surveyLanguageSetting->checkAliasUniqueness();
            if ($surveyLanguageSetting->hasErrors('surveyls_alias')) {
                $languageData = getLanguageData();
                $results['importwarnings'][] = sprintf(
                    gT("The survey alias for '%s' has been cleared because it was already in use by another survey."),
                    $languageData[$insertdata['surveyls_language']]['description']
                );
                unset($surveyLanguageSetting->surveyls_alias);
                $surveyLanguageSetting->clearErrors('surveyls_alias');
            }
            if (!$surveyLanguageSetting->save()) {
                throw new Exception(gT("Error") . ": Failed to import survey language settings - data is invalid.");
            }
        } catch (CDbException $e) {
            throw new Exception(gT("Error") . ": Failed to import survey language settings - data is invalid.");
        }
    }

    if ($wrongAttachmentsFormat) {
        $results['importwarnings'][] = gT("The email attachments have not been imported because they were in an old format.");
    }

    if ($hasOldAttachments) {
        $results['importwarnings'][] = gT("Email templates have attachments but the resources have not been copied. Please update the attachments manually.");
    }

    // Import groups table ===================================================================================
    if (isset($xml->groups->rows->row)) {
        foreach ($xml->groups->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            $iOldSID = $insertdata['sid'];
            $insertdata['sid'] = $iNewSID;
            $oldgid = $insertdata['gid'];
            unset($insertdata['gid']); // save the old qid
            $aDataL10n = array();
            if (!isset($xml->group_l10ns->rows->row)) {
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
                // now translate any links
                if ($bTranslateInsertansTags) {
                    $insertdata['group_name'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['group_name']);
                    $insertdata['description'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['description']);
                }
                $aDataL10n['group_name'] = $insertdata['group_name'];
                $aDataL10n['description'] = $insertdata['description'];
                $aDataL10n['language'] = $insertdata['language'];
                unset($insertdata['group_name']);
                unset($insertdata['description']);
                unset($insertdata['language']);
            }
            if (!isset($aGIDReplacements[$oldgid])) {
                $questionGroup = new QuestionGroup();
                $questionGroup->attributes = $insertdata;
                $questionGroup->sid = $iNewSID;
                if (!$questionGroup->save()) {
                    throw new Exception(gT("Error") . ": Failed to insert data [3]<br /> " . json_encode($questionGroup->getErrors()));
                }
                $newgid = $questionGroup->gid;
                $aGIDReplacements[$oldgid] = $newgid; // add old and new qid to the mapping array
                $results['groups']++;
            }
            if (!empty($aDataL10n)) {
                $aDataL10n['gid'] = $aGIDReplacements[$oldgid];
                $oQuestionGroupL10n = new QuestionGroupL10n();
                $oQuestionGroupL10n->setAttributes($aDataL10n, false);
                $oQuestionGroupL10n->save();
            }
        }
    }
    if (isset($xml->group_l10ns->rows->row)) {
        foreach ($xml->group_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                continue;
            }
            if (isset($aGIDReplacements[$insertdata['gid']])) {
                $insertdata['gid'] = $aGIDReplacements[$insertdata['gid']];
            } else {
                continue; //Skip invalid group ID
            }
            // now translate any links
            $insertdata['group_name'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['group_name']);
            if (isset($insertdata['description'])) {
                $insertdata['description'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['description']);
            }
            // #14646: fix utf8 encoding issue
            if (!mb_detect_encoding($insertdata['group_name'], 'UTF-8', true)) {
                $insertdata['group_name'] = utf8_encode($insertdata['group_name']);
            }
            // Insert the new group
            $oQuestionGroupL10n = new QuestionGroupL10n();
            $oQuestionGroupL10n->setAttributes($insertdata, false);
            if (!$oQuestionGroupL10n->save()) {
                throw new Exception(gT("Error while saving group: ") . print_r($oQuestionGroupL10n->errors, true));
            }
        }
    }

    // Import questions table ===================================================================================

    // We have to run the question table data two times - first to find all main questions
    // then for subquestions (because we need to determine the new qids for the main questions first)
    $aQuestionsMapping = array(); // collect all old and new question codes for replacement
    /** @var Question[] */
    $importedQuestions = [];
    if (isset($xml->questions)) {
        // There could be surveys without a any questions.
        foreach ($xml->questions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }

            if (!isset($xml->question_l10ns->rows->row)) {
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
            }
            if ($insertdata['gid'] == 0) {
                continue;
            }
            if (!isset($insertdata['mandatory']) || trim($insertdata['mandatory']) == '') {
                $insertdata['mandatory'] = 'N';
            }

            $iOldSID = $insertdata['sid'];
            $iOldGID = $insertdata['gid'];
            $insertdata['sid'] = $iNewSID;
            $insertdata['gid'] = $aGIDReplacements[$insertdata['gid']];
            $iOldQID = $insertdata['qid']; // save the old qid
            unset($insertdata['qid']);

            // now translate any links
            if (!isset($xml->question_l10ns->rows->row)) {
                if ($bTranslateInsertansTags) {
                    $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
                    $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
                }
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
                unset($insertdata['question']);
                unset($insertdata['help']);
                unset($insertdata['language']);
            }
            if (!$bConvertInvalidQuestionCodes) {
                $sScenario = 'archiveimport';
            } else {
                $sScenario = 'import';
            }

            $oQuestion = new Question($sScenario);
            $oQuestion->setAttributes($insertdata, false);

            if (!isset($aQIDReplacements[$iOldQID])) {
                // Try to fix question title for valid question code enforcement
                if (!$oQuestion->validate(array('title'))) {
                    $sOldTitle = $oQuestion->title;
                    $sNewTitle = preg_replace("/[^A-Za-z0-9]/", '', $sOldTitle);
                    if (is_numeric(substr($sNewTitle, 0, 1))) {
                        $sNewTitle = 'q' . $sNewTitle;
                    }

                    $oQuestion->title = $sNewTitle;
                }

                $attempts = 0;
                // Try to fix question title for unique question code enforcement
                $index = 0;
                $rand = mt_rand(0, 1024);
                while (!$oQuestion->validate(array('title'))) {
                    $sNewTitle = 'r' . $rand . 'q' . $index;
                    $index++;
                    $oQuestion->title = $sNewTitle;
                    $attempts++;
                    if ($attempts > 10) {
                        throw new Exception(gT("Error") . ": Failed to resolve question code problems after 10 attempts.<br />");
                    }
                }
                if (!$oQuestion->save()) {
                    throw new Exception(gT("Error while saving: ") . print_r($oQuestion->errors, true));
                }
                $aQIDReplacements[$iOldQID] = $oQuestion->qid;
                $results['questions']++;
                $importedQuestions[$aQIDReplacements[$iOldQID]] = $oQuestion;
            }

            if (isset($oQuestionL10n)) {
                $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
                $oQuestionL10n->save();
                unset($oQuestionL10n);
            }
            // Set a warning if question title was updated
            if (isset($sNewTitle) && isset($sOldTitle)) {
                $results['importwarnings'][] = sprintf(gT("Question code %s was updated to %s."), $sOldTitle, $sNewTitle);
                $aQuestionCodeReplacements[$sOldTitle] = $sNewTitle;
                unset($sNewTitle);
                unset($sOldTitle);
            }

            // question codes in format "38612X105X3011" are collected for replacing
            $aQuestionsMapping[$iOldSID . 'X' . $iOldGID . 'X' . $iOldQID] = $iNewSID . 'X' . $oQuestion->gid . 'X' . $oQuestion->qid;
        }
    }

    // Import subquestions -------------------------------------------------------
    if (isset($xml->subquestions)) {
        foreach ($xml->subquestions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }

            if (!isset($xml->question_l10ns->rows->row)) {
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
            }
            if ($insertdata['gid'] == 0) {
                continue;
            }
            if (!isset($insertdata['mandatory']) || trim($insertdata['mandatory']) == '') {
                $insertdata['mandatory'] = 'N';
            }
            $iOldSID = $insertdata['sid'];
            $insertdata['sid'] = $iNewSID;
            $insertdata['gid'] = $aGIDReplacements[(int) $insertdata['gid']];
            $iOldQID = (int) $insertdata['qid'];
            unset($insertdata['qid']); // save the old qid
            $insertdata['parent_qid'] = $aQIDReplacements[(int) $insertdata['parent_qid']]; // remap the parent_qid
            if (!isset($insertdata['help'])) {
                $insertdata['help'] = '';
            }            // now translate any links
            if (!isset($xml->question_l10ns->rows->row)) {
                if ($bTranslateInsertansTags) {
                    $insertdata['question'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']);
                    $insertdata['help'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']);
                }
                $oQuestionL10n = new QuestionL10n();
                $oQuestionL10n->question = $insertdata['question'];
                $oQuestionL10n->help = $insertdata['help'];
                $oQuestionL10n->language = $insertdata['language'];
                unset($insertdata['question']);
                unset($insertdata['help']);
                unset($insertdata['language']);
            }
            if (!$bConvertInvalidQuestionCodes) {
                $sScenario = 'archiveimport';
            } else {
                $sScenario = 'import';
            }

            $oQuestion = new Question($sScenario);
            $oQuestion->setAttributes($insertdata, false);

            if (!isset($aQIDReplacements[$iOldQID])) {
                // Try to fix question title for valid question code enforcement
                if (!$oQuestion->validate(array('title'))) {
                    $sOldTitle = $oQuestion->title;
                    $sNewTitle = preg_replace("/[^A-Za-z0-9]/", '', $sOldTitle);
                    if (is_numeric(substr($sNewTitle, 0, 1))) {
                        $sNewTitle = 'sq' . $sNewTitle;
                    }

                    $oQuestion->title = $sNewTitle;
                }

                $attempts = 0;
                // Try to fix question title for unique question code enforcement
                while (!$oQuestion->validate(array('title'))) {
                    if (!isset($index)) {
                        $index = 0;
                        $rand = mt_rand(0, 1024);
                    } else {
                        $index++;
                    }

                    $sNewTitle = 'r' . $rand . 'sq' . $index;
                    $oQuestion->title = $sNewTitle;
                    $attempts++;

                    if ($attempts > 10) {
                        throw new Exception(gT("Error") . ": Failed to resolve question code problems after 10 attempts.<br />");
                    }
                }
                if (!$oQuestion->save()) {
                    throw new Exception(gT("Error while saving: ") . print_r($oQuestion->errors, true));
                }
                $aQIDReplacements[$iOldQID] = $oQuestion->qid;
                ;
                $results['subquestions']++;
            }

            if (isset($oQuestionL10n)) {
                $oQuestionL10n->qid = $aQIDReplacements[$iOldQID];
                $oQuestionL10n->save();
                unset($oQuestionL10n);
            }

            // Set a warning if question title was updated
            if (isset($sNewTitle) && isset($sOldTitle)) {
                $results['importwarnings'][] = sprintf(gT("Title of subquestion %s was updated to %s."), $sOldTitle, $sNewTitle); // Maybe add the question title ?
                $aQuestionCodeReplacements[$sOldTitle] = $sNewTitle;
                unset($sNewTitle);
                unset($sOldTitle);
            }
        }
    }

    //  Import question_l10ns
    if (isset($xml->question_l10ns->rows->row)) {
        foreach ($xml->question_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            if ($bTranslateInsertansTags) {
                $insertdata['question'] = isset($insertdata['question']) ? translateLinks('survey', $iOldSID, $iNewSID, $insertdata['question']) : '';
                $insertdata['help'] = isset($insertdata['help']) ? translateLinks('survey', $iOldSID, $iNewSID, $insertdata['help']) : '';
            }
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[$insertdata['qid']];
            } else {
                continue; //Skip invalid question ID
            }

            // question codes in format "38612X105X3011" are collected for replacing
            $aQuestionsMapping[$iOldSID . 'X' . $iOldGID . 'X' . $iOldQID . $oQuestion->title] = $iNewSID . 'X' . $oQuestion->gid . 'X' . $oQuestion->qid . $oQuestion->title;
            $oQuestionL10n = new QuestionL10n();
            $oQuestionL10n->setAttributes($insertdata, false);
            $oQuestionL10n->save();
        }
    }

    // Import answers ------------------------------------------------------------
    if (isset($xml->answers)) {
        foreach ($xml->answers->rows->row as $row) {
            $insertdata = array();

            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (isset($xml->answer_l10ns->rows->row) && !empty($insertdata['aid'])) {
                $iOldAID = $insertdata['aid'];
                unset($insertdata['aid']);
            }
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                continue;
            }

            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the parent_qid

            if (!isset($xml->answer_l10ns->rows->row)) {
                // now translate any links
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }
                if ($bTranslateInsertansTags) {
                    $insertdata['answer'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['answer']);
                }
                $oAnswerL10n = new AnswerL10n();
                $oAnswerL10n->answer = $insertdata['answer'];
                $oAnswerL10n->language = $insertdata['language'];
                unset($insertdata['answer']);
                unset($insertdata['language']);
            }

            $oAnswer = new Answer();
            $oAnswer->setAttributes($insertdata, false);
            if ($oAnswer->save() && isset($xml->answer_l10ns->rows->row) && isset($iOldAID)) {
                $aAIDReplacements[$iOldAID] = $oAnswer->aid;
            }
            $results['answers']++;
            if (isset($oAnswerL10n)) {
                $oAnswer = Answer::model()->findByAttributes(['qid' => $insertdata['qid'], 'code' => $insertdata['code'], 'scale_id' => $insertdata['scale_id']]);
                $oAnswerL10n->aid = $oAnswer->aid;
                $oAnswerL10n->save();
                unset($oAnswerL10n);
            }
        }
    }

    //  Import answer_l10ns
    if (isset($xml->answer_l10ns->rows->row)) {
        foreach ($xml->answer_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            unset($insertdata['id']);
            // now translate any links
            if ($bTranslateInsertansTags) {
                $insertdata['answer'] = translateLinks('survey', $iOldSID, $iNewSID, $insertdata['answer']);
            }
            if (isset($aAIDReplacements[$insertdata['aid']])) {
                $insertdata['aid'] = $aAIDReplacements[$insertdata['aid']];
            } else {
                continue; //Skip invalid answer ID
            }
            $oAnswerL10n = new AnswerL10n();
            $oAnswerL10n->setAttributes($insertdata, false);
            $oAnswerL10n->save();
        }
    }

    // Import questionattributes -------------------------------------------------
    if (isset($xml->question_attributes)) {
        $aAllAttributes = questionHelper::getAttributesDefinitions();
        /** @var array<integer,array<string,mixed>> List of "answer order" related attributes, grouped by qid */
        $answerOrderAttributes = [];
        foreach ($xml->question_attributes->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }

            // take care of renaming of date min/max adv. attributes fields
            if ($iDBVersion < 170) {
                if (isset($insertdata['attribute'])) {
                    if ($insertdata['attribute'] == 'dropdown_dates_year_max') {
                        $insertdata['attribute'] = 'date_max';
                    }

                    if ($insertdata['attribute'] == 'dropdown_dates_year_min') {
                        $insertdata['attribute'] = 'date_min';
                    }
                }
            }

            unset($insertdata['qaid']);
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                continue;
            }

            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the qid

            // Question theme was previously stored as a question attribute ('question_template'), but now it
            // is a normal attribute of the Question model. So we must check if the imported question has the
            // 'question_template' attribute and use it for overriding 'question_theme_name' instead of saving
            // as QuestionAttribute.
            if ($insertdata['attribute'] == 'question_template') {
                $oQuestion = Question::model()->findByPk($insertdata['qid']);
                if (!empty($oQuestion)) {
                    $oQuestion->question_theme_name = $insertdata['value'];
                    $oQuestion->save();
                }
                continue;
            }

            // Keep "answer order" related attributes in an array to process later (because we need to combine two attributes)
            if (
                $insertdata['attribute'] == 'alphasort'
                || (
                $insertdata['attribute'] == 'random_order'
                && in_array($importedQuestions[$insertdata['qid']]->type, ['!', 'L', 'O', 'R'])
                )
            ) {
                $answerOrderAttributes[$insertdata['qid']][$insertdata['attribute']] = $insertdata['value'];
                continue;
            }

            if ($iDBVersion < 156 && isset($aAllAttributes[$insertdata['attribute']]['i18n']) && $aAllAttributes[$insertdata['attribute']]['i18n']) {
                foreach ($aLanguagesSupported as $sLanguage) {
                    $insertdata['language'] = $sLanguage;

                    $questionAttribute = new QuestionAttribute();
                    $questionAttribute->attributes = $insertdata;
                    if (!$questionAttribute->save()) {
                        throw new Exception(gT("Error") . ": Failed to insert data[7]<br />");
                    }
                }
            } else {
                $questionAttribute = new QuestionAttribute();
                $questionAttribute->attributes = $insertdata;
                if (!$questionAttribute->save()) {
                    throw new Exception(gT("Error") . ": Failed to insert data[8]<br />");
                }
            }
            checkWrongQuestionAttributes($insertdata['qid']);
            $results['question_attributes']++;
        }

        // Process "answer order" attributes
        foreach ($answerOrderAttributes as $importedQid => $questionAttributes) {
            if (!empty($questionAttributes['random_order'])) {
                $insertdata = [
                'qid' => $importedQid,
                'attribute' => 'answer_order',
                'value' => 'random',
                ];
                App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
                $results['question_attributes']++;
                continue;
            }
            if (!empty($questionAttributes['alphasort'])) {
                $insertdata = [
                'qid' => $importedQid,
                'attribute' => 'answer_order',
                'value' => 'alphabetical',
                ];
                App()->db->createCommand()->insert('{{question_attributes}}', $insertdata);
                $results['question_attributes']++;
            }
        }
    }

    // Import defaultvalues ------------------------------------------------------
    importDefaultValues($xml, $aLanguagesSupported, $aQIDReplacements, $results);

    $aOldNewFieldmap = reverseTranslateFieldNames($iOldSID, $iNewSID, $aGIDReplacements, $aQIDReplacements);

    // Import conditions ---------------------------------------------------------
    if (isset($xml->conditions)) {
        $results['conditions'] = 0;
        $oldcqid = 0;
        $oldqidanscode = 0;
        $oldcgid = 0;
        $oldcsid = 0;
        foreach ($xml->conditions->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            // replace the qid for the new one (if there is no new qid in the $aQIDReplacements array it mean that this condition is orphan -> error, skip this record)
            if (isset($aQIDReplacements[$insertdata['qid']])) {
                $insertdata['qid'] = $aQIDReplacements[$insertdata['qid']]; // remap the qid
            } else {
                // a problem with this answer record -> don't consider
                continue;
            }
            if ($insertdata['cqid'] != 0) {
                if (isset($aQIDReplacements[$insertdata['cqid']])) {
                    $oldcqid = $insertdata['cqid']; //Save for cfield transformation
                    $insertdata['cqid'] = $aQIDReplacements[$insertdata['cqid']]; // remap the qid
                } else {
                    // a problem with this answer record -> don't consider
                    continue;
                }

                list($oldcsid, $oldcgid, $oldqidanscode) = explode("X", $insertdata["cfieldname"], 3);

                // replace the gid for the new one in the cfieldname(if there is no new gid in the $aGIDReplacements array it means that this condition is orphan -> error, skip this record)
                if (!isset($aGIDReplacements[$oldcgid])) {
                    continue;
                }
            }

            unset($insertdata["cid"]);

            // recreate the cfieldname with the new IDs
            if ($insertdata['cqid'] != 0) {
                if (preg_match("/^\+/", $oldcsid)) {
                    $newcfieldname = '+' . $iNewSID . "X" . $aGIDReplacements[$oldcgid] . "X" . $insertdata["cqid"] . substr($oldqidanscode, strlen($oldcqid));
                } else {
                    $newcfieldname = $iNewSID . "X" . $aGIDReplacements[$oldcgid] . "X" . $insertdata["cqid"] . substr($oldqidanscode, strlen($oldcqid));
                }
            } else {
                // The cfieldname is a not a previous question cfield but a {XXXX} replacement field
                $newcfieldname = $insertdata["cfieldname"];
            }
            $insertdata["cfieldname"] = $newcfieldname;
            if (trim($insertdata["method"]) == '') {
                $insertdata["method"] = '==';
            }

            // Now process the value and replace @sgqa@ codes
            if (preg_match("/^@(.*)@$/", $insertdata["value"], $cfieldnameInCondValue)) {
                if (isset($aOldNewFieldmap[$cfieldnameInCondValue[1]])) {
                    $newvalue = '@' . $aOldNewFieldmap[$cfieldnameInCondValue[1]] . '@';
                    $insertdata["value"] = $newvalue;
                }
            }

            // now translate any links
            $result = Condition::model()->insertRecords($insertdata);
            if (!$result) {
                throw new Exception(gT("Error") . ": Failed to insert data[10]<br />");
            }
            $results['conditions']++;
        }
    }

    // Import assessments --------------------------------------------------------
    if (isset($xml->assessments)) {
        foreach ($xml->assessments->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (!isset($insertdata['id']) || (int)$insertdata['id'] < 1) {
                continue;
            }
            $oldasid = $insertdata['id'];
            unset($insertdata['id']);

            if (isset($aASIDReplacements[$oldasid])) {
                $insertdata['id'] = $aASIDReplacements[$oldasid];
            }

            if ($insertdata['gid'] > 0) {
                $insertdata['gid'] = $aGIDReplacements[(int) $insertdata['gid']]; // remap the qid
            }

            $insertdata['sid'] = $iNewSID; // remap the survey id
            // now translate any links
            $result = Assessment::model()->insertRecords($insertdata);
            if (!$result) {
                throw new Exception(gT("Error") . ": Failed to insert data[11]<br />");
            }

            if (!isset($aASIDReplacements[$oldasid])) {
                $aASIDReplacements[$oldasid] = $result->id; // add old and new id to the mapping array
                $results['assessments']++;
            }
        }
    }

    // Import quota --------------------------------------------------------------
    if (isset($xml->quota)) {
        foreach ($xml->quota->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (!isset($insertdata['id']) || (int)$insertdata['id'] < 1) {
                continue;
            }
            $insertdata['sid'] = $iNewSID; // remap the survey id
            $oldid = $insertdata['id'];
            unset($insertdata['id']);
            // now translate any links
            $result = Quota::model()->insertRecords($insertdata);
            if (!$result) {
                throw new Exception(gT("Error") . ": Failed to insert data[12]<br />");
            }
            $aQuotaReplacements[$oldid] = getLastInsertID('{{quota}}');
            $results['quota']++;
        }
    }

    // Import quota_members ------------------------------------------------------
    if (isset($xml->quota_members)) {
        foreach ($xml->quota_members->rows->row as $row) {
            $quotaMember = new QuotaMember();
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (!isset($insertdata['quota_id']) || (int)$insertdata['quota_id'] < 1) {
                continue;
            }
            $insertdata['sid'] = $iNewSID; // remap the survey id
            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the qid
            if (isset($insertdata['quota_id'])) {
                $insertdata['quota_id'] = $aQuotaReplacements[(int) $insertdata['quota_id']]; // remap the qid
            }
            unset($insertdata['id']);
            // now translate any links
            $quotaMember->setAttributes($insertdata, false);

            if (!$quotaMember->validate()) {
                // Display validation errors
                foreach ($quotaMember->errors as $attribute => $errors) {
                    $errorText = '';
                    foreach ($errors as $error) {
                        $errorText .= 'Field "' . $attribute . '": ' . $error . " Value: '{$quotaMember->$attribute}'\n";
                    }
                    throw new Exception(gT("Error:") . " Failed to insert quota member" . "\n" . $errorText);
                }
            }
            if (!$quotaMember->save()) {
                throw new Exception(gT("Error:") . " Failed to insert quota member database entry\n");
            }
            $results['quotamembers']++;
        }
    }

    // Import quota_languagesettings----------------------------------------------
    if (isset($xml->quota_languagesettings)) {
        foreach ($xml->quota_languagesettings->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (!isset($insertdata['quotals_quota_id']) || (int)$insertdata['quotals_quota_id'] < 1) {
                continue;
            }
            $insertdata['autoload_url'] = 0; // used to bypass urlValidator check in QuotaLanguageSetting model
            $insertdata['quotals_quota_id'] = $aQuotaReplacements[(int) $insertdata['quotals_quota_id']]; // remap the qid
            unset($insertdata['quotals_id']);
            $quotaLanguagesSetting = new QuotaLanguageSetting();
            $quotaLanguagesSetting->setAttributes($insertdata, false);
            if (!$quotaLanguagesSetting->save()) {
                throw new Exception(gT("Error") . ": Failed to insert data<br />");
            }
            $results['quotals']++;
        }
    }

    // Import survey_url_parameters ----------------------------------------------
    if (isset($xml->survey_url_parameters)) {
        foreach ($xml->survey_url_parameters->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            $insertdata['sid'] = $iNewSID; // remap the survey id
            if (isset($insertdata['targetsqid']) && $insertdata['targetsqid'] != '') {
                $insertdata['targetsqid'] = $aQIDReplacements[(int) $insertdata['targetsqid']]; // remap the qid
            }
            if (isset($insertdata['targetqid']) && $insertdata['targetqid'] != '') {
                $insertdata['targetqid'] = $aQIDReplacements[(int) $insertdata['targetqid']]; // remap the qid
            }
            unset($insertdata['id']);
            $result = SurveyURLParameter::model()->insertRecord($insertdata);
            if (!$result) {
                throw new Exception(gT("Error") . ": Failed to insert data[14]<br />");
            }
            $results['survey_url_parameters']++;
        }
    }

    // Import Survey plugins settings
    if (isset($xml->plugin_settings)) {
        $pluginNamesWarning = array(); // To shown not exist warning only one time.
        foreach ($xml->plugin_settings->rows->row as $row) {
            // Find plugin id
            if (isset($row->name)) {
                $oPlugin = Plugin::model()->find("name = :name", array(":name" => $row->name));
                if ($oPlugin) {
                    $setting = new PluginSetting();
                    $setting->plugin_id = $oPlugin->id;
                    $setting->model = "Survey";
                    $setting->model_id = $iNewSID;
                    $setting->key = (string) $row->key;
                    $setting->value = (string) $row->value;
                    if ($setting->save()) {
                        $results['plugin_settings']++;
                    } else {
                        $results['importwarnings'][] = sprintf(gT("Error when saving %s for plugin %s"), CHtml::encode($row->key), CHtml::encode($row->name));
                    }
                } elseif (!isset($pluginNamesWarning[(string) $row->name])) {
                    $results['importwarnings'][] = sprintf(gT("Plugin %s didn't exist, settings not imported"), CHtml::encode($row->name));
                    $pluginNamesWarning[(string) $row->name] = 1;
                }
            }
        }
    }

    //// Import Survey theme settings
    $aTemplateConfiguration = array();

    // original theme
    if (isset($xml->themes_inherited)) {
        foreach ($xml->themes_inherited->theme as $theme_key => $theme_row) {
            if ((string)$theme_row->template_name === $sTemplateName) {
                $aTemplateConfiguration['theme_original']['options'] = (array)$theme_row->config->options;
                $results['theme_original'] = json_encode($theme_row->config->options);
            }
        }
    }


    if (isset($xml->themes)) {
        // current theme options
        if (!empty($sTemplateName)) {
            $oTemplateConfigurationCurrent = TemplateConfiguration::getInstance($sTemplateName);
            //$oTemplateConfigurationCurrent->bUseMagicInherit = true;
            $aTemplateConfiguration['theme_current']['options'] = (array)json_decode($oTemplateConfigurationCurrent->attributes['options']);
        }

        // survey theme options
        foreach ($xml->themes->theme as $theme_key => $theme_row) {
            // skip if theme doesn't exist
            if (!Template::checkIfTemplateExists((string)$theme_row->template_name)) {
                // show warning if survey theme doesn't exist
                if ((string)$theme_row->template_name === $sTemplateName) {
                    $results['template_deleted'] = '1';
                }
                continue;
            }
            // insert into Template configuration table
            $result = TemplateManifest::importManifestLss($iNewSID, $theme_row);
            if ($result) {
                $results['themes']++;
                if ((string)$theme_row->template_name === $sTemplateName) {
                    if (isset($theme_row->config->options)) {
                        $options = $theme_row->config->options;

                        // set each key value to 'inherit' if options are set to 'inherit'
                        if ((string)$options === 'inherit' && isset($aTemplateConfiguration['theme_current']['options'])) {
                            $options = $aTemplateConfiguration['theme_current']['options'];
                            $options = array_fill_keys(array_keys($options), 'inherit');
                        }

                        $aThemeOptionsData = array();
                        foreach ((array)$options as $key => $value) {
                            if ($value == 'inherit') {
                                $sOldValue = $aTemplateConfiguration['theme_original']['options'][$key] ?? '';
                                $sNewValue = $aTemplateConfiguration['theme_current']['options'][$key] ?? '';
                                if (!empty($sOldValue) && !empty($sNewValue) && $sOldValue !== $sNewValue) {
                                    // used to send original theme options data to controller action if client wants to restore original theme options
                                    $aThemeOptionsData[$key] = $aTemplateConfiguration['theme_original']['options'][$key];
                                    // used to display difference between options
                                    $aThemeOptionsDifference = array();
                                    $aThemeOptionsDifference['option'] = $key;
                                    $aThemeOptionsDifference['current_value'] = $aTemplateConfiguration['theme_current']['options'][$key];
                                    $aThemeOptionsDifference['original_value'] = $aTemplateConfiguration['theme_original']['options'][$key];
                                    $results['theme_options_differences'][] = $aThemeOptionsDifference;
                                }
                            }
                        }

                        $results['theme_options_original_data'] = json_encode($aThemeOptionsData);
                    }

                    $aTemplateConfiguration['theme_survey']['options'] = (array)$theme_row->config->options;
                }
            } else {
                $results['importwarnings'][] = gT("Error") . ": Failed to insert data[18]<br />";
            }
        }
    }

    // Set survey rights
    Permission::model()->giveAllSurveyPermissions(Yii::app()->session['loginID'], $iNewSID);
    $aOldNewFieldmap = reverseTranslateFieldNames($iOldSID, $iNewSID, $aGIDReplacements, $aQIDReplacements);
    $results['FieldReMap'] = $aOldNewFieldmap;
    LimeExpressionManager::SetSurveyId($iNewSID);
    translateInsertansTags($iNewSID, $iOldSID, $aOldNewFieldmap);
    replaceExpressionCodes($iNewSID, $aQuestionCodeReplacements);
    replaceExpressionCodes($iNewSID, $aQuestionsMapping); // replace question codes in format "38612X105X3011"
    if (count($aQuestionCodeReplacements)) {
        array_unshift($results['importwarnings'], "<span class='warningtitle'>" . gT('Attention: Several question codes were updated. Please check these carefully as the update  may not be perfect with customized expressions.') . '</span>');
    }
    LimeExpressionManager::RevertUpgradeConditionsToRelevance($iNewSID);
    LimeExpressionManager::UpgradeConditionsToRelevance($iNewSID);
    return $results;
}

/**
 * This function checks if there are set wrong values ('Y' or 'N') into table
 * question_attributes. These are set to 1 and 0 if needed.
 *
 * @param $questionId
 */
function checkWrongQuestionAttributes($questionId)
{
    //these attributes could be wrongly set to 'Y' or 'N' instead of 1 and 0
    $attributesTobeChecked = ['statistics_showgraph', 'public_statistics' , 'page_break' , 'other_numbers_only',
        'other_comment_mandatory', 'hide_tip' , 'hidden', 'exclude_all_others_auto',
        'commented_checkbox_auto', 'num_value_int_only', 'alphasort', 'use_dropdown',
        'slider_default_set', 'slider_layout', 'slider_middlestart', 'slider_reset',
        'slider_reversed', 'slider_showminmax', 'value_range_allows_missing'];
    $questionAttributes = QuestionAttribute::model()->findAllByAttributes(['qid' => $questionId]);
    foreach ($questionAttributes as $questionAttribute) {
        if (in_array($questionAttribute->attribute, $attributesTobeChecked)) {
            //now check if value is 0 or 1 (if not then reset the wrong values ('Y' or 'N')
            if ($questionAttribute->value === 'Y') {
                $questionAttribute->value = 1;
                $questionAttribute->save();
            } elseif ($questionAttribute->value === 'N') {
                $questionAttribute->value = 0;
                $questionAttribute->save();
            }
        }
    }
}

/**
 * @param string $sFullFilePath
 * @return mixed
 */
function XMLImportTokens($sFullFilePath, $iSurveyID, $sCreateMissingAttributeFields = true)
{
    Yii::app()->loadHelper('database');
    $survey = Survey::model()->findByPk($iSurveyID);
    $sXMLdata = (string) file_get_contents($sFullFilePath);
    $xml = simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET);
    $results = [];
    $results['warnings'] = array();
    if ($xml->LimeSurveyDocType != 'Tokens') {
        $results['error'] = gT("This is not a valid participant data XML file.");
        return $results;
    }

    if (!isset($xml->tokens->fields)) {
        $results['tokens'] = 0;
        return $results;
    }

    $results['tokens'] = 0;
    $results['tokenfieldscreated'] = 0;

    if ($sCreateMissingAttributeFields) {
        // Get a list with all fieldnames in the XML
        $aXLMFieldNames = array();
        foreach ($xml->tokens->fields->fieldname as $sFieldName) {
            $aXLMFieldNames[] = (string) $sFieldName;
        }
        // Get a list of all fieldnames in the survey participants table
        $aTokenFieldNames = Yii::app()->db->getSchema()->getTable($survey->tokensTableName, true);
        $aTokenFieldNames = array_keys($aTokenFieldNames->columns);
        $aFieldsToCreate = array_diff($aXLMFieldNames, $aTokenFieldNames);
        if (!function_exists('db_upgrade_all')) {
            Yii::app()->loadHelper('update/updatedb');
        }

        foreach ($aFieldsToCreate as $sField) {
            if (strpos($sField, 'attribute') !== false) {
                addColumn($survey->tokensTableName, $sField, 'string');
            }
        }
    }

    switchMSSQLIdentityInsert('tokens_' . $iSurveyID, true);
    foreach ($xml->tokens->rows->row as $row) {
        $insertdata = array();

        foreach ($row as $key => $value) {
            $insertdata[(string) $key] = (string) $value;
        }

        $token = Token::create($iSurveyID, 'allowinvalidemail');
        $token->setAttributes($insertdata, false);
        if (!$token->encryptSave(true)) {
            $results['warnings'][] = CHtml::errorSummary($token, gT("Skipped participant entry:"));
        } else {
            $results['tokens']++;
        }
    }
    switchMSSQLIdentityInsert('tokens_' . $iSurveyID, false);
    if (Yii::app()->db->getDriverName() == 'pgsql') {
        try {
            Yii::app()->db->createCommand("SELECT pg_catalog.setval(pg_get_serial_sequence('{{tokens_" . $iSurveyID . "}}', 'tid'), (SELECT MAX(tid) FROM {{tokens_" . $iSurveyID . "}}))")->execute();
        } catch (Exception $oException) {
        };
    }
    return $results;
}


/**
 * @param string $sFullFilePath
 * @return mixed
 */
function XMLImportResponses($sFullFilePath, $iSurveyID, $aFieldReMap = array())
{
    Yii::app()->loadHelper('database');
    $survey = Survey::model()->findByPk($iSurveyID);

    switchMSSQLIdentityInsert('survey_' . $iSurveyID, true);
    $results = [];
    $results['responses'] = 0;

    if (\PHP_VERSION_ID < 80000) {
        libxml_disable_entity_loader(false);
    }
    $oXMLReader = new XMLReader();
    $oXMLReader->open($sFullFilePath);
    if (\PHP_VERSION_ID < 80000) {
        libxml_disable_entity_loader(true);
    }
    if (Yii::app()->db->schema->getTable($survey->responsesTableName) !== null) {
        $DestinationFields = Yii::app()->db->schema->getTable($survey->responsesTableName)->getColumnNames();
        while ($oXMLReader->read()) {
            if ($oXMLReader->name === 'LimeSurveyDocType' && $oXMLReader->nodeType == XMLReader::ELEMENT) {
                $oXMLReader->read();
                if ($oXMLReader->value != 'Responses') {
                    $results['error'] = gT("This is not a valid response data XML file.");
                    return $results;
                }
            }
            if ($oXMLReader->name === 'rows' && $oXMLReader->nodeType == XMLReader::ELEMENT) {
                while ($oXMLReader->read()) {
                    if ($oXMLReader->name === 'row' && $oXMLReader->nodeType == XMLReader::ELEMENT) {
                        $aInsertData = array();
                        while ($oXMLReader->read() && $oXMLReader->name != 'row') {
                            $sFieldname = $oXMLReader->name;
                            if ($sFieldname[0] == '_') {
                                $sFieldname = substr($sFieldname, 1);
                            }
                            $sFieldname = str_replace('-', '#', $sFieldname);
                            if (isset($aFieldReMap[$sFieldname])) {
                                $sFieldname = $aFieldReMap[$sFieldname];
                            }
                            if (!$oXMLReader->isEmptyElement) {
                                $oXMLReader->read();
                                if (in_array($sFieldname, $DestinationFields)) {
                                    // some old response tables contain invalid column names due to old bugs
                                    $aInsertData[$sFieldname] = $oXMLReader->value;
                                }
                                $oXMLReader->read();
                            } else {
                                if (in_array($sFieldname, $DestinationFields)) {
                                    $aInsertData[$sFieldname] = '';
                                }
                            }
                        }
                        if (!SurveyDynamic::model($iSurveyID)->insertRecords($aInsertData)) {
                            throw new Exception(gT("Error") . ": Failed to insert data in response table<br />");
                        }
                        $results['responses']++;
                    }
                }
            }
        }
        $oXMLReader->close();

        switchMSSQLIdentityInsert('survey_' . $iSurveyID, false);
        if (Yii::app()->db->getDriverName() == 'pgsql') {
            try {
                Yii::app()->db->createCommand("SELECT pg_catalog.setval(pg_get_serial_sequence('" . $survey->responsesTableName . "', 'id'), (SELECT MAX(id) FROM " . $survey->responsesTableName . "))")->execute();
            } catch (Exception $oException) {
            };
        }
        $results['warnings'] = [];
        return $results;
    } else {
        $results['warnings'][] = gT("The survey response table could not be created.") . '<br>' . gT("Usually this is caused by having too many (sub-)questions in your survey. Please try removing questions from your survey.");
        return $results;
    }
}

/**
 * This function imports a CSV file into the response table
 * CSV file is deleted during process
 *
 * @param string $sFullFilePath
 * @param integer $iSurveyId
 * @param array $aOptions
 * Return array $result ("errors","warnings","success")
 */
function CSVImportResponses($sFullFilePath, $iSurveyId, $aOptions = array())
{

    // Default optional
    if (!isset($aOptions['bDeleteFistLine'])) {
        $aOptions['bDeleteFistLine'] = true;
    } // By default delete first line (vvimport)
    if (!isset($aOptions['sExistingId'])) {
        $aOptions['sExistingId'] = "ignore";
    } // By default exclude existing id
    if (!isset($aOptions['bNotFinalized'])) {
        $aOptions['bNotFinalized'] = false;
    } // By default don't change finalized part
    if (!isset($aOptions['sCharset']) || !$aOptions['sCharset']) {
        $aOptions['sCharset'] = "utf8";
    }
    if (!isset($aOptions['sSeparator'])) {
        $aOptions['sSeparator'] = "\t";
    }
    if (!isset($aOptions['sQuoted'])) {
        $aOptions['sQuoted'] = "\"";
    }
    // Fix some part
    if (!array_key_exists($aOptions['sCharset'], aEncodingsArray())) {
        $aOptions['sCharset'] = "utf8";
    }

    // Prepare an array of sentence for result
    $CSVImportResult = array();
    $tmpVVFile = fileCsvToUtf8($sFullFilePath, $aOptions['sCharset']);
    $aFileResponses = array();
    while (($aLineResponse = fgetcsv($tmpVVFile, 0, $aOptions['sSeparator'], $aOptions['sQuoted'])) !== false) {
        $aFileResponses[] = $aLineResponse;
    }
    if (empty($aFileResponses)) {
        $CSVImportResult['errors'][] = sprintf(gT("File is empty or you selected an invalid character set (%s)."), $aOptions['sCharset']);
        return $CSVImportResult;
    }
    if ($aOptions['bDeleteFistLine']) {
        array_shift($aFileResponses);
    }

    $aRealFieldNames = Yii::app()->db->getSchema()->getTable(SurveyDynamic::model($iSurveyId)->tableName())->getColumnNames();
    $aCsvHeader = array_shift($aFileResponses);
    LimeExpressionManager::SetDirtyFlag(); // Be sure survey EM code are up to date
    $aLemFieldNames = LimeExpressionManager::getLEMqcode2sgqa($iSurveyId);
    $aKeyForFieldNames = array(); // An array assicated each fieldname with corresponding responses key
    if (empty($aCsvHeader)) {
        $CSVImportResult['errors'][] = gT("File seems empty or has only one line");
        return $CSVImportResult;
    }
    // Assign fieldname with $aFileResponses[] key
    foreach ($aRealFieldNames as $sFieldName) {
        if (in_array($sFieldName, $aCsvHeader)) {
            // First pass : simple associated
            $aKeyForFieldNames[$sFieldName] = array_search($sFieldName, $aCsvHeader);
        } elseif (in_array($sFieldName, $aLemFieldNames)) {
            // Second pass : LEM associated
            $sLemFieldName = array_search($sFieldName, $aLemFieldNames);
            if (in_array($sLemFieldName, $aCsvHeader)) {
                $aKeyForFieldNames[$sFieldName] = array_search($sLemFieldName, $aCsvHeader);
            } elseif ($aOptions['bForceImport']) {
                // as fallback just map questions in order of apperance

                // find out where the answer data columns start in CSV
                if (!isset($csv_ans_start_index)) {
                    foreach ($aCsvHeader as $i => $name) {
                        if (preg_match('/^\d+X\d+X\d+/', $name)) {
                            $csv_ans_start_index = $i;
                            break;
                        }
                    }
                }
                // find out where the answer data columns start in destination table
                if (!isset($table_ans_start_index)) {
                    foreach ($aRealFieldNames as $i => $name) {
                        if (preg_match('/^\d+X\d+X\d+/', $name)) {
                            $table_ans_start_index = $i;
                            break;
                        }
                    }
                }

                // map answers in order
                if (isset($table_ans_start_index, $csv_ans_start_index)) {
                    $csv_index = (array_search($sFieldName, $aRealFieldNames) - $table_ans_start_index) + $csv_ans_start_index;
                    if ($csv_index < count($aCsvHeader)) {
                        $aKeyForFieldNames[$sFieldName] = $csv_index;
                    } else {
                        $force_import_failed = true;
                        break;
                    }
                }
            }
        }
    }
    // check if forced error failed
    if (isset($force_import_failed)) {
        $CSVImportResult['errors'][] = gT("Import failed: Forced import was requested but the input file doesn't contain enough columns to fill the survey.");
        return $CSVImportResult;
    }

    // make sure at least one answer was imported before commiting
    $isAnswerMapped = array_key_exists('id', $aKeyForFieldNames) ? (count($aKeyForFieldNames) > 1) : (count($aKeyForFieldNames) > 0);
    if (!$isAnswerMapped) {
        $CSVImportResult['errors'][] = gT("Import failed: No answers could be mapped.");
        return $CSVImportResult;
    }

    // Now it's time to import
    // Some var to return
    $iNbResponseLine = 0;
    $aResponsesInserted = array();
    $aResponsesUpdated = array();
    $aResponsesError = array();
    $aExistingsId = array();

    $iMaxId = 0; // If we set the id, keep the max
    // Some specific header (with options)
    $iIdKey = array_search('id', $aCsvHeader); // the id is always needed and used a lot
    if (is_int($iIdKey)) {
        unset($aKeyForFieldNames['id']);
        /* Unset it if option is ignore */
        if ($aOptions['sExistingId'] == 'ignore') {
            $iIdKey = false;
        }
    }
    $iSubmitdateKey = array_search('submitdate', $aCsvHeader); // submitdate can be forced to null
    if (is_int($iSubmitdateKey)) {
        unset($aKeyForFieldNames['submitdate']);
    }
    $iIdResponsesKey = (is_int($iIdKey)) ? $iIdKey : 0; // The key for responses id: id column or first column if not exist

    // Import each responses line here
    while ($aResponses = array_shift($aFileResponses)) {
        $iNbResponseLine++;
        $bExistingsId = false;
        if ($iIdKey !== false) {
            $oSurvey = SurveyDynamic::model($iSurveyId)->findByPk($aResponses[$iIdKey]);
            if ($oSurvey) {
                $bExistingsId = true;
                $aExistingsId[] = $aResponses[$iIdKey];
                // Do according to option
                switch ($aOptions['sExistingId']) {
                    case 'replace':
                        SurveyDynamic::model($iSurveyId)->deleteByPk($aResponses[$iIdKey]);
                        SurveyDynamic::sid($iSurveyId);
                        $oSurvey = new SurveyDynamic();
                        break;
                    case 'replaceanswers':
                        break;
                    case 'skip':
                        $oSurvey = false; // Remove existing survey : don't import again
                        break;
                    case 'renumber':
                    default: // Must not happen, keep it in case
                        SurveyDynamic::sid($iSurveyId);
                        $oSurvey = new SurveyDynamic();
                        break;
                }
            } else {
                SurveyDynamic::sid($iSurveyId);
                $oSurvey = new SurveyDynamic();
            }
        } else {
            SurveyDynamic::sid($iSurveyId);
            $oSurvey = new SurveyDynamic();
        }
        if ($oSurvey) {
            // First rule for id and submitdate
            if (is_int($iIdKey)) {
                // Rule for id: only if id exists in vvimport file
                if (!$bExistingsId) {
                    // If not exist : always import it
                    $oSurvey->id = $aResponses[$iIdKey];
                    $iMaxId = ($aResponses[$iIdKey] > $iMaxId) ? $aResponses[$iIdKey] : $iMaxId;
                } elseif ($aOptions['sExistingId'] == 'replace' || $aOptions['sExistingId'] == 'replaceanswers') {
                    // Set it depending with some options
                    $oSurvey->id = $aResponses[$iIdKey];
                }
            }
            if ($aOptions['bNotFinalized']) {
                $oSurvey->submitdate = new CDbExpression('NULL');
            } elseif (is_int($iSubmitdateKey)) {
                if ($aResponses[$iSubmitdateKey] == '{question_not_shown}' || trim($aResponses[$iSubmitdateKey] == '')) {
                    $oSurvey->submitdate = new CDbExpression('NULL');
                } else {
                    // Maybe control valid date : see http://php.net/manual/en/function.checkdate.php#78362 for example
                    $oSurvey->submitdate = $aResponses[$iSubmitdateKey];
                }
            }

            foreach ($aKeyForFieldNames as $sFieldName => $iFieldKey) {
                if ($aResponses[$iFieldKey] == '{question_not_shown}') {
                    $oSurvey->$sFieldName = new CDbExpression('NULL');
                } else {
                    $sResponse = str_replace(array("{quote}", "{tab}", "{cr}", "{newline}", "{lbrace}"), array("\"", "\t", "\r", "\n", "{"), $aResponses[$iFieldKey]);
                    $oSurvey->$sFieldName = $sResponse;
                }
            }

            //Check if datestamp is set => throws no default error on importing
            if ($oSurvey->hasAttribute('datestamp') && !isset($oSurvey->datestamp)) {
                $oSurvey->datestamp = '1980-01-01 00:00:01';
            }
            //Check if startdate is set => throws no default error on importing
            if ($oSurvey->hasAttribute('startdate') && !isset($oSurvey->startdate)) {
                $oSurvey->startdate = '1980-01-01 00:00:01';
            }

            // We use transaction to prevent DB error
            $oTransaction = Yii::app()->db->beginTransaction();
            try {
                if (isset($oSurvey->id) && !is_null($oSurvey->id)) {
                    switchMSSQLIdentityInsert('survey_' . $iSurveyId, true);
                    $bSwitched = true;
                }
                if ($oSurvey->encryptSave()) {
                    $beforeDataEntryImport = new PluginEvent('beforeDataEntryImport');
                    $beforeDataEntryImport->set('iSurveyID', $iSurveyId);
                    $beforeDataEntryImport->set('oModel', $oSurvey);
                    App()->getPluginManager()->dispatchEvent($beforeDataEntryImport);

                    $oTransaction->commit();
                    if ($bExistingsId && $aOptions['sExistingId'] != 'renumber') {
                        $aResponsesUpdated[] = $aResponses[$iIdResponsesKey];
                    } else {
                        $aResponsesInserted[] = $aResponses[$iIdResponsesKey];
                    }
                } else {
                    // Actually can not be, leave it if we have a $oSurvey->validate() in future release
                    $oTransaction->rollBack();
                    $aResponsesError[] = $aResponses[$iIdResponsesKey];
                }
                if (isset($bSwitched) && $bSwitched == true) {
                    switchMSSQLIdentityInsert('survey_' . $iSurveyId, false);
                    $bSwitched = false;
                }
            } catch (Exception $oException) {
                $oTransaction->rollBack();
                $aResponsesError[] = $aResponses[$iIdResponsesKey];
                // Show some error to user ?
                $CSVImportResult['errors'][] = $oException->getMessage(); // Show it in view
                tracevar($oException->getMessage());// Show it in console (if debug is set)
            }
        }
    }
    // Fix max next id (for pgsql)
    // mysql dot need fix, but what for mssql ?
    // Do a model function for this can be a good idea (see activate_helper/activateSurvey)
    if (Yii::app()->db->driverName == 'pgsql') {
        $sSequenceName = Yii::app()->db->getSchema()->getTable("{{survey_{$iSurveyId}}}")->sequenceName;
        $iActualSerial = Yii::app()->db->createCommand("SELECT last_value FROM  {$sSequenceName}")->queryScalar();
        if ($iActualSerial < $iMaxId) {
            $sQuery = "SELECT setval(pg_get_serial_sequence('{{survey_{$iSurveyId}}}', 'id'),{$iMaxId},false);";
            try {
                Yii::app()->db->createCommand($sQuery)->execute();
            } catch (Exception $oException) {
            };
        }
    }

    // End of import
    // Construction of returned information
    if ($iNbResponseLine) {
        $CSVImportResult['success'][] = sprintf(gT("%s response lines in your file."), $iNbResponseLine);
    } else {
        $CSVImportResult['errors'][] = gT("No response lines in your file.");
    }
    if (count($aResponsesInserted)) {
        $CSVImportResult['success'][] = sprintf(gT("%s responses were inserted."), count($aResponsesInserted));
        // Maybe add implode aResponsesInserted array
    }
    if (count($aResponsesUpdated)) {
        $CSVImportResult['success'][] = sprintf(gT("%s responses were updated."), count($aResponsesUpdated));
    }
    if (count($aResponsesError)) {
        $CSVImportResult['errors'][] = sprintf(gT("%s responses cannot be inserted or updated."), count($aResponsesError));
    }
    if (count($aExistingsId) && ($aOptions['sExistingId'] == 'skip' || $aOptions['sExistingId'] == 'ignore')) {
        $CSVImportResult['warnings'][] = sprintf(gT("%s responses already exist."), count($aExistingsId));
    }
    return $CSVImportResult;
}


/**
 * @param string $sFullFilePath
 */
function XMLImportTimings($sFullFilePath, $iSurveyID, $aFieldReMap = array())
{
    Yii::app()->loadHelper('database');

    $sXMLdata = (string) file_get_contents($sFullFilePath);
    $xml = simplexml_load_string($sXMLdata, 'SimpleXMLElement', LIBXML_NONET);
    $results = [];
    if ($xml->LimeSurveyDocType != 'Timings') {
        $results['error'] = gT("This is not a valid timings data XML file.");
        return $results;
    }

    $results['responses'] = 0;

    $aLanguagesSupported = array();

    foreach ($xml->languages->language as $language) {
        $aLanguagesSupported[] = (string) $language;
    }

    $results['languages'] = count($aLanguagesSupported);
    // Return if there are no timing records to import
    if (!isset($xml->timings->rows)) {
        return $results;
    }
    switchMSSQLIdentityInsert('survey_' . $iSurveyID . '_timings', true);
    foreach ($xml->timings->rows->row as $row) {
        $insertdata = array();

        foreach ($row as $key => $value) {
            if ($key[0] == '_') {
                $key = substr($key, 1);
            }
            if (isset($aFieldReMap[substr($key, 0, -4)])) {
                $key = $aFieldReMap[substr($key, 0, -4)] . 'time';
            }
            $insertdata[$key] = (string) $value;
        }

        if (!SurveyTimingDynamic::model($iSurveyID)->insertRecords($insertdata)) {
            throw new Exception(gT("Error") . ": Failed to insert timings data");
        }

        $results['responses']++;
    }
    switchMSSQLIdentityInsert('survey_' . $iSurveyID . '_timings', false);
    return $results;
}

/**
* Import survey from an TSV file template that does not require assigning of GID or QID values.
* If ID's are presented, they would be respected and used
* Multilanguage imports are supported
* Original function is changed to allow generating of XML instead of creating database objects directly
* Generated XML code is send to existing lss import function
* @param string $sFullFilePath
* @return string XML data
*
* @author TMSWhite
*/
function TSVImportSurvey($sFullFilePath)
{
    $baselang = 'en'; // TODO set proper default

    $aAttributeList = array(); //QuestionAttribute::getQuestionAttributesSettings();
    $tmp = fileCsvToUtf8($sFullFilePath);

    $rowheaders = fgetcsv($tmp, 0, "\t", '"');
    $rowheaders = array_map('trim', $rowheaders);
    // remove BOM from the first header cell, if needed
    $rowheaders[0] = preg_replace("/^\W+/", "", $rowheaders[0]);
    if (preg_match('/class$/', $rowheaders[0])) {
        $rowheaders[0] = 'class'; // second attempt to remove BOM
    }

    $adata = array();
    $iHeaderCount = count($rowheaders);
    while (($row = fgetcsv($tmp, 0, "\t", '"')) !== false) {
        $rowarray = array();
        for ($i = 0; $i < $iHeaderCount; ++$i) {
            $val = ($row[$i] ?? '');
            // if Excel was used, it surrounds strings with quotes and doubles internal double quotes.  Fix that.
            if (preg_match('/^".*"$/', $val)) {
                $val = str_replace('""', '"', substr($val, 1, -1));
            }
            if (mb_strlen($val) > 0) {
                $rowarray[$rowheaders[$i]] = $val;
            }
        }
        $adata[] = $rowarray;
    }
    fclose($tmp);
    unset($rowheaders);
    unset($rowarray) ;

    // collect information about survey and its language settings
    $surveyinfo = array();
    $surveyls = array();
    $groups = array();
    $questions = array();
    $attributes = array();
    $subquestions = array();
    $defaultvalues = array();
    $answers = array();
    $assessments = array();
    $quotas = array();
    $quota_members = array();
    $quota_languagesettings = array();
    $output = array();
    foreach ($adata as $row) {
        switch ($row['class']) {
            case 'S':
                if (isset($row['text']) && $row['name'] != 'datecreated') {
                    $surveyinfo[$row['name']] = $row['text'];
                }
                break;
            case 'SL':
                /*if (!isset($surveyls[$row['language']])) {
                    $surveyls[$row['language']] = array($baselang);
                }*/
                if (isset($row['text'])) {
                    $surveyls[$row['language']][$row['name']] = $row['text'];
                }
                break;
        }
    }


    // Create the survey entry
    $surveyinfo['startdate'] = null;
    $surveyinfo['active'] = 'N';
    // unset($surveyinfo['datecreated']);

    // Set survey group id to 1. Makes no sense to import it without the actual survey group.
    $surveyinfo['gsid'] = 1;

    if (array_key_exists('sid', $surveyinfo)) {
        $iNewSID = $surveyinfo['sid'];
    } else {
        $iNewSID = randomChars(6, '123456789');
    }


    $gidNew = 0;
    $gid = 0;
    $gseq = 1; // group_order
    $qid = 1;
    $qidNew = 0;
    $qseq = 0; // question_order
    $qtype = 'T';
    $aseq = 0; // answer sortorder

    $ginfo = array();
    $qinfo = array();
    $sqinfo = array();
    $asinfo = array();

    if (isset($surveyinfo['language'])) {
        $baselang = $surveyinfo['language']; // the base language
    }
    /* Keep track of id for group */
    $groupIds = [];
    /* Keep track of id for question (can come from tsv and can be broken : issue #17980 */
    $questionsIds = [];
    $rownumber = 1;
    $lastglang = '';
    $lastother = 'N';
    $qseq = 1;
    $iGroupcounter = 1;
    foreach ($adata as $row) {
        $rownumber += 1;
        switch ($row['class']) {
            case 'G':
                // insert group
                $group = array();
                $group['sid'] = $iNewSID;
                $gname = ((!empty($row['name']) ? $row['name'] : 'G' . $gseq));
                $glang = (!empty($row['language']) ? $row['language'] : $baselang);
                // when a multi-lang tsv-file without information on the group id/number (old style) is imported,
                // we make up this information by giving a number 0..[numberofgroups-1] per language.
                // the number and order of groups per language should be the same, so we can also import these files
                if ($lastglang != $glang) {
                    //reset counter on language change
                    $iGroupcounter = 0;
                }
                $lastglang = $glang;
                //use group id/number from file. if missing, use an increasing number (s.a.)
                $sGroupseq = (!empty($row['type/scale']) ? $row['type/scale'] : 'G' . $iGroupcounter++);
                $group['group_name'] = $gname;
                $group['grelevance'] = ($row['relevance'] ?? '');
                $group['description'] = ($row['text'] ?? '');
                $group['language'] = $glang;
                $group['randomization_group'] = ($row['random_group'] ?? '');

                // For multi language survey: same gid/sort order across all languages
                if (isset($ginfo[$sGroupseq])) {
                    $gid = $ginfo[$sGroupseq]['gid'];
                    $group['gid'] = $gid;
                    $group['group_order'] = $ginfo[$sGroupseq]['group_order'];
                } else {
                    /* Get the new gid from file if it's number and not already set*/
                    if (!empty($row['id']) && ctype_digit($row['id']) && !in_array($row['id'], $groupIds)) {
                        $gid = $row['id'];
                    } else {
                        $gidNew += 1;
                        $gid = $gidNew;
                    }
                    $group['gid'] = $gid;
                    $groupIds[] = $gid;
                    $group['group_order'] = $gseq;
                }

                if (!isset($ginfo[$sGroupseq])) {
                    $ginfo[$sGroupseq]['gid'] = $gid;
                    $ginfo[$sGroupseq]['group_order'] = $gseq++;
                }
                $qseq = 0; // reset the question_order

                $groups[] = $group;

                break;

            case 'Q':
                $question = array();
                $question['sid'] = $iNewSID;
                $qtype = ($row['type/scale'] ?? 'T');
                $qname = ($row['name'] ?? 'Q' . $qseq);
                $question['gid'] = $gid;
                $question['type'] = $qtype;
                $question['title'] = $qname;
                $question['question'] = ($row['text'] ?? '');
                $question['relevance'] = ($row['relevance'] ?? '');
                $question['preg'] = ($row['validation'] ?? '');
                $question['help'] = ($row['help'] ?? '');
                $question['language'] = ($row['language'] ?? $baselang);
                $question['mandatory'] = ($row['mandatory'] ?? '');
                $question['encrypted'] = ($row['encrypted'] ?? 'N');
                $lastother = $question['other'] = ($row['other'] ?? 'N'); // Keep trace of other settings for sub question
                $question['same_default'] = ($row['same_default'] ?? 0);
                $question['same_script'] = ($row['same_script'] ?? 0);
                $question['parent_qid'] = 0;

                // For multi language survey : same name, add the gid to have same name on different gid. Bad for EM.
                $fullqname = 'G' . $gid . '_' . $qname;
                if (isset($qinfo[$fullqname])) {
                    $qseq = $qinfo[$fullqname]['question_order'];
                    $qid = $qinfo[$fullqname]['qid'];
                    $question['qid'] = $qid;
                    $question['question_order'] = $qseq;
                } else {
                    /* Get the new qid from file if it's number and not already set*/
                    if (!empty($row['id']) && ctype_digit($row['id']) && !in_array($row['id'], $questionsIds)) {
                        $qid = $row['id'];
                    } else {
                        $qidNew += 1;
                        $qid = $qidNew;
                    }
                    $question['question_order'] = $qseq;
                    $question['qid'] = $qid;
                    $questionsIds[] = $qid;
                }

                $questions[] = $question;

                if (!isset($qinfo[$fullqname])) {
                    $qinfo[$fullqname]['qid'] = $qid;
                    $qinfo[$fullqname]['question_order'] = $qseq++;
                }
                $aseq = 0; //reset the answer sortorder
                $sqseq = 0; //reset the sub question sortorder
                // insert question attributes
                foreach ($row as $key => $val) {
                    switch ($key) {
                        case 'class':
                        case 'type/scale':
                        case 'name':
                        case 'text':
                        case 'validation':
                        case 'relevance':
                        case 'help':
                        case 'language':
                        case 'mandatory':
                        case 'other':
                        case 'same_default':
                        case 'same_script':
                        case 'default':
                            break;
                        default:
                            if ($key != '' && $val != '') {
                                $attribute = array();
                                $attribute['qid'] = $qid;
                                // check if attribute is a i18n attribute. If yes, set language, else set language to null in attribute table
                                $aAttributeList[$qtype] = QuestionAttribute::getQuestionAttributesSettings($qtype);
                                if (!empty($aAttributeList[$qtype][$key]['i18n'])) {
                                    $attribute['language'] = ($row['language'] ?? $baselang);
                                } else {
                                    $attribute['language'] = null;
                                }
                                $attribute['attribute'] = $key;
                                $attribute['value'] = $val;

                                $attributes[] = $attribute;
                            }
                            break;
                    }
                }

                // insert default value
                if (isset($row['default']) && $row['default'] !== "") {
                    $defaultvalue = array();
                    $defaultvalue['qid'] = $qid;
                    $defaultvalue['sqid'] = '';
                    $defaultvalue['language'] = ($row['language'] ?? $baselang);
                    $defaultvalue['defaultvalue'] = $row['default'];
                    $defaultvalues[] = $defaultvalue;
                }
                break;

            case 'SQ':
                $sqname = ($row['name'] ?? 'SQ' . $sqseq);
                $sqid = '';
                if ($qtype == Question::QT_O_LIST_WITH_COMMENT || $qtype == Question::QT_VERTICAL_FILE_UPLOAD) {
                    ;   // these are fake rows to show naming of comment and filecount fields
                } elseif ($sqname == 'other' && $lastother == "Y") {
                    // If last question have other to Y : it's not a real SQ row
                    if ($qtype == Question::QT_EXCLAMATION_LIST_DROPDOWN || $qtype == Question::QT_L_LIST) {
                        // only used to set default value for 'other' in these cases
                        if (isset($row['default']) && $row['default'] != "") {
                            $defaultvalue = array();
                            $defaultvalue['qid'] = $qid;
                            $defaultvalue['sqid'] = $sqid;
                            $defaultvalue['specialtype'] = 'other';
                            $defaultvalue['language'] = ($row['language'] ?? $baselang);
                            $defaultvalue['defaultvalue'] = $row['default'];
                            $defaultvalues[] = $defaultvalue;
                        }
                    }
                } else {
                    $scale_id = ($row['type/scale'] ?? 0);
                    $subquestion = array();
                    $subquestion['sid'] = $iNewSID;
                    $subquestion['gid'] = $gid;
                    $subquestion['parent_qid'] = $qid;
                    $subquestion['type'] = $qtype;
                    $subquestion['title'] = $sqname;
                    $subquestion['question'] = ($row['text'] ?? '');
                    $subquestion['relevance'] = ($row['relevance'] ?? '');
                    $subquestion['preg'] = ($row['validation'] ?? '');
                    $subquestion['help'] = ($row['help'] ?? '');
                    $subquestion['language'] = ($row['language'] ?? $baselang);
                    $subquestion['mandatory'] = ($row['mandatory'] ?? '');
                    $subquestion['scale_id'] = $scale_id;
                    // For multi language, qid is needed, why not gid. name is not unique.
                    $fullsqname = 'G' . $gid . 'Q' . $qid . '_' . $scale_id . '_' . $sqname;
                    if (isset($sqinfo[$fullsqname])) {
                        $qseq = $sqinfo[$fullsqname]['question_order'];
                        $sqid = $sqinfo[$fullsqname]['sqid'];
                        $subquestion['question_order'] = $qseq;
                        $subquestion['qid'] = $sqid;
                    } else {
                        $subquestion['question_order'] = $qseq;
                        /* Get the new qid from file if it's number and not already set : subquestion are question*/
                        if (!empty($row['id']) && ctype_digit($row['id']) && !in_array($row['id'], $questionsIds)) {
                            $sqid = $row['id'];
                        } else {
                            $qidNew += 1;
                            $sqid = $qidNew;
                        }
                        $subquestion['qid'] = $sqid;
                        $questionsIds[] = $sqid;
                    }
                    $subquestions[] = $subquestion;

                    if (!isset($sqinfo[$fullsqname])) {
                        $sqinfo[$fullsqname]['question_order'] = $qseq++;
                        $sqinfo[$fullsqname]['sqid'] = $sqid;
                    }

                    // insert default value
                    if (isset($row['default']) && $row['default'] != "") {
                        $defaultvalue = array();
                        $defaultvalue['qid'] = $qid;
                        $defaultvalue['sqid'] = $sqid;
                        $defaultvalue['scale_id'] = $scale_id;
                        $defaultvalue['language'] = ($row['language'] ?? $baselang);
                        $defaultvalue['defaultvalue'] = $row['default'];
                        $defaultvalues[] = $defaultvalue;
                    }
                }
                break;
            case 'A':
                $answer = array();
                $answer['qid'] = $qid;
                $answer['code'] = ($row['name'] ?? 'A' . $aseq);
                $answer['answer'] = ($row['text'] ?? '');
                $answer['scale_id'] = ($row['type/scale'] ?? 0);
                $answer['language'] = ($row['language'] ?? $baselang);
                $answer['assessment_value'] = (int) ($row['assessment_value'] ?? '');
                $answer['sortorder'] = ++$aseq;
                $answers[] = $answer;
                break;
            case 'AS':
                $assessment = array();
                $assessment['sid'] = $iNewSID;
                $assessment['scope'] = $row['type/scale'] ?? '';
                $assessment['gid'] = $gid;
                $assessment['name'] = $row['name'] ?? '';
                $assessment['minimum'] = $row['min_num_value'] ?? '';
                $assessment['maximum'] = $row['max_num_value'] ?? '';
                $assessment['message'] = $row['text'] ?? '';
                $assessment['language'] = $row['language'] ?? '';
                $assessment['id'] = $row['id'] ?? '';
                $assessments[] = $assessment;
                break;
            case 'QTA':
                $quota = array();
                $quota['id'] = $row['id'] ?? '';
                $quota['sid'] = $iNewSID;
                $quota['name'] = $row['name'] ?? '';
                $quota['qlimit'] = $row['mandatory'] ?? '';
                $quota['action'] = $row['other'] ?? '';
                $quota['active'] = $row['default'] ?? '';
                $quota['autoload_url'] = $row['same_default'] ?? '';
                $quotas[] = $quota;
                break;
            case 'QTAM':
                $quota_member = array();
                $quota_member['quota_id'] = $row['related_id'] ?? '';
                $quota_member['sid'] = $iNewSID;
                $quota_member['qid'] = $qid;
                $quota_member['code'] = $row['name'] ?? '';
                $quota_members[] = $quota_member;
                break;
            case 'QTALS':
                $quota_languagesetting = array();
                $quota_languagesetting['quotals_quota_id'] = $row['related_id'] ?? '';
                $quota_languagesetting['quotals_language'] = $row['language'] ?? '';
                //$quota_languagesetting['quotals_name'] = isset($row['name'])?$row['name']:'';
                $quota_languagesetting['quotals_message'] = $row['relevance'] ?? '';
                $quota_languagesetting['quotals_url'] = $row['text'] ?? '';
                $quota_languagesetting['quotals_urldescrip'] = $row['help'] ?? '';
                $quota_languagesettings[] = $quota_languagesetting;
                break;
            case 'C':
                $condition = array();
                $condition['qid'] = $qid;
                $condition['scenario'] = $row['type/scale'];
                $condition['cqid'] = $row['related_id'] ?? '';
                $condition['cfieldname'] = $row['name'];
                $condition['method'] = $row['relevance'];
                $condition['value'] = $row['text'] ?? '';
                $conditions[] = $condition;
                break;
        }
    }
    unset($adata);
    // combine all xml data into $output variable
    if (!empty($surveyinfo)) {
        $output['surveys']['fields']['fieldname'] = array_keys($surveyinfo);
        $output['surveys']['rows']['row'] = $surveyinfo;
    }

    if (!empty($surveyls)) {
        $output['surveys_languagesettings']['fields']['fieldname'] = array_keys($surveyls[$baselang]);
        $output['surveys_languagesettings']['rows']['row'] = $surveyls;
    }

    if (!empty($groups)) {
        $output['groups']['fields']['fieldname'] = array_keys($groups[0]);
        $output['groups']['rows']['row'] = $groups;
    }

    if (!empty($questions)) {
        $output['questions']['fields']['fieldname'] = array_keys($questions[0]);
        $output['questions']['rows']['row'] = $questions;
    }

    if (!empty($attributes)) {
        $output['question_attributes']['fields']['fieldname'] = array_keys($attributes[0]);
        $output['question_attributes']['rows']['row'] = $attributes;
    }

    if (!empty($defaultvalues)) {
        $output['defaultvalues']['fields']['fieldname'] = array_keys($defaultvalues[0]);
        $output['defaultvalues']['rows']['row'] = $defaultvalues;
    }

    if (!empty($subquestions)) {
        $output['subquestions']['fields']['fieldname'] = array_keys($subquestions[0]);
        $output['subquestions']['rows']['row'] = $subquestions;
    }

    if (!empty($answers)) {
        $output['answers']['fields']['fieldname'] = array_keys($answers[0]);
        $output['answers']['rows']['row'] = $answers;
    }

    if (!empty($assessments)) {
        $output['assessments']['fields']['fieldname'] = array_keys($assessments[0]);
        $output['assessments']['rows']['row'] = $assessments;
    }


    if (!empty($quotas)) {
        $output['quota']['fields']['fieldname'] = array_keys($quotas[0]);
        $output['quota']['rows']['row'] = $quotas;
    }

    if (!empty($quota_members)) {
        $output['quota_members']['fields']['fieldname'] = array_keys($quota_members[0]);
        $output['quota_members']['rows']['row'] = $quota_members;
    }

    if (!empty($quota_languagesettings)) {
        $output['quota_languagesettings']['fields']['fieldname'] = array_keys($quota_languagesettings[0]);
        $output['quota_languagesettings']['rows']['row'] = $quota_languagesettings;
    }

    if (!empty($conditions)) {
        $output['conditions']['fields']['fieldname'] = array_keys($conditions[0]);
        $output['conditions']['rows']['row'] = $conditions;
    }

    // generate xml document
    $xml = createXMLfromData($output);
    // send xml document into XMLImportSurvey function and display results
    return XMLImportSurvey('null', $xml);
}

function createXMLfromData($aData = array())
{
    // get survey languages
    $surveylanguage = array_key_exists('language', $aData['surveys']['rows']['row']) ? (array)$aData['surveys']['rows']['row']['language'] : array('en');
    $surveyAdditionalLanguages = array_key_exists('additional_languages', $aData['surveys']['rows']['row']) && !empty($aData['surveys']['rows']['row']['additional_languages']) ? explode(' ', $aData['surveys']['rows']['row']['additional_languages']) : array();
    if (count($surveyAdditionalLanguages) == 0) {
        $surveylanguages = $surveylanguage;
    } else {
        $surveylanguages = array_merge($surveylanguage, $surveyAdditionalLanguages);
    }
    $i = 0;
    if (array_key_exists('surveys_languagesettings', $aData)) {
        foreach ($aData['surveys_languagesettings']['rows']['row'] as $language => $value) {
            if (!array_key_exists('surveyls_title', $value)) {
                $aData['surveys_languagesettings']['rows']['row'][$language]['surveyls_title'] = 'Missing Title';
            }
            if (!array_key_exists('surveyls_language', $value)) {
                $aData['surveys_languagesettings']['rows']['row'][$language]['surveyls_language'] = $language;
            }
            $i += 1;
        }
    }
    $xml = new XMLWriter();
    $xml->openMemory();
    $xml->setIndent(true);

    //header
    $xml->startDocument('1.0', 'UTF-8');
    $xml->startElement('document');
    $xml->writeElement('LimeSurveyDocType', 'Survey');
    $xml->writeElement('DBVersion', App()->getConfig("DBVersion"));

    $xml->startElement('languages');
    foreach ($surveylanguages as $surveylanguage) {
        $xml->writeElement('language', $surveylanguage);
    }
    $xml->endElement();

    $index3 = 0;
    foreach ($aData as $key1 => $value1) {
        $xml->startElement($key1);
        foreach ($value1 as $key2 => $value2) {
            $xml->startElement($key2);
            foreach ($value2 as $key3 => $value3) {
                $index3 = 0;
                if (is_array($value3)) {
                    foreach ($value3 as $key4 => $value4) {
                        if (is_array($value4)) {
                            //$xml->startElement('row');
                            $xml->startElement($key3);
                            foreach ($value4 as $key5 => $value5) {
                                if (!is_array($value5)) {
                                    $xml->startElement($key5);
                                    $xml->writeCdata($value5);
                                    $xml->endElement();
                                }
                            }
                            $xml->endElement();
                        } else {
                            if (is_integer($key4)) {
                                $xml->writeElement($key3, $value4);
                            } else {
                                if ($key3 == 'row') {
                                    if ($index3 === 0) {
                                        $xml->startElement($key3);
                                    }
                                    $xml->startElement($key4);
                                    $xml->writeCdata($value4);
                                    $xml->endElement();
                                    $index3 += 1;
                                    if ($index3 === count($value3)) {
                                        $xml->endElement();
                                    }
                                } else {
                                    $xml->writeElement($key3, $key4);
                                }
                            }
                        }
                    }
                } else {
                }
            }
            $xml->endElement();
        }
        $xml->endElement();
    }

    $xml->endElement();
    $xml->endDocument();
    return $xml->outputMemory(true);
}

/**
 * Import default values inside $xml, record process in $results
 * Also imports defaultvalue_l10ns.
 *
 * @param SimpleXMLElement $xml
 * @param array $aLanguagesSupported
 * @param array &$results
 * @return void
 */
function importDefaultValues(SimpleXMLElement $xml, $aLanguagesSupported, $aQIDReplacements, array &$results)
{
    // Default value id replacements
    $aDvidReplacements = [];

    if (isset($xml->defaultvalues)) {
        $results['defaultvalues'] = 0;
        $aInsertData = array();
        foreach ($xml->defaultvalues->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            if (isset($xml->defaultvalue_l10ns->rows->row) && !empty($insertdata['dvid'])) {
                $iDvidOld = $insertdata['dvid'];
                unset($insertdata['dvid']);
            }
            if (!isset($aQIDReplacements[(int) $insertdata['qid']])) {
                continue;
            }

            $insertdata['qid'] = $aQIDReplacements[(int) $insertdata['qid']]; // remap the qid
            if (isset($aQIDReplacements[(int) $insertdata['sqid']])) {
                // remap the subquestion id
                $insertdata['sqid'] = $aQIDReplacements[(int) $insertdata['sqid']];
            }

            if (!isset($xml->defaultvalue_l10ns->rows->row)) {
                if (!in_array($insertdata['language'], $aLanguagesSupported)) {
                    continue;
                }

                $aInsertData[$insertdata['qid']][$insertdata['scale_id']][$insertdata['sqid']][$insertdata['specialtype']][$insertdata['language']] = [$insertdata['defaultvalue']];
            } else {
                $defaultValue = new DefaultValue();
                $defaultValue->setAttributes($insertdata, false);
                if ($defaultValue->save()) {
                    if ($iDvidOld > 0) {
                        $aDvidReplacements[$iDvidOld] = $defaultValue->dvid;
                    }
                } else {
                    throw new Exception(gT("Error") . ": Failed to insert data[9]<br />");
                }
                $results['defaultvalues']++;
            }
        }

        // insert default values from LS v3 which doesn't have defaultvalue_l10ns
        if (!empty($aInsertData)) {
            foreach ($aInsertData as $qid => $aQid) {
                foreach ($aQid as $scaleId => $aScaleId) {
                    foreach ($aScaleId as $sqid => $aSqid) {
                        foreach ($aSqid as $specialtype => $aSpecialtype) {
                            $oDefaultValue = new DefaultValue();
                            $oDefaultValue->setAttributes(array('qid' => $qid, 'scale_id' => $scaleId, 'sqid' => $sqid, 'specialtype' => $specialtype), false);
                            if ($oDefaultValue->save()) {
                                $results['defaultvalues']++;
                                foreach ($aSpecialtype as $language => $defaultvalue) {
                                    $oDefaultValueL10n = new DefaultValueL10n();
                                    $oDefaultValueL10n->dvid = $oDefaultValue->dvid;
                                    $oDefaultValueL10n->language = $language;
                                    $oDefaultValueL10n->defaultvalue = $defaultvalue[0];
                                    $oDefaultValueL10n->save();
                                    unset($oDefaultValueL10n);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Import defaultvalue_l10ns ------------------------------------------------------
    if (isset($xml->defaultvalue_l10ns)) {
        foreach ($xml->defaultvalue_l10ns->rows->row as $row) {
            $insertdata = array();
            foreach ($row as $key => $value) {
                $insertdata[(string) $key] = (string) $value;
            }
            $insertdata['dvid'] = $aDvidReplacements[$insertdata['dvid']];
            unset($insertdata['id']);

            $oDefaultValueL10n = new DefaultValueL10n();
            $oDefaultValueL10n->setAttributes($insertdata, false);
            if (!$oDefaultValueL10n->save()) {
                throw new Exception(gT("Error") . ": Failed to insert data[19]<br />");
            }
        }
    }
}

/**
 * Read a csv file and return a tmp resources to same file in utf8
 * CSV file is deleted during process
 *
 * @param string $fullfilepath
 * @param string $encoding from
 * @return resource
 */
function fileCsvToUtf8($fullfilepath, $encoding = 'auto')
{
    $handle = fopen($fullfilepath, 'r');
    if ($handle === false) {
        throw new Exception("Can't open file");
    }
    $aEncodings = aEncodingsArray();
    if (!array_key_exists($encoding, $aEncodings)) {
        $encoding = 'auto';
    }
    if ($encoding == 'auto') {
        $bom = fread($handle, 2);
        rewind($handle);
        // Excel tends to save CSV as UTF-16, which PHP does not properly detect
        if ($bom === chr(0xff) . chr(0xfe) || $bom === chr(0xfe) . chr(0xff)) {
            // UTF16 Byte Order Mark present
            $encoding = 'UTF-16';
        } else {
            $file_sample = (string) fread($handle, 10000) . 'e'; //read first 1000 bytes
            // + e is a workaround for mb_string bug
            rewind($handle);
            $encoding = mb_detect_encoding(
                $file_sample,
                'UTF-8, UTF-7, ASCII, EUC-JP,SJIS, eucJP-win, SJIS-win, JIS, ISO-2022-JP'
            );
        }
        if ($encoding === false) {
            $encoding = 'utf8';
        }
    }
    if ($encoding != 'utf8' && $encoding != 'UTF-8') {
        stream_filter_append($handle, 'convert.iconv.' . $encoding . '/UTF-8');
    }

    $file = stream_get_contents($handle);
    fclose($handle);
    // fix Excel non-breaking space
    $file = str_replace("0xC20xA0", ' ', $file);
    // Replace all different newlines styles with \n
    $file = preg_replace('~\R~u', "\n", $file);
    $tmp = fopen('php://temp', 'r+');
    fwrite($tmp, $file);
    // Release the file, otherwise it will stay in memory
    unset($file);
    // Delete not needed file
    unlink($fullfilepath);
    /* Return the tempory ressource */
    rewind($tmp);
    return $tmp;
}
