Merge branch 'MDL-61407-master' of git://github.com/andrewnicols/moodle

This commit is contained in:
Jake Dallimore 2018-05-04 11:50:26 +08:00
commit 209f6e1eda
123 changed files with 5000 additions and 33 deletions

View file

@ -386,6 +386,29 @@ $string['penaltyforeachincorrecttry_help'] = 'When questions are run using the \
The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.'; The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.';
$string['previewquestion'] = 'Preview question: {$a}'; $string['previewquestion'] = 'Preview question: {$a}';
$string['privacy:metadata:database:question'] = 'The details about an specific question.';
$string['privacy:metadata:database:question:createdby'] = 'The person who created the question.';
$string['privacy:metadata:database:question:generalfeedback'] = 'The general feedback for this question.';
$string['privacy:metadata:database:question:modifiedby'] = 'The person who last updated the question.';
$string['privacy:metadata:database:question:name'] = 'The name of the question.';
$string['privacy:metadata:database:question:questiontext'] = 'The question text.';
$string['privacy:metadata:database:question:timecreated'] = 'The date and time when this question was created.';
$string['privacy:metadata:database:question:timemodified'] = 'The date and time when this question was updated.';
$string['privacy:metadata:database:question_attempt_step_data'] = 'Question attempt steps may have additional data specific to that step. The data is stored in the step_data table.';
$string['privacy:metadata:database:question_attempt_step_data:name'] = 'The name of the data item.';
$string['privacy:metadata:database:question_attempt_step_data:value'] = 'The value of the data item.';
$string['privacy:metadata:database:question_attempt_steps'] = 'Each question attempt has a number of steps to indicate the different phases from beginning to completion to marking. This table stores the information for each of these steps.';
$string['privacy:metadata:database:question_attempt_steps:fraction'] = 'The grade that was awarded to this question attempt scaled to a value out of 1.';
$string['privacy:metadata:database:question_attempt_steps:state'] = 'The state of this question attempt step at the end of the step transition.';
$string['privacy:metadata:database:question_attempt_steps:timecreated'] = 'The date and time that this step transition begun.';
$string['privacy:metadata:database:question_attempt_steps:userid'] = 'The user who performed the step transition.';
$string['privacy:metadata:database:question_attempts'] = 'The information about an attempt at a specific question.';
$string['privacy:metadata:database:question_attempts:flagged'] = 'An indication that the user has flagged this question within the attempt.';
$string['privacy:metadata:database:question_attempts:responsesummary'] = 'A summary of the question response.';
$string['privacy:metadata:database:question_attempts:timemodified'] = 'The time that the question attempt was updated.';
$string['privacy:metadata:link:qbehaviour'] = 'The Question subsystem makes use of the Question Behaviour plugintype.';
$string['privacy:metadata:link:qformat'] = 'The Question subsystem makes use of the Question Format plugintype for the purpose of importing and exporting questions in different formats.';
$string['privacy:metadata:link:qtype'] = 'The Question subsystem interacts with the Question Type plugintype which contains the different types of questions.';
$string['questionbehaviouradminsetting'] = 'Question behaviour settings'; $string['questionbehaviouradminsetting'] = 'Question behaviour settings';
$string['questionbehavioursdisabled'] = 'Question behaviours to disable'; $string['questionbehavioursdisabled'] = 'Question behaviours to disable';
$string['questionbehavioursdisabledexplained'] = 'Enter a comma separated list of behaviours you do not want to appear in dropdown menu'; $string['questionbehavioursdisabledexplained'] = 'Enter a comma separated list of behaviours you do not want to appear in dropdown menu';
@ -422,6 +445,7 @@ $string['specificfeedback_help'] = 'Feedback that depends on what response the s
$string['started'] = 'Started'; $string['started'] = 'Started';
$string['state'] = 'State'; $string['state'] = 'State';
$string['step'] = 'Step'; $string['step'] = 'Step';
$string['steps'] = 'Steps';
$string['submissionoutofsequence'] = 'Access out of sequence. Please do not click the back button when working on quiz questions.'; $string['submissionoutofsequence'] = 'Access out of sequence. Please do not click the back button when working on quiz questions.';
$string['submissionoutofsequencefriendlymessage'] = "You have entered data outside the normal sequence. This can occur if you use your browser's Back or Forward buttons; please don't use these during the test. It can also happen if you click on something while a page is loading. Click <strong>Continue</strong> to resume."; $string['submissionoutofsequencefriendlymessage'] = "You have entered data outside the normal sequence. This can occur if you use your browser's Back or Forward buttons; please don't use these during the test. It can also happen if you click on something while a page is loading. Click <strong>Continue</strong> to resume.";
$string['submit'] = 'Submit'; $string['submit'] = 'Submit';
@ -450,4 +474,3 @@ $string['whichtries'] = 'Which tries';
$string['withselected'] = 'With selected'; $string['withselected'] = 'With selected';
$string['xoutofmax'] = '{$a->mark} out of {$a->max}'; $string['xoutofmax'] = '{$a->mark} out of {$a->max}';
$string['yougotnright'] = 'You have correctly selected {$a->num}.'; $string['yougotnright'] = 'You have correctly selected {$a->num}.';

View file

@ -2073,7 +2073,7 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f
} }
// export data to moodle file pool // export data to moodle file pool
if (!$content = $qformat->exportprocess(true)) { if (!$content = $qformat->exportprocess()) {
send_file_not_found(); send_file_not_found();
} }

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_delaybetweenattempts.
*
* @package quizaccess_delaybetweenattempts
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_delaybetweenattempts\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_delaybetweenattempts implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,5 +28,6 @@ defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'Delay between attempts quiz access rule'; $string['pluginname'] = 'Delay between attempts quiz access rule';
$string['privacy:metadata'] = 'The Delay between attempts quiz access rule plugin does not store any personal data.';
$string['youcannotwait'] = 'This quiz closes before you will be allowed to start another attempt.'; $string['youcannotwait'] = 'This quiz closes before you will be allowed to start another attempt.';
$string['youmustwait'] = 'You must wait before you may re-attempt this quiz. You will be allowed to start another attempt after {$a}.'; $string['youmustwait'] = 'You must wait before you may re-attempt this quiz. You will be allowed to start another attempt after {$a}.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_ipaddress.
*
* @package quizaccess_ipaddress
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_ipaddress\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_ipaddress implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,4 +28,5 @@ defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'IP address quiz access rule'; $string['pluginname'] = 'IP address quiz access rule';
$string['privacy:metadata'] = 'The IP address quiz access rule plugin does not store any personal data.';
$string['subnetwrong'] = 'This quiz is only accessible from certain locations, and this computer is not on the allowed list.'; $string['subnetwrong'] = 'This quiz is only accessible from certain locations, and this computer is not on the allowed list.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_numattempts.
*
* @package quizaccess_numattempts
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_numattempts\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_numattempts implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -29,3 +29,4 @@ defined('MOODLE_INTERNAL') || die();
$string['attemptsallowedn'] = 'Attempts allowed: {$a}'; $string['attemptsallowedn'] = 'Attempts allowed: {$a}';
$string['pluginname'] = 'Number of attempts quiz access rule'; $string['pluginname'] = 'Number of attempts quiz access rule';
$string['privacy:metadata'] = 'The Number of attempts quiz access rule plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_offlineattempts.
*
* @package quizaccess_offlineattempts
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_offlineattempts\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_offlineattempts implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -34,4 +34,4 @@ $string['offlineattemptserror'] = 'It is not possible for a quiz to be attempted
$string['offlinedatamessage'] = 'You have worked on this attempt using a mobile device. Data was last saved to this site {$a} ago.'; $string['offlinedatamessage'] = 'You have worked on this attempt using a mobile device. Data was last saved to this site {$a} ago.';
$string['pleaseconfirm'] = 'Please check and confirm that you do not have any unsaved work.'; $string['pleaseconfirm'] = 'Please check and confirm that you do not have any unsaved work.';
$string['pluginname'] = 'Offline attempts access rule'; $string['pluginname'] = 'Offline attempts access rule';
$string['privacy:metadata'] = 'The Offline attempts quiz access rule plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_openclosedate.
*
* @package quizaccess_openclosedate
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_openclosedate\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_openclosedate implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -29,4 +29,5 @@ defined('MOODLE_INTERNAL') || die();
$string['notavailable'] = 'This quiz is not currently available'; $string['notavailable'] = 'This quiz is not currently available';
$string['pluginname'] = 'Open and close date access rule'; $string['pluginname'] = 'Open and close date access rule';
$string['privacy:metadata'] = 'The Open and close date quiz access rule plugin does not store any personal data.';
$string['quiznotavailable'] = 'The quiz will not be available until {$a}'; $string['quiznotavailable'] = 'The quiz will not be available until {$a}';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_password.
*
* @package quizaccess_password
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_password\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_password implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,5 +28,6 @@ defined('MOODLE_INTERNAL') || die();
$string['passworderror'] = 'The password entered was incorrect'; $string['passworderror'] = 'The password entered was incorrect';
$string['pluginname'] = 'Password quiz access rule'; $string['pluginname'] = 'Password quiz access rule';
$string['privacy:metadata'] = 'The Password quiz access rule plugin does not store any personal data.';
$string['quizpassword'] = 'Quiz password'; $string['quizpassword'] = 'Quiz password';
$string['requirepasswordmessage'] = 'To attempt this quiz you need to know the quiz password'; $string['requirepasswordmessage'] = 'To attempt this quiz you need to know the quiz password';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_safebrowser.
*
* @package quizaccess_safebrowser
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_safebrowser\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_safebrowser implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'Safe Exam Browser quiz access rule'; $string['pluginname'] = 'Safe Exam Browser quiz access rule';
$string['privacy:metadata'] = 'The Safe Exam Browser quiz access rule plugin does not store any personal data.';
$string['requiresafeexambrowser'] = 'Require the use of Safe Exam Browser'; $string['requiresafeexambrowser'] = 'Require the use of Safe Exam Browser';
$string['safebrowsererror'] = 'This quiz has been set up so that it may only be attempted using the Safe Exam Browser. You cannot attempt it from this web browser.'; $string['safebrowsererror'] = 'This quiz has been set up so that it may only be attempted using the Safe Exam Browser. You cannot attempt it from this web browser.';
$string['safebrowsernotice'] = 'This quiz has been configured so that students may only attempt it using the Safe Exam Browser.'; $string['safebrowsernotice'] = 'This quiz has been configured so that students may only attempt it using the Safe Exam Browser.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_securewindow.
*
* @package quizaccess_securewindow
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_securewindow\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_securewindow implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -29,3 +29,4 @@ defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'JavaScript security quiz access rule'; $string['pluginname'] = 'JavaScript security quiz access rule';
$string['popupwithjavascriptsupport'] = 'Full screen pop-up with some JavaScript security'; $string['popupwithjavascriptsupport'] = 'Full screen pop-up with some JavaScript security';
$string['privacy:metadata'] = 'The JavaScript security quiz access rule plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quizaccess_timelimit.
*
* @package quizaccess_timelimit
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_timelimit\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quizaccess_timelimit implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -30,4 +30,5 @@ defined('MOODLE_INTERNAL') || die();
$string['confirmstartheader'] = 'Timed quiz'; $string['confirmstartheader'] = 'Timed quiz';
$string['confirmstart'] = 'The quiz has a time limit of {$a}. Time will count down from the moment you start your attempt and you must submit before it expires. Are you sure that you wish to start now?'; $string['confirmstart'] = 'The quiz has a time limit of {$a}. Time will count down from the moment you start your attempt and you must submit before it expires. Are you sure that you wish to start now?';
$string['pluginname'] = 'Time limit quiz access rule'; $string['pluginname'] = 'Time limit quiz access rule';
$string['privacy:metadata'] = 'The Time limit quiz access rule plugin does not store any personal data.';
$string['quiztimelimit'] = 'Time limit: {$a}'; $string['quiztimelimit'] = 'Time limit: {$a}';

View file

@ -0,0 +1,65 @@
<?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/>.
/**
* Privacy Subsystem helper for mod_quiz.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\privacy;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\deletion_criteria;
use \core_privacy\local\metadata\collection;
use \core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Privacy Subsystem implementation for mod_quiz.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Determine the subcontext for the specified quiz attempt.
*
* @param \stdClass $attempt The attempt data retrieved from the database.
* @param \stdClass $user The user record.
* @return \array The calculated subcontext.
*/
public static function get_quiz_attempt_subcontext(\stdClass $attempt, \stdClass $user) {
$subcontext = [
get_string('attempts', 'mod_quiz'),
];
if ($attempt->userid != $user->id) {
$subcontext[] = fullname($user);
}
$subcontext[] = $attempt->attempt;
return $subcontext;
}
}

View file

