Skip to content

Commit

Permalink
Fixed issue #18071: Importing Question through Remote Control may fa…
Browse files Browse the repository at this point in the history
…il if the imported question already exists (#3016)
  • Loading branch information
Shnoulle committed Apr 3, 2023
1 parent d47fe8a commit 9b2e442
Show file tree
Hide file tree
Showing 5 changed files with 765 additions and 85 deletions.
204 changes: 119 additions & 85 deletions application/helpers/remotecontrol/remotecontrol_handle.php
Expand Up @@ -1471,102 +1471,136 @@ public function delete_question($sSessionKey, $iQuestionID)
public function import_question($sSessionKey, $iSurveyID, $iGroupID, $sImportData, $sImportDataType, $sMandatory = 'N', $sNewQuestionTitle = null, $sNewqQuestion = null, $sNewQuestionHelp = null)
{
$bOldEntityLoaderState = null;
if ($this->_checkSessionKey($sSessionKey)) {
$iSurveyID = (int) $iSurveyID;
$iGroupID = (int) $iGroupID;
$oSurvey = Survey::model()->findByPk($iSurveyID);
if (!isset($oSurvey)) {
return array('status' => 'Error: Invalid survey ID');
}

if (Permission::model()->hasSurveyPermission($iSurveyID, 'survey', 'update')) {
if ($oSurvey->isActive) {
return array('status' => 'Error:Survey is Active and not editable');
}
if (!$this->_checkSessionKey($sSessionKey)) {
return array('status' => self::INVALID_SESSION_KEY);
}
$iSurveyID = (int) $iSurveyID;
$iGroupID = (int) $iGroupID;
$oSurvey = Survey::model()->findByPk($iSurveyID);
if (!isset($oSurvey)) {
return array('status' => 'Error: Invalid survey ID');
}
if (!Permission::model()->hasSurveyPermission($iSurveyID, 'surveycontent', 'update') && !Permission::model()->hasSurveyPermission($iSurveyID, 'surveycontent', 'import')) {
return array('status' => 'No permission');
}
if ($oSurvey->isActive) {
return array('status' => 'Error:Survey is Active and not editable');
}

$oGroup = QuestionGroup::model()->findByAttributes(array('gid' => $iGroupID));
if (!isset($oGroup)) {
return array('status' => 'Error: Invalid group ID');
}
$oGroup = QuestionGroup::model()->findByAttributes(array('gid' => $iGroupID));
if (!isset($oGroup)) {
return array('status' => 'Error: Invalid group ID');
}

$sGroupSurveyID = $oGroup['sid'];
if ($sGroupSurveyID != $iSurveyID) {
return array('status' => 'Error: Missmatch in surveyid and groupid');
}
$sGroupSurveyID = $oGroup['sid'];
if ($sGroupSurveyID != $iSurveyID) {
return array('status' => 'Error: Missmatch in surveyid and groupid');
}
/* Check unicity of title, and set autorename to true if it's set */
$importOptions = ['autorename' => false];
if (!empty($sNewQuestionTitle)) {
$countQuestionTitle = intval(Question::model()->count(
"sid = :sid and parent_qid = 0 and title = :title",
array(
":sid" => $iSurveyID,
":title" => $sNewQuestionTitle
)
));
if ($countQuestionTitle > 0) {
return array('status' => 'Error: Question title already exist in this survey.');
}
/* This allow import with existing title */
$importOptions = ['autorename' => true];
}
if (!strtolower($sImportDataType) == 'lsq') {
return array('status' => 'Invalid extension');
}

if (!strtolower($sImportDataType) == 'lsq') {
return array('status' => 'Invalid extension');
}
libxml_use_internal_errors(true);
Yii::app()->loadHelper('admin/import');
// First save the data to a temporary file
$sFullFilePath = Yii::app()->getConfig('tempdir') . DIRECTORY_SEPARATOR . randomChars(40) . '.' . $sImportDataType;
file_put_contents($sFullFilePath, base64_decode(chunk_split($sImportData)));
libxml_use_internal_errors(true);
Yii::app()->loadHelper('admin.import');

if (strtolower($sImportDataType) == 'lsq') {
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) {
unlink($sFullFilePath);
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 array('status' => 'Error: Invalid LimeSurvey question structure XML ');
}
$aImportResults = XMLImportQuestion($sFullFilePath, $iSurveyID, $iGroupID);
} else {
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 array('status' => 'Really Invalid extension'); //just for symmetry!
}
// First save the data to a temporary file
$sFullFilePath = App()->getConfig('tempdir') . DIRECTORY_SEPARATOR . randomChars(40) . '.' . $sImportDataType;
file_put_contents($sFullFilePath, base64_decode(chunk_split($sImportData)));

if (strtolower($sImportDataType) == 'lsq') {
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) {
unlink($sFullFilePath);
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 array('status' => 'Error: Invalid LimeSurvey question structure XML ');
}
$aImportResults = XMLImportQuestion($sFullFilePath, $iSurveyID, $iGroupID, $importOptions);
} else {
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 array('status' => 'Really Invalid extension'); //just for symmetry!
}
unlink($sFullFilePath);
$iNewqid = 0;
if (isset($aImportResults['fatalerror'])) {
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 array('status' => 'Error: ' . $aImportResults['fatalerror']);
} else {
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
}
fixLanguageConsistency($iSurveyID);

if (isset($aImportResults['fatalerror'])) {
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 array('status' => 'Error: ' . $aImportResults['fatalerror']);
} else {
fixLanguageConsistency($iSurveyID);
$iNewqid = $aImportResults['newqid'];
$iNewqid = $aImportResults['newqid'];
/* @var array[] validation errors */
$errors = [];
$oQuestion = Question::model()->findByAttributes(array('sid' => $iSurveyID, 'gid' => $iGroupID, 'qid' => $iNewqid));
if (in_array($sMandatory, array('Y', 'S', 'N'))) {
$oQuestion->setAttribute('mandatory', $sMandatory);
} else {
$oQuestion->setAttribute('mandatory', 'N');
}
if (!empty($sNewQuestionTitle)) {
$oQuestion->setAttribute('title', $sNewQuestionTitle);
}

$oQuestion = Question::model()->findByAttributes(array('sid' => $iSurveyID, 'gid' => $iGroupID, 'qid' => $iNewqid));
if ($sNewQuestionTitle != null) {
$oQuestion->setAttribute('title', $sNewQuestionTitle);
}
if ($sNewqQuestion != '') {
$oQuestion->setAttribute('question', $sNewqQuestion);
}
if ($sNewQuestionHelp != '') {
$oQuestion->setAttribute('help', $sNewQuestionHelp);
}
if (in_array($sMandatory, array('Y', 'S', 'N'))) {
$oQuestion->setAttribute('mandatory', $sMandatory);
} else {
$oQuestion->setAttribute('mandatory', 'N');
}
if (!$oQuestion->save()) {
return array(
'status' => 'Error when update question',
'errors' => $oQuestion->getErrors()
);
}

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
}
$oQuestionL10ns = QuestionL10n::model()->findAll(
"qid = :qid",
array(':qid' => $iNewqid)
);

try {
$oQuestion->save();
} catch (Exception $e) {
// no need to throw exception
}
return (int) $aImportResults['newqid'];
foreach ($oQuestionL10ns as $oQuestionL10n) {
if (!empty($sNewqQuestion)) {
$oQuestionL10n->setAttribute('question', $sNewqQuestion);
}
if (!empty($sNewQuestionHelp)) {
$oQuestionL10n->setAttribute('help', $sNewQuestionHelp);
}
if (!$oQuestionL10n->save()) {
$errors[] = $oQuestionL10n->getErrors();
}
} else {
return array('status' => 'No permission');
}
} else {
return array('status' => self::INVALID_SESSION_KEY);

if (!empty($errors)) {
return array(
'status' => 'Error when update question',
'errors' => $errors
);
}

return intval($iNewqid);
}
}

