Skip to content

Commit

Permalink
Questionnaire: option to auto-delete responses after X time
Browse files Browse the repository at this point in the history
  • Loading branch information
tai.letan committed Sep 17, 2024
1 parent f2ffa46 commit d039fc2
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 3 deletions.
2 changes: 1 addition & 1 deletion backup/moodle2/backup_questionnaire_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
4 changes: 4 additions & 0 deletions classes/task/cleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
1 change: 1 addition & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<FIELD NAME="completionsubmit" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Questionnaire marked as 'complete' when submitted."/>
<FIELD NAME="autonum" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="3" SEQUENCE="false" COMMENT="option for auto numbering questions and pages (both selected by default)"/>
<FIELD NAME="progressbar" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display a progress bar at top of questionnaire."/>
<FIELD NAME="removeafter" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Remove old responses after certain period. 1 for one month, 2 for two months...12 for one year, 13 for two years and 14 for three years."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
14 changes: 14 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
10 changes: 10 additions & 0 deletions lang/en/questionnaire.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.';
Expand All @@ -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.';
Expand Down Expand Up @@ -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.';
Expand Down Expand Up @@ -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.';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down
60 changes: 60 additions & 0 deletions locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
30 changes: 29 additions & 1 deletion mod_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

}
16 changes: 16 additions & 0 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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);
}
51 changes: 51 additions & 0 deletions tests/responsetypes_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit d039fc2

Please sign in to comment.