diff --git a/backup/moodle2/backup_questionnaire_stepslib.php b/backup/moodle2/backup_questionnaire_stepslib.php index 0b0dafe9..db7bd5e5 100644 --- a/backup/moodle2/backup_questionnaire_stepslib.php +++ b/backup/moodle2/backup_questionnaire_stepslib.php @@ -41,7 +41,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 1dc8396e..029f5adf 100644 --- a/classes/task/cleanup.php +++ b/classes/task/cleanup.php @@ -41,5 +41,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 ca290ae7..b8796ef1 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -979,6 +979,20 @@ function xmldb_questionnaire_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2020062301, 'questionnaire'); } + if ($oldversion < 2021092800) { + // 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, 2021092800, 'questionnaire'); + } + return $result; } diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php index 7ac3af8b..f1419033 100644 --- a/lang/en/questionnaire.php +++ b/lang/en/questionnaire.php @@ -117,6 +117,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['date'] = 'Date'; $string['date_help'] = 'Use this question type if you expect the response to be a correctly formatted date.'; $string['date_link'] = 'mod/questionnaire/questions#Date'; @@ -385,6 +386,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'; @@ -548,6 +550,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['save'] = 'Save'; $string['saveasnew'] = 'Save as New Question'; $string['savedbutnotsubmitted'] = 'This questionnaire has been saved but not yet submitted.'; diff --git a/locallib.php b/locallib.php index 42879e67..bf0ae907 100644 --- a/locallib.php +++ b/locallib.php @@ -879,3 +879,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}} r WHERE r.response_id $sqlparam"; + $DB->execute($sql, $params); + } + // Delete the response from the main table. + $sql = "DELETE FROM {questionnaire_response} r WHERE r.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 b7053639..77000ee4 100644 --- a/mod_form.php +++ b/mod_form.php @@ -31,7 +31,7 @@ class mod_questionnaire_mod_form extends moodleform_mod { protected function definition() { - global $COURSE; + global $COURSE, $CFG; global $questionnairetypes, $questionnairerespondents, $questionnaireresponseviewers, $autonumbering; $questionnaire = new questionnaire($this->_instance, null, $COURSE, $this->_cm); @@ -139,6 +139,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. @@ -198,4 +209,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 1b61f757..10e4a881 100644 --- a/settings.php +++ b/settings.php @@ -23,6 +23,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')); @@ -50,4 +51,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 fb1c78e0..69ba11ed 100644 --- a/tests/responsetypes_test.php +++ b/tests/responsetypes_test.php @@ -316,4 +316,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 3b5a4b7c..dcb05d32 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020111101; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2021092800; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2020061500; // Moodle version (3.9). $plugin->component = 'mod_questionnaire';