@ -0,0 +1,66 @@
<?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/>.
/**
* This file contains the polyfil to allow a plugin to operate with Moodle 3.3 up.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* The trait used to provide a backwards compatibility for third-party plugins.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait legacy_quizaccess_polyfill {
/**
* Export all user data for the specified user, for the specified quiz.
*
* @param \quiz $quiz The quiz being exported
* @param \stdClass $user The user to export data for
* @return \stdClass The data to be exported for this access rule.
*/
public static function export_quizaccess_user_data(\quiz $quiz, \stdClass $user) : \stdClass {
return static::_export_quizaccess_user_data($quiz, $user);
}
/**
* Delete all data for all users in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
*/
public static function delete_quizaccess_data_for_all_users_in_context(\quiz $quiz) {
static::_delete_quizaccess_data_for_all_users_in_context($quiz);
}
/**
* Delete all user data for the specified user, in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \stdClass $user The user to export data for
*/
public static function delete_quizaccess_data_for_user(\quiz $quiz, \stdClass $user) {
static::_delete_quizaccess_data_for_user($quiz, $user);
}
}

View file

@ -0,0 +1,442 @@
<?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/>.
/**
* Privacy Subsystem implementation for mod_quiz.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\privacy;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\deletion_criteria;
use \core_privacy\local\metadata\collection;
use \core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Privacy Subsystem implementation for mod_quiz.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This plugin has data.
\core_privacy\local\metadata\provider,
// This plugin currently implements the original plugin_provider interface.
\core_privacy\local\request\plugin\provider {
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param collection $items The collection to add metadata to.
* @return collection The array of metadata
*/
public static function get_metadata(collection $items) : collection {
// The table 'quiz' stores a record for each quiz.
// It does not contain user personal data, but data is returned from it for contextual requirements.
// The table 'quiz_attempts' stores a record of each quiz attempt.
// It contains a userid which links to the user making the attempt and contains information about that attempt.
$items->add_database_table('quiz_attempts', [
'attempt' => 'privacy:metadata:quiz_attempts:attempt',
'currentpage' => 'privacy:metadata:quiz_attempts:currentpage',
'preview' => 'privacy:metadata:quiz_attempts:preview',
'state' => 'privacy:metadata:quiz_attempts:state',
'timestart' => 'privacy:metadata:quiz_attempts:timestart',
'timefinish' => 'privacy:metadata:quiz_attempts:timefinish',
'timemodified' => 'privacy:metadata:quiz_attempts:timemodified',
'timemodifiedoffline' => 'privacy:metadata:quiz_attempts:timemodifiedoffline',
'timecheckstate' => 'privacy:metadata:quiz_attempts:timecheckstate',
'sumgrades' => 'privacy:metadata:quiz_attempts:sumgrades',
], 'privacy:metadata:quiz_attempts');
// The table 'quiz_feedback' contains the feedback responses which will be shown to users depending upon the
// grade they achieve in the quiz.
// It does not identify the user who wrote the feedback item so cannot be returned directly and is not
// described, but relevant feedback items will be included with the quiz export for a user who has a grade.
// The table 'quiz_grades' contains the current grade for each quiz/user combination.
$items->add_database_table('quiz_grades', [
'quiz' => 'privacy:metadata:quiz_grades:quiz',
'userid' => 'privacy:metadata:quiz_grades:userid',
'grade' => 'privacy:metadata:quiz_grades:grade',
'timemodified' => 'privacy:metadata:quiz_grades:timemodified',
], 'privacy:metadata:quiz_grades');
// The table 'quiz_overrides' contains any user or group overrides for users.
// It should be included where data exists for a user.
$items->add_database_table('quiz_overrides', [
'quiz' => 'privacy:metadata:quiz_overrides:quiz',
'userid' => 'privacy:metadata:quiz_overrides:userid',
'timeopen' => 'privacy:metadata:quiz_overrides:timeopen',
'timeclose' => 'privacy:metadata:quiz_overrides:timeclose',
'timelimit' => 'privacy:metadata:quiz_overrides:timelimit',
], 'privacy:metadata:quiz_overrides');
// These define the structure of the quiz.
// The table 'quiz_sections' contains data about the structure of a quiz.
// It does not contain any user identifying data and does not need a mapping.
// The table 'quiz_slots' contains data about the structure of a quiz.
// It does not contain any user identifying data and does not need a mapping.
// The table 'quiz_reports' does not contain any user identifying data and does not need a mapping.
// The table 'quiz_statistics' contains abstract statistics about question usage and cannot be mapped to any
// specific user.
// It does not contain any user identifying data and does not need a mapping.
// The quiz links to the 'core_question' subsystem for all question functionality.
$items->add_subsystem_link('core_question', [], 'privacy:metadata:core_question');
// The quiz has two subplugins..
$items->add_plugintype_link('quiz', [], 'privacy:metadata:quiz');
$items->add_plugintype_link('quizaccess', [], 'privacy:metadata:quizaccess');
// Although the quiz supports the core_completion API and defines custom completion items, these will be
// noted by the manager as all activity modules are capable of supporting this functionality.
return $items;
}
/**
* Get the list of contexts where the specified user has attempted a quiz, or been involved with manual marking
* and/or grading of a quiz.
*
* @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 {
// Get the SQL used to link indirect question usages for the user.
// This includes where a user is the manual marker on a question attempt.
$qubaid = \core_question\privacy\provider::get_related_question_usages_for_user('rel', 'mod_quiz', 'qa.uniqueid', $userid);
// Select the context of any quiz attempt where a user has an attempt, plus the related usages.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {quiz} q ON q.id = cm.instance
JOIN {quiz_attempts} qa ON qa.quiz = q.id
LEFT JOIN {quiz_overrides} qo ON qo.quiz = q.id AND qo.userid = :qouserid
" . $qubaid->from . "
WHERE (
qa.userid = :qauserid OR
" . $qubaid->where() . "
) AND qa.preview = 0
";
$params = array_merge(
[
'contextlevel' => CONTEXT_MODULE,
'modname' => 'quiz',
'qauserid' => $userid,
'qouserid' => $userid,
],
$qubaid->from_where_params()
);
$resultset = new contextlist();
$resultset->add_from_sql($sql, $params);
return $resultset;
}
/**
* Delete all data for all users in the specified context.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
if (!count($contextlist)) {
return;
}
$user = $contextlist->get_user();
$userid = $user->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$sql = "SELECT
q.*,
qg.id AS hasgrade,
qg.grade AS bestgrade,
qg.timemodified AS grademodified,
qo.id AS hasoverride,
qo.timeopen AS override_timeopen,
qo.timeclose AS override_timeclose,
qo.timelimit AS override_timelimit,
c.id AS contextid,
cm.id AS cmid
FROM {context} c
INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
INNER JOIN {quiz} q ON q.id = cm.instance
LEFT JOIN {quiz_overrides} qo ON qo.quiz = q.id AND qo.userid = :qouserid
LEFT JOIN {quiz_grades} qg ON qg.quiz = q.id AND qg.userid = :qguserid
WHERE c.id {$contextsql}";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'quiz',
'qguserid' => $userid,
'qouserid' => $userid,
];
$params += $contextparams;
// Fetch the individual quizzes.
$quizzes = $DB->get_recordset_sql($sql, $params);
foreach ($quizzes as $quiz) {
list($course, $cm) = get_course_and_cm_from_cmid($quiz->cmid, 'quiz');
$quizobj = new \quiz($quiz, $cm, $course);
$context = $quizobj->get_context();
$quizdata = \core_privacy\local\request\helper::get_context_data($context, $contextlist->get_user());
\core_privacy\local\request\helper::export_context_files($context, $contextlist->get_user());
if (!empty($quizdata->timeopen)) {
$quizdata->timeopen = transform::datetime($quiz->timeopen);
}
if (!empty($quizdata->timeclose)) {
$quizdata->timeclose = transform::datetime($quiz->timeclose);
}
if (!empty($quizdata->timelimit)) {
$quizdata->timelimit = $quiz->timelimit;
}
if (!empty($quiz->hasoverride)) {
$quizdata->override = (object) [];
if (!empty($quizdata->override_override_timeopen)) {
$quizdata->override->timeopen = transform::datetime($quiz->override_timeopen);
}
if (!empty($quizdata->override_timeclose)) {
$quizdata->override->timeclose = transform::datetime($quiz->override_timeclose);
}
if (!empty($quizdata->override_timelimit)) {
$quizdata->override->timelimit = $quiz->override_timelimit;
}
}
$quizdata->accessdata = (object) [];
$components = \core_component::get_plugin_list('quizaccess');
$exportparams = [
$quizobj,
$user,
];
foreach (array_keys($components) as $component) {
$classname = manager::get_provider_classname_for_component("quizaccess_$component");
if (class_exists($classname) && is_subclass_of($classname, quizaccess_provider::class)) {
$result = component_class_callback($classname, 'export_quizaccess_user_data', $exportparams);
if (count((array) $result)) {
$quizdata->accessdata->$component = $result;
}
}
}
if (empty((array) $quizdata->accessdata)) {
unset($quizdata->accessdata);
}
writer::with_context($context)
->export_data([], $quizdata);
}
$quizzes->close();
// Store all quiz attempt data.
static::export_quiz_attempts($contextlist);
}
/**
* 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) {
$cm = get_coursemodule_from_id('quiz', $context->instanceid);
if (!$cm) {
// Only quiz module will be handled.
return;
}
$quiz = \quiz::create($cm->instance);
// Handle the 'quizaccess' subplugin.
manager::plugintype_class_callback(
'quizaccess',
quizaccess_provider::class,
'delete_subplugin_data_for_all_users_in_context',
[$quiz]
);
// Delete all overrides - do not log.
quiz_delete_all_overrides($quiz, false);
// This will delete all question attempts, quiz attempts, and quiz grades for this quiz.
quiz_delete_all_attempts($quiz);
}
/**
* 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;
foreach ($contextlist as $context) {
$cm = get_coursemodule_from_id('quiz', $context->instanceid);
$quiz = \quiz::create($cm->instance);
$user = $contextlist->get_user();
// Handle the 'quizaccess' quizaccess.
manager::plugintype_class_callback(
'quizaccess',
quizaccess_provider::class,
'delete_quizaccess_data_for_user',
[$quiz, $user]
);
$overrides = $DB->get_records('quiz_overrides' , [
'quiz' => $quiz->get_quizid(),
'userid' => $user->id,
]);
foreach ($overrides as $override) {
quiz_delete_override($quiz, $override->id, false);
}
// This will delete all question attempts, quiz attempts, and quiz grades for this quiz.
quiz_delete_user_attempts($quiz, $user);
}
}
/**
* Store all quiz attempts for the contextlist.
*
* @param approved_contextlist $contextlist
*/
protected static function export_quiz_attempts(approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$qubaid = \core_question\privacy\provider::get_related_question_usages_for_user('rel', 'mod_quiz', 'qa.uniqueid', $userid);
$sql = "SELECT
c.id AS contextid,
cm.id AS cmid,
qa.*
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = 'quiz'
JOIN {quiz} q ON q.id = cm.instance
JOIN {quiz_attempts} qa ON qa.quiz = q.id
" . $qubaid->from. "
WHERE (
qa.userid = :qauserid OR
" . $qubaid->where() . "
) AND qa.preview = 0
";
$params = array_merge(
[
'contextlevel' => CONTEXT_MODULE,
'qauserid' => $userid,
],
$qubaid->from_where_params()
);
$attempts = $DB->get_recordset_sql($sql, $params);
foreach ($attempts as $attempt) {
$quiz = $DB->get_record('quiz', ['id' => $attempt->quiz]);
$context = \context_module::instance($attempt->cmid);
$attemptsubcontext = helper::get_quiz_attempt_subcontext($attempt, $contextlist->get_user());
$options = quiz_get_review_options($quiz, $attempt, $context);
if ($attempt->userid == $userid) {
// This attempt was made by the user.
// They 'own' all data on it.
// Store the question usage data.
\core_question\privacy\provider::export_question_usage($userid,
$context,
$attemptsubcontext,
$attempt->uniqueid,
$options,
true
);
// Store the quiz attempt data.
$data = (object) [
'state' => \quiz_attempt::state_name($attempt->state),
];
if (!empty($attempt->timestart)) {
$data->timestart = transform::datetime($attempt->timestart);
}
if (!empty($attempt->timefinish)) {
$data->timefinish = transform::datetime($attempt->timefinish);
}
if (!empty($attempt->timemodified)) {
$data->timemodified = transform::datetime($attempt->timemodified);
}
if (!empty($attempt->timemodifiedoffline)) {
$data->timemodifiedoffline = transform::datetime($attempt->timemodifiedoffline);
}
if (!empty($attempt->timecheckstate)) {
$data->timecheckstate = transform::datetime($attempt->timecheckstate);
}
if ($options->marks == \question_display_options::MARK_AND_MAX) {
$grade = quiz_rescale_grade($attempt->sumgrades, $quiz, false);
$data->grade = (object) [
'grade' => quiz_format_grade($quiz, $grade),
'feedback' => quiz_feedback_for_grade($grade, $quiz, $context),
];
}
writer::with_context($context)
->export_data($attemptsubcontext, $data);
} else {
// This attempt was made by another user.
// The current user may have marked part of the quiz attempt.
\core_question\privacy\provider::export_question_usage(
$userid,
$context,
$attemptsubcontext,
$attempt->uniqueid,
$options,
false
);
}
}
$attempts->close();
}
}

View file

