Merge branch 'MDL-61862-master' of git://github.com/FMCorz/moodle

This commit is contained in:
Jun Pataleta 2018-04-16 23:30:42 +08:00
commit d51c946700
3 changed files with 843 additions and 0 deletions

View File

@ -0,0 +1,403 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Data provider.
*
* @package mod_feedback
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_feedback\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use context_helper;
use stdClass;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
require_once($CFG->dirroot . '/mod/feedback/lib.php');
/**
* Data provider class.
*
* @package mod_feedback
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
$completedfields = [
'userid' => 'privacy:metadata:completed:userid',
'timemodified' => 'privacy:metadata:completed:timemodified',
'anonymous_response' => 'privacy:metadata:completed:anonymousresponse',
];
$collection->add_database_table('feedback_completed', $completedfields, 'privacy:metadata:completed');
$collection->add_database_table('feedback_completedtmp', $completedfields, 'privacy:metadata:completedtmp');
$valuefields = [
'value' => 'privacy:metadata:value:value'
];
$collection->add_database_table('feedback_value', $valuefields, 'privacy:metadata:value');
$collection->add_database_table('feedback_valuetmp', $valuefields, 'privacy:metadata:valuetmp');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$sql = "
SELECT DISTINCT ctx.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
JOIN {context} ctx
ON ctx.instanceid = cm.id
AND ctx.contextlevel = :modlevel
WHERE fc.userid = :userid";
$params = ['feedback' => 'feedback', 'modlevel' => CONTEXT_MODULE, 'userid' => $userid];
$contextlist = new contextlist();
$contextlist->add_from_sql(sprintf($sql, 'feedback_completed'), $params);
$contextlist->add_from_sql(sprintf($sql, 'feedback_completedtmp'), $params);
return $contextlist;
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$user = $contextlist->get_user();
$userid = $user->id;
$contextids = array_map(function($context) {
return $context->id;
}, array_filter($contextlist->get_contexts(), function($context) {
return $context->contextlevel == CONTEXT_MODULE;
}));
if (empty($contextids)) {
return;
}
$flushdata = function($context, $data) use ($user) {
$contextdata = helper::get_context_data($context, $user);
helper::export_context_files($context, $user);
$mergeddata = array_merge((array) $contextdata, (array) $data);
// Drop the temporary keys.
if (array_key_exists('submissions', $mergeddata)) {
$mergeddata['submissions'] = array_values($mergeddata['submissions']);
}
writer::with_context($context)->export_data([], (object) $mergeddata);
};
$lastctxid = null;
$data = (object) [];
list($sql, $params) = static::prepare_export_query($contextids, $userid);
$recordset = $DB->get_recordset_sql($sql, $params);
foreach ($recordset as $record) {
if ($lastctxid && $lastctxid != $record->contextid) {
$flushdata(context::instance_by_id($lastctxid), $data);
$data = (object) [];
}
context_helper::preload_from_record($record);
$id = ($record->istmp ? 'tmp' : 'notmp') . $record->submissionid;
if (!isset($data->submissions)) {
$data->submissions = [];
}
if (!isset($data->submissions[$id])) {
$data->submissions[$id] = [
'inprogress' => transform::yesno($record->istmp),
'anonymousresponse' => transform::yesno($record->anonymousresponse == FEEDBACK_ANONYMOUS_YES),
'timemodified' => transform::datetime($record->timemodified),
'answers' => []
];
}
$item = static::extract_item_record_from_record($record);
$value = static::extract_value_record_from_record($record);
$itemobj = feedback_get_item_class($record->itemtyp);
$data->submissions[$id]['answers'][] = [
'question' => format_text($record->itemname, FORMAT_HTML, [
'context' => context::instance_by_id($record->contextid),
'para' => false,
'noclean' => true,
]),
'answer' => $itemobj->get_printval($item, $value)
];
$lastctxid = $record->contextid;
}
if (!empty($lastctxid)) {
$flushdata(context::instance_by_id($lastctxid), $data);
}
$recordset->close();
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
// This should not happen, but just in case.
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
// Prepare SQL to gather all completed IDs.
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE cm.id = :cmid";
$completedparams = ['cmid' => $context->instanceid, 'feedback' => 'feedback'];
// Delete temp answers and submissions.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}
// Delete answers and submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
// Ensure that we only act on module contexts.
$contextids = array_map(function($context) {
return $context->instanceid;
}, array_filter($contextlist->get_contexts(), function($context) {
return $context->contextlevel == CONTEXT_MODULE;
}));
// Prepare SQL to gather all completed IDs.
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$completedsql = "
SELECT fc.id
FROM {%s} fc
JOIN {modules} m
ON m.name = :feedback
JOIN {course_modules} cm
ON cm.instance = fc.feedback
AND cm.module = m.id
WHERE fc.userid = :userid
AND cm.id $insql";
$completedparams = array_merge($inparams, ['userid' => $userid, 'feedback' => 'feedback']);
// Delete all submissions in progress.
$completedtmpids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completedtmp'), $completedparams);
if (!empty($completedtmpids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedtmpids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_valuetmp', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completedtmp', "id $insql", $inparams);
}
// Delete all final submissions.
$completedids = $DB->get_fieldset_sql(sprintf($completedsql, 'feedback_completed'), $completedparams);
if (!empty($completedids)) {
list($insql, $inparams) = $DB->get_in_or_equal($completedids, SQL_PARAMS_NAMED);
$DB->delete_records_select('feedback_value', "completed $insql", $inparams);
$DB->delete_records_select('feedback_completed', "id $insql", $inparams);
}
}
/**
* Extract an item record from a database record.
*
* @param stdClass $record The record.
* @return The item record.
*/
protected static function extract_item_record_from_record(stdClass $record) {
$newrec = new stdClass();
foreach ($record as $key => $value) {
if (strpos($key, 'item') !== 0) {
continue;
}
$key = substr($key, 4);
$newrec->{$key} = $value;
}
return $newrec;
}
/**
* Extract a value record from a database record.
*
* @param stdClass $record The record.
* @return The value record.
*/
protected static function extract_value_record_from_record(stdClass $record) {
$newrec = new stdClass();
foreach ($record as $key => $value) {
if (strpos($key, 'value') !== 0) {
continue;
}
$key = substr($key, 5);
$newrec->{$key} = $value;
}
return $newrec;
}
/**
* Prepare the query to export all data.
*
* Doing it this way allows for merging all records from both the temporary and final tables
* as most of their columns are shared. It is a lot easier to deal with the records when
* exporting as we do not need to try to manually group the two types of submissions in the
* same reported dataset.
*
* The ordering may affect performance on large datasets.
*
* @param array $contextids The context IDs.
* @param int $userid The user ID.
* @return array With SQL and params.
*/
protected static function prepare_export_query(array $contextids, $userid) {
global $DB;
$makefetchsql = function($istmp) use ($DB, $contextids, $userid) {
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$i = $istmp ? 0 : 1;
$istmpsqlval = $istmp ? 1 : 0;
$prefix = $istmp ? 'idtmp' : 'id';
$uniqid = $DB->sql_concat("'$prefix'", 'fc.id');
$sql = "
SELECT $uniqid AS uniqid,
f.id AS feedbackid,
ctx.id AS contextid,
$istmpsqlval AS istmp,
fc.id AS submissionid,
fc.anonymous_response AS anonymousresponse,
fc.timemodified AS timemodified,
fv.id AS valueid,
fv.course_id AS valuecourse_id,
fv.item AS valueitem,
fv.completed AS valuecompleted,
fv.tmp_completed AS valuetmp_completed,
fv.value AS valuevalue,
fi.id AS itemid,
fi.feedback AS itemfeedback,
fi.template AS itemtemplate,
fi.name AS itemname,
fi.label AS itemlabel,
fi.presentation AS itempresentation,
fi.typ AS itemtyp,
fi.hasvalue AS itemhasvalue,
fi.position AS itemposition,
fi.required AS itemrequired,
fi.dependitem AS itemdependitem,
fi.dependvalue AS itemdependvalue,
fi.options AS itemoptions,
$ctxfields
FROM {context} ctx
JOIN {course_modules} cm
ON cm.id = ctx.instanceid
JOIN {feedback} f
ON f.id = cm.instance
JOIN {%s} fc
ON fc.feedback = f.id
JOIN {%s} fv
ON fv.completed = fc.id
JOIN {feedback_item} fi
ON fi.id = fv.item
WHERE ctx.id $insql
AND fc.userid = :userid{$i}";
$params = array_merge($inparams, [
'userid' . $i => $userid,
]);
$completedtbl = $istmp ? 'feedback_completedtmp' : 'feedback_completed';
$valuetbl = $istmp ? 'feedback_valuetmp' : 'feedback_value';
return [sprintf($sql, $completedtbl, $valuetbl), $params];
};
list($nontmpsql, $nontmpparams) = $makefetchsql(false);
list($tmpsql, $tmpparams) = $makefetchsql(true);
$sql = "
SELECT q.*
FROM ($nontmpsql UNION $tmpsql) q
ORDER BY q.contextid, q.istmp, q.submissionid, q.valueid";
$params = array_merge($nontmpparams, $tmpparams);
return [$sql, $params];
}
}

