From 88e90cde610a0a05e013f1bf21b239cd35652fba Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 6 Jul 2015 13:42:48 +0200 Subject: [PATCH] Dev Improved new survey import. --- application/components/MigrationManager.php | 2 +- application/core/LSYii_Validators.php | 10 ++- .../helpers/import/importers/Import178.php | 72 +++++++++++++------ ...092139_unique_question_codes_per_scale.php | 29 ++++++++ application/models/Answer.php | 25 +------ application/models/LSActiveRecord.php | 4 +- application/models/Question.php | 23 ++++-- application/models/SurveyLanguageSetting.php | 4 +- .../models/questions/EquationQuestion.php | 13 ++++ application/views/questions/update.php | 4 +- 10 files changed, 123 insertions(+), 63 deletions(-) create mode 100644 application/migrations/m150706_092139_unique_question_codes_per_scale.php create mode 100644 application/models/questions/EquationQuestion.php diff --git a/application/components/MigrationManager.php b/application/components/MigrationManager.php index 30a5c0ab8e3..5068922d930 100644 --- a/application/components/MigrationManager.php +++ b/application/components/MigrationManager.php @@ -103,7 +103,7 @@ public function migrateUp($class, $captureOutput = false) } // If capturing output and migration succeeded, return the captured output. - return $captureOutput && $result ? ob_get_clean() : ob_end_clean() && $result; + return $captureOutput && $result ? ob_get_clean() : $result; } protected function instantiateMigration($class) diff --git a/application/core/LSYii_Validators.php b/application/core/LSYii_Validators.php index 60a28fead5b..97bd5f5ce07 100644 --- a/application/core/LSYii_Validators.php +++ b/application/core/LSYii_Validators.php @@ -23,7 +23,7 @@ class LSYii_Validators extends CValidator { * Filter attribute for XSS * @var boolean */ - public $xssfilter=true; + public $xssfilter; /** * Filter attribute for url * @var boolean @@ -42,9 +42,7 @@ class LSYii_Validators extends CValidator { public function __construct() { - if(Yii::app()->getConfig('DBVersion')< 172) // Permission::model exist only after 172 DB version - return $this->xssfilter=($this->xssfilter && Yii::app()->getConfig('filterxsshtml')); - $this->xssfilter=($this->xssfilter && Yii::app()->getConfig('filterxsshtml') && !App()->user->checkAccess('superadmin')); + $this->xssfilter = !App()->user->checkAccess('superadmin') && \SettingGlobal::get('filterxsshtml', true); } protected function validateAttribute($object,$attribute) @@ -125,16 +123,17 @@ public function xssFilter($value) 'news' => true, ) ); + // To allow script BUT purify : HTML.Trusted=true (plugin idea for admin or without XSS filtering ?) /** Start to get complete filtered value with url decode {QCODE} (bug #09300). This allow only question number in url, seems OK with XSS protection **/ $sFiltered=preg_replace('#%7B([a-zA-Z0-9\.]*)%7D#','{$1}',$filter->purify($value)); - Yii::import('application.helpers.expressions.em_core_helper');// Already imported in em_manager_helper.php ? $oExpressionManager= new ExpressionManager; /** We get 2 array : one filtered, other unfiltered **/ $aValues=$oExpressionManager->asSplitStringOnExpressions($value);// Return array of array : 0=>the string,1=>string length,2=>string type (STRING or EXPRESSION) $aFilteredValues=$oExpressionManager->asSplitStringOnExpressions($sFiltered);// Same but for the filtered string $bCountIsOk=count($aValues)==count($aFilteredValues); + /** Construction of new string with unfiltered EM and filtered HTML **/ $sNewValue=""; foreach($aValues as $key=>$aValue){ @@ -157,7 +156,6 @@ public function xssFilter($value) $sNewValue.="}"; } } - gc_collect_cycles(); // To counter a high memory usage of HTML-Purifier return $sNewValue; } /** diff --git a/application/helpers/import/importers/Import178.php b/application/helpers/import/importers/Import178.php index 101a02ecfaf..d9d84b6b99c 100644 --- a/application/helpers/import/importers/Import178.php +++ b/application/helpers/import/importers/Import178.php @@ -23,7 +23,6 @@ class Import178 extends BaseElementXmlImport{ public function run() { $transaction = App()->db->beginTransaction(); - try { /** @var \Survey $survey */ $result = $this->importSurvey($this->parsedDocument); @@ -43,6 +42,7 @@ public function run() } protected function importTranslation(TranslatableBehavior $translatable, array $data) { + \Yii::beginProfile('importTranslation'); $translatedFields = []; foreach ($translatable->attributes as $attribute) { if (isset($data[$attribute])) { @@ -60,6 +60,7 @@ protected function importTranslation(TranslatableBehavior $translatable, array $ } } + \Yii::endProfile('importTranslation'); return true; } protected function prepareGroup(array $data, \Survey $survey) { @@ -72,6 +73,8 @@ protected function prepareGroup(array $data, \Survey $survey) { } protected function importGroup(array $data, \Survey $survey, array &$questionMap) { $group = new \QuestionGroup(); + // Set related model. + $group->survey = $survey; $data = $this->prepareGroup($data, $survey); $translations = \TbArray::popValue('translations', $data, []); $questions = \TbArray::popValue('questions', $data, []); @@ -135,6 +138,7 @@ protected function importSurvey($data) { protected function importSurveyTranslation($data, \Survey $survey) { $languageSetting = new \SurveyLanguageSetting(); + $languageSetting->survey = $survey; $languageSetting->setAttributes($data, false); $languageSetting->surveyls_survey_id = $survey->primaryKey; if (false === $result = $languageSetting->save()) { @@ -144,9 +148,7 @@ protected function importSurveyTranslation($data, \Survey $survey) { } protected function prepareQuestion($data, \QuestionGroup $group, \Question $parent = null) { - // Translate gid. -// $data['id'] = $data['gid']; -// unset($data['gid']); + \Yii::beginProfile('prepareQuestion'); unset($data['language']); $data['gid'] = $group->primaryKey; $data['sid'] = $group->sid; @@ -158,21 +160,39 @@ protected function prepareQuestion($data, \QuestionGroup $group, \Question $pare if (isset($parent)) { $data['parent_qid'] = $parent->primaryKey; } + \Yii::endProfile('prepareQuestion'); return $data; } - protected function importCondition($data, \Question $question, array &$questionMap) + protected function importCondition($data, \Question $question, array $questionMap) { - throw new \Exception('Condition import not finished'); - return true; - $data['qid'] = $question->primaryKey; - $data['cqid'] = $questionMap[$data['cqid']]; - return $data; + \Yii::beginProfile('importCondition'); + $oldTargetQid = intval($data['cqid']); + if (!isset($questionMap[$oldTargetQid])) { + throw new \Exception("Could not convert condition."); + } + $newTargetQid = $questionMap[$oldTargetQid]; + $pattern = '/\d+X\d+X' . $oldTargetQid . '(?.*)/'; + if (preg_match($pattern, $data['cfieldname'], $matches)) { + $condition = new \Condition(); + $condition->setAttributes($data, false); + $condition->qid = $question->primaryKey; + $condition->cqid = $newTargetQid; + $condition->primaryKey = null; + $condition->cfieldname = "{$question->sid}X{$question->gid}X{$newTargetQid}{$matches['field']}"; + + $result = $condition->save(); + } else { + throw new \Exception("Pattern '$pattern' does not match {$data['cfieldname']}"); + } + \Yii::endProfile('importCondition'); + return $result; } protected function importQuestion(array $data, \QuestionGroup $group, array &$questionMap, \Question $parent = null) { /** * If we only have 1 language, use it even if it is not the "base" language. */ + \Yii::beginProfile('importQuestion'); $translations = \TbArray::popValue('translations', $data, []); $subQuestions = \TbArray::popValue('subquestions', $data, []); $conditions = \TbArray::popValue('conditions', $data, []); @@ -181,8 +201,11 @@ protected function importQuestion(array $data, \QuestionGroup $group, array &$qu // We want the "correct class". $class = \Question::resolveClass($data['type']); /** @var \Question $question */ - $question = new $class(); + $question = new $class('import'); $question->type = $data['type']; + // Set related models. + $question->group = $group; + $question->survey = $group->survey; foreach($data as $key => $value) { if (!($question->canSetProperty($key) || $question->hasAttribute($key))) { throw new \Exception("Could not set property $key for " . get_class($question)); @@ -193,8 +216,10 @@ protected function importQuestion(array $data, \QuestionGroup $group, array &$qu $oldKey = $question->primaryKey; $question->primaryKey = null; $question->parent_qid = !isset($parent) ? 0 : $parent->primaryKey; - - if ($result = $question->save()) { + \Yii::beginProfile('saveQuestion'); + $result = $question->save(); + \Yii::endProfile('saveQuestion'); + if ($result) { $question->group = $group; $questionMap[$oldKey] = $question->primaryKey; foreach($translations as $translation) { @@ -217,6 +242,7 @@ protected function importQuestion(array $data, \QuestionGroup $group, array &$qu var_dump($question->errors); die('failed importing question'); } + \Yii::endProfile('importQuestion'); return $result; } @@ -228,7 +254,10 @@ protected function prepareAnswer($data, \Question $question) { } protected function importAnswer($data, \Question $question) { - $answer = new \Answer(); + \Yii::beginProfile('importAnswer'); + $answer = new \Answer('import'); + // Set related model. + $answer->question = $question; $translations = \TbArray::popValue('translations', $data, []); $data = $this->prepareAnswer($data, $question); @@ -237,22 +266,19 @@ protected function importAnswer($data, \Question $question) if (!($answer->canSetProperty($key) || $answer->hasAttribute($key))) { throw new \Exception("Could not set property $key"); } + $answer->$key = $value; } - $answer->setAttributes($data, false); +// $answer->setAttributes($data, false); $answer->primaryKey = null; + \Yii::beginProfile('answerQuery'); if ($result = $answer->save()) { - $answer->question = $question; - foreach ($translations as $translation) { $result = $result && $this->importTranslation($answer->translatable, $translation, $answer->primaryKey); } - } else { - var_dump($answer->errors); - die(); - } - if (!$result) { - echo "Failed to import answer"; } + + \Yii::endProfile('answerQuery'); + \Yii::endProfile('importAnswer'); return $result; } } \ No newline at end of file diff --git a/application/migrations/m150706_092139_unique_question_codes_per_scale.php b/application/migrations/m150706_092139_unique_question_codes_per_scale.php new file mode 100644 index 00000000000..f0b09ae69fe --- /dev/null +++ b/application/migrations/m150706_092139_unique_question_codes_per_scale.php @@ -0,0 +1,29 @@ +dropIndex('unique_question_codes', '{{questions}}'); + $this->createIndex('unique_question_codes', '{{questions}}', ['sid', 'title', 'parent_qid', 'scale_id'], true); + return true; + } + + /** + * @return boolean True if migration was a success. + */ + + public function safeDown() + { + echo "m150706_092139_unique_question_codes_per_scale does not support migration down.\\n"; + return false; + + } + +} \ No newline at end of file diff --git a/application/models/Answer.php b/application/models/Answer.php index f8bd0a38f21..ae0a1f9397c 100644 --- a/application/models/Answer.php +++ b/application/models/Answer.php @@ -15,6 +15,7 @@ /** * Class Answer * @property string $answer + * @property Question $question * @property string $code */ class Answer extends LSActiveRecord @@ -64,12 +65,12 @@ public function relations() public function rules() { return array( - ['question_id','exist', 'className' => Question::class, 'attributeName' => 'qid'], + ['question_id', CExistValidator::class, 'className' => Question::class, 'attributeName' => 'qid', 'on' => ['update', 'insert']], ['code','length', 'min' => 1, 'max'=>5, 'allowEmpty' => false], ['code', 'required'], ['code', 'match', 'pattern' => '/^[a-z0-9]*$/i', 'message' => gT('Answer codes may only contain alphanumeric characters.')], ['sortorder','numerical', 'integerOnly'=>true,'allowEmpty'=>true], - array('answer','LSYii_Validators'), + ['answer', LSYii_Validators::class], array('assessment_value','numerical', 'integerOnly'=>true,'allowEmpty'=>true), array('scale_id','numerical', 'integerOnly'=>true,'allowEmpty'=>true), @@ -122,26 +123,6 @@ public function oldNewInsertansTags($newsid,$oldsid) return $this->with('questions')->findAll($criteria); } - public function updateRecord($data, $condition=FALSE) - { - return Yii::app()->db->createCommand()->update(self::tableName(), $data, $condition ? $condition : ''); - } - - function insertRecords($data) - { - $ans = new self; - foreach ($data as $k => $v) - $ans->$k = $v; - try - { - return $ans->save(); - } - catch(Exception $e) - { - return false; - } - } - /** * Updates sort order of answers inside a question * diff --git a/application/models/LSActiveRecord.php b/application/models/LSActiveRecord.php index f404395373d..52bc198b643 100644 --- a/application/models/LSActiveRecord.php +++ b/application/models/LSActiveRecord.php @@ -154,9 +154,7 @@ public function deleteAllByAttributes($attributes,$condition='',$params=array()) $builder=$this->getCommandBuilder(); $table=$this->getTableSchema(); $criteria=$builder->createColumnCriteria($table,$attributes,$condition,$params); - var_dump($this->behaviors()); - die(); - $this->dispatchPluginModelEvent('before'.get_class($this).'DeleteMany', $criteria); + $this->dispatchPluginModelEvent('before'.get_class($this).'DeleteMany', $criteria); $this->dispatchPluginModelEvent('beforeModelDeleteMany', $criteria); return parent::deleteAllByAttributes(array(), $criteria, array()); } diff --git a/application/models/Question.php b/application/models/Question.php index af62d4eaf6d..02e998f27c8 100644 --- a/application/models/Question.php +++ b/application/models/Question.php @@ -36,12 +36,20 @@ class Question extends LSActiveRecord protected function afterFind() { parent::afterFind(); + + } + + protected function getCustomAttribute($name) { + if (array_key_exists($name, $this->customAttributes)) { + return $this->customAttributes[$name]; + } + // Fill the question attributes. foreach($this->questionAttributes as $questionAttribute) { - if (!isset($questionAttribute->language)) { - $this->customAttributes[$questionAttribute->attribute] = $questionAttribute; + if (!isset($questionAttribute->language) && $questionAttribute->attribute === $name) { + $this->customAttributes[$name] = $questionAttribute->value; + return $questionAttribute->value; } - } } @@ -67,6 +75,7 @@ protected function updateAttributes() { $rows = []; foreach ($this->customAttributes as $key => $value) { $rows[] = [ + 'qid' => $this->primaryKey, 'attribute' => $key, 'value' => $value, 'language' => null @@ -154,7 +163,7 @@ public function __get($name) if (substr($name, 0, 5) == 'bool_') { $result = parent::__get(substr($name, 5)) === 'Y'; } elseif ($name != 'type' && in_array($name, $this->customAttributeNames())) { - $result = isset($this->customAttributes[$name]) ? $this->customAttributes[$name] : null; + $result = $this->getCustomAttribute($name); } else { $result = parent::__get($name); } @@ -220,7 +229,7 @@ public function rules() ['preg', 'safe'], ['before', 'numerical', 'on' => 'insert', 'integerOnly' => true], ['type', 'in', 'range' => array_keys($this->typeList())], - ['gid', 'exist', 'className' => QuestionGroup::class, 'attributeName' => 'id', 'allowEmpty' => false], + ['gid', 'exist', 'className' => QuestionGroup::class, 'attributeName' => 'id', 'allowEmpty' => false, 'on' => ['insert', 'update']], ['title', 'required', 'on' => ['update', 'insert']], ['title','length', 'min' => 1, 'max'=>20,'on' => ['update', 'insert']], ['title,question,help', 'LSYii_Validators'], @@ -886,6 +895,7 @@ public static function resolveClass($type) { $class = \ls\models\questions\RankingQuestion::class; break; case 'F': // Array + case ':': // Array numbers $class = \ls\models\questions\ArrayQuestion::class; break; case '5': // 5 point choice @@ -897,6 +907,9 @@ public static function resolveClass($type) { case 'M': // Multiple choice $class = \ls\models\questions\MultipleChoiceQuestion::class; break; + case '*': + $class = \ls\models\questions\EquationQuestion::class; + break; default: die("noo class for type {$type}"); diff --git a/application/models/SurveyLanguageSetting.php b/application/models/SurveyLanguageSetting.php index fb21b874cc2..ef850bf0f8c 100644 --- a/application/models/SurveyLanguageSetting.php +++ b/application/models/SurveyLanguageSetting.php @@ -86,7 +86,7 @@ public function relations() { $alias = $this->getTableAlias(); return array( - 'survey' => array(self::BELONGS_TO, 'Survey', '', 'on' => "$alias.surveyls_survey_id = survey.sid"), + 'survey' => array(self::BELONGS_TO, Survey::class, 'surveyls_survey_id'), 'owner' => array(self::BELONGS_TO, 'User', '', 'on' => 'survey.owner_id = owner.uid'), ); } @@ -147,7 +147,7 @@ public function rules() public function lsdefault($attribute,$params) { if (isset($this->surveyls_survey_id)) { - $oSurvey = Survey::model()->findByPk($this->surveyls_survey_id); + $oSurvey = $this->survey; $sEmailFormat = $oSurvey->htmlemail == 'Y' ? 'html' : ''; $aDefaultTexts = templateDefaultTexts($this->surveyls_language, 'unescaped', $sEmailFormat); diff --git a/application/models/questions/EquationQuestion.php b/application/models/questions/EquationQuestion.php new file mode 100644 index 00000000000..372f00d2a6a --- /dev/null +++ b/application/models/questions/EquationQuestion.php @@ -0,0 +1,13 @@ +
title} ({$question->typeName}) -- class: " . get_class($question)); // This is an update view so we use PUT. /** @var TbActiveForm $form */ @@ -35,7 +36,8 @@ 'content' => "@todo", ], [ 'label' => gT('Statistics'), - 'content' => $this->renderPartial('update/statistics', ['question' => $question, 'form' => $form], true), + 'content' => $question->hasProperty('statistics_graphtype') ? $this->renderPartial('update/statistics', ['question' => $question, 'form' => $form], true) : '', + 'visible' => $question->hasProperty('statistics_graphtype') ], [ 'label' => gT('Subquestions'), 'visible' => $question->hasSubQuestions,