MDL-62137 core_completion: Privacy files for core_completion.

This commit is contained in:
Adrian Greeve 2018-04-28 14:26:48 +08:00
parent 446d8483a3
commit ebab932f71
4 changed files with 457 additions and 0 deletions

View File

@ -0,0 +1,205 @@
<?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 class for requesting user data.
*
* @package core_completion
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_completion\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
require_once($CFG->dirroot . '/comment/lib.php');
/**
* Privacy class for requesting user data.
*
* @package core_completion
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @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\subsystem\plugin_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_database_table('course_completions', [
'userid' => 'privacy:metadata:userid',
'course' => 'privacy:metadata:course',
'timeenrolled' => 'privacy:metadata:timeenrolled',
'timestarted' => 'privacy:metadata:timestarted',
'timecompleted' => 'privacy:metadata:timecompleted',
'reaggregate' => 'privacy:metadata:reaggregate'
], 'privacy:metadata:coursesummary');
$collection->add_database_table('course_modules_completion', [
'userid' => 'privacy:metadata:userid',
'coursemoduleid' => 'privacy:metadata:coursemoduleid',
'completionstate' => 'privacy:metadata:completionstate',
'viewed' => 'privacy:metadata:viewed',
'overrideby' => 'privacy:metadata:overrideby',
'timemodified' => 'privacy:metadata:timemodified'
], 'privacy:metadata:coursemodulesummary');
$collection->add_database_table('course_completion_crit_compl', [
'userid' => 'privacy:metadata:userid',
'course' => 'privacy:metadata:course',
'gradefinal' => 'privacy:metadata:gradefinal',
'unenroled' => 'privacy:metadata:unenroled',
'timecompleted' => 'privacy:metadata:timecompleted'
], 'privacy:metadata:coursecompletedsummary');
return $collection;
}
/**
* Get join sql to retrieve courses the user is in.
*
* @param int $userid The user ID
* @param string $prefix A unique prefix for these joins.
* @param string $joinfield A field to join these tables to. Joins to course ID.
* @return array The join, where, and params for this join.
*/
public static function get_course_completion_join_sql(int $userid, string $prefix, string $joinfield) : array {
$cccalias = "{$prefix}_ccc"; // Course completion criteria.
$cmcalias = "{$prefix}_cmc"; // Course modules completion.
$ccccalias = "{$prefix}_cccc"; // Course completion criteria completion.
$join = "JOIN {course_completion_criteria} {$cccalias} ON {$joinfield} = {$cccalias}.course
LEFT JOIN {course_modules_completion} {$cmcalias} ON {$cccalias}.moduleinstance = {$cmcalias}.coursemoduleid
LEFT JOIN {course_completion_crit_compl} {$ccccalias} ON {$ccccalias}.criteriaid = {$cccalias}.id";
$where = "{$cmcalias}.userid = :{$prefix}_moduleuserid OR {$ccccalias}.userid = :{$prefix}_courseuserid";
$params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_courseuserid" => $userid];
return [$join, $where, $params];
}
/**
* Returns activity completion information about a user.
*
* @param \stdClass $user The user to return information about.
* @param \stdClass $course The course the user is in.
* @param \stdClass $cm Course module information.
* @return \stdClass Activity completion information.
*/
public static function get_activity_completion_info(\stdClass $user, \stdClass $course, $cm) : \stdClass {
$completioninfo = new \completion_info($course);
$completion = $completioninfo->is_enabled($cm);
return ($completion != COMPLETION_TRACKING_NONE) ? $completioninfo->get_data($cm, true, $user->id) : new \stdClass();
}
/**
* Returns course completion information for a user.
*
* @param \stdClass $user The user that we are getting completion information for.
* @param \stdClass $course The course we are interested in.
* @return \stdClass Course completion information.
*/
public static function get_course_completion_info(\stdClass $user, \stdClass $course) : array {
$completioninfo = new \completion_info($course);
$completion = $completioninfo->is_enabled();
if ($completion == COMPLETION_ENABLED) {
$coursecomplete = $completioninfo->is_course_complete($user->id);
$criteriacomplete = $completioninfo->count_course_user_data($user->id);
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$status = ($coursecomplete) ? get_string('complete') : '';
$status = (!$criteriacomplete && !$ccompletion->timestarted) ? get_string('notyetstarted', 'completion') :
get_string('inprogress', 'completion');
$completions = $completioninfo->get_completions($user->id);
$overall = get_string('nocriteriaset', 'completion');
if (!empty($completions)) {
if ($completioninfo->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
$overall = get_string('criteriarequiredall', 'completion');
} else {
$overall = get_string('criteriarequiredany', 'completion');
}
}
$coursecompletiondata = [
'status' => $status,
'required' => $overall,
];
$coursecompletiondata['criteria'] = array_map(function($completion) use ($completioninfo) {
$criteria = $completion->get_criteria();
$aggregation = $completioninfo->get_aggregation_method($criteria->criteriatype);
$required = ($aggregation == COMPLETION_AGGREGATION_ALL) ? get_string('all', 'completion') :
get_string('any', 'completion');
$data = [
'required' => $required,
'completed' => transform::yesno($completion->is_complete()),
'timecompleted' => isset($completion->timecompleted) ? transform::datetime($completion->timecompleted) : ''
];
$details = $criteria->get_details($completion);
$data = array_merge($data, $details);
return $data;
}, $completions);
return $coursecompletiondata;
}
}
/**
* Delete completion information for users.
*
* @param \stdClass $user The user. If provided will delete completion information for just this user. Else all users.
* @param int $courseid The course id. Provide this if you want course completion and activity completion deleted.
* @param int $cmid The course module id. Provide this if you only want activity completion deleted.
*/
public static function delete_completion(\stdClass $user = null, int $courseid = null, int $cmid = null) {
global $DB;
if (isset($cmid)) {
$params = (isset($user)) ? ['userid' => $user->id, 'coursemoduleid' => $cmid] : ['coursemoduleid' => $cmid];
// Only delete the record for course modules completion.
$DB->delete_records('course_modules_completion', $params);
return;
}
if (isset($courseid)) {
$usersql = isset($user) ? 'AND cmc.userid = :userid' : '';
$params = isset($user) ? ['course' => $courseid, 'userid' => $user->id] : ['course' => $courseid];
// Find records relating to course modules.
$sql = "SELECT cmc.id
FROM {course_completion_criteria} ccc
JOIN {course_modules_completion} cmc ON ccc.moduleinstance = cmc.coursemoduleid
WHERE ccc.course = :course $usersql";
$recordids = $DB->get_records_sql($sql, $params);
$ids = array_keys($recordids);
if (!empty($ids)) {
list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
$deletesql = 'id ' . $deletesql;
$DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
}
$DB->delete_records('course_completion_crit_compl', $params);
$DB->delete_records('course_completions', $params);
}
}
}