@ -0,0 +1,63 @@
<?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/>.
/**
* The quizaccess_provider interface provides the expected interface for all 'quizaccess' quizaccesss.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
/**
* The quizaccess_provider interface provides the expected interface for all 'quizaccess' quizaccesss.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface quizaccess_provider extends \core_privacy\local\request\plugin\subplugin_provider {
/**
* Export all user data for the specified user, for the specified quiz.
*
* @param \quiz $quiz The quiz being exported
* @param \stdClass $user The user to export data for
* @return \stdClass The data to be exported for this access rule.
*/
public static function export_quizaccess_user_data(\quiz $quiz, \stdClass $user) : \stdClass;
/**
* Delete all data for all users in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
*/
public static function delete_quizaccess_data_for_all_users_in_context(\quiz $quiz);
/**
* Delete all user data for the specified user, in the specified quiz.
*
* @param \quiz $quiz The quiz being deleted
* @param \stdClass $user The user to export data for
*/
public static function delete_quizaccess_data_for_user(\quiz $quiz, \stdClass $user);
}

View file

@ -626,6 +626,31 @@ $string['previewquestion'] = 'Preview question';
$string['previewquiz'] = 'Preview {$a}'; $string['previewquiz'] = 'Preview {$a}';
$string['previewquiznow'] = 'Preview quiz now'; $string['previewquiznow'] = 'Preview quiz now';
$string['previous'] = 'Previous state'; $string['previous'] = 'Previous state';
$string['privacy:metadata:core_question'] = 'The quiz activity stores question usage information in the core_question subsystem.';
$string['privacy:metadata:quiz'] = 'The quiz activity makes use of quiz reports.';
$string['privacy:metadata:quiz_attempts'] = 'Details about each attempt on a quiz.';
$string['privacy:metadata:quiz_attempts:attempt'] = 'The attempt number.';
$string['privacy:metadata:quiz_attempts:currentpage'] = 'The current page that the user is on.';
$string['privacy:metadata:quiz_attempts:preview'] = 'Whether this is a preview of the quiz.';
$string['privacy:metadata:quiz_attempts:state'] = 'The current state of the attempt.';
$string['privacy:metadata:quiz_attempts:sumgrades'] = 'The sum of grades in the attempt.';
$string['privacy:metadata:quiz_attempts:timecheckstate'] = 'The time that the state was checked.';
$string['privacy:metadata:quiz_attempts:timefinish'] = 'The time that the attempt was completed.';
$string['privacy:metadata:quiz_attempts:timemodified'] = 'The time that the attempt was updated.';
$string['privacy:metadata:quiz_attempts:timemodifiedoffline'] = 'The time that the attempt was updated via an offline update.';
$string['privacy:metadata:quiz_attempts:timestart'] = 'The time that the attempt was started.';
$string['privacy:metadata:quiz_grades'] = 'Details about the overall grade for this quiz.';
$string['privacy:metadata:quiz_grades:grade'] = 'The overall grade for this quiz.';
$string['privacy:metadata:quiz_grades:quiz'] = 'The quiz that was graded.';
$string['privacy:metadata:quiz_grades:timemodified'] = 'The time that the grade was modified.';
$string['privacy:metadata:quiz_grades:userid'] = 'The user who was graded.';
$string['privacy:metadata:quiz_overrides'] = 'Details about overrides for this quiz.';
$string['privacy:metadata:quiz_overrides:quiz'] = 'The quiz with override information.';
$string['privacy:metadata:quiz_overrides:timeclose'] = 'The new close time for the quiz.';
$string['privacy:metadata:quiz_overrides:timelimit'] = 'The new time limit for the quiz.';
$string['privacy:metadata:quiz_overrides:timeopen'] = 'The new open time for the quiz.';
$string['privacy:metadata:quiz_overrides:userid'] = 'The user being overridden.';
$string['privacy:metadata:quizaccess'] = 'The quiz activity makes use of quiz access rules.';
$string['publish'] = 'Publish'; $string['publish'] = 'Publish';
$string['publishedit'] = 'You must have permission in the publishing course to add or edit questions in this category'; $string['publishedit'] = 'You must have permission in the publishing course to add or edit questions in this category';
$string['qname'] = 'name'; $string['qname'] = 'name';

View file

@ -214,9 +214,10 @@ function quiz_delete_instance($id) {
* *
* @param object $quiz The quiz object. * @param object $quiz The quiz object.
* @param int $overrideid The id of the override being deleted * @param int $overrideid The id of the override being deleted
* @param bool $log Whether to trigger logs.
* @return bool true on success * @return bool true on success
*/ */
function quiz_delete_override($quiz, $overrideid) { function quiz_delete_override($quiz, $overrideid, $log = true) {
global $DB; global $DB;
if (!isset($quiz->cmid)) { if (!isset($quiz->cmid)) {
@ -244,26 +245,28 @@ function quiz_delete_override($quiz, $overrideid) {
$DB->delete_records('quiz_overrides', array('id' => $overrideid)); $DB->delete_records('quiz_overrides', array('id' => $overrideid));
// Set the common parameters for one of the events we will be triggering. if ($log) {
$params = array( // Set the common parameters for one of the events we will be triggering.
'objectid' => $override->id, $params = array(
'context' => context_module::instance($quiz->cmid), 'objectid' => $override->id,
'other' => array( 'context' => context_module::instance($quiz->cmid),
'quizid' => $override->quiz 'other' => array(
) 'quizid' => $override->quiz
); )
// Determine which override deleted event to fire. );
if (!empty($override->userid)) { // Determine which override deleted event to fire.
$params['relateduserid'] = $override->userid; if (!empty($override->userid)) {
$event = \mod_quiz\event\user_override_deleted::create($params); $params['relateduserid'] = $override->userid;
} else { $event = \mod_quiz\event\user_override_deleted::create($params);
$params['other']['groupid'] = $override->groupid; } else {
$event = \mod_quiz\event\group_override_deleted::create($params); $params['other']['groupid'] = $override->groupid;
} $event = \mod_quiz\event\group_override_deleted::create($params);
}
// Trigger the override deleted event. // Trigger the override deleted event.
$event->add_record_snapshot('quiz_overrides', $override); $event->add_record_snapshot('quiz_overrides', $override);
$event->trigger(); $event->trigger();
}
return true; return true;
} }
@ -272,13 +275,14 @@ function quiz_delete_override($quiz, $overrideid) {
* Deletes all quiz overrides from the database and clears any corresponding calendar events * Deletes all quiz overrides from the database and clears any corresponding calendar events
* *
* @param object $quiz The quiz object. * @param object $quiz The quiz object.
* @param bool $log Whether to trigger logs.
*/ */
function quiz_delete_all_overrides($quiz) { function quiz_delete_all_overrides($quiz, $log = true) {
global $DB; global $DB;
$overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id'); $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id');
foreach ($overrides as $override) { foreach ($overrides as $override) {
quiz_delete_override($quiz, $override->id); quiz_delete_override($quiz, $override->id, $log);
} }
} }
@ -405,6 +409,24 @@ function quiz_delete_all_attempts($quiz) {
$DB->delete_records('quiz_grades', array('quiz' => $quiz->id)); $DB->delete_records('quiz_grades', array('quiz' => $quiz->id));
} }
/**
* Delete all the attempts belonging to a user in a particular quiz.
*
* @param object $quiz The quiz object.
* @param object $user The user object.
*/
function quiz_delete_user_attempts($quiz, $user) {
global $CFG, $DB;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
question_engine::delete_questions_usage_by_activities(new qubaids_for_quiz_user($quiz->get_quizid(), $user->id));
$params = [
'quiz' => $quiz->get_quizid(),
'userid' => $user->id,
];
$DB->delete_records('quiz_attempts', $params);
$DB->delete_records('quiz_grades', $params);
}
/** /**
* Get the best current grade for a particular user in a quiz. * Get the best current grade for a particular user in a quiz.
* *

View file

@ -1972,7 +1972,6 @@ class mod_quiz_display_options extends question_display_options {
} }
} }
/** /**
* A {@link qubaid_condition} for finding all the question usages belonging to * A {@link qubaid_condition} for finding all the question usages belonging to
* a particular quiz. * a particular quiz.
@ -1998,6 +1997,41 @@ class qubaids_for_quiz extends qubaid_join {
} }
} }
/**
* A {@link qubaid_condition} for finding all the question usages belonging to a particular user and quiz combination.
*
* @copyright 2018 Andrew Nicols <andrwe@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qubaids_for_quiz_user extends qubaid_join {
/**
* Constructor for this qubaid.
*
* @param int $quizid The quiz to search.
* @param int $userid The user to filter on
* @param bool $includepreviews Whether to include preview attempts
* @param bool $onlyfinished Whether to only include finished attempts or not
*/
public function __construct($quizid, $userid, $includepreviews = true, $onlyfinished = false) {
$where = 'quiza.quiz = :quizaquiz AND quiza.userid = :quizauserid';
$params = [
'quizaquiz' => $quizid,
'quizauserid' => $userid,
];
if (!$includepreviews) {
$where .= ' AND preview = 0';
}
if ($onlyfinished) {
$where .= ' AND state = :statefinished';
$params['statefinished'] = quiz_attempt::FINISHED;
}
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);
}
}
/** /**
* Creates a textual representation of a question for display. * Creates a textual representation of a question for display.
* *

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quiz_grading.
*
* @package quiz_grading
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quiz_grading\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quiz_grading implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -68,6 +68,7 @@ $string['nothingfound'] = 'Nothing to display';
$string['options'] = 'Options'; $string['options'] = 'Options';
$string['orderattempts'] = 'Order attempts'; $string['orderattempts'] = 'Order attempts';
$string['pluginname'] = 'Manual grading'; $string['pluginname'] = 'Manual grading';
$string['privacy:metadata'] = 'The Quiz Manual grading plugin does not store any personal data. It provides an interface for users to store data without storing any data itself.';
$string['qno'] = 'Q #'; $string['qno'] = 'Q #';
$string['questionname'] = 'Question name'; $string['questionname'] = 'Question name';
$string['questionsperpage'] = 'Questions per page'; $string['questionsperpage'] = 'Questions per page';

View file

@ -0,0 +1,78 @@
<?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/>.
/**
* Privacy Subsystem implementation for quiz_overview..
*
* @package quiz_overview
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quiz_overview\privacy;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\metadata\collection;
use \core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem implementation for quiz_overview..
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @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\user_preference_provider {
/**
* Returns meta data about this system.
*
* @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 {
$collection->add_user_preference('quiz_overview_slotmarks', 'privacy:metadata:preference:quiz_overview_slotmarks');
return $collection;
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preference = get_user_preferences('quiz_overview_slotmarks', null);
if (null !== $preference) {
if (empty($preference)) {
$description = get_string('privacy:preference:slotmarks:no', 'quiz_overview');
} else {
$description = get_string('privacy:preference:slotmarks:yes', 'quiz_overview');
}
writer::export_user_preference(
'quiz_overview',
'slotmarks',
transform::yesno($preference),
$description
);
}
}
}

View file

@ -53,6 +53,9 @@ $string['pluginname'] = 'Grades';
$string['preferencespage'] = 'Preferences just for this page'; $string['preferencespage'] = 'Preferences just for this page';
$string['preferencessave'] = 'Show report'; $string['preferencessave'] = 'Show report';
$string['preferencesuser'] = 'Your preferences for this report'; $string['preferencesuser'] = 'Your preferences for this report';
$string['privacy:metadata:preference:quiz_overview_slotmarks'] = 'Whether to show marks for each question slot.';
$string['privacy:preference:slotmarks:yes'] = 'Marks are shown alongside the question slot.';
$string['privacy:preference:slotmarks:no'] = 'Marks are not shown alongside the question slot.';
$string['regrade'] = 'Regrade'; $string['regrade'] = 'Regrade';
$string['regradeall'] = 'Regrade all'; $string['regradeall'] = 'Regrade all';
$string['regradealldry'] = 'Dry run a full regrade'; $string['regradealldry'] = 'Dry run a full regrade';

View file

@ -0,0 +1,99 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package quiz_overview
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_privacy\local\metadata\collection;
use quiz_overview\privacy\provider;
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider tests class.
*
* @package quiz_overview
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_overview_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
/**
* When no preference exists, there should be no export.
*/
public function test_preference_unset() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
provider::export_user_preferences($USER->id);
$this->assertFalse(writer::with_context(\context_system::instance())->has_any_data());
}
/**
* Preference does exist.
*/
public function test_preference_yes() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('quiz_overview_slotmarks', 1);
provider::export_user_preferences($USER->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('quiz_overview');
$this->assertNotEmpty($preferences->slotmarks);
$this->assertEquals(transform::yesno(1), $preferences->slotmarks->value);
$description = get_string('privacy:preference:slotmarks:yes', 'quiz_overview');
$this->assertEquals($description, $preferences->slotmarks->description);
}
/**
* Preference does exist and is no.
*/
public function test_preference_no() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('quiz_overview_slotmarks', 0);
provider::export_user_preferences($USER->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('quiz_overview');
$this->assertNotEmpty($preferences->slotmarks);
$this->assertEquals(transform::yesno(0), $preferences->slotmarks->value);
$description = get_string('privacy:preference:slotmarks:no', 'quiz_overview');
$this->assertEquals($description, $preferences->slotmarks->description);
}
}