View File

@ -218,6 +218,14 @@ $string['pluginadministration'] = 'Feedback administration';
$string['pluginname'] = 'Feedback';
$string['position'] = 'Position';
$string['previous_page'] = 'Previous page';
$string['privacy:metadata:completed'] = 'A record of the submissions to the feedback';
$string['privacy:metadata:completed:anonymousresponse'] = 'Whether the submission is to be used anonymously.';
$string['privacy:metadata:completed:timemodified'] = 'The time at which the submission was last modified.';
$string['privacy:metadata:completed:userid'] = 'The user ID';
$string['privacy:metadata:completedtmp'] = 'A record of the submissions which are still in progress.';
$string['privacy:metadata:value'] = 'A record of the answer to a question.';
$string['privacy:metadata:value:value'] = 'The chosen answer.';
$string['privacy:metadata:valuetmp'] = 'A record of the answer to a question in a submission in progress.';
$string['public'] = 'Public';
$string['question'] = 'Question';
$string['questionandsubmission'] = 'Question and submission settings';

View File

@ -0,0 +1,432 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Data provider tests.
*
* @package mod_feedback
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use mod_feedback\privacy\provider;
require_once($CFG->dirroot . '/mod/feedback/lib.php');
/**
* Data provider testcase class.
*
* @package mod_feedback
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_feedback_privacy_testcase extends provider_testcase {
public function setUp() {
$this->resetAfterTest();
}
/**
* Test getting the contexts for a user.
*/
public function test_get_contexts_for_userid() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');
$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0a = $dg->create_module('feedback', ['course' => SITEID]);
$cm1a = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$cm1b = $dg->create_module('feedback', ['course' => $c1]);
$cm2a = $dg->create_module('feedback', ['course' => $c2]);
$cm2b = $dg->create_module('feedback', ['course' => $c2]);
$cm2c = $dg->create_module('feedback', ['course' => $c2]);
$u1 = $dg->create_user();
$u2 = $dg->create_user();
foreach ([$cm0a, $cm1a, $cm1b, $cm2a] as $feedback) {
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
if ($feedback == $cm1b) {
$this->create_submission_with_answers($feedback, $u2, $answers);
} else {
$this->create_submission_with_answers($feedback, $u1, $answers);
}
}
// Unsaved submission for u1 in cm2b.
$feedback = $cm2b;
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
$this->create_tmp_submission_with_answers($feedback, $u1, $answers);
// Unsaved submission for u2 in cm2c.
$feedback = $cm2c;
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
$this->create_tmp_submission_with_answers($feedback, $u2, $answers);
$contextids = provider::get_contexts_for_userid($u1->id)->get_contextids();
$this->assertCount(4, $contextids);
$this->assertTrue(in_array(context_module::instance($cm0a->cmid)->id, $contextids));
$this->assertTrue(in_array(context_module::instance($cm1a->cmid)->id, $contextids));
$this->assertTrue(in_array(context_module::instance($cm2a->cmid)->id, $contextids));
$this->assertFalse(in_array(context_module::instance($cm1b->cmid)->id, $contextids));
$this->assertTrue(in_array(context_module::instance($cm2b->cmid)->id, $contextids));
$this->assertFalse(in_array(context_module::instance($cm2c->cmid)->id, $contextids));
$contextids = provider::get_contexts_for_userid($u2->id)->get_contextids();
$this->assertCount(2, $contextids);
$this->assertFalse(in_array(context_module::instance($cm0a->cmid)->id, $contextids));
$this->assertFalse(in_array(context_module::instance($cm1a->cmid)->id, $contextids));
$this->assertFalse(in_array(context_module::instance($cm2a->cmid)->id, $contextids));
$this->assertTrue(in_array(context_module::instance($cm1b->cmid)->id, $contextids));
$this->assertFalse(in_array(context_module::instance($cm2b->cmid)->id, $contextids));
$this->assertTrue(in_array(context_module::instance($cm2c->cmid)->id, $contextids));
}
/**
* Test deleting user data.
*/
public function test_delete_data_for_user() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');
$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0a = $dg->create_module('feedback', ['course' => SITEID]);
$cm1a = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$cm2a = $dg->create_module('feedback', ['course' => $c2]);
$u1 = $dg->create_user();
$u2 = $dg->create_user();
// Create a bunch of data.
foreach ([$cm1a, $cm0a, $cm2a] as $feedback) {
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
// Create u2 user data for this module.
if ($feedback == $cm1a) {
$this->create_submission_with_answers($feedback, $u2, $answers);
$this->create_tmp_submission_with_answers($feedback, $u2, $answers);
}
$this->create_submission_with_answers($feedback, $u1, $answers);
$this->create_tmp_submission_with_answers($feedback, $u1, $answers);
}
$appctx = new approved_contextlist($u1, 'mod_feedback', [
context_module::instance($cm0a->cmid)->id,
context_module::instance($cm1a->cmid)->id
]);
provider::delete_data_for_user($appctx);
// Confirm all data is gone in those, except for u2.
foreach ([$cm0a, $cm1a] as $feedback) {
$this->assert_no_feedback_data_for_user($feedback, $u1);
if ($feedback == $cm1a) {
$this->assert_feedback_data_for_user($feedback, $u2);
$this->assert_feedback_tmp_data_for_user($feedback, $u2);
}
}
// Confirm cm2a wasn't affected.
$this->assert_feedback_data_for_user($cm2a, $u1);
$this->assert_feedback_tmp_data_for_user($cm2a, $u1);
}
/**
* Test deleting a whole context.
*/
public function test_delete_data_for_all_users_in_context() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');
$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0a = $dg->create_module('feedback', ['course' => SITEID]);
$cm1a = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$u1 = $dg->create_user();
$u2 = $dg->create_user();
// Create a bunch of data.
foreach ([$cm1a, $cm0a] as $feedback) {
$i1 = $fg->create_item_numeric($feedback);
$i2 = $fg->create_item_multichoice($feedback);
$answers = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
$this->create_submission_with_answers($feedback, $u1, $answers);
$this->create_tmp_submission_with_answers($feedback, $u1, $answers);
$this->create_submission_with_answers($feedback, $u2, $answers);
$this->create_tmp_submission_with_answers($feedback, $u2, $answers);
}
provider::delete_data_for_all_users_in_context(context_module::instance($cm1a->cmid));
$this->assert_no_feedback_data_for_user($cm1a, $u1);
$this->assert_no_feedback_data_for_user($cm1a, $u2);
$this->assert_feedback_data_for_user($cm0a, $u1);
$this->assert_feedback_data_for_user($cm0a, $u2);
$this->assert_feedback_tmp_data_for_user($cm0a, $u1);
$this->assert_feedback_tmp_data_for_user($cm0a, $u2);
}
/**
* Test exporting data.
*/
public function test_export_user_data() {
global $DB;
$dg = $this->getDataGenerator();
$fg = $dg->get_plugin_generator('mod_feedback');
$c1 = $dg->create_course();
$c2 = $dg->create_course();
$cm0a = $dg->create_module('feedback', ['course' => SITEID]);
$cm1a = $dg->create_module('feedback', ['course' => $c1, 'anonymous' => FEEDBACK_ANONYMOUS_NO]);
$cm2a = $dg->create_module('feedback', ['course' => $c2, 'anonymous' => FEEDBACK_ANONYMOUS_YES, 'multiple_submit' => 1]);
$cm2b = $dg->create_module('feedback', ['course' => $c2]);
$cm2c = $dg->create_module('feedback', ['course' => $c2]);
$u1 = $dg->create_user();
$u2 = $dg->create_user();
// Create a bunch of data.
foreach ([$cm0a, $cm1a, $cm2a, $cm2b] as $feedback) {
$i1 = $fg->create_item_numeric($feedback, ['name' => 'Q1', 'label' => 'L1']);
$i2 = $fg->create_item_multichoice($feedback, ['name' => 'Q2', 'label' => 'L2']);
$answersu1 = ['numeric_' . $i1->id => '1', 'multichoice_' . $i2->id => [1]];
$answersu2 = ['numeric_' . $i1->id => '2', 'multichoice_' . $i2->id => [2]];
if ($cm0a == $feedback) {
$this->create_submission_with_answers($feedback, $u1, $answersu1);
$this->create_tmp_submission_with_answers($feedback, $u1, $answersu1);
} else if ($cm1a == $feedback) {
$this->create_tmp_submission_with_answers($feedback, $u1, $answersu1);
} else if ($cm2a == $feedback) {
$this->create_submission_with_answers($feedback, $u1, $answersu1);
$this->create_submission_with_answers($feedback, $u1, ['numeric_' . $i1->id => '1337'], 2);
} else if ($cm2c == $feedback) {
$this->create_submission_with_answers($feedback, $u1, $answersu1);
$this->create_tmp_submission_with_answers($feedback, $u1, $answersu1);
}
$this->create_submission_with_answers($feedback, $u2, $answersu2);
$this->create_tmp_submission_with_answers($feedback, $u2, $answersu2);
}
$appctx = new approved_contextlist($u1, 'mod_feedback', [
context_module::instance($cm0a->cmid)->id,
context_module::instance($cm1a->cmid)->id,
context_module::instance($cm2a->cmid)->id,
context_module::instance($cm2b->cmid)->id,
]);
provider::export_user_data($appctx);
// CM0A.
$data = writer::with_context(context_module::instance($cm0a->cmid))->get_data();
$this->assertCount(2, $data->submissions);
$submission = $data->submissions[0];
$this->assertEquals(transform::yesno(false), $submission['inprogress']);
$this->assertEquals(transform::yesno(true), $submission['anonymousresponse']);
$this->assertCount(2, $submission['answers']);
$this->assertEquals('Q1', $submission['answers'][0]['question']);
$this->assertEquals('1', $submission['answers'][0]['answer']);
$this->assertEquals('Q2', $submission['answers'][1]['question']);
$this->assertEquals('a', $submission['answers'][1]['answer']);
$submission = $data->submissions[1];
$this->assertEquals(transform::yesno(true), $submission['inprogress']);
$this->assertEquals(transform::yesno(true), $submission['anonymousresponse']);
$this->assertCount(2, $submission['answers']);
$this->assertEquals('Q1', $submission['answers'][0]['question']);
$this->assertEquals('1', $submission['answers'][0]['answer']);
$this->assertEquals('Q2', $submission['answers'][1]['question']);
$this->assertEquals('a', $submission['answers'][1]['answer']);
// CM1A.
$data = writer::with_context(context_module::instance($cm1a->cmid))->get_data();
$this->assertCount(1, $data->submissions);
$submission = $data->submissions[0];
$this->assertEquals(transform::yesno(true), $submission['inprogress']);
$this->assertEquals(transform::yesno(false), $submission['anonymousresponse']);
$this->assertCount(2, $submission['answers']);
$this->assertEquals('Q1', $submission['answers'][0]['question']);
$this->assertEquals('1', $submission['answers'][0]['answer']);
$this->assertEquals('Q2', $submission['answers'][1]['question']);
$this->assertEquals('a', $submission['answers'][1]['answer']);
// CM2A.
$data = writer::with_context(context_module::instance($cm2a->cmid))->get_data();
$this->assertCount(2, $data->submissions);
$submission = $data->submissions[0];
$this->assertEquals(transform::yesno(false), $submission['inprogress']);
$this->assertEquals(transform::yesno(true), $submission['anonymousresponse']);
$this->assertCount(2, $submission['answers']);
$this->assertEquals('Q1', $submission['answers'][0]['question']);
$this->assertEquals('1', $submission['answers'][0]['answer']);
$this->assertEquals('Q2', $submission['answers'][1]['question']);
$this->assertEquals('a', $submission['answers'][1]['answer']);
$submission = $data->submissions[1];
$this->assertEquals(transform::yesno(false), $submission['inprogress']);
$this->assertEquals(transform::yesno(true), $submission['anonymousresponse']);
$this->assertCount(1, $submission['answers']);
$this->assertEquals('Q1', $submission['answers'][0]['question']);
$this->assertEquals('1337', $submission['answers'][0]['answer']);
// CM2B (no data).
$data = writer::with_context(context_module::instance($cm2b->cmid))->get_data();
$this->assertEmpty($data);
// CM2C (not exported).
$data = writer::with_context(context_module::instance($cm2b->cmid))->get_data();
$this->assertEmpty($data);
}
/**
* Assert there is no feedback data for a user.
*
* @param object $feedback The feedback.
* @param object $user The user.
* @return void
*/
protected function assert_no_feedback_data_for_user($feedback, $user) {
global $DB;
$this->assertFalse($DB->record_exists('feedback_completed', ['feedback' => $feedback->id, 'userid' => $user->id]));
$this->assertFalse($DB->record_exists('feedback_completedtmp', ['feedback' => $feedback->id, 'userid' => $user->id]));
// Check that there aren't orphan values because we can't check by userid.
$sql = "
SELECT fv.id
FROM {%s} fv
LEFT JOIN {%s} fc
ON fc.id = fv.completed
WHERE fc.id IS NULL";
$this->assertFalse($DB->record_exists_sql(sprintf($sql, 'feedback_value', 'feedback_completed'), []));
$this->assertFalse($DB->record_exists_sql(sprintf($sql, 'feedback_valuetmp', 'feedback_completedtmp'), []));
}
/**
* Assert there are submissions and answers for user.
*
* @param object $feedback The feedback.
* @param object $user The user.
* @param int $submissioncount The number of submissions.
* @param int $valuecount The number of values per submission.
* @return void
*/
protected function assert_feedback_data_for_user($feedback, $user, $submissioncount = 1, $valuecount = 2) {
global $DB;
$completeds = $DB->get_records('feedback_completed', ['feedback' => $feedback->id, 'userid' => $user->id]);
$this->assertCount($submissioncount, $completeds);
foreach ($completeds as $record) {
$this->assertEquals($valuecount, $DB->count_records('feedback_value', ['completed' => $record->id]));
}
}
/**
* Assert there are temporary submissions and answers for user.
*
* @param object $feedback The feedback.
* @param object $user The user.
* @param int $submissioncount The number of submissions.
* @param int $valuecount The number of values per submission.
* @return void
*/
protected function assert_feedback_tmp_data_for_user($feedback, $user, $submissioncount = 1, $valuecount = 2) {
global $DB;
$completedtmps = $DB->get_records('feedback_completedtmp', ['feedback' => $feedback->id, 'userid' => $user->id]);
$this->assertCount($submissioncount, $completedtmps);
foreach ($completedtmps as $record) {
$this->assertEquals($valuecount, $DB->count_records('feedback_valuetmp', ['completed' => $record->id]));
}
}
/**
* Create an submission with answers.
*
* @param object $feedback The feedback.
* @param object $user The user.
* @param array $answers Answers.
* @param int $submissioncount The number of submissions expected after this entry.
* @return void
*/
protected function create_submission_with_answers($feedback, $user, $answers, $submissioncount = 1) {
global $DB, $USER;
$origuser = $USER;
$this->setUser($user);
$modinfo = get_fast_modinfo($feedback->course);
$cm = $modinfo->get_cm($feedback->cmid);
$feedbackcompletion = new mod_feedback_completion($feedback, $cm, $feedback->course);
$feedbackcompletion->save_response_tmp((object) $answers);
$feedbackcompletion->save_response();
$this->assertEquals($submissioncount, $DB->count_records('feedback_completed', ['feedback' => $feedback->id,
'userid' => $user->id]));
$this->assertEquals(count($answers), $DB->count_records('feedback_value', [
'completed' => $feedbackcompletion->get_completed()->id]));
$this->setUser($origuser);
}
/**
* Create a temporary submission with answers.
*
* @param object $feedback The feedback.
* @param object $user The user.
* @param array $answers Answers.
* @return void
*/
protected function create_tmp_submission_with_answers($feedback, $user, $answers) {
global $DB, $USER;
$origuser = $USER;
$this->setUser($user);
$modinfo = get_fast_modinfo($feedback->course);
$cm = $modinfo->get_cm($feedback->cmid);
$feedbackcompletion = new mod_feedback_completion($feedback, $cm, $feedback->course);
$feedbackcompletion->save_response_tmp((object) $answers);
$this->assertEquals(1, $DB->count_records('feedback_completedtmp', ['feedback' => $feedback->id, 'userid' => $user->id]));
$this->assertEquals(2, $DB->count_records('feedback_valuetmp', [
'completed' => $feedbackcompletion->get_current_completed_tmp()->id]));
$this->setUser($origuser);
}
}