diff --git a/.gitignore b/.gitignore index 1c051f5909a..21416e99002 100644 --- a/.gitignore +++ b/.gitignore @@ -64,12 +64,6 @@ !/tmp/runtime/index.html !/tmp/upload/index.html -# ignore plugins directory by default except LS plugin -# if you want to add a new distributed plugin : add the directory here -/plugins/* -!/plugins/index.html -!/plugins/Demo/ - # ignore editor contents - reserved for future use /editor/* diff --git a/application/commands/TwigCommand.php b/application/commands/TwigCommand.php index bb038c618ce..be50f7fb15e 100644 --- a/application/commands/TwigCommand.php +++ b/application/commands/TwigCommand.php @@ -154,22 +154,27 @@ public function actionGenerateQuestionsCache($sQuestionDir = null) */ public function actionGenerateAdminCache($sAdminDir = null) { - $this->aLogs["action"] = "actionGenerateAdminCache $sAdminDir"; - // Generate cache for admin area - $sAdminDir = $sAdminDir ?? dirname(__FILE__) . '/../views/admin'; - $oAdminDirectory = new RecursiveDirectoryIterator($sAdminDir); - $oAdminIterator = new RecursiveIteratorIterator($oAdminDirectory); - $oAdminRegex = new RegexIterator($oAdminIterator, '/^.+\.twig$/i', RecursiveRegexIterator::GET_MATCH); - - $aAdminData = array(); - foreach ($oAdminRegex as $oTwigFile) { - $sTwigFile = $oTwigFile[0]; - if (file_exists($sTwigFile)) { - $this->aLogs["twig"] = "$sTwigFile"; - $line = file_get_contents($sTwigFile); - $sHtml = Yii::app()->twigRenderer->convertTwigToHtml($line); + // Generate cache for admin area + // Set directories to search for twig files + $directories = array( + dirname(__FILE__) . '/../views/admin', + dirname(__FILE__) . '/../views/questionAdministration', + ); + foreach ($directories as $sAdminDir) { + $sAdminDir = $sAdminDir ?? dirname(__FILE__) . '/../views/admin'; + $oAdminDirectory = new RecursiveDirectoryIterator($sAdminDir); + $oAdminIterator = new RecursiveIteratorIterator($oAdminDirectory); + $oAdminRegex = new RegexIterator($oAdminIterator, '/^.+\.twig$/i', RecursiveRegexIterator::GET_MATCH); + $aAdminData = array(); + foreach ($oAdminRegex as $oTwigFile) { + $sTwigFile = $oTwigFile[0]; + if (file_exists($sTwigFile)) { + $this->aLogs["twig"] = "$sTwigFile"; + $line = file_get_contents($sTwigFile); + $sHtml = Yii::app()->twigRenderer->convertTwigToHtml($line); + } } } } diff --git a/application/config/config-defaults.php b/application/config/config-defaults.php index 81f55f6c14b..d91fdaa8edd 100644 --- a/application/config/config-defaults.php +++ b/application/config/config-defaults.php @@ -82,12 +82,12 @@ $config['deletenonvalues'] = 1; // By default, LimeSurvey does not save responses to conditional questions that haven't been answered/shown. To have LimeSurvey save these responses change this value to 0. $config['stringcomparizonoperators'] = 0; // By default, LimeSurvey assumes the numrical order for comparizon operators in conditions. If you need string comparizon operators, set this parameter to 1 $config['shownoanswer'] = 2; // Show 'no answer' for non mandatory questions ( 0 = no , 1 = yes , 2 = overridden by survey settings ) -$config['blacklistallsurveys'] = 'N'; // Blacklist all current surveys for participant once the global field is set -$config['blacklistnewsurveys'] = 'N'; // Blacklist participant for any new added survey once the global field is set -$config['blockaddingtosurveys'] = 'Y'; // Don't allow blacklisted participants to be added to new survey -$config['hideblacklisted'] = 'N'; // Don't show blacklisted participants -$config['deleteblacklisted'] = 'N'; // Delete globally blacklisted participant from the database -$config['allowunblacklist'] = 'N'; // Allow participant to unblacklist himself/herself +$config['blacklistallsurveys'] = 'N'; // Blocklist all current surveys for participant once the global field is set +$config['blacklistnewsurveys'] = 'N'; // Blocklist participant for any new added survey once the global field is set +$config['blockaddingtosurveys'] = 'Y'; // Don't allow blocklisted participants to be added to new survey +$config['hideblacklisted'] = 'N'; // Don't show blocklisted participants +$config['deleteblacklisted'] = 'N'; // Delete globally blocklisted participant from the database +$config['allowunblacklist'] = 'N'; // Allow participant to unblocklist himself/herself $config['userideditable'] = 'N'; // Allow editing of user IDs $config['defaulttheme'] = 'fruity_twentythree'; // This setting specifys the default theme used for the 'public list' of surveys @@ -797,13 +797,13 @@ $config['force_xmlsettings_for_survey_rendering'] = false; /** - * When this setting is true, plugins that are not in the white list (see 'pluginWhitelist') cannot be installed nor loaded. This may disable + * When this setting is true, plugins that are not in the allowlist (see 'pluginWhitelist') cannot be installed nor loaded. This may disable * already installed plugins. - * Core plugins are implicitly whitelisted, but can be excluded using the black list. + * Core plugins are implicitly allowlisted, but can be excluded using the blocklist. */ $config['usePluginWhitelist'] = false; -// List of plugin names allowed to be installed and loaded when 'usePluginWhitelist' is true. Core plugins are implicitly whitelisted. +// List of plugin names allowed to be installed and loaded when 'usePluginWhitelist' is true. Core plugins are implicitly allowlisted. $config['pluginWhitelist'] = []; // List of core plugin names forbidden when 'usePluginWhitelist' is true. diff --git a/application/config/version.php b/application/config/version.php index d6646bd1fe8..d0de585254c 100644 --- a/application/config/version.php +++ b/application/config/version.php @@ -12,9 +12,9 @@ */ $config['versionnumber'] = '6.5.0-dev'; -$config['dbversionnumber'] = 621; +$config['dbversionnumber'] = 623; $config['buildnumber'] = ''; $config['updatable'] = true; $config['templateapiversion'] = 3; -$config['assetsversionnumber'] = '30370'; +$config['assetsversionnumber'] = '30379'; return $config; diff --git a/application/controllers/AssessmentController.php b/application/controllers/AssessmentController.php index c3abcd7057b..8299eca6e8c 100644 --- a/application/controllers/AssessmentController.php +++ b/application/controllers/AssessmentController.php @@ -48,7 +48,7 @@ protected function beforeRender($view) * Renders th view for: show the list of assessments(if assessment is activated) * or the button to activate assessment mode * - * @param int $surveyid the survey id + * @param int $surveyid the survey ID * */ public function actionIndex($surveyid) diff --git a/application/controllers/OptinController.php b/application/controllers/OptinController.php index 2f1ac3a565d..dd34dfe36e0 100644 --- a/application/controllers/OptinController.php +++ b/application/controllers/OptinController.php @@ -39,7 +39,7 @@ public function actiontokens() $languageCode = Yii::app()->request->getQuery('langcode'); $accessToken = Token::sanitizeToken(Yii::app()->request->getQuery('token')); - //IF there is no survey id, redirect back to the default public page + //IF there is no survey ID, redirect back to the default public page if (!$surveyId) { $this->redirect(['/']); } @@ -91,7 +91,7 @@ public function actionparticipants() $languageCode = Yii::app()->request->getQuery('langcode'); $accessToken = Token::sanitizeToken(Yii::app()->request->getQuery('token')); - //IF there is no survey id, redirect back to the default public page + //IF there is no survey ID, redirect back to the default public page if (!$surveyId) { $this->redirect(['/']); } @@ -127,7 +127,7 @@ public function actionparticipants() $isBlacklisted = !empty($participant) && $participant->blacklisted == 'Y'; if (!Yii::app()->getConfig('allowunblacklist') == "Y") { - $message = gT('Removing yourself from the blacklist is currently disabled.'); + $message = gT('Removing yourself from the blocklist is currently disabled.'); } elseif ($isBlacklisted) { $message = gT('Please confirm that you want to be added back to the central participants list for this site.'); $link = Yii::app()->createUrl('optin/addtokens', ['surveyid' => $surveyId, 'langcode' => $baseLanguage, 'token' => $accessToken, 'global' => true]); @@ -150,8 +150,8 @@ public function actionparticipants() } /** - * Add token back to the survey (remove 'OptOut' status) and/or add participant back to the CPDB (remove from blacklist). - * The participant is only removed from the blacklist if the 'global' URL param is true and 'allowunblacklist' is enabled. + * Add token back to the survey (remove 'OptOut' status) and/or add participant back to the CPDB (remove from blocklist). + * The participant is only removed from the blocklist if the 'global' URL param is true and 'allowunblacklist' is enabled. */ public function actionaddtokens() { @@ -199,7 +199,7 @@ public function actionaddtokens() } else { $message = gT('You have been already removed from this survey.'); } - // If the $global param is true and 'allowunblacklist' is enabled, remove from the blacklist + // If the $global param is true and 'allowunblacklist' is enabled, remove from the blocklist if ($global && Yii::app()->getConfig('allowunblacklist') == "Y") { $blacklistHandler = new LimeSurvey\Models\Services\ParticipantBlacklistHandler(); $blacklistResult = $blacklistHandler->removeFromBlacklist($token); diff --git a/application/controllers/OptoutController.php b/application/controllers/OptoutController.php index 952ec972b95..f97704b62c4 100755 --- a/application/controllers/OptoutController.php +++ b/application/controllers/OptoutController.php @@ -37,7 +37,7 @@ public function actiontokens() Yii::app()->loadHelper('database'); Yii::app()->loadHelper('sanitize'); - //IF there is no survey id, redirect back to the default public page + //IF there is no survey ID, redirect back to the default public page if (!$iSurveyID) { $this->redirect(array('/')); } @@ -85,7 +85,7 @@ public function actionparticipants() Yii::app()->loadHelper('database'); Yii::app()->loadHelper('sanitize'); - //IF there is no survey id, redirect back to the default public page + //IF there is no survey ID, redirect back to the default public page if (!$surveyId) { $this->redirect(array('/')); } @@ -149,7 +149,7 @@ public function actionremovetokens() Yii::app()->loadHelper('database'); Yii::app()->loadHelper('sanitize'); - // If there is no survey id, redirect back to the default public page + // If there is no survey ID, redirect back to the default public page if (!$surveyId) { $this->redirect(['/']); } diff --git a/application/controllers/QuestionAdministrationController.php b/application/controllers/QuestionAdministrationController.php index 64e1e230491..c99dc7b53cf 100644 --- a/application/controllers/QuestionAdministrationController.php +++ b/application/controllers/QuestionAdministrationController.php @@ -316,7 +316,7 @@ public function actionListQuestions($surveyid, $landOnSideMenuTab = 'settings') if (!in_array($landOnSideMenuTab, ['settings', 'structure', ''])) { $landOnSideMenuTab = 'settings'; } - // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey id + // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey ID // Ensure Last GetLastPrettyPrintExpression get info from this sid and default lang LimeExpressionManager::SetEMLanguage(Survey::model()->findByPk($iSurveyID)->language); LimeExpressionManager::SetSurveyId($iSurveyID); @@ -358,7 +358,7 @@ public function actionListQuestions($surveyid, $landOnSideMenuTab = 'settings') App()->user->setState('pageSize', (int) $_GET['pageSize']); } $aData['pageSize'] = App()->user->getState('pageSize', App()->params['defaultPageSize']); - // We filter the current survey id + // We filter the current survey ID $questionModel->sid = $oSurvey->sid; $aData['questionModel'] = $questionModel; @@ -712,7 +712,7 @@ public function actionGetSubquestionRowForAllLanguages($surveyid, $gid, $codes, { $oSurvey = Survey::model()->findByPk($surveyid); if (empty($oSurvey)) { - throw new CHttpException(404, gT("Invalid survey id")); + throw new CHttpException(404, gT("Invalid survey ID")); } if (!Permission::model()->hasSurveyPermission($oSurvey->sid, 'surveycontent', 'update')) { throw new CHttpException(403, gT("No permission")); @@ -784,7 +784,7 @@ public function actionGetAnswerOptionRowForAllLanguages($surveyid, $gid, $codes, { $oSurvey = Survey::model()->findByPk($surveyid); if (empty($oSurvey)) { - throw new CHttpException(404, gT("Invalid survey id")); + throw new CHttpException(404, gT("Invalid survey ID")); } if (!Permission::model()->hasSurveyPermission($oSurvey->sid, 'surveycontent', 'update')) { throw new CHttpException(403, gT("No permission")); @@ -1189,6 +1189,13 @@ public function actionImport() return; } + // If there are warnings, we don't jump to the question. + // We need to show the warnings to the user, and they may be too important + // and/or too many to be shown in a flash message. + if (!empty($aImportResults['importwarnings'])) { + $jumptoquestion = false; + } + unlink($sFullFilepath); $aData['aImportResults'] = $aImportResults; @@ -1374,8 +1381,8 @@ public function actionDelete($qid = null, $massAction = false, $redirectTo = nul $qid = Yii::app()->getRequest()->getPost('qid'); } - // @todo: request should specify the survey id of the question to be deleted - // - survey id is verified before deletion + // @todo: request should specify the survey ID of the question to be deleted + // - survey ID is verified before deletion $oQuestion = Question::model()->findByPk($qid); $surveyid = $oQuestion->sid; @@ -2015,7 +2022,7 @@ protected function isValidCSSClass($class) * * @param array $aQids All question id's affected * @param string $sOther the "other" value 'Y' or 'N' - * @param int $iSid survey id + * @param int $iSid survey ID */ public static function setMultipleQuestionOtherState($aQids, $sOther, $iSid) { @@ -2035,7 +2042,7 @@ public static function setMultipleQuestionOtherState($aQids, $sOther, $iSid) * * @param array $aQids All question id's affected * @param string $sMandatory The mandatory va - * @param int $iSid survey id + * @param int $iSid survey ID */ public static function setMultipleQuestionMandatoryState($aQids, $sMandatory, $iSid) { @@ -2602,7 +2609,7 @@ public function actionCheckQuestionValidateTitle($sid, int $qid, string $code) $survey = Survey::model()->findByPk($sid); if (empty($survey)) { - throw new CHttpException(404, gT("Invalid survey id")); + throw new CHttpException(404, gT("Invalid survey ID")); } if ($qid) { $oQuestion = Question::model()->findByAttributes(['qid' => $qid, 'sid' => $sid]); diff --git a/application/controllers/QuestionGroupsAdministrationController.php b/application/controllers/QuestionGroupsAdministrationController.php index 7faea8271ec..22ca9d75639 100644 --- a/application/controllers/QuestionGroupsAdministrationController.php +++ b/application/controllers/QuestionGroupsAdministrationController.php @@ -80,7 +80,7 @@ protected function beforeRender($view) /** * Renders the html for the question group view. * - * @param int $surveyid survey id is important here for new questiongroups without groupid + * @param int $surveyid survey ID is important here for new questiongroups without groupid * @param int $gid * @param string $landOnSideMenuTab * @param string $mode either 'overview' or 'auto'. The 'overview' mode ignores the 'noViewMode' user setting @@ -150,7 +150,7 @@ public function actionView(int $surveyid, int $gid, $landOnSideMenuTab = 'struct /** * Renders the html for the question group edit. * - * @param int $surveyid survey id is important here if group does not exist + * @param int $surveyid survey ID is important here if group does not exist * @param int $gid * @param string $landOnSideMenuTab * @@ -720,7 +720,7 @@ private function getQuestionGroupServiceClass() } /** - * Sets survey id and object into passed array + * Sets survey ID and object into passed array * @param array $aData * @return array */ diff --git a/application/controllers/QuotasController.php b/application/controllers/QuotasController.php index e6b28c3bce3..531a3a40e3b 100644 --- a/application/controllers/QuotasController.php +++ b/application/controllers/QuotasController.php @@ -116,7 +116,7 @@ public function actionQuickCSVReport($surveyid) if (!empty($oSurvey->quotas)) { foreach ($oSurvey->quotas as $oQuota) { $completed = $oQuota->completeCount; - echo $oQuota->name . "," . $oQuota->qlimit . "," . + echo csvEscape($oQuota->name) . "," . $oQuota->qlimit . "," . $completed . "," . ($oQuota->qlimit - $completed) . "\r\n"; } } diff --git a/application/controllers/RegisterController.php b/application/controllers/RegisterController.php index 016e64c02c1..bfcdf04d6a2 100644 --- a/application/controllers/RegisterController.php +++ b/application/controllers/RegisterController.php @@ -187,7 +187,7 @@ public function getRegisterErrors($iSurveyId) /** * Creates the array for the registration success page * - * @param Integer $iSurveyId The survey id + * @param Integer $iSurveyId The survey ID * @param Integer $iTokenId The token id * * @return array The rendereable array @@ -325,7 +325,7 @@ public function getTokenId($iSurveyId) if ($oToken->usesleft < 1 && $aSurveyInfo['alloweditaftercompletion'] != 'Y') { $this->aRegisterErrors[] = gT("The email address you have entered is already registered and the survey has been completed."); } elseif (strtolower(substr(trim((string) $oToken->emailstatus), 0, 6)) === "optout") { - // And global blacklisting ? + // And global blocklisting ? { $this->aRegisterErrors[] = gT("This email address cannot be used because it was opted out of this survey."); } @@ -432,7 +432,7 @@ private function display($iSurveyId, $iTokenId, $registerContent) $this->aReplacementData['sMessage'] = $this->sMessage; $oTemplate = Template::model()->getInstance('', $iSurveyId); - $aSurveyInfo = getsurveyinfo($iSurveyId); + $aSurveyInfo = getsurveyinfo($iSurveyId, $sLanguage); if ($iTokenId !== null) { $aData['aSurveyInfo'] = self::getRegisterSuccess($iSurveyId, $iTokenId); diff --git a/application/controllers/ResponsesController.php b/application/controllers/ResponsesController.php index b19e012c2f6..497644bc33c 100644 --- a/application/controllers/ResponsesController.php +++ b/application/controllers/ResponsesController.php @@ -167,6 +167,8 @@ public function actionView(int $surveyId, int $id, string $browseLang = ''): voi $fnames[] = ["submitdate", gT("Submission date"), gT("Completed"), "0", 'D', 'code' => 'submitdate']; } $fnames[] = ["completed", gT("Completed"), "0"]; + $qids = []; + $fields = []; foreach ($fieldmap as $field) { if ($field['fieldname'] == 'lastpage' || $field['fieldname'] == 'submitdate') { @@ -192,7 +194,21 @@ public function actionView(int $surveyId, int $id, string $browseLang = ''): voi 'code' => viewHelper::getFieldCode($field, ['LEMcompat' => true]) ]; } elseif ($field['aid'] !== 'filecount') { - $qidattributes = QuestionAttribute::model()->getQuestionAttributes($field['qid']); + $qids[] = $field['qid']; + $fields[] = $field; + } else { + $fnames[] = [$field['fieldname'], gT("File count")]; + } + } + + if (count($qids)) { + $rawQuestions = Question::model()->findAllByPk($qids); + $questions = []; + foreach ($rawQuestions as $rawQuestion) { + $questions[$rawQuestion->qid] = $rawQuestion; + } + foreach ($fields as $field) { + $qidattributes = QuestionAttribute::model()->getQuestionAttributes($questions[$field['qid']]); for ($i = 0; $i < $qidattributes['max_num_of_files']; $i++) { $filenum = sprintf(gT("File %s"), $i + 1); @@ -236,8 +252,6 @@ public function actionView(int $surveyId, int $id, string $browseLang = ''): voi "index" => $i ]; } - } else { - $fnames[] = [$field['fieldname'], gT("File count")]; } } @@ -630,7 +644,7 @@ public function actionDelete(int $surveyId): void /** * Deletes a single response and redirects to the gridview. * - * @param int $surveyId -- the survey id + * @param int $surveyId -- the survey ID * @param int $responseId -- the response id to be deleted * @throws CDbException * @throws CHttpException @@ -662,7 +676,7 @@ public function actionDeleteSingle(int $surveyId, int $responseId): void * Download individual file by response and filename * * @access public - * @param int $surveyId : survey id + * @param int $surveyId : survey ID * @param int $responseId * @param int $qid * @param int $index @@ -731,7 +745,7 @@ public function actionDownloadfile(int $surveyId, int $responseId, int $qid, int * Construct a zip files from a list of response * * @access public - * @param int $surveyId : survey id + * @param int $surveyId : survey ID * @param string $responseIds : list of responses as string * @return void application/zip * @throws CException @@ -1036,7 +1050,7 @@ private function getData(int $surveyId = null, int $responseId = null, string $l $thissurvey = getSurveyInfo($surveyId); - // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey id + // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey ID // Ensure Last GetLastPrettyPrintExpression get info from this sid and default lang LimeExpressionManager::SetEMLanguage($thissurvey['oSurvey']->language); LimeExpressionManager::SetSurveyId($surveyId); diff --git a/application/controllers/SurveyAdministrationController.php b/application/controllers/SurveyAdministrationController.php index d9be6d14785..42a8f281abc 100644 --- a/application/controllers/SurveyAdministrationController.php +++ b/application/controllers/SurveyAdministrationController.php @@ -137,7 +137,7 @@ public function actionView() $aData = array('aAdditionalLanguages' => $survey->additionalLanguages); - // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey id + // Reinit LEMlang and LEMsid: ensure LEMlang are set to default lang, surveyid are set to this survey ID // Ensure Last GetLastPrettyPrintExpression get info from this sid and default lang LimeExpressionManager::SetEMLanguage($baselang); LimeExpressionManager::SetSurveyId($iSurveyID); @@ -2581,7 +2581,7 @@ public function actionExpireMultipleSurveys() /** * Try to get the get-parameter from request. - * At the moment there are three namings for a survey id: + * At the moment there are three namings for a survey ID: * 'sid' * 'surveyid' * 'iSurveyID' diff --git a/application/controllers/ThemeOptionsController.php b/application/controllers/ThemeOptionsController.php index ef3065a18b8..280c6527735 100644 --- a/application/controllers/ThemeOptionsController.php +++ b/application/controllers/ThemeOptionsController.php @@ -782,7 +782,7 @@ private function updateCommon(TemplateConfiguration $model, int $sid = null, int $basePageTitle = sprintf('Survey options for theme %s', $templateName); if (!is_null($sid)) { - $addictionalSubtitle = gT(" for survey id: $sid"); + $addictionalSubtitle = gT(" for survey ID: $sid"); } elseif (!is_null($gsid)) { $addictionalSubtitle = gT(" for survey group id: $gsid"); } else { @@ -812,7 +812,7 @@ private function updateCommon(TemplateConfiguration $model, int $sid = null, int /** * Try to get the get-parameter from request. - * At the moment there are three namings for a survey id: + * At the moment there are three namings for a survey ID: * 'sid' * 'surveyid' * 'iSurveyID' diff --git a/application/controllers/UserManagementController.php b/application/controllers/UserManagementController.php index 83013be2979..10afb604856 100644 --- a/application/controllers/UserManagementController.php +++ b/application/controllers/UserManagementController.php @@ -933,6 +933,16 @@ public function actionImportUsers(string $importFormat = 'csv'): string if ($oUser !== null) { if ($overwriteUsers) { + /* Check permission to edit this user */ + if (!$oUser->canEdit()) { + Yii::app()->setFlashMessage(sprintf(gT("You don't have permission to edit user %s."), $aNewUser['users_name']), 'warning'); + continue; + } + /* Check permission to edit self */ + if ($oUser->uid == App()->user->id) { + Yii::app()->setFlashMessage(gT("You can't use the import function to update your own account."), 'warning'); + continue; + } $oUser->full_name = $aNewUser['full_name']; $oUser->email = $aNewUser['email']; $oUser->parent_id = App()->user->id; @@ -975,8 +985,9 @@ public function actionImportUsers(string $importFormat = 'csv'): string } } } - - Yii::app()->setFlashMessage(gT("Users imported successfully."), 'success'); + if (count($created) || count($updated)) { + Yii::app()->setFlashMessage(gT("Users imported successfully."), 'success'); + } $this->redirect(['userManagement/index']); } diff --git a/application/controllers/admin/CheckIntegrity.php b/application/controllers/admin/CheckIntegrity.php index 41a2cfc1f76..ce0c230fd1c 100644 --- a/application/controllers/admin/CheckIntegrity.php +++ b/application/controllers/admin/CheckIntegrity.php @@ -606,7 +606,8 @@ protected function checkintegrity() } // Deactivate surveys that have a missing response table - $oSurveys = Survey::model()->findAll(array('order' => 'sid')); + $survey = new SurveyLight(); + $oSurveys = $survey->findAll(array('order' => 'sid')); $oDB = Yii::app()->getDb(); $oDB->schemaCachingDuration = 0; // Deactivate schema caching Yii::app()->setConfig('Updating', true); @@ -618,83 +619,89 @@ protected function checkintegrity() } } - /** Check for active surveys if questions are in the correct group **/ - foreach ($oSurveys as $oSurvey) { - // This actually clears the schema cache, not just refreshes it - $oDB->schema->refresh(); - // We get the active surveys - if ($oSurvey->isActive && $oSurvey->hasResponsesTable) { - $model = SurveyDynamic::model($oSurvey->sid); - $aColumns = $model->getMetaData()->columns; - $aQids = array(); - - // We get the columns of the responses table - foreach ($aColumns as $oColumn) { - // Question columns start with the SID - if (strpos((string) $oColumn->name, (string)$oSurvey->sid) !== false) { - // Fileds are separated by X - $aFields = explode('X', (string) $oColumn->name); - - if (isset($aFields[1])) { - $sGid = $aFields[1]; - - // QID field can be more than just QID, like: 886other or 886A1 - // So we clean it by finding the first alphabetical character - $sDirtyQid = $aFields[2]; - preg_match('~[a-zA-Z_#]~i', $sDirtyQid, $match, PREG_OFFSET_CAPTURE); - - if (isset($match[0][1])) { - $sQID = substr($sDirtyQid, 0, $match[0][1]); - } else { - // It was just the QID.... (maybe) - $sQID = $sDirtyQid; - } + /** + * Check for active surveys if questions are in the correct group + * This will only run if an additional URL parameter checkResponseTableFields=y is set + * This is to prevent this costly check from running on every page load + */ + if (Yii::app()->request->getParam('checkResponseTableFields') == 'y') { + foreach ($oSurveys as $oSurvey) { + // This actually clears the schema cache, not just refreshes it + $oDB->schema->refresh(); + // We get the active surveys + if ($oSurvey->isActive && $oSurvey->hasResponsesTable) { + $model = SurveyDynamic::model($oSurvey->sid); + $aColumns = $model->getMetaData()->columns; + $aQids = array(); + + // We get the columns of the responses table + foreach ($aColumns as $oColumn) { + // Question columns start with the SID + if (strpos((string) $oColumn->name, (string)$oSurvey->sid) !== false) { + // Fileds are separated by X + $aFields = explode('X', (string) $oColumn->name); + + if (isset($aFields[1])) { + $sGid = $aFields[1]; + + // QID field can be more than just QID, like: 886other or 886A1 + // So we clean it by finding the first alphabetical character + $sDirtyQid = $aFields[2]; + preg_match('~[a-zA-Z_#]~i', $sDirtyQid, $match, PREG_OFFSET_CAPTURE); + + if (isset($match[0][1])) { + $sQID = substr($sDirtyQid, 0, $match[0][1]); + } else { + // It was just the QID.... (maybe) + $sQID = $sDirtyQid; + } - // Here, we get the question as defined in backend - try { - $oQuestion = Question::model()->findByAttributes(['qid' => $sQID , 'sid' => $oSurvey->sid]); - } catch (Exception $e) { - // QID potentially invalid , see #17458, reset $oQuestion - $oQuestion = null; - } - if (is_a($oQuestion, 'Question')) { - // We check if its GID is the same as the one defined in the column name - if ($oQuestion->gid != $sGid) { - // If not, we change the column name - $sNvColName = $oSurvey->sid . 'X' . $oQuestion->group->gid . 'X' . $sDirtyQid; - - if (array_key_exists($sNvColName, $aColumns)) { - // This case will not happen often, only when QID + Subquestion ID == QID of a question in the target group - // So we'll change the group of the question question group table (so in admin interface, not in frontend) - $oQuestion->gid = $sGid; - $oQuestion->save(); - } else { - $oTransaction = $oDB->beginTransaction(); - $oDB->createCommand()->renameColumn($model->tableName(), $oColumn->name, $sNvColName); - $oTransaction->commit(); + // Here, we get the question as defined in backend + try { + $oQuestion = Question::model()->findByAttributes(['qid' => $sQID , 'sid' => $oSurvey->sid]); + } catch (Exception $e) { + // QID potentially invalid , see #17458, reset $oQuestion + $oQuestion = null; + } + if (is_a($oQuestion, 'Question')) { + // We check if its GID is the same as the one defined in the column name + if ($oQuestion->gid != $sGid) { + // If not, we change the column name + $sNvColName = $oSurvey->sid . 'X' . $oQuestion->group->gid . 'X' . $sDirtyQid; + + if (array_key_exists($sNvColName, $aColumns)) { + // This case will not happen often, only when QID + Subquestion ID == QID of a question in the target group + // So we'll change the group of the question question group table (so in admin interface, not in frontend) + $oQuestion->gid = $sGid; + $oQuestion->save(); + } else { + $oTransaction = $oDB->beginTransaction(); + $oDB->createCommand()->renameColumn($model->tableName(), $oColumn->name, $sNvColName); + $oTransaction->commit(); + } } + } else { + // QID not found: The function to split the fieldname into the SGQA data is not 100% reliable + // So for certain question types (for example Text Array) the field name cannot be properly derived + // In this case just ignore the field - see also https://bugs.limesurvey.org/view.php?id=15642 + // There is still a extremely low chance that an unwanted rename happens if a collision like this happens in the same survey } - } else { - // QID not found: The function to split the fieldname into the SGQA data is not 100% reliable - // So for certain question types (for example Text Array) the field name cannot be properly derived - // In this case just ignore the field - see also https://bugs.limesurvey.org/view.php?id=15642 - // There is still a extremely low chance that an unwanted rename happens if a collision like this happens in the same survey } } } } } - } - $oDB->schema->refresh(); - $oDB->schemaCachingDuration = 3600; - $oDB->schema->getTables(); - $oDB->active = false; - $oDB->active = true; - User::model()->refreshMetaData(); - Yii::app()->db->schema->getTable('{{surveys}}', true); - Yii::app()->db->schema->getTable('{{templates}}', true); - Survey::model()->refreshMetaData(); + $oDB->schema->refresh(); + $oDB->schemaCachingDuration = 3600; + $oDB->schema->getTables(); + $oDB->active = false; + $oDB->active = true; + User::model()->refreshMetaData(); + Yii::app()->db->schema->getTable('{{surveys}}', true); + Yii::app()->db->schema->getTable('{{templates}}', true); + Survey::model()->refreshMetaData(); + } /* Check method before using #14596 */ if (method_exists(Yii::app()->cache, 'flush')) { Yii::app()->cache->flush(); @@ -877,10 +884,9 @@ protected function checkintegrity() $oCriteria = new CDbCriteria(); $oCriteria->compare('scope', 'T'); $assessments = Assessment::model()->findAll($oCriteria); - + $sSurveyIDs = Yii::app()->db->createCommand("select sid from {{surveys}}")->queryColumn(); foreach ($assessments as $assessment) { - $iAssessmentCount = count(Survey::model()->findAllByPk($assessment['sid'])); - if (!$iAssessmentCount) { + if (!in_array($assessment['sid'], $sSurveyIDs)) { $aDelete['assessments'][] = array('id' => $assessment['id'], 'assessment' => $assessment['name'], 'reason' => gT('No matching survey')); } } @@ -888,9 +894,11 @@ protected function checkintegrity() $oCriteria = new CDbCriteria(); $oCriteria->compare('scope', 'G'); $assessments = Assessment::model()->findAll($oCriteria); + $quotedGroups = Yii::app()->db->quoteTableName('{{groups}}'); + + $groupIds = Yii::app()->db->createCommand("select gid from $quotedGroups")->queryColumn(); foreach ($assessments as $assessment) { - $iAssessmentCount = count(QuestionGroup::model()->findAllByPk(array('gid' => $assessment['gid'], 'language' => $assessment['language']))); - if (!$iAssessmentCount) { + if (!in_array($assessment['gid'], $groupIds)) { $aDelete['assessments'][] = array('id' => $assessment['id'], 'assessment' => $assessment['name'], 'reason' => gT('No matching group')); } } @@ -921,30 +929,27 @@ protected function checkintegrity() /* Check survey languagesettings and restore them if they don't exist */ /***************************************************************************/ - $surveys = Survey::model()->findAll(); + $surveyModel = new SurveyLight(); + $surveys = $surveyModel->findAll(); foreach ($surveys as $survey) { $aLanguages = $survey->additionalLanguages; $aLanguages[] = $survey->language; + $languages = Yii::app()->db->createCommand("select surveyls_language from {{surveys_languagesettings}} where surveyls_survey_id=" . $survey->sid)->queryColumn(); foreach ($aLanguages as $langname) { - if ($langname) { - $oLanguageSettings = SurveyLanguageSetting::model()->find('surveyls_survey_id=:surveyid AND surveyls_language=:langname', array(':surveyid' => $survey->sid, ':langname' => $langname)); - // A simple find starts to eat up memory, so we need to free it - gc_collect_cycles(); - if (!$oLanguageSettings) { - $oLanguageSettings = new SurveyLanguageSetting(); - $languagedetails = getLanguageDetails($langname); - $insertdata = array( - 'surveyls_survey_id' => $survey->sid, - 'surveyls_language' => $langname, - 'surveyls_title' => '', - 'surveyls_dateformat' => $languagedetails['dateformat'] - ); - foreach ($insertdata as $k => $v) { - $oLanguageSettings->$k = $v; - } - $oLanguageSettings->save(); - $bDirectlyFixed = true; + if (!in_array($langname, $languages)) { + $oLanguageSettings = new SurveyLanguageSetting(); + $languagedetails = getLanguageDetails($langname); + $insertdata = array( + 'surveyls_survey_id' => $survey->sid, + 'surveyls_language' => $langname, + 'surveyls_title' => '', + 'surveyls_dateformat' => $languagedetails['dateformat'] + ); + foreach ($insertdata as $k => $v) { + $oLanguageSettings->$k = $v; } + $oLanguageSettings->save(); + $bDirectlyFixed = true; } } } @@ -1025,8 +1030,8 @@ protected function checkintegrity() /**********************************************************************/ /* Check old survey tables */ /**********************************************************************/ - //1: Get list of 'old_survey' tables and extract the survey id - //2: Check if that survey id still exists + //1: Get list of 'old_survey' tables and extract the survey ID + //2: Check if that survey ID still exists //3: If it doesn't offer it for deletion $sQuery = dbSelectTablesLike('{{old_survey}}%'); $aTables = Yii::app()->db->createCommand($sQuery)->queryColumn(); @@ -1039,12 +1044,8 @@ protected function checkintegrity() $aFullOldSIDs[$iSurveyID][] = $sTable; } $aOldSIDs = array_unique($aOldSIDs); - $surveys = Survey::model()->findAll(); - $aSIDs = array(); - foreach ($surveys as $survey) { - $aSIDs[] = $survey['sid']; - } + $aSIDs = Yii::app()->db->createCommand("select sid from {{surveys}}")->queryColumn(); foreach ($aOldSIDs as $iOldSID) { if (!in_array($iOldSID, $aSIDs)) { foreach ($aFullOldSIDs[$iOldSID] as $sTableName) { @@ -1093,8 +1094,8 @@ protected function checkintegrity() /**********************************************************************/ /* CHECK OLD TOKEN TABLES */ /**********************************************************************/ - //1: Get list of 'old_token' tables and extract the survey id - //2: Check if that survey id still exists + //1: Get list of 'old_token' tables and extract the survey ID + //2: Check if that survey ID still exists //3: If it doesn't offer it for deletion $sQuery = dbSelectTablesLike('{{old_token}}%'); $aTables = Yii::app()->db->createCommand($sQuery)->queryColumn(); @@ -1108,12 +1109,7 @@ protected function checkintegrity() $aFullOldTokenSIDs[$iSurveyID][] = $sTable; } $aOldTokenSIDs = array_unique($aTokenSIDs); - $surveys = Survey::model()->findAll(); - - $aSIDs = array(); - foreach ($surveys as $survey) { - $aSIDs[] = $survey['sid']; - } + $aSIDs = Yii::app()->db->createCommand("select sid from {{surveys}}")->queryColumn(); foreach ($aOldTokenSIDs as $iOldTokenSID) { if (!in_array($iOldTokenSID, $aOldTokenSIDs)) { foreach ($aFullOldTokenSIDs[$iOldTokenSID] as $sTableName) { @@ -1185,7 +1181,7 @@ protected function checkintegrity() /**********************************************************************/ /* CHECK CPDB SURVEY_LINKS TABLE FOR REDUNDENT Survey participants tableS */ /**********************************************************************/ - //1: Get distinct list of survey_link survey ids, check if tokens + //1: Get distinct list of survey_link survey IDs, check if tokens // table still exists for each one, and remove if not @@ -1240,7 +1236,7 @@ protected function checkGroupOrderDuplicates() */ protected function checkQuestionOrderDuplicates() { - $quotedGroups = Yii::app()->db->quoteTableName('{{groups}}'); + $quotedGroups = Yii::app()->db->quoteTableName('{{groups}}'); $sQuery = " SELECT q.sid, diff --git a/application/controllers/admin/DataEntry.php b/application/controllers/admin/DataEntry.php index 6840f37581e..118de9277c8 100644 --- a/application/controllers/admin/DataEntry.php +++ b/application/controllers/admin/DataEntry.php @@ -2010,7 +2010,7 @@ private function returnClosedAccessSurveyErrorMessage(): string private function returnAccessCodeIsNotValidOrAlreadyInUseErrorMessage(): string { $errormsg = CHtml::tag('div', array('class' => 'warningheader'), gT("Error")); - $errormsg .= CHtml::tag('p', array(), gT("The access code have provided is not valid or has already been used.")); + $errormsg .= CHtml::tag('p', array(), gT("The provided access code is not valid or has already been used.")); return $errormsg; } diff --git a/application/controllers/admin/ExpressionValidate.php b/application/controllers/admin/ExpressionValidate.php index 7d0e626a4de..f9b70b17aef 100644 --- a/application/controllers/admin/ExpressionValidate.php +++ b/application/controllers/admin/ExpressionValidate.php @@ -16,7 +16,7 @@ class ExpressionValidate extends SurveyCommonAction public $layout = 'popup'; /** - * @var integer : The survey id to start to fill know vars + * @var integer : The survey ID to start to fill know vars */ private $iSurveyId; /** @@ -31,7 +31,7 @@ public function index() /** * Check the Expression in quota - * @param integer $iSurveyId : the survey id : can be sid/surveyid url GET parameters + * @param integer $iSurveyId : the survey ID : can be sid/surveyid url GET parameters * @param integer $quota : the quota id * @param string $lang : the survey language, optional : if not set get all language of survey * @@ -86,7 +86,7 @@ public function quota($iSurveyId, $quota, $lang = null) } /** * Check the Expression in email - * @param integer $iSurveyId : the survey id : can be sid/surveyid url GET parameters + * @param integer $iSurveyId : the survey ID : can be sid/surveyid url GET parameters * @param string $lang : the mail language * * @author Denis Chenu diff --git a/application/controllers/admin/ParticipantsAction.php b/application/controllers/admin/ParticipantsAction.php index f1151135837..76801e7c1cd 100644 --- a/application/controllers/admin/ParticipantsAction.php +++ b/application/controllers/admin/ParticipantsAction.php @@ -1306,7 +1306,7 @@ public function blacklistControl() } /** - * Stores the blacklist setting to the database + * Stores the blocklist setting to the database * @return void */ public function storeBlacklistValues() @@ -1339,7 +1339,7 @@ public function storeBlacklistValues() } /** - * AJAX Method to change the blacklist status of a participant + * AJAX Method to change the blocklist status of a participant * Requires POST with 'participant_id' (varchar) and 'blacklist' (boolean) * Echos JSON-encoded array with 'success' (boolean) and 'newValue' ('Y' || 'N') * @return void @@ -2098,7 +2098,7 @@ public function editShareInfo() * Receives an ajax call containing the participant id in the fourth segment of the url * Supplies list of survey links - surveys of which this participant is on the tokens table * URL: [localurl]/limesurvey/admin/participants/getSurveyInfoJson/pid/[participant_id] - * Echoes json data containing linked survey information (Survey name, survey id, token_id and date_added) + * Echoes json data containing linked survey information (Survey name, survey ID, token_id and date_added) * @return void * @todo Where is this called from? */ @@ -2559,7 +2559,7 @@ public function addToTokenattmap() } if ($response['blacklistskipped'] > 0) { echo "
"; - printf(gT("%s entries were skipped because they are blacklisted"), "" . $response['blacklistskipped'] . ""); + printf(gT("%s entries were skipped because they are blocklisted"), "" . $response['blacklistskipped'] . ""); echo "
"; } if ($response['overwriteauto'] == "true" || $response['overwriteman'] == "true") { diff --git a/application/controllers/admin/PluginManagerController.php b/application/controllers/admin/PluginManagerController.php index d1cd9ac4872..db42d928753 100644 --- a/application/controllers/admin/PluginManagerController.php +++ b/application/controllers/admin/PluginManagerController.php @@ -560,7 +560,7 @@ public function uploadConfirm() if (!$installer->isWhitelisted()) { $installer->abort(); - $this->errorAndRedirect(gT('The plugin is not in the plugin whitelist.')); + $this->errorAndRedirect(gT('The plugin is not in the plugin allowlist.')); } if (!$config->isCompatible()) { diff --git a/application/controllers/admin/globalsettings.php b/application/controllers/admin/globalsettings.php index ac3b3f319ce..12fae152302 100644 --- a/application/controllers/admin/globalsettings.php +++ b/application/controllers/admin/globalsettings.php @@ -305,7 +305,7 @@ private function saveSettings() SettingGlobal::setSetting('allow_unstable_extension_update', sanitize_paranoid_string(Yii::app()->getRequest()->getPost('allow_unstable_extension_update', false))); } - SettingGlobal::setSetting('createsample', (bool) Yii::app()->getRequest()->getPost('createsample')); + SettingGlobal::setSetting('createsample', Yii::app()->getRequest()->getPost('createsample')); if (!Yii::app()->getConfig('demoMode')) { $sTemplate = Yii::app()->getRequest()->getPost("defaulttheme"); @@ -321,12 +321,12 @@ private function saveSettings() $validatedLoginIpWhitelistInput = $this->validateIpAddresses(Yii::app()->getRequest()->getPost('loginIpWhitelist')); SettingGlobal::setSetting('loginIpWhitelist', $validatedLoginIpWhitelistInput['valid']); if (!empty($validatedLoginIpWhitelistInput['invalid'])) { - $warning .= sprintf(gT("Warning! Invalid IP addresses have been excluded from '%s' setting."), gT("IP whitelist for login")) . '