View File

@ -0,0 +1,118 @@
<?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/>.
/**
* Trait for course completion creation in unit tests
*
* @package core_completion
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/completion/criteria/completion_criteria.php');
require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php');
require_once($CFG->dirroot . '/completion/criteria/completion_criteria_role.php');
/**
* Trait for unit tests and completion.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait completion_creation {
/** @var stdClass The course object. */
public $course;
/** @var context The course context object. */
public $coursecontext;
/** @var stdClass The course module object */
public $cm;
/**
* Create completion information.
*/
public function create_course_completion() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$coursecontext = context_course::instance($course->id);
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'completion' => 1]);
$modulecontext = context_module::instance($assign->cmid);
$cm = get_coursemodule_from_id('assign', $assign->cmid);
// Set completion rules.
$completion = new \completion_info($course);
$criteriadata = (object) [
'id' => $course->id,
'criteria_activity' => [
$cm->id => 1
]
];
$criterion = new \completion_criteria_activity();
$criterion->update_config($criteriadata);
$criteriadata = (object) [
'id' => $course->id,
'criteria_role' => [3 => 3]
];
$criterion = new \completion_criteria_role();
$criterion->update_config($criteriadata);
// Handle overall aggregation.
$aggdata = array(
'course' => $course->id,
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY
);
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
$aggregation->save();
$aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ROLE;
$aggregation = new \completion_aggregation($aggdata);
$aggregation->setMethod(COMPLETION_AGGREGATION_ANY);
$aggregation->save();
// Set variables for access in tests.
$this->course = $course;
$this->coursecontext = $coursecontext;
$this->cm = $cm;
}
/**
* Complete some of the course completion criteria.
*
* @param stdClass $user The user object
*/
public function complete_course($user) {
$this->getDataGenerator()->enrol_user($user->id, $this->course->id, 'student');
$completion = new \completion_info($this->course);
$criteriacompletions = $completion->get_completions($user->id, COMPLETION_CRITERIA_TYPE_ROLE);
$criteria = completion_criteria::factory(['id' => 3, 'criteriatype' => COMPLETION_CRITERIA_TYPE_ROLE]);
foreach ($criteriacompletions as $ccompletion) {
$criteria->complete($ccompletion);
}
// Set activity as complete.
$completion->update_state($this->cm, COMPLETION_COMPLETE, $user->id);
}
}

