From d039fc237c8a8158711b05625d1e27fbb963a01d Mon Sep 17 00:00:00 2001 From: "tai.letan" Date: Tue, 17 Sep 2024 09:57:12 +0700 Subject: [PATCH] Questionnaire: option to auto-delete responses after X time --- .../moodle2/backup_questionnaire_stepslib.php | 2 +- classes/task/cleanup.php | 4 ++ db/install.xml | 1 + db/upgrade.php | 14 +++++ lang/en/questionnaire.php | 10 ++++ locallib.php | 60 +++++++++++++++++++ mod_form.php | 30 +++++++++- settings.php | 16 +++++ tests/responsetypes_test.php | 51 ++++++++++++++++ version.php | 2 +- 10 files changed, 187 insertions(+), 3 deletions(-) diff --git a/backup/moodle2/backup_questionnaire_stepslib.php b/backup/moodle2/backup_questionnaire_stepslib.php index 747b304b..430fe151 100644 --- a/backup/moodle2/backup_questionnaire_stepslib.php +++ b/backup/moodle2/backup_questionnaire_stepslib.php @@ -39,7 +39,7 @@ protected function define_structure() { $questionnaire = new backup_nested_element('questionnaire', array('id'), array( 'course', 'name', 'intro', 'introformat', 'qtype', 'respondenttype', 'resp_eligible', 'resp_view', 'notifications', 'opendate', - 'closedate', 'resume', 'navigate', 'grade', 'sid', 'timemodified', 'completionsubmit', 'autonum')); + 'closedate', 'resume', 'navigate', 'grade', 'sid', 'timemodified', 'completionsubmit', 'autonum', 'removeafter')); $surveys = new backup_nested_element('surveys'); diff --git a/classes/task/cleanup.php b/classes/task/cleanup.php index 7cd40678..908d3885 100644 --- a/classes/task/cleanup.php +++ b/classes/task/cleanup.php @@ -43,5 +43,9 @@ public function execute() { require_once($CFG->dirroot . '/mod/questionnaire/locallib.php'); questionnaire_cleanup(); + $isautodelete = (bool) get_config('questionnaire', 'autodeleteresponse'); + if ($isautodelete) { + questionnaire_delete_old_responses(); + } } } diff --git a/db/install.xml b/db/install.xml index a1b7af0b..e5892b26 100644 --- a/db/install.xml +++ b/db/install.xml @@ -26,6 +26,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index dd5fd989..182e7f8e 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -1002,6 +1002,20 @@ function xmldb_questionnaire_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2022121600.02, 'questionnaire'); } + if ($oldversion < 2024091600.00) { + // Add removeafter fields. + $table = new xmldb_table('questionnaire'); + $field = new xmldb_field('removeafter', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0, 'progressbar'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Questionnaire savepoint reached. + upgrade_mod_savepoint(true, 2024091600.00, 'questionnaire'); + } + return true; } diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php index 7de8d9f7..8ecbfd6e 100644 --- a/lang/en/questionnaire.php +++ b/lang/en/questionnaire.php @@ -50,6 +50,8 @@ $string['answerquestions'] = 'Answer the questions...'; $string['attempted'] = 'This questionnaire has been submitted.'; $string['attemptstillinprogress'] = 'In progress. Saved on:'; +$string['autodeletereponse'] = 'Auto delete responses after [?]'; +$string['autodeletereponse_desc'] = 'Enable this setting to automatically delete old responses based on the time set in “Manage old responses after.” Disable to retain all responses.'; $string['autonumbering'] = 'Auto numbering'; $string['autonumbering_help'] = 'Automatic numbering of questions and pages. You might want to disable automatic numbering for questionnaires with conditional branching.'; @@ -67,6 +69,7 @@ $string['boxesnbmin'] = 'a minimum of {$a} box(es).'; $string['boxesnbreq'] = 'For this question you must tick '; $string['by'] = ' by '; +$string['disabled'] = 'Disabled'; $string['missingname'] = 'Question {$a} cannot be used in this feedback section because it does not have a name.'; $string['missingrequired'] = 'Question {$a} cannot be used in this feedback section because it is not required.'; $string['missingnameandrequired'] = 'Question {$a} cannot be used in this feedback section because it does not have a name and it is not required.'; @@ -118,6 +121,7 @@ $string['createcontent_help'] = 'Select one of the radio button options. \'Create new\' is the default.'; $string['createcontent_link'] = 'mod/questionnaire/mod#Content_Options'; $string['createnew'] = 'Create new'; +$string['configremoveoldresponses'] = 'Setting which will be used as default on all new questionares.'; $string['centerlabel'] = 'Centre label'; $string['date'] = 'Date'; $string['date_help'] = 'Use this question type if you expect the response to be a correctly formatted date.'; @@ -168,6 +172,7 @@ \'allowemailreporting\' must be enabled in module settings to access this.'; $string['emailsend'] = 'Send reports'; $string['emailssent'] = 'Downloads sent to specified email(s).'; +$string['enabled'] = 'Enabled'; $string['errnewname'] = 'Sorry, name already in use. Pick a new name.'; $string['erroropening'] = 'Error opening questionnaire.'; $string['errortable'] = 'Error system table corrupt.'; @@ -403,6 +408,7 @@ $string['overviewnumrespvw'] = 'responses'; $string['overviewnumrespvw1'] = 'response'; $string['owner'] = 'Owner'; +$string['onemonth'] = '1 month'; $string['page'] = 'Page'; $string['pageof'] = 'Page {$a->page} of {$a->totpages}'; $string['parent'] = 'Parent'; @@ -566,6 +572,10 @@ $string['resume_link'] = 'mod/questionnaire/mod#Save/Resume_answers'; $string['resumesurvey'] = 'Resume questionnaire'; $string['return'] = 'Return'; +$string['removeoldresponsesdefault'] = 'Never remove'; +$string['removeoldresponses'] = 'Manage old responses'; +$string['removeoldresponsesafter'] = 'Manage old responses after'; +$string['removeoldresponses_help'] = 'The system can automatically remove responses after a certain length of time.'; $string['rightlabel'] = 'Right label'; $string['rightpart'] = ' and {$a->max} is {$a->rightlabel}'; $string['rightpartdefault'] = ' and {$a->max} is maximum slider range'; diff --git a/locallib.php b/locallib.php index 4fce8590..830dfa5f 100644 --- a/locallib.php +++ b/locallib.php @@ -949,3 +949,63 @@ function questionnaire_get_standard_page_items($id = null, $a = null) { return (array($cm, $course, $questionnaire)); } + + +/** + * Create options for remove old responses in the questionare. + * + * @return array + */ +function questionnaire_create_remove_options() { + $options = []; + $options[0] = get_string('removeoldresponsesdefault', 'questionnaire'); + for ($i = 1; $i <= 36; $i++) { + $options[$i * 2592000] = $i > 1 ? get_string('nummonths', 'moodle', $i) : get_string('onemonth', 'questionnaire'); + } + return $options; +} + +/** + * Delete all the old responses when we have setting the questionnaire. + * + * @throws coding_exception + * @throws dml_exception + */ +function questionnaire_delete_old_responses() { + global $DB; + $currenttime = time(); + + $sql = "SELECT qr.id + FROM {questionnaire} q + JOIN {questionnaire_response} qr ON qr.questionnaireid = q.id AND qr.complete = 'y' + WHERE q.removeafter <> 0 AND (q.removeafter < :currettime - qr.submitted)"; + // Get all old response from questionnaires. + $oldresponsesid = $DB->get_records_sql($sql, ['currettime' => $currenttime]); + if (!empty($oldresponsesid)) { + try { + $oldresponsesid = array_keys($oldresponsesid); + $count = count($oldresponsesid); + if (!PHPUNIT_TEST) { + mtrace("\nBeginning deleting $count old responses requests"); + } + // Delete all of the response data for a response. + $responsetables = [ + 'questionnaire_response_bool', 'questionnaire_response_date', 'questionnaire_resp_multiple', + 'questionnaire_response_other', 'questionnaire_response_rank', 'questionnaire_resp_single', + 'questionnaire_response_text']; + list ($sqlparam, $params) = $DB->get_in_or_equal($oldresponsesid, SQL_PARAMS_QM); + foreach ($responsetables as $tablename) { + $sql = "DELETE FROM {{$tablename}} WHERE response_id $sqlparam"; + $DB->execute($sql, $params); + } + // Delete the response from the main table. + $sql = "DELETE FROM {questionnaire_response} WHERE id $sqlparam"; + $DB->execute($sql, $params); + if (!PHPUNIT_TEST) { + mtrace("\nCompleted deleting $count old responses requests"); + } + } catch (\dml_exception $ex) { + debugging('Error: ' . $ex->getMessage(), DEBUG_DEVELOPER); + } + } +} diff --git a/mod_form.php b/mod_form.php index 26fcd227..587f0841 100644 --- a/mod_form.php +++ b/mod_form.php @@ -34,7 +34,7 @@ class mod_questionnaire_mod_form extends moodleform_mod { * Form definition. */ protected function definition() { - global $COURSE; + global $COURSE, $CFG; global $questionnairetypes, $questionnairerespondents, $questionnaireresponseviewers, $autonumbering; $questionnaire = new questionnaire($COURSE, $this->_cm, $this->_instance, null); @@ -142,6 +142,20 @@ protected function definition() { $mform->setDefault('create', 'new-0'); } + // Remove old responses. + $isautodelete = (bool) get_config('questionnaire', 'autodeleteresponse'); + if ($isautodelete) { + $options = questionnaire_create_remove_options(); + $mform->addElement('header', 'responsehdr', get_string('removeoldresponses', 'questionnaire')); + $mform->addElement('select', 'removeafter', + get_string('removeoldresponsesafter', 'questionnaire'), $options); + $mform->addHelpButton('removeafter', 'removeoldresponses', 'questionnaire'); + // Just set default value when creating a new questionare. + if (empty($questionnaire->sid)) { + $defaultconfig = get_config('questionnaire', 'removeoldresponses'); + $mform->setDefault('removeafter', $defaultconfig); + } + } $this->standard_coursemodule_elements(); // Buttons. @@ -223,4 +237,18 @@ public function completion_rule_enabled($data) { return !empty($data['completionsubmit']); } + /** + * Create options for remove old responses in the questionare. + * + * @return array + */ + public function create_remove_options() { + $options = []; + $options[0] = get_string('removeoldresponsesdefault', 'questionnaire'); + for ($i = 1; $i <= 36; $i++) { + $options[$i * 2592000] = $i > 1 ? get_string('nummonths', 'moodle', $i) : get_string('onemonth', 'questionnaire'); + } + return $options; + } + } diff --git a/settings.php b/settings.php index 6a21c51d..41705854 100644 --- a/settings.php +++ b/settings.php @@ -24,6 +24,7 @@ */ defined('MOODLE_INTERNAL') || die; +require_once($CFG->dirroot . '/mod/questionnaire/locallib.php'); if ($ADMIN->fulltree) { $options = array(0 => get_string('no'), 1 => get_string('yes')); @@ -52,4 +53,19 @@ $settings->add(new admin_setting_configcheckbox('questionnaire/allowemailreporting', get_string('configemailreporting', 'questionnaire'), get_string('configemailreportinglong', 'questionnaire'), 0)); + + // Manage old responses after. The default value is 24 months. + $options = questionnaire_create_remove_options(); + $settings->add(new admin_setting_configselect('questionnaire/removeoldresponses', + get_string('removeoldresponsesafter', 'questionnaire'), + get_string('configremoveoldresponses', 'questionnaire'), 0, $options)); + + $options = [ + '0' => new lang_string('disabled', 'questionnaire'), + '1' => new lang_string('enabled', 'questionnaire'), + ]; + $name = get_string('autodeletereponse', 'questionnaire'); + $desc = get_string('autodeletereponse_desc', 'questionnaire'); + $setting = new admin_setting_configselect('questionnaire/autodeleteresponse', $name, $desc, 0, $options); + $settings->add($setting); } diff --git a/tests/responsetypes_test.php b/tests/responsetypes_test.php index e10db5ef..3d95409e 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -419,4 +419,55 @@ private function response_tests($questionnaireid, $responseid, $userid, $this->assertArrayHasKey($responseid, $responses); $this->assertEquals($responseid, $responses[$responseid]->id); } + + public function test_create_old_response_boolean() { + global $DB; + + $this->resetAfterTest(); + + // Some common variables used below. + $userid = 1; + + // Set up a questinnaire with one boolean response question. + $course = $this->getDataGenerator()->create_course(); + $generator = $this->getDataGenerator()->get_plugin_generator('mod_questionnaire'); + // Add a questionnaire that will delete old responses after one month. + $questionnaire1 = $generator->create_test_questionnaire($course, QUESYESNO, ['content' => 'Enter yes or no']); + $question1 = reset($questionnaire1->questions); + $response1 = $generator->create_question_response($questionnaire1, $question1, 'y', $userid); + + $questionnaire2 = $generator->create_test_questionnaire($course, QUESYESNO, ['content' => 'Enter yes or no']); + $question2 = reset($questionnaire2->questions); + $response2 = $generator->create_question_response($questionnaire2, $question2, 'y', $userid); + + $this->response_tests($questionnaire1->id, $response1->id, $userid); + $this->response_tests($questionnaire2->id, $response2->id, $userid); + + // Set the removeafterfield for questionnaires. + $newquestionairre1 = new \stdClass(); + $newquestionairre1->id = $questionnaire1->id; + $newquestionairre1->removeafter = 2592000; + $newquestionairre2 = new \stdClass(); + $newquestionairre2->id = $questionnaire2->id; + $newquestionairre2->removeafter = 2592000; + $DB->update_record('questionnaire', $newquestionairre1); + $DB->update_record('questionnaire', $newquestionairre2); + // Retrieve the specific boolean response. + $booleanresponses1 = $DB->get_record('questionnaire_response', ['id' => $response1->id]); + $booleanresponses2 = $DB->get_record('questionnaire_response', ['id' => $response2->id]); + // Set the submitted time to 31 day in the past. + $booleanresponses1->submitted = $booleanresponses1->submitted - 2592000 - 86400; + $booleanresponses2->submitted = $booleanresponses2->submitted - 2592000 - 86400; + $DB->update_record('questionnaire_response', $booleanresponses1); + $DB->update_record('questionnaire_response', $booleanresponses2); + questionnaire_delete_old_responses(); + $responseresult1 = $DB->record_exists('questionnaire_response', ['id' => $response1->id]); + $responseresult2 = $DB->record_exists('questionnaire_response', ['id' => $response2->id]); + $this->assertEmpty($responseresult1); + $this->assertEmpty($responseresult2); + $boolresponseresult1 = $DB->record_exists('questionnaire_response_bool', ['response_id' => $response1->id]); + $boolresponseresult2 = $DB->record_exists('questionnaire_response_bool', ['response_id' => $response2->id]); + $this->assertEmpty($boolresponseresult1); + $this->assertEmpty($boolresponseresult2); + } } diff --git a/version.php b/version.php index 3d6851d0..4affd0a0 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022121601; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024091600; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2024042200.00; // Moodle version (4.4.0). $plugin->component = 'mod_questionnaire';