View file

@ -0,0 +1,99 @@
<?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/>.
/**
* Privacy Subsystem implementation for quiz_responses.
*
* @package quiz_responses
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quiz_responses\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/engine/questionattempt.php');
/**
* Privacy Subsystem for quiz_responses with user preferences.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @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\user_preference_provider {
/**
* Returns meta data about this system.
*
* @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 {
$collection->add_user_preference('quiz_report_responses_qtext', 'privacy:preference:qtext');
$collection->add_user_preference('quiz_report_responses_resp', 'privacy:preference:resp');
$collection->add_user_preference('quiz_report_responses_right', 'privacy:preference:right');
$collection->add_user_preference('quiz_report_responses_which_tries', 'privacy:preference:which_tries');
return $collection;
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preferences = [
'qtext',
'resp',
'right',
];
foreach ($preferences as $key) {
$preference = get_user_preferences("quiz_report_responses_{$key}", null, $userid);
if (null !== $preference) {
$desc = get_string("privacy:preference:{$key}", 'quiz_responses');
writer::export_user_preference('quiz_responses', $key, transform::yesno($preference), $desc);
}
}
$preference = get_user_preferences("quiz_report_responses_which_tries", null, $userid);
if (null !== $preference) {
switch($preference) {
case \question_attempt::FIRST_TRY:
$value = get_string("privacy:preference:which_tries:first", 'quiz_responses');
break;
case \question_attempt::LAST_TRY:
$value = get_string("privacy:preference:which_tries:last", 'quiz_responses');
break;
case \question_attempt::ALL_TRIES:
$value = get_string("privacy:preference:which_tries:all", 'quiz_responses');
break;
}
$desc = get_string("privacy:preference:which_tries", 'quiz_responses');
writer::export_user_preference('quiz_responses', 'which_tries', $value, $desc);
}
}
}

View file

@ -27,6 +27,13 @@ $string['include'] = 'Include the';
$string['mustselectcols'] = 'You must include something.'; $string['mustselectcols'] = 'You must include something.';
$string['pagesize'] = 'Page size'; $string['pagesize'] = 'Page size';
$string['pluginname'] = 'Responses'; $string['pluginname'] = 'Responses';
$string['privacy:preference:qtext'] = 'Whether to show the question text columns.';
$string['privacy:preference:resp'] = 'Whether to show the students\' response columns.';
$string['privacy:preference:right'] = 'Whether to show the correct response columns.';
$string['privacy:preference:which_tries'] = 'Which tries to show responses from.';
$string['privacy:preference:which_tries:first'] = 'The first try at a question during an attempt by a user.';
$string['privacy:preference:which_tries:last'] = 'The last try at a question during an attempt by a user.';
$string['privacy:preference:which_tries:all'] = 'All tries at a question during an attempt by a user.';
$string['questiontext'] = 'question text'; $string['questiontext'] = 'question text';
$string['reportresponses'] = 'Responses'; $string['reportresponses'] = 'Responses';
$string['response'] = 'response'; $string['response'] = 'response';

View file

@ -0,0 +1,139 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package quiz_responses
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_privacy\local\metadata\collection;
use quiz_responses\privacy\provider;
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/engine/questionattempt.php');
/**
* Privacy provider tests class.
*
* @package quiz_responses
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_responses_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
/**
* When no preference exists, there should be no export.
*/
public function test_preference_unset() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
provider::export_user_preferences($USER->id);
$this->assertFalse(writer::with_context(\context_system::instance())->has_any_data());
}
/**
* Preference does exist.
*/
public function test_preference_bool_true() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('quiz_report_responses_qtext', true);
set_user_preference('quiz_report_responses_resp', true);
set_user_preference('quiz_report_responses_right', true);
provider::export_user_preferences($USER->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('quiz_responses');
$this->assertNotEmpty($preferences->qtext);
$this->assertEquals(transform::yesno(1), $preferences->qtext->value);
$this->assertNotEmpty($preferences->resp);
$this->assertEquals(transform::yesno(1), $preferences->resp->value);
$this->assertNotEmpty($preferences->right);
$this->assertEquals(transform::yesno(1), $preferences->right->value);
}
/**
* Preference does exist.
*/
public function test_preference_bool_false() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('quiz_report_responses_qtext', false);
set_user_preference('quiz_report_responses_resp', false);
set_user_preference('quiz_report_responses_right', false);
provider::export_user_preferences($USER->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('quiz_responses');
$this->assertNotEmpty($preferences->qtext);
$this->assertEquals(transform::yesno(0), $preferences->qtext->value);
$this->assertNotEmpty($preferences->resp);
$this->assertEquals(transform::yesno(0), $preferences->resp->value);
$this->assertNotEmpty($preferences->right);
$this->assertEquals(transform::yesno(0), $preferences->right->value);
}
/**
* Preference does exist.
*/
public function test_preference_bool_which_first() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('quiz_report_responses_which_tries', question_attempt::FIRST_TRY);
provider::export_user_preferences($USER->id);
$writer = writer::with_context(\context_system::instance());
$this->assertTrue($writer->has_any_data());
$preferences = $writer->get_user_preferences('quiz_responses');
$expected = get_string("privacy:preference:which_tries:first", 'quiz_responses');
$this->assertNotEmpty($preferences->which_tries);
$this->assertEquals($expected, $preferences->which_tries->value);
}
}

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for quiz_statistics.
*
* @package quiz_statistics
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quiz_statistics\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for quiz_statistics implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -86,6 +86,7 @@ $string['nostudentsingroup'] = 'There are no students in this group yet';
$string['optiongrade'] = 'Partial credit'; $string['optiongrade'] = 'Partial credit';
$string['partofquestion'] = 'Part of question'; $string['partofquestion'] = 'Part of question';
$string['pluginname'] = 'Statistics'; $string['pluginname'] = 'Statistics';
$string['privacy:metadata'] = 'Although the Quiz Statistics plugin has database tables, the data is aggregate data and does not describe a unique indidividual.';
$string['position'] = 'Position'; $string['position'] = 'Position';
$string['positions'] = 'Position(s)'; $string['positions'] = 'Position(s)';
$string['questioninformation'] = 'Question information'; $string['questioninformation'] = 'Question information';
@ -115,4 +116,3 @@ $string['statistics:view'] = 'View statistics report';
$string['statsfor'] = 'Quiz statistics (for {$a})'; $string['statsfor'] = 'Quiz statistics (for {$a})';
$string['variant'] = 'Variant'; $string['variant'] = 'Variant';
$string['whichtries'] = 'Analyze responses for'; $string['whichtries'] = 'Analyze responses for';

View file