Expand Down Expand Up @@ -2182,7 +2216,7 @@ public function list_participants($sSessionKey, $iSurveyID, $iStart = 0, $iLimit
$aAttributeValues = array();
if (count($aConditions) > 0) {
$aConditionFields = array_flip(Token::model($iSurveyID)->getMetaData()->tableSchema->columnNames);
// NB: $valueOrTuple is either a value or tuple like [$operator, $value].
// NB: $valueOrTuple is either a value or tuple like [$operator, $value].
foreach ($aConditions as $columnName => $valueOrTuple) {
if (is_array($valueOrTuple)) {
/** @var string[] List of operators allowed in query. */
Expand Down
125 changes: 125 additions & 0 deletions tests/data/surveys/limesurvey_question_import_question_test.lsq
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<document>
<LimeSurveyDocType>Question</LimeSurveyDocType>
<DBVersion>366</DBVersion>
<languages>
<language>en</language>
</languages>
<questions>
<fields>
<fieldname>qid</fieldname>
<fieldname>parent_qid</fieldname>
<fieldname>sid</fieldname>
<fieldname>gid</fieldname>
<fieldname>type</fieldname>
<fieldname>title</fieldname>
<fieldname>question</fieldname>
<fieldname>preg</fieldname>
<fieldname>help</fieldname>
<fieldname>other</fieldname>
<fieldname>mandatory</fieldname>
<fieldname>question_order</fieldname>
<fieldname>language</fieldname>
<fieldname>scale_id</fieldname>
<fieldname>same_default</fieldname>
<fieldname>relevance</fieldname>
<fieldname>modulename</fieldname>
</fields>
<rows>
<row>
<qid><![CDATA[26957]]></qid>
<parent_qid><![CDATA[0]]></parent_qid>
<sid><![CDATA[124268]]></sid>
<gid><![CDATA[1388]]></gid>
<type><![CDATA[T]]></type>
<title><![CDATA[Q00]]></title>
<question><![CDATA[Test question. Please answer this question:]]></question>
<preg/>
<help><![CDATA[This is a question help text.]]></help>
<other><![CDATA[N]]></other>
<mandatory><![CDATA[N]]></mandatory>
<question_order><![CDATA[0]]></question_order>
<language><![CDATA[en]]></language>
<scale_id><![CDATA[0]]></scale_id>
<same_default><![CDATA[0]]></same_default>
<relevance><![CDATA[1]]></relevance>
</row>
</rows>
</questions>
<question_attributes>
<fields>
<fieldname>qid</fieldname>
<fieldname>attribute</fieldname>
<fieldname>value</fieldname>
<fieldname>language</fieldname>
</fields>
<rows>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[encrypted]]></attribute>
<value><![CDATA[N]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[hidden]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[hide_tip]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[id]]></attribute>
<value><![CDATA[17435]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[page_break]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[same_script]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[statistics_graphtype]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[statistics_showgraph]]></attribute>
<value><![CDATA[1]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[time_limit_action]]></attribute>
<value><![CDATA[1]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[time_limit_disable_next]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
<row>
<qid><![CDATA[26957]]></qid>
<attribute><![CDATA[time_limit_disable_prev]]></attribute>
<value><![CDATA[0]]></value>
<language/>
</row>
</rows>
</question_attributes>
</document>

0 comments on commit 9b2e442

Please sign in to comment.