View File

@ -0,0 +1,118 @@
<?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 request helper.
*
* @package core_completion
* @category test
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/completion/tests/fixtures/completion_creation.php');
/**
* Tests for the \core_completion API's provider functionality.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_completion_privacy_test extends \core_privacy\tests\provider_testcase {
use completion_creation;
/**
* Test joining course completion data to an sql statement.
*/
public function test_get_course_completion_join_sql() {
global $DB;
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
list($join, $where, $params) = \core_completion\privacy\provider::get_course_completion_join_sql($user->id, 'comp', 'c.id');
$sql = "SELECT DISTINCT c.id
FROM {course} c
{$join}
WHERE {$where}";
$records = $DB->get_records_sql($sql, $params);
$data = array_shift($records);
$this->assertEquals($this->course->id, $data->id);
}
/**
* Test getting course completion information.
*/
public function test_get_course_completion_info() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $this->course);
$this->assertEquals('In progress', $coursecompletion['status']);
$this->assertCount(2, $coursecompletion['criteria']);
}
/**
* Test getting activity completion information.
*/
public function test_get_activity_completion_info() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
$activitycompletion = \core_completion\privacy\provider::get_activity_completion_info($user, $this->course,
$this->cm);
$this->assertEquals($user->id, $activitycompletion->userid);
$this->assertEquals($this->cm->id, $activitycompletion->coursemoduleid);
$this->assertEquals(1, $activitycompletion->completionstate);
}
/**
* Test deleting activity completion information for a user.
*/
public function test_delete_completion_activity_user() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
\core_completion\privacy\provider::delete_completion($user, null, $this->cm->id);
$activitycompletion = \core_completion\privacy\provider::get_activity_completion_info($user, $this->course,
$this->cm);
$this->assertEquals(0, $activitycompletion->completionstate);
}
/**
* Test deleting course completion information.
*/
public function test_delete_completion_course() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->create_course_completion();
$this->complete_course($user);
\core_completion\privacy\provider::delete_completion(null, $this->course->id);
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $this->course);
foreach ($coursecompletion['criteria'] as $criterion) {
$this->assertEquals('No', $criterion['completed']);
}
}
}

View File

@ -181,6 +181,22 @@ $string['overallaggregation_all'] = 'Course is complete when ALL conditions are
$string['overallaggregation_any'] = 'Course is complete when ANY of the conditions are met';
$string['pending'] = 'Pending';
$string['periodpostenrolment'] = 'Period post enrolment';
$string['privacy:metadata:completionstate'] = 'If the course module has been completed.';
$string['privacy:metadata:course'] = 'A course identifier.';
$string['privacy:metadata:coursecompletedsummary'] = 'Holds information about students that have completed criteria in a course.';
$string['privacy:metadata:coursemoduleid'] = 'The identifier to the course module.';
$string['privacy:metadata:coursemodulesummary'] = 'Stores the course module completion data for a user.';
$string['privacy:metadata:coursesummary'] = 'Stores the course completion data for a user.';
$string['privacy:metadata:gradefinal'] = 'Final grade recieved for the course completion.';
$string['privacy:metadata:overrideby'] = 'The user ID of the person who overrode the module completion.';
$string['privacy:metadata:reaggregate'] = 'If the course completion was reaggregated.';
$string['privacy:metadata:timecompleted'] = 'The time that the course was completed.';
$string['privacy:metadata:timeenrolled'] = 'The time that the user was enrolled into the course.';
$string['privacy:metadata:timemodified'] = 'The time the course module completion was modified.';
$string['privacy:metadata:timestarted'] = 'The time the course was started.';
$string['privacy:metadata:viewed'] = 'If the course module was viewed.';
$string['privacy:metadata:userid'] = 'Course and activity completion is stored against this user.';
$string['privacy:metadata:unenroled'] = 'If the user has been unenroled from the course.';
$string['progress'] = 'Student progress';
$string['progress-title'] = '{$a->user}, {$a->activity}: {$a->state} {$a->date}';
$string['progresstotal'] = 'Progress: {$a->complete} / {$a->total}';