@ -0,0 +1,164 @@
<?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/>.
/**
* Unit tests for the privacy legacy polyfill for quiz access rules.
*
* @package mod_quiz
* @category test
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
/**
* Unit tests for the privacy legacy polyfill for quiz access rules.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_legacy_quizaccess_polyfill_test extends advanced_testcase {
/**
* Test that the core_quizaccess\privacy\legacy_polyfill works and that the static _export_quizaccess_user_data can
* be called.
*/
public function test_export_quizaccess_user_data() {
$quiz = $this->createMock(quiz::class);
$user = (object) [];
$returnvalue = (object) [];
$mock = $this->createMock(test_privacy_legacy_quizaccess_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_export_quizaccess_user_data', [$quiz, $user])
->willReturn($returnvalue);
test_privacy_legacy_quizaccess_polyfill_provider::$mock = $mock;
$result = test_privacy_legacy_quizaccess_polyfill_provider::export_quizaccess_user_data($quiz, $user);
$this->assertSame($returnvalue, $result);
}
/**
* Test the _delete_quizaccess_for_context shim.
*/
public function test_delete_quizaccess_for_context() {
$context = context_system::instance();
$quiz = $this->createMock(quiz::class);
$mock = $this->createMock(test_privacy_legacy_quizaccess_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_delete_quizaccess_data_for_all_users_in_context', [$quiz]);
test_privacy_legacy_quizaccess_polyfill_provider::$mock = $mock;
test_privacy_legacy_quizaccess_polyfill_provider::delete_quizaccess_data_for_all_users_in_context($quiz);
}
/**
* Test the _delete_quizaccess_for_context shim.
*/
public function test_delete_quizaccess_for_user() {
$context = context_system::instance();
$quiz = $this->createMock(quiz::class);
$user = (object) [];
$mock = $this->createMock(test_privacy_legacy_quizaccess_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_delete_quizaccess_data_for_user', [$quiz, $user]);
test_privacy_legacy_quizaccess_polyfill_provider::$mock = $mock;
test_privacy_legacy_quizaccess_polyfill_provider::delete_quizaccess_data_for_user($quiz, $user);
}
}
/**
* Legacy polyfill test class for the quizaccess_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_privacy_legacy_quizaccess_polyfill_provider implements
\core_privacy\local\metadata\provider,
\mod_quiz\privacy\quizaccess_provider {
use \mod_quiz\privacy\legacy_quizaccess_polyfill;
use \core_privacy\local\legacy_polyfill;
/**
* @var test_privacy_legacy_quizaccess_polyfill_provider $mock.
*/
public static $mock = null;
/**
* Export all user data for the quizaccess plugin.
*
* @param \quiz $quiz
* @param \stdClass $user
*/
protected static function _export_quizaccess_user_data($quiz, $user) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* Deletes all user data for the given context.
*
* @param \quiz $quiz
*/
protected static function _delete_quizaccess_data_for_all_users_in_context($quiz) {
static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* Delete personal data for the given user and context.
*
* @param \quiz $quiz The quiz being deleted
* @param \stdClass $user The user to export data for
*/
protected static function _delete_quizaccess_data_for_user($quiz, $user) {
static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* Returns metadata about this plugin.
*
* @param \core_privacy\local\metadata\collection $collection The initialised collection to add items to.
* @return \core_privacy\local\metadata\collection A listing of user data stored through this system.
*/
protected static function _get_metadata(\core_privacy\local\metadata\collection $collection) {
return $collection;
}
}
/**
* Called inside the polyfill methods in the test polyfill provider, allowing us to ensure these are called with correct params.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_privacy_legacy_quizaccess_polyfill_mock_wrapper {
/**
* Get the return value for the specified item.
*/
public function get_return_value() {
}
}

View file

@ -0,0 +1,279 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_privacy\local\metadata\collection;
use core_privacy\local\request\deletion_criteria;
use core_privacy\local\request\writer;
use mod_quiz\privacy\provider;
use mod_quiz\privacy\helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/tests/privacy_helper.php');
/**
* Privacy provider tests class.
*
* @package mod_quiz
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
use core_question_privacy_helper;
/**
* Test that a user who has no data gets no contexts
*/
public function test_get_contexts_for_userid_no_data() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$contextlist = provider::get_contexts_for_userid($USER->id);
$this->assertEmpty($contextlist);
}
/**
* The export function should handle an empty contextlist properly.
*/
public function test_export_user_data_no_data() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($USER->id),
'mod_quiz',
[]
);
provider::export_user_data($approvedcontextlist);
$this->assertDebuggingNotCalled();
// No data should have been exported.
$writer = \core_privacy\local\request\writer::with_context(\context_system::instance());
$this->assertFalse($writer->has_any_data_in_any_context());
}
/**
* The delete function should handle an empty contextlist properly.
*/
public function test_delete_data_for_user_no_data() {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($USER->id),
'mod_quiz',
[]
);
provider::delete_data_for_user($approvedcontextlist);
$this->assertDebuggingNotCalled();
}
/**
* Export + Delete quiz data for a user who has made a single attempt.
*/
public function test_user_with_data() {
global $DB;
$this->resetAfterTest(true);
// Make a quiz.
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance([
'course' => $course->id,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
quiz_add_quiz_question($saq->id, $quiz);
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
quiz_add_quiz_question($numq->id, $quiz);
// Run as the user and make an attempt on the quiz.
$this->setUser($user);
$starttime = time();
$quizobj = quiz::create($quiz->id, $user->id);
$context = $quizobj->get_context();
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
// Start the attempt.
$attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Answer the questions.
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = [
1 => ['answer' => 'frog'],
2 => ['answer' => '3.14'],
];
$attemptobj->process_submitted_actions($starttime, false, $tosubmit);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
$attemptobj->process_finish($starttime, false);
// Fetch the contexts -only one context should be returned.
$this->setUser();
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(1, $contextlist);
$this->assertEquals($context, $contextlist->current());
// Perform the export and check the data.
$this->setUser($user);
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'mod_quiz',
$contextlist->get_contextids()
);
provider::export_user_data($approvedcontextlist);
// Ensure that the quiz data was exported correctly.
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$quizdata = $writer->get_data([]);
$this->assertEquals($quizobj->get_quiz_name(), $quizdata->name);
// Every module has an intro.
$this->assertTrue(isset($quizdata->intro));
// Fetch the attempt data.
$attemptsubcontext = [
get_string('attempts', 'mod_quiz'),
$attempt->attempt,
];
$attemptdata = writer::with_context($context)->get_data($attemptsubcontext);
$attempt = $attemptobj->get_attempt();
$this->assertTrue(isset($attemptdata->state));
$this->assertEquals(\quiz_attempt::state_name($attemptobj->get_state()), $attemptdata->state);
$this->assertTrue(isset($attemptdata->timestart));
$this->assertTrue(isset($attemptdata->timefinish));
$this->assertTrue(isset($attemptdata->timemodified));
$this->assertFalse(isset($attemptdata->timemodifiedoffline));
$this->assertFalse(isset($attemptdata->timecheckstate));
$this->assertTrue(isset($attemptdata->grade));
$this->assertEquals(100.00, $attemptdata->grade->grade);
// Check that the exported question attempts are correct.
$attemptsubcontext = helper::get_quiz_attempt_subcontext($attemptobj->get_attempt(), $user);
$this->assert_question_attempt_exported(
$context,
$attemptsubcontext,
\question_engine::load_questions_usage_by_activity($attemptobj->get_uniqueid()),
quiz_get_review_options($quiz, $attemptobj->get_attempt(), $context),
$user
);
// Delete the data and check it is removed.
$this->setUser();
provider::delete_data_for_user($approvedcontextlist);
$this->expectException(\dml_missing_record_exception::class);
\quiz_attempt::create($attemptobj->get_quizid());
}
/**
* Export + Delete quiz data for a user who has made a single attempt.
*/
public function test_user_with_preview() {
global $DB;
$this->resetAfterTest(true);
// Make a quiz.
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance([
'course' => $course->id,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
]);
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
quiz_add_quiz_question($saq->id, $quiz);
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
quiz_add_quiz_question($numq->id, $quiz);
// Run as the user and make an attempt on the quiz.
$this->setUser($user);
$starttime = time();
$quizobj = quiz::create($quiz->id, $user->id);
$context = $quizobj->get_context();
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
// Start the attempt.
$attempt = quiz_create_attempt($quizobj, 1, false, $starttime, true, $user->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Answer the questions.
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = [
1 => ['answer' => 'frog'],
2 => ['answer' => '3.14'],
];
$attemptobj->process_submitted_actions($starttime, false, $tosubmit);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
$attemptobj->process_finish($starttime, false);
// Fetch the contexts - no context should be returned.
$this->setUser();
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(0, $contextlist);
}
}

View file

@ -73,6 +73,8 @@ class content_writer implements \core_privacy\local\request\content_writer {
/** /**
* Whether any data has been exported at all within the current context. * Whether any data has been exported at all within the current context.
*
* @return bool
*/ */
public function has_any_data() { public function has_any_data() {
$hasdata = !empty($this->data->{$this->context->id}); $hasdata = !empty($this->data->{$this->context->id});
@ -96,6 +98,32 @@ class content_writer implements \core_privacy\local\request\content_writer {
return $hasanydata; return $hasanydata;
} }
/**
* Whether any data has been exported for any context.
*
* @return bool
*/
public function has_any_data_in_any_context() {
$checkfordata = function($location) {
foreach ($location as $context => $data) {
if (!empty($data)) {
return true;
}
}
return false;
};
$hasanydata = $checkfordata($this->data);
$hasanydata = $hasanydata || $checkfordata($this->relateddata);
$hasanydata = $hasanydata || $checkfordata($this->metadata);
$hasanydata = $hasanydata || $checkfordata($this->files);
$hasanydata = $hasanydata || $checkfordata($this->customfiles);
$hasanydata = $hasanydata || $checkfordata($this->userprefs);
return $hasanydata;
}
/** /**
* Constructor for the content writer. * Constructor for the content writer.
* *

View file

@ -40,6 +40,14 @@ use \core_privacy\local\request\writer;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
class writer_test extends advanced_testcase { class writer_test extends advanced_testcase {
/**
* Ensure that the writer is cleared away as appropriate after each
* test.
*/
public function tearDown() {
writer::reset();
}
/** /**
* Test that calling with_context multiple times will return the same write instance. * Test that calling with_context multiple times will return the same write instance.
*/ */

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_adaptive.
*
* @package qbehaviour_adaptive
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_adaptive\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_adaptive implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -32,6 +32,7 @@ $string['gradingdetailswithpenalty'] = 'Marks for this submission: {$a->raw}/{$a
$string['gradingdetailswithtotalpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. This submission attracted a penalty of {$a->penalty}. Total penalties so far: {$a->totalpenalty}.'; $string['gradingdetailswithtotalpenalty'] = 'Marks for this submission: {$a->raw}/{$a->max}. This submission attracted a penalty of {$a->penalty}. Total penalties so far: {$a->totalpenalty}.';
$string['notcomplete'] = 'Not complete'; $string['notcomplete'] = 'Not complete';
$string['pluginname'] = 'Adaptive mode'; $string['pluginname'] = 'Adaptive mode';
$string['privacy:metadata'] = 'The Adaptive mode question behaviour plugin does not store any personal data.';
// Old strings these are currently only used in the unit tests, to verify that the new // Old strings these are currently only used in the unit tests, to verify that the new
// strings give the same results as the old strings. // strings give the same results as the old strings.

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_adaptivenopenalty.
*
* @package qbehaviour_adaptivenopenalty
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_adaptivenopenalty\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_adaptivenopenalty implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,3 +24,4 @@
*/ */
$string['pluginname'] = 'Adaptive mode (no penalties)'; $string['pluginname'] = 'Adaptive mode (no penalties)';
$string['privacy:metadata'] = 'The Adaptive mode (no penalties) question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_deferredcbm.
*
* @package qbehaviour_deferredcbm
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_deferredcbm\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_deferredcbm implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -66,6 +66,7 @@ $string['howcertainareyou'] = 'Certainty{$a->help}: {$a->choices}';
$string['noquestions'] = 'No responses'; $string['noquestions'] = 'No responses';
$string['overconfident'] = 'over-confident'; $string['overconfident'] = 'over-confident';
$string['pluginname'] = 'Deferred feedback with CBM'; $string['pluginname'] = 'Deferred feedback with CBM';
$string['privacy:metadata'] = 'The Deferred feedback with CBM question behaviour plugin does not store any personal data.';
$string['slightlyoverconfident'] = 'a bit over-confident'; $string['slightlyoverconfident'] = 'a bit over-confident';
$string['slightlyunderconfident'] = 'a bit under-confident'; $string['slightlyunderconfident'] = 'a bit under-confident';
$string['underconfident'] = 'under-confident'; $string['underconfident'] = 'under-confident';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_deferredfeedback.
*
* @package qbehaviour_deferredfeedback
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_deferredfeedback\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_deferredfeedback implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,3 +24,4 @@
*/ */
$string['pluginname'] = 'Deferred feedback'; $string['pluginname'] = 'Deferred feedback';
$string['privacy:metadata'] = 'The Deferred feedback behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_immediatecbm.
*
* @package qbehaviour_immediatecbm
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_immediatecbm\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_immediatecbm implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['pleaseselectacertainty'] = 'Please select a certainty.'; $string['pleaseselectacertainty'] = 'Please select a certainty.';
$string['pluginname'] = 'Immediate feedback with CBM'; $string['pluginname'] = 'Immediate feedback with CBM';
$string['privacy:metadata'] = 'The Immediate feedback with CBM question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_immediatefeedback.
*
* @package qbehaviour_immediatefeedback
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_immediatefeedback\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_immediatefeedback implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['notcomplete'] = 'Not complete'; $string['notcomplete'] = 'Not complete';
$string['pluginname'] = 'Immediate feedback'; $string['pluginname'] = 'Immediate feedback';
$string['privacy:metadata'] = 'The Immediate feedback question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_informationitem.
*
* @package qbehaviour_informationitem
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_informationitem\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_informationitem implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,4 +24,5 @@
*/ */
$string['pluginname'] = 'Behaviour for information items'; $string['pluginname'] = 'Behaviour for information items';
$string['privacy:metadata'] = 'The Information items question behaviour plugin does not store any personal data.';
$string['seen'] = 'Seen'; $string['seen'] = 'Seen';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_interactive.
*
* @package qbehaviour_interactive
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_interactive\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_interactive implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,5 +24,6 @@
*/ */
$string['pluginname'] = 'Interactive with multiple tries'; $string['pluginname'] = 'Interactive with multiple tries';
$string['privacy:metadata'] = 'The Interactive with multiple tries question behaviour plugin does not store any personal data.';
$string['triesremaining'] = 'Tries remaining: {$a}'; $string['triesremaining'] = 'Tries remaining: {$a}';
$string['tryagain'] = 'Try again'; $string['tryagain'] = 'Try again';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_interactivecountback.
*
* @package qbehaviour_interactivecountback
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_interactivecountback\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_interactivecountback implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,3 +24,4 @@
*/ */
$string['pluginname'] = 'Interactive with multiple tries (credit for earlier tries)'; $string['pluginname'] = 'Interactive with multiple tries (credit for earlier tries)';
$string['privacy:metadata'] = 'The Interactive with multiple tries (credit for earlier tries) question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_manualgraded.
*
* @package qbehaviour_manualgraded
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_manualgraded\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_manualgraded implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,3 +24,4 @@
*/ */
$string['pluginname'] = 'Manually graded'; $string['pluginname'] = 'Manually graded';
$string['privacy:metadata'] = 'The Manually graded question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qbehaviour_missing.
*
* @package qbehaviour_missing
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbehaviour_missing\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qbehaviour_missing implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['pluginname'] = 'Missing behaviour'; $string['pluginname'] = 'Missing behaviour';
$string['questionusedunknownmodel'] = 'This question was attempted with a behaviour that is not currently available. The question is being displayed as well as possible, but some parts may be missing or wrong.'; $string['questionusedunknownmodel'] = 'This question was attempted with a behaviour that is not currently available. The question is being displayed as well as possible, but some parts may be missing or wrong.';
$string['privacy:metadata'] = 'The Missing question behaviour plugin does not store any personal data.';

View file

@ -0,0 +1,467 @@
<?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/>.
/**
* Privacy Subsystem implementation for core_question.
*
* @package core_question
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\privacy;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/format.php');
require_once($CFG->dirroot . '/question/editlib.php');
require_once($CFG->dirroot . '/question/engine/datalib.php');
/**
* Privacy Subsystem implementation for core_question.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This component has data.
// We need to return all question information where the user is
// listed in either the question.createdby or question.modifiedby fields.
// We may also need to fetch this informtion from individual plugins in some cases.
// e.g. to fetch the full and other question-specific meta-data.
\core_privacy\local\metadata\provider,
// This is a subsysytem which provides information to core.
\core_privacy\local\request\subsystem\provider,
// This is a subsysytem which provides information to plugins.
\core_privacy\local\request\subsystem\plugin_provider
{
/**
* Describe the types of data stored by the question subsystem.
*
* @param collection $items The collection to add metadata to.
* @return collection The array of metadata
*/
public static function get_metadata(collection $items) : collection {
// Other tables link against it.
// The 'question_usages' table does not contain any user data.
// The table links the but doesn't store itself.
// The 'question_attempts' table contains data about question attempts.
// It does not contain any user ids - these are stored by the caller.
$items->add_database_table('question_attempts', [
'flagged' => 'privacy:metadata:database:question_attempts:flagged',
'responsesummary' => 'privacy:metadata:database:question_attempts:responsesummary',
'timemodified' => 'privacy:metadata:database:question_attempts:timemodified',
], 'privacy:metadata:database:question_attempts');;
// The 'question_attempt_steps' table contains data about changes to the state of a question attempt.
$items->add_database_table('question_attempt_steps', [
'state' => 'privacy:metadata:database:question_attempt_steps:state',
'timecreated' => 'privacy:metadata:database:question_attempt_steps:timecreated',
'fraction' => 'privacy:metadata:database:question_attempt_steps:fraction',
'userid' => 'privacy:metadata:database:question_attempt_steps:userid',
], 'privacy:metadata:database:question_attempt_steps');
// The 'question_attempt_step_data' table contains specific all metadata for each state.
$items->add_database_table('question_attempt_step_data', [
'name' => 'privacy:metadata:database:question_attempt_step_data:name',
'value' => 'privacy:metadata:database:question_attempt_step_data:value',
], 'privacy:metadata:database:question_attempt_step_data');
// These are all part of the set of the question definition
// The 'question' table is used to store instances of each question.
// It contains a createdby and modifiedby which related to specific users.
$items->add_database_table('question', [
'name' => 'privacy:metadata:database:question:name',
'questiontext' => 'privacy:metadata:database:question:questiontext',
'generalfeedback' => 'privacy:metadata:database:question:generalfeedback',
'timecreated' => 'privacy:metadata:database:question:timecreated',
'timemodified' => 'privacy:metadata:database:question:timemodified',
'createdby' => 'privacy:metadata:database:question:createdby',
'modifiedby' => 'privacy:metadata:database:question:modifiedby',
], 'privacy:metadata:database:question');
// The 'question_answers' table is used to store the set of answers, with appropriate feedback for each question.
// It does not contain user data.
// The 'question_hints' table is used to store hints about the correct answer for a question.
// It does not contain user data.
// The 'question_categories' table contains structural information about how questions are presented in the UI.
// It does not contain user data.
// The 'question_statistics' table contains aggregated statistics about responses.
// It does not contain any identifiable user data.
// The question subsystem makes use of the qtype, qformat, and qbehaviour plugin types.
$items->add_plugintype_link('qtype', [], 'privacy:metadata:link:qtype');
$items->add_plugintype_link('qformat', [], 'privacy:metadata:link:qformat');
$items->add_plugintype_link('qbehaviour', [], 'privacy:metadata:link:qbehaviour');
return $items;
}
/**
* Export the data for all question attempts on this question usage.
*
* Where a user is the owner of the usage, then the full detail of that usage will be included.
* Where a user has been involved in the usage, but it is not their own usage, then only their specific
* involvement will be exported.
*
* @param int $userid The userid to export.
* @param \context $context The context that the question was used within.
* @param array $usagecontext The subcontext of this usage.
* @param int $usage The question usage ID.
* @param \question_display_options $options The display options used for formatting.
* @param bool $isowner Whether the user being exported is the user who used the question.
*/
public static function export_question_usage(
int $userid,
\context $context,
array $usagecontext,
int $usage,
\question_display_options $options,
bool $isowner
) {
// Determine the questions in this usage.
$quba = \question_engine::load_questions_usage_by_activity($usage);
$basepath = $usagecontext;
$questionscontext = array_merge($usagecontext, [
get_string('questions', 'core_question'),
]);
foreach ($quba->get_attempt_iterator() as $qa) {
$question = $qa->get_question();
$slotno = $qa->get_slot();
$questionnocontext = array_merge($questionscontext, [$slotno]);
if ($isowner) {
// This user is the overal owner of the question attempt and all data wil therefore be exported.
//
// Respect _some_ of the question_display_options to ensure that they don't have access to
// generalfeedback and mark if the display options prevent this.
// This is defensible because they can submit questions without completing a quiz and perform an SAR to
// get prior access to the feedback and mark to improve upon it.
// Export the response.
$data = (object) [
'name' => $question->name,
'question' => $qa->get_question_summary(),
'answer' => $qa->get_response_summary(),
'timemodified' => transform::datetime($qa->timemodified),
];
if ($options->marks >= \question_display_options::MARK_AND_MAX) {
$data->mark = $qa->format_mark($options->markdp);
}
if ($options->flags != \question_display_options::HIDDEN) {
$data->flagged = transform::yesno($qa->is_flagged());
}
if ($options->generalfeedback != \question_display_options::HIDDEN) {
$data->generalfeedback = $question->format_generalfeedback($qa);
}
if ($options->manualcomment != \question_display_options::HIDDEN) {
$behaviour = $qa->get_behaviour();
if ($qa->has_manual_comment()) {
// Note - the export of the step data will ensure that the files are exported.
// No need to do it again here.
list($comment, $commentformat, $step) = $qa->get_manual_comment();
$comment = writer::with_context($context)
->rewrite_pluginfile_urls(
$questionnocontext,
'question',
'response_bf_comment',
$step->get_id(),
$comment
);
$data->comment = $behaviour->format_comment($comment, $commentformat);
}
}
writer::with_context($context)
->export_data($questionnocontext, $data);
// Export the step data.
static::export_question_attempt_steps($userid, $context, $questionnocontext, $qa, $options, $isowner);
}
}
}
/**
* Export the data for each step transition for each question in each question attempt.
*
* Where a user is the owner of the usage, then all steps in the question usage will be exported.
* Where a user is not the owner, but has been involved in the usage, then only their specific
* involvement will be exported.
*
* @param int $userid The user to export for
* @param \context $context The context that the question was used within.
* @param array $questionnocontext The subcontext of this question number.
* @param \question_attempt $qa The attempt being checked
* @param \question_display_options $options The display options used for formatting.
* @param bool $isowner Whether the user being exported is the user who used the question.
*/
public static function export_question_attempt_steps(
int $userid,
\context $context,
array $questionnocontext,
\question_attempt $qa,
\question_display_options $options,
$isowner
) {
$attemptdata = (object) [
'steps' => [],
];
$stepno = 0;
foreach ($qa->get_step_iterator() as $i => $step) {
$stepno++;
if ($isowner || ($step->get_user_id() != $userid)) {
// The user is the owner, or the author of the step.
$restrictedqa = new \question_attempt_with_restricted_history($qa, $i, null);
$stepdata = (object) [
// Note: Do not include the user here.
'time' => transform::datetime($step->get_timecreated()),
'action' => $qa->summarise_action($step),
];
if ($options->marks >= \question_display_options::MARK_AND_MAX) {
$stepdata->mark = $qa->format_fraction_as_mark($step->get_fraction(), $options->markdp);
}
if ($options->correctness != \question_display_options::HIDDEN) {
$stepdata->state = $restrictedqa->get_state_string($options->correctness);
}
if ($step->has_behaviour_var('comment')) {
$behaviour = $qa->get_behaviour();
$comment = $step->get_behaviour_var('comment');
$commentformat = $step->get_behaviour_var('commentformat');
if (empty(trim($comment))) {
// Skip empty comments.
continue;
}
// Format the comment.
$comment = writer::with_context($context)
->rewrite_pluginfile_urls(
$questionnocontext,
'question',
'response_bf_comment',
$step->get_id(),
$comment
);
writer::with_context($context)
->export_area_files(
$questionnocontext,
'question',
"response_bf_comment",
$step->get_id()
);
$stepdata->comment = $behaviour->format_comment($comment, $commentformat);
}
$attemptdata->steps[$stepno] = $stepdata;
}
}
if (!empty($attemptdata->steps)) {
writer::with_context($context)
->export_related_data($questionnocontext, 'steps', $attemptdata);
}
}
/**
* Get the list of contexts where the specified user has either created, or edited a question.
*
* To export usage of a question, please call {@link provider::export_question_usage()} from the module which
* instantiated the usage of the question.
*
* @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 {
$contextlist = new contextlist();
// A user may have created or updated a question.
// Questions are linked against a question category, which has a contextid field.
$sql = "SELECT cat.contextid
FROM {question} q
INNER JOIN {question_categories} cat ON cat.id = q.category
WHERE
q.createdby = :useridcreated OR
q.modifiedby = :useridmodified";
$params = [
'useridcreated' => $userid,
'useridmodified' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Determine related question usages for a user.
*
* @param string $prefix A unique prefix to add to the table alias
* @param string $component The name of the component to fetch usages for.
* @param string $joinfield The SQL field name to use in the JOIN ON - e.g. q.usageid
* @param int $userid The user to search.
* @return \qubaid_join
*/
public static function get_related_question_usages_for_user(string $prefix, string $component, string $joinfield, int $userid) : \qubaid_join {
return new \qubaid_join("
JOIN {question_usages} {$prefix}_qu ON {$prefix}_qu.id = {$joinfield}
AND {$prefix}_qu.component = :{$prefix}_usagecomponent
JOIN {question_attempts} {$prefix}_qa ON {$prefix}_qa.questionusageid = {$prefix}_qu.id
JOIN {question_attempt_steps} {$prefix}_qas ON {$prefix}_qas.questionattemptid = {$prefix}_qa.id",
"{$prefix}_qu.id",
"{$prefix}_qas.userid = :{$prefix}_stepuserid",
[
"{$prefix}_stepuserid" => $userid,
"{$prefix}_usagecomponent" => $component,
]);
}
/**
* 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 $CFG, $DB, $SITE;
if (empty($contextlist)) {
return;
}
// Use the Moodle XML Data format.
// It is the only lossless format that we support.
$format = "xml";
require_once($CFG->dirroot . "/question/format/{$format}/format.php");
// THe export system needs questions in a particular format.
// The easiest way to fetch these is with get_questions_category() which takes the details of a question
// category.
// We fetch the root question category for each context and the get_questions_category function recurses to
// After fetching them, we filter out any not created or modified by the requestor.
$user = $contextlist->get_user();
$userid = $user->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$categories = $DB->get_records_select('question_categories', "contextid {$contextsql} AND parent = 0", $contextparams);
$classname = "qformat_{$format}";
foreach ($categories as $category) {
$context = \context::instance_by_id($category->contextid);
$questions = get_questions_category($category, true);
$questions = array_filter($questions, function($question) use ($userid) {
return ($question->createdby == $userid) || ($question->modifiedby == $userid);
}, ARRAY_FILTER_USE_BOTH);
if (empty($questions)) {
continue;
}
$qformat = new $classname();
$qformat->setQuestions($questions);
$qformat->setContexts([$context]);
$qformat->setContexttofile(true);
// We do not know which course this belongs to, and it's not actually used except in error, so use Site.
$qformat->setCourse($SITE);
$content = '';
if ($qformat->exportpreprocess()) {
$content = $qformat->exportprocess(false);
}
$subcontext = [
get_string('questionbank', 'core_question'),
];
writer::with_context($context)->export_custom_file($subcontext, 'questions.xml', $content);
}
}
/**
* 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;
// Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
// user. They are still exported in the list of a users data, but they are not removed.
// The userid is instead anonymised.
$DB->set_field_select('question', 'createdby', 0,
'category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)',
[
'contextid' => $context->id,
]);
$DB->set_field_select('question', 'modifiedby', 0,
'category IN (SELECT id FROM {question_categories} WHERE contextid = :contextid)',
[
'contextid' => $context->id,
]);
}
/**
* 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;
// Questions are considered to be 'owned' by the institution, even if they were originally written by a specific
// user. They are still exported in the list of a users data, but they are not removed.
// The userid is instead anonymised.
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$contextparams['createdby'] = $contextlist->get_user()->id;
$DB->set_field_select('question', 'createdby', 0, "
category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
AND createdby = :createdby", $contextparams);
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$contextparams['modifiedby'] = $contextlist->get_user()->id;
$DB->set_field_select('question', 'modifiedby', 0, "
category IN (SELECT id FROM {question_categories} WHERE contextid {$contextsql})
AND modifiedby = :modifiedby", $contextparams);
}
}

View file

@ -793,11 +793,13 @@ class qformat_default {
} }
/** /**
* Do the export * Perform the export.
* For most types this should not need to be overrided * For most types this should not need to be overrided.
* @return stored_file *
* @param bool $checkcapabilities Whether to check capabilities when exporting the questions.
* @return string The content of the export.
*/ */
public function exportprocess() { public function exportprocess($checkcapabilities = true) {
global $CFG, $OUTPUT, $DB, $USER; global $CFG, $OUTPUT, $DB, $USER;
// get the questions (from database) in this category // get the questions (from database) in this category
@ -857,7 +859,7 @@ class qformat_default {
// export the question displaying message // export the question displaying message
$count++; $count++;
if (question_has_capability_on($question, 'view')) { if (!$checkcapabilities || question_has_capability_on($question, 'view')) {
$expout .= $this->writequestion($question, $contextid) . "\n"; $expout .= $this->writequestion($question, $contextid) . "\n";
} }
} }

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_aiken.
*
* @package qformat_aiken
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_aiken\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_aiken implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['pluginname'] = 'Aiken format'; $string['pluginname'] = 'Aiken format';
$string['pluginname_help'] = 'This is a simple format for importing multiple choice questions from a text file.'; $string['pluginname_help'] = 'This is a simple format for importing multiple choice questions from a text file.';
$string['pluginname_link'] = 'qformat/aiken'; $string['pluginname_link'] = 'qformat/aiken';
$string['privacy:metadata'] = 'The Aiken question format plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_blackboard_six.
*
* @package qformat_blackboard_six
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_blackboard_six\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_blackboard_six implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -31,4 +31,5 @@ $string['importedcategory'] = 'Imported category {$a}';
$string['notenoughtsubans'] = 'Unable to import matching question \'{$a}\' because a matching question must comprise at least two questions and three answers.'; $string['notenoughtsubans'] = 'Unable to import matching question \'{$a}\' because a matching question must comprise at least two questions and three answers.';
$string['pluginname'] = 'Blackboard'; $string['pluginname'] = 'Blackboard';
$string['pluginname_help'] = 'Blackboard format enables questions saved in all Blackboard export formats to be imported via a dat or zip file. For zip files, images import is supported.'; $string['pluginname_help'] = 'Blackboard format enables questions saved in all Blackboard export formats to be imported via a dat or zip file. For zip files, images import is supported.';
$string['privacy:metadata'] = 'The Blackbard question format plugin does not store any personal data.';
$string['unhandledpresblock'] = 'Unhandled presentation block'; $string['unhandledpresblock'] = 'Unhandled presentation block';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_examview.
*
* @package qformat_examview
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_examview\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_examview implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -24,3 +24,4 @@
$string['pluginname'] = 'Examview'; $string['pluginname'] = 'Examview';
$string['pluginname_help'] = 'Examview format enables the import of questions from Examview 4 XML files. For newer versions of Examview, Blackboard format may be used.'; $string['pluginname_help'] = 'Examview format enables the import of questions from Examview 4 XML files. For newer versions of Examview, Blackboard format may be used.';
$string['privacy:metadata'] = 'The Examview question format plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_gift.
*
* @package qformat_gift
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_gift\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_gift implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -34,3 +34,4 @@ $string['nohandler'] = 'No handler for question type {$a}';
$string['pluginname'] = 'Gift format'; $string['pluginname'] = 'Gift format';
$string['pluginname_help'] = 'Gift format enables multiple-choice, true-false, short answer, matching, missing word, numerical and essay questions to be imported or exported via text file.'; $string['pluginname_help'] = 'Gift format enables multiple-choice, true-false, short answer, matching, missing word, numerical and essay questions to be imported or exported via text file.';
$string['pluginname_link'] = 'qformat/gift'; $string['pluginname_link'] = 'qformat/gift';
$string['privacy:metadata'] = 'The Gift question format plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_missingword.
*
* @package qformat_missingword
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_missingword\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_missingword implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,3 +28,4 @@ $string['pluginname_link'] = 'Missing word format';
$string['beginanswernotfound'] = 'Could not find a required "{" character in imported file content.'; $string['beginanswernotfound'] = 'Could not find a required "{" character in imported file content.';
$string['endanswernotfound'] = 'Could not find a required "}" character in imported file content.'; $string['endanswernotfound'] = 'Could not find a required "}" character in imported file content.';
$string['noanswerfound'] = 'No answers found in question'; $string['noanswerfound'] = 'No answers found in question';
$string['privacy:metadata'] = 'The Missing word question format plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_multianswer.
*
* @package qformat_multianswer
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_multianswer\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_multianswer implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['pluginname'] = 'Embedded answers (Cloze)'; $string['pluginname'] = 'Embedded answers (Cloze)';
$string['pluginname_help'] = 'Embedded answers (Cloze) format enables the import of a passage of text with questions such as multiple-choice and short answer embedded within it.'; $string['pluginname_help'] = 'Embedded answers (Cloze) format enables the import of a passage of text with questions such as multiple-choice and short answer embedded within it.';
$string['pluginname_link'] = 'question/type/multianswer'; $string['pluginname_link'] = 'question/type/multianswer';
$string['privacy:metadata'] = 'The Embedded answers question plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_webct.
*
* @package qformat_webct
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_webct\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_webct implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -28,6 +28,7 @@ $string['missingquestion'] = 'Missing question label after line {$a}';
$string['pluginname'] = 'WebCT format'; $string['pluginname'] = 'WebCT format';
$string['pluginname_help'] = 'WebCT format enables multiple-choice and short answer questions saved in WebCT\'s text-based format to be imported.'; $string['pluginname_help'] = 'WebCT format enables multiple-choice and short answer questions saved in WebCT\'s text-based format to be imported.';
$string['pluginname_link'] = 'qformat/webct'; $string['pluginname_link'] = 'qformat/webct';
$string['privacy:metadata'] = 'The WebCT question format plugin does not store any personal data.';
$string['questionnametoolong'] = 'Question name too long at line {$a} (255 char. max). It has been truncated.'; $string['questionnametoolong'] = 'Question name too long at line {$a} (255 char. max). It has been truncated.';
$string['unknowntype'] = 'Unknown question type after line {$a}'; $string['unknowntype'] = 'Unknown question type after line {$a}';
$string['warningsdetected'] = '{$a} warning(s) detected'; $string['warningsdetected'] = '{$a} warning(s) detected';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_xhtml.
*
* @package qformat_xhtml
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_xhtml\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_xhtml implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -25,3 +25,4 @@
$string['pluginname'] = 'XHTML format'; $string['pluginname'] = 'XHTML format';
$string['pluginname_help'] = 'XHTML format enables all questions in the category to be exported to a single page of strict XHTML for possible use in another application.'; $string['pluginname_help'] = 'XHTML format enables all questions in the category to be exported to a single page of strict XHTML for possible use in another application.';
$string['pluginname_link'] = 'qformat/xhtml'; $string['pluginname_link'] = 'qformat/xhtml';
$string['privacy:metadata'] = 'The XHTML question format plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qformat_xml.
*
* @package qformat_xml
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qformat_xml\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qformat_xml implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -26,6 +26,7 @@ $string['invalidxml'] = 'Invalid XML file - string expected (use CDATA?)';
$string['pluginname'] = 'Moodle XML format'; $string['pluginname'] = 'Moodle XML format';
$string['pluginname_help'] = 'This is a Moodle-specific format for importing and exporting questions.'; $string['pluginname_help'] = 'This is a Moodle-specific format for importing and exporting questions.';
$string['pluginname_link'] = 'qformat/xml'; $string['pluginname_link'] = 'qformat/xml';
$string['privacy:metadata'] = 'The XML question format plugin does not store any personal data.';
$string['truefalseimporterror'] = '<b>Warning</b>: The true/false question \'{$a->questiontext}\' could not be imported properly. It was not clear whether the correct answer is true or false. The question has been imported assuming that the answer is \'{$a->answer}\'. If this is not correct, you will need to edit the question.'; $string['truefalseimporterror'] = '<b>Warning</b>: The true/false question \'{$a->questiontext}\' could not be imported properly. It was not clear whether the correct answer is true or false. The question has been imported assuming that the answer is \'{$a->answer}\'. If this is not correct, you will need to edit the question.';
$string['unsupportedexport'] = 'Question type {$a} is not supported by XML export'; $string['unsupportedexport'] = 'Question type {$a} is not supported by XML export';
$string['xmlimportnoname'] = 'Missing question name in XML file'; $string['xmlimportnoname'] = 'Missing question name in XML file';

View file

@ -87,6 +87,30 @@ class core_question_generator extends component_generator_base {
$question->category = $fromform->category; $question->category = $fromform->category;
$question->qtype = $qtype; $question->qtype = $qtype;
$question->createdby = 0; $question->createdby = 0;
return $this->update_question($question, $which, $overrides);
}
/**
* Update an existing question.
*
* @param stdClass $question the question data to update.
* @param string $which as for the corresponding argument of
* {@link question_test_helper::get_question_form_data}. null for the default one.
* @param array|stdClass $overrides any fields that should be different from the base example.
*/
public function update_question($question, $which = null, $overrides = null) {
global $CFG;
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
$qtype = $question->qtype;
$fromform = test_question_maker::get_question_form_data($qtype, $which);
$fromform = (object) $this->datagenerator->combine_defaults_and_record(
(array) $question, $fromform);
$fromform = (object) $this->datagenerator->combine_defaults_and_record(
(array) $fromform, $overrides);
return question_bank::get_qtype($qtype)->save_question($question, $fromform); return question_bank::get_qtype($qtype)->save_question($question, $fromform);
} }

View file

@ -0,0 +1,103 @@
<?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/>.
/**
* Helper for privacy tests.
*
* @package core_question
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\request\writer;
/**
* Helper for privacy tests.
*
* @package core_question
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait core_question_privacy_helper {
/**
* Assert that the question usage in the supplied slot matches the expected format
* and usage for a question.
*
* @param \question_usage_by_activity $quba The Question Usage to test against.
* @param int $slotno The slot number to compare
* @param \question_display_options $options The display options used for formatting.
* @param \stdClass $data The data to check.
*/
public function assert_question_slot_equals(
\question_usage_by_activity $quba,
$slotno,
\question_display_options $options,
$data
) {
$attempt = $quba->get_question_attempt($slotno);
$question = $attempt->get_question();
// Check the question data exported.
$this->assertEquals($data->name, $question->name);
$this->assertEquals($data->question, $question->questiontext);
// Check the answer exported.
$this->assertEquals($attempt->get_response_summary(), $data->answer);
if ($options->marks != \question_display_options::HIDDEN) {
$this->assertEquals($attempt->get_mark(), $data->mark);
} else {
$this->assertFalse(isset($data->mark));
}
if ($options->flags != \question_display_options::HIDDEN) {
$this->assertEquals($attempt->is_flagged(), (int) $data->flagged);
} else {
$this->assertFalse(isset($data->flagged));
}
if ($options->generalfeedback != \question_display_options::HIDDEN) {
$this->assertEquals($question->format_generalfeedback($attempt), $data->generalfeedback);
} else {
$this->assertFalse(isset($data->generalfeedback));
}
}
/**
* Assert that a question attempt was exported.
*
* @param \context $context The context which the attempt should be in
* @param array $subcontext The base of the export
* @param question_usage_by_activity $quba The question usage expected
* @param \question_display_options $options The display options used for formatting.
* @param \stdClass $user The user exported
*/
public function assert_question_attempt_exported(\context $context, array $subcontext, $quba, $options, $user) {
$usagecontext = array_merge(
$subcontext,
[get_string('questions', 'core_question')]
);
$writer = writer::with_context($context);
foreach ($quba->get_slots() as $slotno) {
$data = $writer->get_data(array_merge($usagecontext, [$slotno]));
$this->assert_question_slot_equals($quba, $slotno, $options, $data);
}
}
}

View file

@ -0,0 +1,420 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package core_question
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_privacy\local\metadata\collection;
use core_privacy\local\request\deletion_criteria;
use core_privacy\local\request\writer;
use core_question\privacy\provider;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/xmlize.php');
require_once(__DIR__ . '/privacy_helper.php');
require_once(__DIR__ . '/../engine/tests/helpers.php');
/**
* Privacy provider tests class.
*
* @package core_question
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_question_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
// Include the privacy helper which has assertions on it.
use core_question_privacy_helper;
/**
* Prepare a question attempt.
*
* @return question_usage_by_activity
*/
protected function prepare_question_attempt() {
// Create a question with a usage from the current user.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$quba = question_engine::make_questions_usage_by_activity('core_question_preview', context_system::instance());
$quba->set_preferred_behaviour('deferredfeedback');
$questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
$question = question_bank::load_question($questiondata->id);
$quba->add_question($question);
$quba->start_all_questions();
question_engine::save_questions_usage_by_activity($quba);
return $quba;
}
/**
* Test that calling export_question_usage on a usage belonging to a
* different user does not export any data.
*/
public function test_export_question_usage_no_usage() {
$this->resetAfterTest();
$quba = $this->prepare_question_attempt();
// Create a question with a usage from the current user.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$quba = question_engine::make_questions_usage_by_activity('core_question_preview', context_system::instance());
$quba->set_preferred_behaviour('deferredfeedback');
$questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
$question = question_bank::load_question($questiondata->id);
$quba->add_question($question);
$quba->start_all_questions();
question_engine::save_questions_usage_by_activity($quba);
// Set the user.
$testuser = $this->getDataGenerator()->create_user();
$this->setUser($testuser);
$context = $quba->get_owning_context();
$options = new \question_display_options();
provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, false);
$writer = writer::with_context($context);
$this->assertFalse($writer->has_any_data_in_any_context());
}
/**
* Test that calling export_question_usage on a usage belonging to a
* different user but ignoring the user match
*/
public function test_export_question_usage_with_usage() {
$this->resetAfterTest();
$quba = $this->prepare_question_attempt();
// Create a question with a usage from the current user.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$quba = question_engine::make_questions_usage_by_activity('core_question_preview', context_system::instance());
$quba->set_preferred_behaviour('deferredfeedback');
$questiondata = $questiongenerator->create_question('truefalse', 'true', ['category' => $cat->id]);
$quba->add_question(question_bank::load_question($questiondata->id));
$questiondata = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$quba->add_question(question_bank::load_question($questiondata->id));
// Set the user and answer the questions.
$testuser = $this->getDataGenerator()->create_user();
$this->setUser($testuser);
$quba->start_all_questions();
$quba->process_action(1, ['answer' => 1]);
$quba->process_action(2, ['answer' => 'cat']);
$quba->finish_all_questions();
question_engine::save_questions_usage_by_activity($quba);
$context = $quba->get_owning_context();
// Export all questions for this attempt.
$options = new \question_display_options();
provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data_in_any_context());
$this->assertTrue($writer->has_any_data());
$slots = $quba->get_slots();
$this->assertCount(2, $slots);
foreach ($slots as $slotno) {
$data = $writer->get_data([get_string('questions', 'core_question'), $slotno]);
$this->assertNotNull($data);
$this->assert_question_slot_equals($quba, $slotno, $options, $data);
}
$this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
// Disable some options and re-export.
writer::reset();
$options = new \question_display_options();
$options->hide_all_feedback();
$options->flags = \question_display_options::HIDDEN;
$options->marks = \question_display_options::HIDDEN;
provider::export_question_usage($testuser->id, $context, [], $quba->get_id(), $options, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data_in_any_context());
$this->assertTrue($writer->has_any_data());
$slots = $quba->get_slots();
$this->assertCount(2, $slots);
foreach ($slots as $slotno) {
$data = $writer->get_data([get_string('questions', 'core_question'), $slotno]);
$this->assertNotNull($data);
$this->assert_question_slot_equals($quba, $slotno, $options, $data);
}
$this->assertEmpty($writer->get_data([get_string('questions', 'core_question'), $quba->next_slot_number()]));
}
/**
* Test that questions owned by a user are exported and never deleted.
*/
public function test_question_owned_is_handled() {
global $DB;
$this->resetAfterTest();
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
// Create the two test users.
$user = $this->getDataGenerator()->create_user();
$otheruser = $this->getDataGenerator()->create_user();
// Create one question as each user in diferent contexts.
$this->setUser($user);
$userdata = $questiongenerator->setup_course_and_questions();
$expectedcontext = \context_course::instance($userdata[1]->id);
$this->setUser($otheruser);
$otheruserdata = $questiongenerator->setup_course_and_questions();
$unexpectedcontext = \context_course::instance($otheruserdata[1]->id);
// And create another one where we'll update a question as the test user.
$moreotheruserdata = $questiongenerator->setup_course_and_questions();
$otherexpectedcontext = \context_course::instance($moreotheruserdata[1]->id);
$morequestions = $moreotheruserdata[3];
// Update the third set of questions.
$this->setUser($user);
foreach ($morequestions as $question) {
$questiongenerator->update_question($question);
}
// Run the get_contexts_for_userid as default user.
$this->setUser();
// There should be two contexts returned - the first course, and the third.
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(2, $contextlist);
$expectedcontexts = [
$expectedcontext->id,
$otherexpectedcontext->id,
];
$this->assertEquals($expectedcontexts, $contextlist->get_contextids(), 'Contexts not equal', 0.0, 10, true);
// Run the export_user_Data as the test user.
$this->setUser($user);
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($user->id),
'core_question',
$expectedcontexts
);
provider::export_user_data($approvedcontextlist);
// There should be data for the user's question context.
$writer = writer::with_context($expectedcontext);
$this->assertTrue($writer->has_any_data());
// And for the course we updated.
$otherwriter = writer::with_context($otherexpectedcontext);
$this->assertTrue($otherwriter->has_any_data());
// But not for the other user's course.
$otherwriter = writer::with_context($unexpectedcontext);
$this->assertFalse($otherwriter->has_any_data());
// The question data is exported as an XML export in custom files.
$writer = writer::with_context($expectedcontext);
$subcontext = [get_string('questionbank', 'core_question')];
$exportfile = $writer->get_custom_file($subcontext, 'questions.xml');
$this->assertNotEmpty($exportfile);
$xmlized = xmlize($exportfile);
$xmlquestions = $xmlized['quiz']['#']['question'];
$this->assertCount(2, $xmlquestions);
// Run the delete functions as default user.
$this->setUser();
// The delete functions should do nothing here.
$this->assertCount(6, $DB->get_records('question'));
// Delete for all users in context.
provider::delete_data_for_all_users_in_context($expectedcontext);
$this->assertCount(6, $DB->get_records('question'));
provider::delete_data_for_user($approvedcontextlist);
$this->assertCount(6, $DB->get_records('question'));
}
/**
* Deleting questions should only unset their created and modified user.
*/
public function test_question_delete_data_for_user_anonymised() {
global $DB;
$this->resetAfterTest(true);
$user = \core_user::get_user_by_username('admin');
$otheruser = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$othercourse = $this->getDataGenerator()->create_course();
$othercontext = \context_course::instance($othercourse->id);
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category([
'contextid' => $context->id,
]);
$othercat = $questiongenerator->create_question_category([
'contextid' => $othercontext->id,
]);
// Create questions:
// Q1 - Created by the UUT, Modified by UUT.
// Q2 - Created by the UUT, Modified by the other user.
// Q3 - Created by the other user, Modified by UUT
// Q4 - Created by the other user, Modified by the other user.
// Q5 - Created by the UUT, Modified by the UUT, but in a different context.
$this->setUser($user);
$q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$this->setUser($otheruser);
$questiongenerator->update_question($q2);
$q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$this->setUser($user);
$questiongenerator->update_question($q3);
$q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
$user,
'core_question',
[$context->id]
);
// Delete the data and check it is removed.
$this->setUser();
provider::delete_data_for_user($approvedcontextlist);
$this->assertCount(5, $DB->get_records('question'));
$qrecord = $DB->get_record('question', ['id' => $q1->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q2->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals($otheruser->id, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q3->id]);
$this->assertEquals($otheruser->id, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q4->id]);
$this->assertEquals($otheruser->id, $qrecord->createdby);
$this->assertEquals($otheruser->id, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q5->id]);
$this->assertEquals($user->id, $qrecord->createdby);
$this->assertEquals($user->id, $qrecord->modifiedby);
}
/**
* Deleting questions should only unset their created and modified user for all questions in a context.
*/
public function test_question_delete_data_for_all_users_in_context_anonymised() {
global $DB;
$this->resetAfterTest(true);
$user = \core_user::get_user_by_username('admin');
$otheruser = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
$othercourse = $this->getDataGenerator()->create_course();
$othercontext = \context_course::instance($othercourse->id);
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category([
'contextid' => $context->id,
]);
$othercat = $questiongenerator->create_question_category([
'contextid' => $othercontext->id,
]);
// Create questions:
// Q1 - Created by the UUT, Modified by UUT.
// Q2 - Created by the UUT, Modified by the other user.
// Q3 - Created by the other user, Modified by UUT
// Q4 - Created by the other user, Modified by the other user.
// Q5 - Created by the UUT, Modified by the UUT, but in a different context.
$this->setUser($user);
$q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$this->setUser($otheruser);
$questiongenerator->update_question($q2);
$q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$this->setUser($user);
$questiongenerator->update_question($q3);
$q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
// Delete the data and check it is removed.
$this->setUser();
provider::delete_data_for_all_users_in_context($context);
$this->assertCount(5, $DB->get_records('question'));
$qrecord = $DB->get_record('question', ['id' => $q1->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q2->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q3->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q4->id]);
$this->assertEquals(0, $qrecord->createdby);
$this->assertEquals(0, $qrecord->modifiedby);
$qrecord = $DB->get_record('question', ['id' => $q5->id]);
$this->assertEquals($user->id, $qrecord->createdby);
$this->assertEquals($user->id, $qrecord->modifiedby);
}
}

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_calculated.
*
* @package qtype_calculated
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_calculated\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_calculated implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -115,6 +115,7 @@ $string['pluginname_link'] = 'question/type/calculated';
$string['pluginnameadding'] = 'Adding a Calculated question'; $string['pluginnameadding'] = 'Adding a Calculated question';
$string['pluginnameediting'] = 'Editing a Calculated question'; $string['pluginnameediting'] = 'Editing a Calculated question';
$string['pluginnamesummary'] = 'Calculated questions are like numerical questions but with the numbers used selected randomly from a set when the quiz is taken.'; $string['pluginnamesummary'] = 'Calculated questions are like numerical questions but with the numbers used selected randomly from a set when the quiz is taken.';
$string['privacy:metadata'] = 'The Calculated question type plugin does not store any personal data.';
$string['possiblehdr'] = 'Possible wild cards present only in the question text'; $string['possiblehdr'] = 'Possible wild cards present only in the question text';
$string['questiondatasets'] = 'Question datasets'; $string['questiondatasets'] = 'Question datasets';
$string['questiondatasets_help'] = 'Question datasets of wild cards that will be used in each individual question'; $string['questiondatasets_help'] = 'Question datasets of wild cards that will be used in each individual question';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_calculatedmulti.
*
* @package qtype_calculatedmulti
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_calculatedmulti\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_calculatedmulti implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -30,3 +30,4 @@ $string['pluginname_link'] = 'question/type/calculatedmulti';
$string['pluginnameadding'] = 'Adding a Calculated multichoice question'; $string['pluginnameadding'] = 'Adding a Calculated multichoice question';
$string['pluginnameediting'] = 'Editing a Calculated multichoice question'; $string['pluginnameediting'] = 'Editing a Calculated multichoice question';
$string['pluginnamesummary'] = 'Calculated multichoice questions are like multichoice questions which choice elements can include formula results from numeric values that are selected randomly from a set when the quiz is taken.'; $string['pluginnamesummary'] = 'Calculated multichoice questions are like multichoice questions which choice elements can include formula results from numeric values that are selected randomly from a set when the quiz is taken.';
$string['privacy:metadata'] = 'The Calculated multichoice question type plugin does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_calculatedsimple.
*
* @package qtype_calculatedsimple
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_calculatedsimple\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_calculatedsimple implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -34,6 +34,7 @@ $string['pluginname_link'] = 'question/type/calculatedsimple';
$string['pluginnameadding'] = 'Adding a Simple calculated question'; $string['pluginnameadding'] = 'Adding a Simple calculated question';
$string['pluginnameediting'] = 'Editing a Simple calculated question'; $string['pluginnameediting'] = 'Editing a Simple calculated question';
$string['pluginnamesummary'] = 'A simpler version of calculated questions which are like numerical questions but with the numbers used selected randomly from a set when the quiz is taken.'; $string['pluginnamesummary'] = 'A simpler version of calculated questions which are like numerical questions but with the numbers used selected randomly from a set when the quiz is taken.';
$string['privacy:metadata'] = 'The Calculated simple question type plugin does not store any personal data.';
$string['setno'] = 'Set {$a}'; $string['setno'] = 'Set {$a}';
$string['setwildcardvalues'] = 'set(s) of wild card(s) values'; $string['setwildcardvalues'] = 'set(s) of wild card(s) values';
$string['showitems'] = 'Display'; $string['showitems'] = 'Display';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_ddimageortext.
*
* @package qtype_ddimageortext
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_ddimageortext\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_ddimageortext implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -61,6 +61,7 @@ $string['pluginnameediting'] = 'Editing drag and drop onto image';
$string['pluginnamesummary'] = 'Images or text labels are dragged and dropped into drop zones on a background image.'; $string['pluginnamesummary'] = 'Images or text labels are dragged and dropped into drop zones on a background image.';
$string['previewareaheader'] = 'Preview'; $string['previewareaheader'] = 'Preview';
$string['previewareamessage'] = 'Select a background image, specify draggable items and define drop zones on the background image into which they must be dragged.'; $string['previewareamessage'] = 'Select a background image, specify draggable items and define drop zones on the background image into which they must be dragged.';
$string['privacy:metadata'] = 'The Drag and drop onto image question type does not store any personal data.';
$string['refresh'] = 'Refresh preview'; $string['refresh'] = 'Refresh preview';
$string['shuffleimages'] = 'Shuffle drag items each time question is attempted'; $string['shuffleimages'] = 'Shuffle drag items each time question is attempted';
$string['summarisechoice'] = '{$a->no}. {$a->text}'; $string['summarisechoice'] = '{$a->no}. {$a->text}';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_ddmarker.
*
* @package qtype_ddmarker
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_ddmarker\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_ddmarker implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -69,6 +69,7 @@ $string['pluginnameediting'] = 'Editing drag and drop markers';
$string['pluginnamesummary'] = 'Markers are dragged and dropped onto a background image.'; $string['pluginnamesummary'] = 'Markers are dragged and dropped onto a background image.';
$string['previewareaheader'] = 'Preview'; $string['previewareaheader'] = 'Preview';
$string['previewareamessage'] = 'Select a background image file, enter text labels for markers and define the drop zones on the background image to which they must be dragged.'; $string['previewareamessage'] = 'Select a background image file, enter text labels for markers and define the drop zones on the background image to which they must be dragged.';
$string['privacy:metadata'] = 'The Drag and drop markers question type does not store any personal data.';
$string['refresh'] = 'Refresh preview'; $string['refresh'] = 'Refresh preview';
$string['clearwrongparts'] = 'Move incorrectly placed markers back to default start position below image'; $string['clearwrongparts'] = 'Move incorrectly placed markers back to default start position below image';
$string['shape'] = 'Shape'; $string['shape'] = 'Shape';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_ddwtos.
*
* @package qtype_ddwtos
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_ddwtos\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_ddwtos implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -34,3 +34,4 @@ $string['pluginname_link'] = 'question/type/ddwtos';
$string['pluginnameadding'] = 'Adding a drag and drop into text'; $string['pluginnameadding'] = 'Adding a drag and drop into text';
$string['pluginnameediting'] = 'Editing a drag and drop into text'; $string['pluginnameediting'] = 'Editing a drag and drop into text';
$string['pluginnamesummary'] = 'Missing words in the question text are filled in using drag and drop.'; $string['pluginnamesummary'] = 'Missing words in the question text are filled in using drag and drop.';
$string['privacy:metadata'] = 'The Drag and drop into text question type does not store any personal data.';

View file

@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for qtype_description.
*
* @package qtype_description
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_description\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for qtype_description implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View file

@ -31,3 +31,4 @@ The question text is displayed both during the attempt and on the review page. A
$string['pluginnameadding'] = 'Adding a description'; $string['pluginnameadding'] = 'Adding a description';
$string['pluginnameediting'] = 'Editing a Description'; $string['pluginnameediting'] = 'Editing a Description';
$string['pluginnamesummary'] = 'This is not actually a question. Instead it is a way to add some instructions, rubric or other content to the activity. This is similar to the way that labels can be used to add content to the course page.'; $string['pluginnamesummary'] = 'This is not actually a question. Instead it is a way to add some instructions, rubric or other content to the activity. This is similar to the way that labels can be used to add content to the course page.';
$string['privacy:metadata'] = 'The Description question type plugin does not store any personal data.';

Some files were not shown because too many files have changed in this diff Show more