From a3b9acef0030a5c79e04c665761df20a1df6352d Mon Sep 17 00:00:00 2001 From: toanlam Date: Tue, 4 Jun 2024 18:36:51 +0700 Subject: [PATCH] Questionnaire: option to auto-delete responses after X time --- .../moodle2/backup_questionnaire_stepslib.php | 2 +- classes/task/cleanup.php | 1 + db/install.xml | 1 + db/upgrade.php | 14 +++++ lang/en/questionnaire.php | 6 ++ locallib.php | 60 +++++++++++++++++++ mod_form.php | 27 ++++++++- settings.php | 7 +++ tests/responsetypes_test.php | 51 ++++++++++++++++ version.php | 2 +- 10 files changed, 168 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..d11b95c8 100644 --- a/classes/task/cleanup.php +++ b/classes/task/cleanup.php @@ -43,5 +43,6 @@ public function execute() { require_once($CFG->dirroot . '/mod/questionnaire/locallib.php'); questionnaire_cleanup(); + 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..ce1ecd26 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 < 2024060400.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, 2024060400.00, 'questionnaire'); + } + return true; } diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php index f6e35b11..b49da5ca 100644 --- a/lang/en/questionnaire.php +++ b/lang/en/questionnaire.php @@ -118,6 +118,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.'; @@ -403,6 +404,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 +568,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..d4132dda 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,17 @@ protected function definition() { $mform->setDefault('create', 'new-0'); } + // Remove old responses. + $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 +234,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..c98fc35b 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,10 @@ $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)); } 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 ac5eb9bf..1dbadc50 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022121600.02; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024060400.00; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2022112800.00; // Moodle version (4.1.0). $plugin->component = 'mod_questionnaire';