mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 13:38:32 +01:00
Merge branch 'wip-MDL-61976-master' of git://github.com/marinaglancy/moodle
This commit is contained in:
commit
6fe7510ae0
494
mod/wiki/classes/privacy/provider.php
Normal file
494
mod/wiki/classes/privacy/provider.php
Normal file
@ -0,0 +1,494 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package mod_wiki
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_wiki\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use context_user;
|
||||
use context;
|
||||
use core_privacy\local\request\helper;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package mod_wiki
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
|
||||
$collection->add_database_table('wiki_subwikis', [
|
||||
'userid' => 'privacy:metadata:wiki_subwikis:userid',
|
||||
'groupid' => 'privacy:metadata:wiki_subwikis:groupid',
|
||||
], 'privacy:metadata:wiki_subwikis');
|
||||
|
||||
$collection->add_database_table('wiki_pages', [
|
||||
'userid' => 'privacy:metadata:wiki_pages:userid',
|
||||
'title' => 'privacy:metadata:wiki_pages:title',
|
||||
'cachedcontent' => 'privacy:metadata:wiki_pages:cachedcontent',
|
||||
'timecreated' => 'privacy:metadata:wiki_pages:timecreated',
|
||||
'timemodified' => 'privacy:metadata:wiki_pages:timemodified',
|
||||
'timerendered' => 'privacy:metadata:wiki_pages:timerendered',
|
||||
'pageviews' => 'privacy:metadata:wiki_pages:pageviews',
|
||||
'readonly' => 'privacy:metadata:wiki_pages:readonly',
|
||||
], 'privacy:metadata:wiki_pages');
|
||||
|
||||
$collection->add_database_table('wiki_versions', [
|
||||
'userid' => 'privacy:metadata:wiki_versions:userid',
|
||||
'content' => 'privacy:metadata:wiki_versions:content',
|
||||
'contentformat' => 'privacy:metadata:wiki_versions:contentformat',
|
||||
'version' => 'privacy:metadata:wiki_versions:version',
|
||||
'timecreated' => 'privacy:metadata:wiki_versions:timecreated',
|
||||
], 'privacy:metadata:wiki_versions');
|
||||
|
||||
$collection->add_database_table('wiki_locks', [
|
||||
'userid' => 'privacy:metadata:wiki_locks:userid',
|
||||
'sectionname' => 'privacy:metadata:wiki_locks:sectionname',
|
||||
'lockedat' => 'privacy:metadata:wiki_locks:lockedat',
|
||||
], 'privacy:metadata:wiki_locks');
|
||||
|
||||
$collection->link_subsystem('core_files', 'privacy:metadata:core_files');
|
||||
$collection->link_subsystem('core_tag', 'privacy:metadata:core_tag');
|
||||
$collection->link_subsystem('core_comment', 'privacy:metadata:core_comment');
|
||||
|
||||
// We do not report on wiki, wiki_synonyms, wiki_links because this is just context-related data.
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
$contextlist = new contextlist();
|
||||
|
||||
$contextlist->add_from_sql('SELECT ctx.id
|
||||
FROM {modules} m
|
||||
JOIN {course_modules} cm ON cm.module = m.id AND m.name = :modname
|
||||
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :contextlevel
|
||||
JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
|
||||
LEFT JOIN {wiki_pages} p ON p.subwikiid = s.id
|
||||
LEFT JOIN {wiki_versions} v ON v.pageid = p.id AND v.userid = :userid3
|
||||
LEFT JOIN {wiki_locks} l ON l.pageid = p.id AND l.userid = :userid4
|
||||
LEFT JOIN {comments} com ON com.itemid = p.id AND com.commentarea = :commentarea
|
||||
AND com.contextid = ctx.id AND com.userid = :userid5
|
||||
WHERE s.userid = :userid1 OR p.userid = :userid2 OR v.id IS NOT NULL OR l.id IS NOT NULL OR com.id IS NOT NULL',
|
||||
['modname' => 'wiki', 'contextlevel' => CONTEXT_MODULE, 'userid1' => $userid, 'userid2' => $userid,
|
||||
'userid3' => $userid, 'userid4' => $userid, 'commentarea' => 'wiki_page', 'userid5' => $userid]);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one subwiki to the export
|
||||
*
|
||||
* Each page is added as related data because all pages in one subwiki share the same filearea
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @param context $context
|
||||
* @param array $subwiki
|
||||
* @param string $wikimode
|
||||
*/
|
||||
protected static function export_subwiki($user, context $context, $subwiki, $wikimode) {
|
||||
if (empty($subwiki)) {
|
||||
return;
|
||||
}
|
||||
$subwikiid = key($subwiki);
|
||||
$pages = $subwiki[$subwikiid]['pages'];
|
||||
unset($subwiki[$subwikiid]['pages']);
|
||||
writer::with_context($context)->export_data([$subwikiid], (object)$subwiki[$subwikiid]);
|
||||
$allfiles = $wikimode === 'individual'; // Whether to export all files or only the ones that are used.
|
||||
|
||||
$alltexts = ''; // Store all texts that reference files to search which files are used.
|
||||
foreach ($pages as $page => $entry) {
|
||||
// Preprocess current page contents.
|
||||
if (!$allfiles && self::text_has_files($entry['page']['cachedcontent'])) {
|
||||
$alltexts .= $entry['page']['cachedcontent'];
|
||||
}
|
||||
$entry['page']['cachedcontent'] = format_text(writer::with_context($context)
|
||||
->rewrite_pluginfile_urls([$subwikiid], 'mod_wiki', 'attachments',
|
||||
$subwikiid, $entry['page']['cachedcontent']), FORMAT_HTML, ['context' => $context]);
|
||||
// Add page tags.
|
||||
$pagetags = \core_tag_tag::get_item_tags_array('mod_wiki', 'page', $entry['page']['id']);
|
||||
if ($pagetags) {
|
||||
$entry['page']['tags'] = $pagetags;
|
||||
}
|
||||
|
||||
// Preprocess revisions.
|
||||
if (!empty($entry['revisions'])) {
|
||||
// For each revision this user has made preprocess the contents.
|
||||
foreach ($entry['revisions'] as &$revision) {
|
||||
if ((!$allfiles && self::text_has_files($revision['content']))) {
|
||||
$alltexts .= $revision['content'];
|
||||
}
|
||||
$revision['content'] = writer::with_context($context)
|
||||
->rewrite_pluginfile_urls([$subwikiid], 'mod_wiki', 'attachments', $subwikiid, $revision['content']);
|
||||
}
|
||||
}
|
||||
$comments = self::get_page_comments($user, $context, $entry['page']['id'], !array_key_exists('userid', $entry['page']));
|
||||
if ($comments) {
|
||||
$entry['page']['comments'] = $comments;
|
||||
}
|
||||
writer::with_context($context)->export_related_data([$subwikiid], $page, $entry);
|
||||
}
|
||||
|
||||
if ($allfiles) {
|
||||
// Export all files.
|
||||
writer::with_context($context)->export_area_files([$subwikiid], 'mod_wiki', 'attachments', $subwikiid);
|
||||
} else {
|
||||
// Analyze which files are used in the texts.
|
||||
self::export_used_files($context, $subwikiid, $alltexts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves page comments
|
||||
*
|
||||
* We can not use \core_comment\privacy\provider::export_comments() because it expects each item to have a separate
|
||||
* subcontext and we store wiki pages as related data to subwiki because the files are shared between pages.
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @param \context $context
|
||||
* @param int $pageid
|
||||
* @param bool $onlyforthisuser
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_page_comments($user, \context $context, $pageid, $onlyforthisuser = true) {
|
||||
global $USER, $DB;
|
||||
$params = [
|
||||
'contextid' => $context->id,
|
||||
'commentarea' => 'wiki_page',
|
||||
'itemid' => $pageid
|
||||
];
|
||||
$sql = "SELECT c.id, c.content, c.format, c.timecreated, c.userid
|
||||
FROM {comments} c
|
||||
WHERE c.contextid = :contextid AND
|
||||
c.commentarea = :commentarea AND
|
||||
c.itemid = :itemid";
|
||||
if ($onlyforthisuser) {
|
||||
$sql .= " AND c.userid = :userid";
|
||||
$params['userid'] = $USER->id;
|
||||
}
|
||||
$sql .= " ORDER BY c.timecreated DESC";
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
$comments = [];
|
||||
foreach ($rs as $record) {
|
||||
if ($record->userid != $user->id) {
|
||||
// Clean HTML in comments that were added by other users.
|
||||
$comment = ['content' => format_text($record->content, $record->format, ['context' => $context])];
|
||||
} else {
|
||||
// Export comments made by this user as they are stored.
|
||||
$comment = ['content' => $record->content, 'contentformat' => $record->format];
|
||||
}
|
||||
$comment += [
|
||||
'time' => transform::datetime($record->timecreated),
|
||||
'userid' => transform::user($record->userid),
|
||||
];
|
||||
$comments[] = (object)$comment;
|
||||
}
|
||||
$rs->close();
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text has embedded files
|
||||
*
|
||||
* @param string $str
|
||||
* @return bool
|
||||
*/
|
||||
protected static function text_has_files($str) {
|
||||
return strpos($str, '@@PLUGINFILE@@') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze which files are used in the texts and export
|
||||
* @param context $context
|
||||
* @param int $subwikiid
|
||||
* @param string $alltexts
|
||||
* @return int|void
|
||||
*/
|
||||
protected static function export_used_files($context, $subwikiid, $alltexts) {
|
||||
if (!self::text_has_files($alltexts)) {
|
||||
return;
|
||||
}
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files($context->id, 'mod_wiki', 'attachments', $subwikiid,
|
||||
'filepath, filename', false);
|
||||
if (empty($files)) {
|
||||
return;
|
||||
}
|
||||
usort($files, function($file1, $file2) {
|
||||
return strcmp($file2->get_filepath(), $file1->get_filename());
|
||||
});
|
||||
foreach ($files as $file) {
|
||||
$filepath = $file->get_filepath() . $file->get_filename();
|
||||
$needles = ['@@PLUGINFILE@@' . s($filepath),
|
||||
'@@PLUGINFILE@@' . $filepath,
|
||||
'@@PLUGINFILE@@' . str_replace(' ', '%20', $filepath),
|
||||
'@@PLUGINFILE@@' . s($filepath),
|
||||
'@@PLUGINFILE@@' . s(str_replace(' ', '%20', $filepath))
|
||||
];
|
||||
$needles = array_unique($needles);
|
||||
$newtext = str_replace($needles, '', $alltexts);
|
||||
if ($newtext !== $alltexts) {
|
||||
$alltexts = $newtext;
|
||||
writer::with_context($context)->export_file([$subwikiid], $file);
|
||||
if (!self::text_has_files($alltexts)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
foreach ($contextlist as $context) {
|
||||
if ($context->contextlevel != CONTEXT_MODULE) {
|
||||
continue;
|
||||
}
|
||||
$user = $contextlist->get_user();
|
||||
|
||||
$rs = $DB->get_recordset_sql('SELECT w.wikimode, s.id AS subwikiid,
|
||||
s.groupid AS subwikigroupid, s.userid AS subwikiuserid,
|
||||
p.id AS pageid, p.userid AS pageuserid, p.title, p.cachedcontent, p.timecreated AS pagetimecreated,
|
||||
p.timemodified AS pagetimemodified, p.timerendered AS pagetimerendered, p.pageviews, p.readonly,
|
||||
v.id AS versionid, v.content, v.contentformat, v.version, v.timecreated AS versiontimecreated,
|
||||
l.id AS lockid, l.sectionname, l.lockedat
|
||||
FROM {course_modules} cm
|
||||
JOIN {wiki} w ON w.id = cm.instance
|
||||
JOIN {wiki_subwikis} s ON cm.instance = s.wikiid
|
||||
LEFT JOIN {wiki_pages} p ON p.subwikiid = s.id
|
||||
LEFT JOIN {wiki_versions} v ON v.pageid = p.id AND v.userid = :user4
|
||||
LEFT JOIN {wiki_locks} l ON l.pageid = p.id AND l.userid = :user5
|
||||
WHERE cm.id = :cmid AND (s.userid = :user1 OR p.userid = :user2 OR v.userid = :user3 OR l.userid = :user6 OR
|
||||
EXISTS (SELECT 1 FROM {comments} com WHERE com.itemid = p.id AND com.commentarea = :commentarea
|
||||
AND com.contextid = :ctxid AND com.userid = :user7)
|
||||
)
|
||||
ORDER BY s.id, p.id, v.id',
|
||||
['cmid' => $context->instanceid,
|
||||
'user1' => $user->id, 'user2' => $user->id, 'user3' => $user->id, 'user4' => $user->id,
|
||||
'user5' => $user->id, 'user6' => $user->id, 'user7' => $user->id, 'commentarea' => 'wiki_page',
|
||||
'ctxid' => $context->id]);
|
||||
|
||||
if (!$rs->current()) {
|
||||
$rs->close();
|
||||
continue;
|
||||
}
|
||||
|
||||
$subwiki = [];
|
||||
$wikimode = null;
|
||||
foreach ($rs as $record) {
|
||||
if ($wikimode === null) {
|
||||
$wikimode = $record->wikimode;
|
||||
}
|
||||
if (!isset($subwiki[$record->subwikiid])) {
|
||||
self::export_subwiki($user, $context, $subwiki, $wikimode);
|
||||
$subwiki = [$record->subwikiid => [
|
||||
'groupid' => $record->subwikigroupid,
|
||||
'userid' => $record->subwikiuserid ? transform::user($record->subwikiuserid) : 0,
|
||||
'pages' => []
|
||||
]];
|
||||
}
|
||||
|
||||
if (!$record->pageid) {
|
||||
// This is an empty individual wiki.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepend page title with the page id to guarantee uniqueness.
|
||||
$pagetitle = format_string($record->title, true, ['context' => $context]);
|
||||
$page = $record->pageid . ' ' . $pagetitle;
|
||||
if (!isset($subwiki[$record->subwikiid]['pages'][$page])) {
|
||||
// Export basic details about the page.
|
||||
$subwiki[$record->subwikiid]['pages'][$page] = ['page' => [
|
||||
'id' => $record->pageid,
|
||||
'title' => $pagetitle,
|
||||
'cachedcontent' => $record->cachedcontent,
|
||||
]];
|
||||
if ($record->pageuserid == $user->id) {
|
||||
// This page belongs to this user. Export all details.
|
||||
$subwiki[$record->subwikiid]['pages'][$page]['page'] += [
|
||||
'userid' => transform::user($user->id),
|
||||
'timecreated' => transform::datetime($record->pagetimecreated),
|
||||
'timemodified' => transform::datetime($record->pagetimemodified),
|
||||
'timerendered' => transform::datetime($record->pagetimerendered),
|
||||
'pageviews' => $record->pageviews,
|
||||
'readonly' => $record->readonly,
|
||||
];
|
||||
|
||||
$subwiki[$record->subwikiid]['pages'][$page]['page']['userid'] = transform::user($user->id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($record->versionid) {
|
||||
$subwiki[$record->subwikiid]['pages'][$page]['revisions'][$record->versionid] = [
|
||||
'content' => $record->content,
|
||||
'contentformat' => $record->contentformat,
|
||||
'version' => $record->version,
|
||||
'timecreated' => transform::datetime($record->versiontimecreated)
|
||||
];
|
||||
}
|
||||
|
||||
if ($record->lockid) {
|
||||
$subwiki[$record->subwikiid]['pages'][$page]['locks'][$record->lockid] = [
|
||||
'sectionname' => $record->sectionname,
|
||||
'lockedat' => transform::datetime($record->lockedat),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
self::export_subwiki($user, $context, $subwiki, $wikimode);
|
||||
|
||||
if ($subwiki) {
|
||||
// Export wiki itself.
|
||||
$contextdata = helper::get_context_data($context, $user);
|
||||
helper::export_context_files($context, $user);
|
||||
writer::with_context($context)->export_data([], $contextdata);
|
||||
}
|
||||
|
||||
$rs->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
global $DB;
|
||||
|
||||
if ($context->contextlevel != CONTEXT_MODULE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subwikis = $DB->get_fieldset_sql('SELECT s.id
|
||||
FROM {course_modules} cm
|
||||
JOIN {modules} m ON m.name = :wiki AND cm.module = m.id
|
||||
JOIN {wiki_subwikis} s ON s.wikiid = cm.instance
|
||||
WHERE cm.id = :cmid',
|
||||
['cmid' => $context->instanceid, 'wiki' => 'wiki']);
|
||||
if (!$subwikis) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fs = get_file_storage();
|
||||
$fs->delete_area_files($context->id, 'mod_wiki', 'attachments');
|
||||
|
||||
\core_tag\privacy\provider::delete_item_tags($context, 'mod_wiki', 'page');
|
||||
|
||||
\core_comment\privacy\provider::delete_comments_for_all_users($context, 'mod_wiki', 'wiki_page');
|
||||
|
||||
list($sql, $params) = $DB->get_in_or_equal($subwikis);
|
||||
$DB->delete_records_select('wiki_locks', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid '.$sql.')', $params);
|
||||
$DB->delete_records_select('wiki_versions', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid '.$sql.')', $params);
|
||||
$DB->delete_records_select('wiki_synonyms', 'subwikiid '.$sql, $params);
|
||||
$DB->delete_records_select('wiki_links', 'subwikiid '.$sql, $params);
|
||||
$DB->delete_records_select('wiki_pages', 'subwikiid '.$sql, $params);
|
||||
$DB->delete_records_select('wiki_subwikis', 'id '.$sql, $params);
|
||||
|
||||
$DB->delete_records('tag_instance', ['contextid' => $context->id, 'component' => 'mod_wiki', 'itemtype' => 'page']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
$contextids = $contextlist->get_contextids();
|
||||
|
||||
if (!$contextids) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove only individual subwikis. Contributions to collaborative wikis is not considered personal contents.
|
||||
list($ctxsql, $ctxparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
|
||||
$subwikis = $DB->get_records_sql_menu('SELECT s.id, ctx.id AS ctxid
|
||||
FROM {context} ctx
|
||||
JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextmod
|
||||
JOIN {modules} m ON m.name = :wiki AND cm.module = m.id
|
||||
JOIN {wiki_subwikis} s ON s.wikiid = cm.instance AND s.userid = :userid
|
||||
WHERE ctx.id ' . $ctxsql,
|
||||
['userid' => (int)$contextlist->get_user()->id, 'wiki' => 'wiki', 'contextmod' => CONTEXT_MODULE] + $ctxparams);
|
||||
|
||||
if ($subwikis) {
|
||||
// We found individual subwikis that need to be deleted completely.
|
||||
|
||||
$fs = get_file_storage();
|
||||
foreach ($subwikis as $subwikiid => $contextid) {
|
||||
$fs->delete_area_files($contextid, 'mod_wiki', 'attachments', $subwikiid);
|
||||
\core_comment\privacy\provider::delete_comments_for_all_users_select(context::instance_by_id($contextid),
|
||||
'mod_wiki', 'wiki_page', "IN (SELECT id FROM {wiki_pages} WHERE subwikiid=:subwikiid)",
|
||||
['subwikiid' => $subwikiid]);
|
||||
}
|
||||
|
||||
list($sql, $params) = $DB->get_in_or_equal(array_keys($subwikis), SQL_PARAMS_NAMED);
|
||||
|
||||
$DB->execute("DELETE FROM {tag_instance} WHERE component=:component AND itemtype=:itemtype AND itemid IN
|
||||
(SELECT id FROM {wiki_pages} WHERE subwikiid $sql)",
|
||||
['component' => 'mod_wiki', 'itemtype' => 'page'] + $params);
|
||||
|
||||
$DB->delete_records_select('wiki_locks', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid ' . $sql . ')',
|
||||
$params);
|
||||
$DB->delete_records_select('wiki_versions', 'pageid IN (SELECT id FROM {wiki_pages} WHERE subwikiid ' . $sql . ')',
|
||||
$params);
|
||||
$DB->delete_records_select('wiki_synonyms', 'subwikiid ' . $sql, $params);
|
||||
$DB->delete_records_select('wiki_links', 'subwikiid ' . $sql, $params);
|
||||
$DB->delete_records_select('wiki_pages', 'subwikiid ' . $sql, $params);
|
||||
$DB->delete_records_select('wiki_subwikis', 'id ' . $sql, $params);
|
||||
}
|
||||
|
||||
// Remove comments made by this user on all other wiki pages.
|
||||
\core_comment\privacy\provider::delete_comments_for_user($contextlist, 'mod_wiki', 'wiki_page');
|
||||
}
|
||||
}
|
@ -199,6 +199,31 @@ $string['pluginadministration'] = 'Wiki administration';
|
||||
$string['pluginname'] = 'Wiki';
|
||||
$string['prettyprint'] = 'Printer-friendly version';
|
||||
$string['print'] = 'Print';
|
||||
$string['privacy:metadata:core_comment'] = 'Comments on wiki page';
|
||||
$string['privacy:metadata:core_files'] = 'Files attached to subwikis';
|
||||
$string['privacy:metadata:core_tag'] = 'Tags associated with wiki pages';
|
||||
$string['privacy:metadata:wiki_locks'] = 'Temporary storage for wiki edit locks';
|
||||
$string['privacy:metadata:wiki_locks:userid'] = 'User who locked a page';
|
||||
$string['privacy:metadata:wiki_locks:sectionname'] = 'Name of the locked page section';
|
||||
$string['privacy:metadata:wiki_locks:lockedat'] = 'Date when locked';
|
||||
$string['privacy:metadata:wiki_pages'] = 'Information about wiki pages';
|
||||
$string['privacy:metadata:wiki_pages:userid'] = 'Last user who edited the page';
|
||||
$string['privacy:metadata:wiki_pages:title'] = 'Name of the page';
|
||||
$string['privacy:metadata:wiki_pages:cachedcontent'] = 'Cached content in HTML format';
|
||||
$string['privacy:metadata:wiki_pages:timecreated'] = 'Time when page was first created';
|
||||
$string['privacy:metadata:wiki_pages:timemodified'] = 'Time when page was last modified';
|
||||
$string['privacy:metadata:wiki_pages:timerendered'] = 'Time when page was last rendered';
|
||||
$string['privacy:metadata:wiki_pages:pageviews'] = 'Number of times page was viewed';
|
||||
$string['privacy:metadata:wiki_pages:readonly'] = 'Whether a page is read-only';
|
||||
$string['privacy:metadata:wiki_subwikis'] = 'Information about subwikis (in case of group or individual mode)';
|
||||
$string['privacy:metadata:wiki_subwikis:userid'] = 'User who owns a subwiki (for individual wikis)';
|
||||
$string['privacy:metadata:wiki_subwikis:groupid'] = 'Group that owns a subwiki';
|
||||
$string['privacy:metadata:wiki_versions'] = 'Information about wiki pages history';
|
||||
$string['privacy:metadata:wiki_versions:userid'] = 'User who created revision';
|
||||
$string['privacy:metadata:wiki_versions:content'] = 'Revision content';
|
||||
$string['privacy:metadata:wiki_versions:contentformat'] = 'Revision content format';
|
||||
$string['privacy:metadata:wiki_versions:version'] = 'Version number';
|
||||
$string['privacy:metadata:wiki_versions:timecreated'] = 'Time when revision was created';
|
||||
$string['previewwarning'] = 'This is a preview. Changes have not been saved yet.';
|
||||
$string['rated']='You rated this page as a {$a}';
|
||||
$string['rating']='Rating';
|
||||
|
@ -76,6 +76,42 @@ class mod_wiki_generator extends testing_module_generator {
|
||||
return $this->create_page($wiki, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves or generates a subwiki and returns its id
|
||||
*
|
||||
* @param stdClass $wiki
|
||||
* @param int $subwikiid
|
||||
* @param int $group
|
||||
* @param int $userid
|
||||
* @return int
|
||||
*/
|
||||
public function get_subwiki($wiki, $subwikiid = null, $group = null, $userid = null) {
|
||||
global $USER, $DB;
|
||||
|
||||
if ($subwikiid) {
|
||||
$params = ['id' => $subwikiid, 'wikiid' => $wiki->id];
|
||||
if ($group !== null) {
|
||||
$params['group'] = $group;
|
||||
}
|
||||
if ($userid !== null) {
|
||||
$params['userid'] = $userid;
|
||||
}
|
||||
return $DB->get_field('wiki_subwikis', 'id', $params, MUST_EXIST);
|
||||
}
|
||||
|
||||
if ($userid === null) {
|
||||
$userid = ($wiki->wikimode == 'individual') ? $USER->id : 0;
|
||||
}
|
||||
if ($group === null) {
|
||||
$group = 0;
|
||||
}
|
||||
if ($subwiki = wiki_get_subwiki_by_group($wiki->id, $group, $userid)) {
|
||||
return $subwiki->id;
|
||||
} else {
|
||||
return wiki_add_subwiki($wiki->id, $group, $userid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a page in wiki.
|
||||
*
|
||||
@ -92,23 +128,15 @@ class mod_wiki_generator extends testing_module_generator {
|
||||
'title' => 'wiki page '.$this->pagecount,
|
||||
'wikiid' => $wiki->id,
|
||||
'subwikiid' => 0,
|
||||
'group' => 0,
|
||||
'group' => null,
|
||||
'userid' => null,
|
||||
'content' => 'Wiki page content '.$this->pagecount,
|
||||
'format' => $wiki->defaultformat
|
||||
);
|
||||
if (empty($record['wikiid']) && empty($record['subwikiid'])) {
|
||||
throw new coding_exception('wiki page generator requires either wikiid or subwikiid');
|
||||
}
|
||||
if (!$record['subwikiid']) {
|
||||
if (!isset($record['userid'])) {
|
||||
$record['userid'] = ($wiki->wikimode == 'individual') ? $USER->id : 0;
|
||||
}
|
||||
if ($subwiki = wiki_get_subwiki_by_group($record['wikiid'], $record['group'], $record['userid'])) {
|
||||
$record['subwikiid'] = $subwiki->id;
|
||||
} else {
|
||||
$record['subwikiid'] = wiki_add_subwiki($record['wikiid'], $record['group'], $record['userid']);
|
||||
}
|
||||
}
|
||||
$record['subwikiid'] = $this->get_subwiki($wiki, $record['subwikiid'], $record['group'], $record['userid']);
|
||||
|
||||
$wikipage = wiki_get_page_by_title($record['subwikiid'], $record['title']);
|
||||
if (!$wikipage) {
|
||||
|
532
mod/wiki/tests/privacy_test.php
Normal file
532
mod/wiki/tests/privacy_test.php
Normal file
@ -0,0 +1,532 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package mod_wiki
|
||||
* @category test
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use mod_wiki\privacy\provider;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
require_once($CFG->dirroot.'/mod/wiki/locallib.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package mod_wiki
|
||||
* @category test
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_wiki_privacy_testcase extends provider_testcase {
|
||||
|
||||
/** @var array */
|
||||
protected $users = [];
|
||||
/** @var array */
|
||||
protected $pages = [];
|
||||
/** @var array */
|
||||
protected $contexts = [];
|
||||
/** @var array */
|
||||
protected $subwikis = [];
|
||||
/** @var array */
|
||||
protected $pagepaths = [];
|
||||
|
||||
/**
|
||||
* Set up for each test.
|
||||
*
|
||||
* There are three users and four wikis.
|
||||
* 1 : collaborative wiki, has context $this->contexts[1] and has a single subwiki $this->subwikis[1]
|
||||
* 2 : individual wiki, has context $this->contexts[2] and three subwikis (one for each user):
|
||||
* $this->subwikis[21], $this->subwikis[22], $this->subwikis[23],
|
||||
* the subwiki for the third user is empty
|
||||
* 3 : collaborative wiki, has context $this->contexts[3] and has a single subwiki $this->subwikis[3]
|
||||
* 4 : collaborative wiki, has context $this->contexts[4], this wiki is empty
|
||||
*
|
||||
* Each subwiki (except for "23") has pages, for example, in $this->subwiki[1] there are pages
|
||||
* $this->pages[1][1], $this->pages[1][2] and $this->pages[1][3]
|
||||
* In the export data they have paths:
|
||||
* $this->pagepaths[1][1], $this->pagepaths[1][2], $this->pagepaths[1][3]
|
||||
*/
|
||||
public function setUp() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$course = $dg->create_course();
|
||||
|
||||
$this->users[1] = $dg->create_user();
|
||||
$this->users[2] = $dg->create_user();
|
||||
$this->users[3] = $dg->create_user();
|
||||
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->getDataGenerator()->enrol_user($this->users[1]->id, $course->id, $studentrole->id, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($this->users[2]->id, $course->id, $studentrole->id, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($this->users[3]->id, $course->id, $studentrole->id, 'manual');
|
||||
|
||||
$cm1 = $this->getDataGenerator()->create_module('wiki', ['course' => $course->id]);
|
||||
$cm2 = $this->getDataGenerator()->create_module('wiki', ['course' => $course->id, 'wikimode' => 'individual']);
|
||||
$cm3 = $this->getDataGenerator()->create_module('wiki', ['course' => $course->id]);
|
||||
$cm4 = $this->getDataGenerator()->create_module('wiki', ['course' => $course->id]); // Empty.
|
||||
|
||||
// User1.
|
||||
$this->setUser($this->users[1]);
|
||||
|
||||
// Create and modify pages in collaborative wiki.
|
||||
$this->pages[1][1] = $this->create_first_page($cm1);
|
||||
$this->pages[1][2] = $this->create_page($cm1, ['content' => 'initial content']);
|
||||
$this->update_page($cm1, $this->pages[1][2], ['content' => 'update1 <img src="@@PLUGINFILE@@/Dog%20jump.jpg">']);
|
||||
$this->attach_file($cm1, "Dog jump.jpg", 'jpg:Doggy');
|
||||
$this->update_page($cm1, $this->pages[1][2], ['content' => 'update2']);
|
||||
|
||||
// Create pages in individual wiki, add files that are not used in text.
|
||||
$this->pages[21][1] = $this->create_first_page($cm2);
|
||||
$this->pages[21][2] = $this->create_page($cm2);
|
||||
$this->attach_file($cm2, "mycat.jpg", 'jpg:Cat');
|
||||
|
||||
// User2.
|
||||
$this->setUser($this->users[2]);
|
||||
|
||||
// Modify existing pages in the first collaborative wiki.
|
||||
$this->update_page($cm1, $this->pages[1][2], ['content' => 'update3 <img src="@@PLUGINFILE@@/Hamster.jpg">']);
|
||||
$this->attach_file($cm1, "Hamster.jpg", 'jpg:Hamster');
|
||||
|
||||
// Create pages in individual wiki.
|
||||
$this->pages[22][1] = $this->create_first_page($cm2);
|
||||
$this->pages[22][2] = $this->create_page($cm2);
|
||||
|
||||
// Create pages in the third wiki.
|
||||
$this->pages[3][1] = $this->create_first_page($cm3);
|
||||
|
||||
// User3 (testing locks and empty subwiki).
|
||||
$this->setUser($this->users[3]);
|
||||
|
||||
// Create a subwiki in the individual wiki without any pages.
|
||||
$subwiki23 = $dg->get_plugin_generator('mod_wiki')->get_subwiki($cm2);
|
||||
|
||||
// Create a page in the first wiki and then lock it.
|
||||
$this->pages[1][3] = $this->create_page($cm1);
|
||||
wiki_set_lock($this->pages[1][3]->id, $this->users[3]->id, null, true);
|
||||
|
||||
// Lock a page in the third wiki without having any revisions on it.
|
||||
wiki_set_lock($this->pages[3][1]->id, $this->users[3]->id, null, true);
|
||||
|
||||
$this->subwikis = [
|
||||
1 => $this->pages[1][1]->subwikiid,
|
||||
21 => $this->pages[21][1]->subwikiid,
|
||||
22 => $this->pages[22][1]->subwikiid,
|
||||
23 => $subwiki23,
|
||||
3 => $this->pages[3][1]->subwikiid,
|
||||
];
|
||||
|
||||
$this->contexts = [
|
||||
1 => context_module::instance($cm1->cmid),
|
||||
2 => context_module::instance($cm2->cmid),
|
||||
3 => context_module::instance($cm3->cmid),
|
||||
4 => context_module::instance($cm4->cmid),
|
||||
];
|
||||
|
||||
$this->pagepaths = [
|
||||
1 => [
|
||||
1 => $this->pages[1][1]->id . ' ' . $this->pages[1][1]->title,
|
||||
2 => $this->pages[1][2]->id . ' ' . $this->pages[1][2]->title,
|
||||
3 => $this->pages[1][3]->id . ' ' . $this->pages[1][3]->title,
|
||||
],
|
||||
21 => [
|
||||
1 => $this->pages[21][1]->id . ' ' . $this->pages[21][1]->title,
|
||||
2 => $this->pages[21][2]->id . ' ' . $this->pages[21][2]->title,
|
||||
],
|
||||
22 => [
|
||||
1 => $this->pages[22][1]->id . ' ' . $this->pages[22][1]->title,
|
||||
2 => $this->pages[22][2]->id . ' ' . $this->pages[22][2]->title,
|
||||
],
|
||||
3 => [
|
||||
1 => $this->pages[3][1]->id . ' ' . $this->pages[3][1]->title,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate first page in wiki as current user
|
||||
*
|
||||
* @param stdClass $wiki
|
||||
* @param array $record
|
||||
* @return mixed
|
||||
*/
|
||||
protected function create_first_page($wiki, $record = []) {
|
||||
$dg = $this->getDataGenerator();
|
||||
$wg = $dg->get_plugin_generator('mod_wiki');
|
||||
return $wg->create_first_page($wiki, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a page in wiki as current user
|
||||
*
|
||||
* @param stdClass $wiki
|
||||
* @param array $record
|
||||
* @return mixed
|
||||
*/
|
||||
protected function create_page($wiki, $record = []) {
|
||||
$dg = $this->getDataGenerator();
|
||||
$wg = $dg->get_plugin_generator('mod_wiki');
|
||||
return $wg->create_page($wiki, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing page in wiki as current user
|
||||
*
|
||||
* @param stdClass $wiki
|
||||
* @param stdClass $page
|
||||
* @param array $record
|
||||
* @return mixed
|
||||
*/
|
||||
protected function update_page($wiki, $page, $record = []) {
|
||||
$dg = $this->getDataGenerator();
|
||||
$wg = $dg->get_plugin_generator('mod_wiki');
|
||||
return $wg->create_page($wiki, ['title' => $page->title] + $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach file to a wiki as a current user
|
||||
*
|
||||
* @param stdClass $wiki
|
||||
* @param string $filename
|
||||
* @param string $filecontent
|
||||
* @return stored_file
|
||||
*/
|
||||
protected function attach_file($wiki, $filename, $filecontent) {
|
||||
$dg = $this->getDataGenerator();
|
||||
$wg = $dg->get_plugin_generator('mod_wiki');
|
||||
$subwikiid = $wg->get_subwiki($wiki);
|
||||
|
||||
$fs = get_file_storage();
|
||||
return $fs->create_file_from_string([
|
||||
'contextid' => context_module::instance($wiki->cmid)->id,
|
||||
'component' => 'mod_wiki',
|
||||
'filearea' => 'attachments',
|
||||
'itemid' => $subwikiid,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
], $filecontent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the contexts for a user.
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
|
||||
// Get contexts for the first user.
|
||||
$contextids = provider::get_contexts_for_userid($this->users[1]->id)->get_contextids();
|
||||
$this->assertEquals([
|
||||
$this->contexts[1]->id,
|
||||
$this->contexts[2]->id,
|
||||
], $contextids, '', 0.0, 10, true);
|
||||
|
||||
// Get contexts for the second user.
|
||||
$contextids = provider::get_contexts_for_userid($this->users[2]->id)->get_contextids();
|
||||
$this->assertEquals([
|
||||
$this->contexts[1]->id,
|
||||
$this->contexts[2]->id,
|
||||
$this->contexts[3]->id,
|
||||
], $contextids, '', 0.0, 10, true);
|
||||
|
||||
// Get contexts for the third user.
|
||||
$contextids = provider::get_contexts_for_userid($this->users[3]->id)->get_contextids();
|
||||
$this->assertEquals([
|
||||
$this->contexts[1]->id,
|
||||
$this->contexts[2]->id,
|
||||
$this->contexts[3]->id,
|
||||
], $contextids, '', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export data for user 1
|
||||
*/
|
||||
public function test_export_user_data1() {
|
||||
|
||||
// Export all contexts for the first user.
|
||||
$contextids = array_values(array_map(function($c) {
|
||||
return $c->id;
|
||||
}, $this->contexts));
|
||||
$appctx = new approved_contextlist($this->users[1], 'mod_wiki', $contextids);
|
||||
provider::export_user_data($appctx);
|
||||
|
||||
// First wiki has two pages ever touched by this user.
|
||||
$data = writer::with_context($this->contexts[1])->get_related_data([$this->subwikis[1]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[1][1],
|
||||
$this->pagepaths[1][2]
|
||||
], array_keys($data));
|
||||
// First page was initially created by this user and all its information is returned to this user.
|
||||
$data11 = $data[$this->pagepaths[1][1]];
|
||||
$this->assertEquals($this->pages[1][1]->cachedcontent, $data11['page']['cachedcontent']);
|
||||
$this->assertNotEmpty($data11['page']['timecreated']);
|
||||
// Wiki creates two revisions when page is created, first one with empty content.
|
||||
$this->assertEquals(2, count($data11['revisions']));
|
||||
$this->assertFalse(array_key_exists('locks', $data11));
|
||||
// Only one file is returned that was in the revision made by this user.
|
||||
|
||||
// The second page was last modified by a different user, so userid in the wiki_pages table is different,
|
||||
// additional page information is not exported.
|
||||
$data12 = $data[$this->pagepaths[1][2]];
|
||||
$this->assertFalse(isset($data12['page']['timecreated']));
|
||||
// There are two revisions for creating the page and two additional revisions made by this user.
|
||||
$this->assertEquals(4, count($data12['revisions']));
|
||||
$lastrevision = array_pop($data12['revisions']);
|
||||
$this->assertEquals('update2', $lastrevision['content']);
|
||||
|
||||
// There is one file that was used in this user's contents - "Dog face.jpg" and one file in page cachedcontents.
|
||||
$files = writer::with_context($this->contexts[1])->get_files([$this->subwikis[1]]);
|
||||
$this->assertEquals(['Dog jump.jpg', 'Hamster.jpg'], array_keys($files), '', 0, 10, true);
|
||||
|
||||
// Second (individual) wiki for the first user, two pages are returned for this user's subwiki.
|
||||
$data = writer::with_context($this->contexts[2])->get_related_data([$this->subwikis[21]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[21][1],
|
||||
$this->pagepaths[21][2]
|
||||
], array_keys($data));
|
||||
$files = writer::with_context($this->contexts[2])->get_files([$this->subwikis[21]]);
|
||||
$this->assertEquals(['mycat.jpg'], array_keys($files));
|
||||
|
||||
// Second (individual) wiki for the first user, nothing is returned for the second user's subwiki.
|
||||
$this->assertFalse(writer::with_context($this->contexts[2])->has_any_data([$this->subwikis[22]]));
|
||||
|
||||
// Third wiki for the first user, there were no contributions by the first user.
|
||||
$this->assertFalse(writer::with_context($this->contexts[3])->has_any_data([$this->subwikis[3]]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test export data for user 2
|
||||
*/
|
||||
public function test_export_user_data2() {
|
||||
|
||||
// Export all contexts for the second user.
|
||||
$contextids = array_values(array_map(function($c) {
|
||||
return $c->id;
|
||||
}, $this->contexts));
|
||||
$appctx = new approved_contextlist($this->users[2], 'mod_wiki', $contextids);
|
||||
provider::export_user_data($appctx);
|
||||
|
||||
// First wiki - this user only modified the second page.
|
||||
$data = writer::with_context($this->contexts[1])->get_related_data([$this->subwikis[1]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[1][2]
|
||||
], array_keys($data));
|
||||
|
||||
// This user was the last one to modify this page, so the page info is returned.
|
||||
$data12 = $data[$this->pagepaths[1][2]];
|
||||
$this->assertEquals('update3 <img src="files/Hamster.jpg" alt="Hamster.jpg" />', $data12['page']['cachedcontent']);
|
||||
// He made one revision.
|
||||
$this->assertEquals(1, count($data12['revisions']));
|
||||
$lastrevision = reset($data12['revisions']);
|
||||
$this->assertEquals('update3 <img src="files/Hamster.jpg">', $lastrevision['content']);
|
||||
|
||||
// Only one file was used in the first wiki by this user - Hamster.jpg.
|
||||
$files = writer::with_context($this->contexts[1])->get_files([$this->subwikis[1]]);
|
||||
$this->assertEquals(['Hamster.jpg'], array_keys($files));
|
||||
|
||||
// Export second (individual) wiki, nothing is returned for the other user's subwiki.
|
||||
$this->assertFalse(writer::with_context($this->contexts[2])->has_any_data([$this->subwikis[21]]));
|
||||
|
||||
// Export second (individual) wiki, two pages are returned for this user's subwiki.
|
||||
$data = writer::with_context($this->contexts[2])->get_related_data([$this->subwikis[22]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[22][1],
|
||||
$this->pagepaths[22][2]
|
||||
], array_keys($data));
|
||||
$files = writer::with_context($this->contexts[2])->get_files([$this->subwikis[22]]);
|
||||
$this->assertEmpty($files);
|
||||
|
||||
// Second user made contributions to the third wiki.
|
||||
$data = writer::with_context($this->contexts[3])->get_related_data([$this->subwikis[3]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[3][1]
|
||||
], array_keys($data));
|
||||
$files = writer::with_context($this->contexts[3])->get_files([$this->subwikis[3]]);
|
||||
$this->assertEmpty($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test export data for user 3 (locks, empty individual wiki)
|
||||
*/
|
||||
public function test_export_user_data3() {
|
||||
|
||||
// Export all contexts for the third user.
|
||||
$contextids = array_values(array_map(function($c) {
|
||||
return $c->id;
|
||||
}, $this->contexts));
|
||||
$appctx = new approved_contextlist($this->users[3], 'mod_wiki', $contextids);
|
||||
provider::export_user_data($appctx);
|
||||
|
||||
// For the third page of the first wiki there are 2 revisions and 1 lock.
|
||||
$data = writer::with_context($this->contexts[1])->get_related_data([$this->subwikis[1]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[1][3]
|
||||
], array_keys($data));
|
||||
|
||||
$data13 = $data[$this->pagepaths[1][3]];
|
||||
$this->assertNotEmpty($data13['page']['timecreated']);
|
||||
$this->assertEquals(2, count($data13['revisions']));
|
||||
$this->assertEquals(1, count($data13['locks']));
|
||||
$files = writer::with_context($this->contexts[1])->get_files([$this->subwikis[1]]);
|
||||
$this->assertEmpty($files);
|
||||
|
||||
// Empty individual wiki.
|
||||
$this->assertTrue(writer::with_context($this->contexts[2])->has_any_data());
|
||||
$data = writer::with_context($this->contexts[2])->get_data([$this->subwikis[23]]);
|
||||
$this->assertEquals((object)[
|
||||
'groupid' => 0,
|
||||
'userid' => $this->users[3]->id
|
||||
], $data);
|
||||
$files = writer::with_context($this->contexts[2])->get_files([$this->subwikis[23]]);
|
||||
$this->assertEmpty($files);
|
||||
|
||||
// For the third wiki there is no page information, no revisions and one lock.
|
||||
$data = writer::with_context($this->contexts[3])->get_related_data([$this->subwikis[3]]);
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[3][1]
|
||||
], array_keys($data));
|
||||
|
||||
$data31 = $data[$this->pagepaths[3][1]];
|
||||
$this->assertTrue(empty($data31['page']['timecreated']));
|
||||
$this->assertTrue(empty($data31['revisions']));
|
||||
$this->assertEquals(1, count($data31['locks']));
|
||||
|
||||
$files = writer::with_context($this->contexts[3])->get_files([$this->subwikis[3]]);
|
||||
$this->assertEmpty($files);
|
||||
|
||||
// No data for the forth wiki.
|
||||
$this->assertFalse(writer::with_context($this->contexts[4])->has_any_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comment object
|
||||
*
|
||||
* @param stdClass $page
|
||||
* @param string $text
|
||||
* @return comment The comment object.
|
||||
*/
|
||||
protected function add_comment($page, $text) {
|
||||
global $DB, $CFG, $USER;
|
||||
require_once($CFG->dirroot . '/comment/lib.php');
|
||||
$record = $DB->get_record_sql('SELECT cm.id, cm.course FROM {course_modules} cm
|
||||
JOIN {modules} m ON m.name = ? AND m.id = cm.module
|
||||
JOIN {wiki} w ON cm.instance = w.id
|
||||
JOIN {wiki_subwikis} s ON s.wikiid = w.id
|
||||
WHERE s.id=?', ['wiki', $page->subwikiid]);
|
||||
$context = context_module::instance($record->id);
|
||||
$args = new stdClass;
|
||||
$args->context = $context;
|
||||
$args->courseid = $record->course;
|
||||
$args->area = 'wiki_page';
|
||||
$args->itemid = $page->id;
|
||||
$args->component = 'mod_wiki';
|
||||
$comment = new comment($args);
|
||||
$comment->set_post_permission(true);
|
||||
$comment->add($text);
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test export data when there are comments.
|
||||
*/
|
||||
public function test_export_user_data_with_comments() {
|
||||
global $DB;
|
||||
// Comment on each page in the first wiki as the first user.
|
||||
$this->setUser($this->users[1]);
|
||||
$this->add_comment($this->pages[1][1], 'Hello111');
|
||||
$this->add_comment($this->pages[1][2], 'Hello112');
|
||||
$this->add_comment($this->pages[1][3], 'Hello113');
|
||||
|
||||
// Comment on second and third page as the third user.
|
||||
$this->setUser($this->users[3]);
|
||||
$this->add_comment($this->pages[1][2], 'Hello312');
|
||||
$this->add_comment($this->pages[1][3], 'Hello313');
|
||||
|
||||
// Export all contexts for the third user.
|
||||
$contextids = array_values(array_map(function($c) {
|
||||
return $c->id;
|
||||
}, $this->contexts));
|
||||
$appctx = new approved_contextlist($this->users[3], 'mod_wiki', $contextids);
|
||||
provider::export_user_data($appctx);
|
||||
|
||||
$data = writer::with_context($this->contexts[1])->get_related_data([$this->subwikis[1]]);
|
||||
// Now user has two pages (comparing to previous test where he had one).
|
||||
$this->assertEquals([
|
||||
$this->pagepaths[1][2],
|
||||
$this->pagepaths[1][3]
|
||||
], array_keys($data));
|
||||
|
||||
// Page 1-2 was exported and it has one comment that this user made (comment from another user was not exported).
|
||||
$data12 = $data[$this->pagepaths[1][2]];
|
||||
$this->assertTrue(empty($data12['page']['timecreated']));
|
||||
$this->assertTrue(empty($data12['revisions']));
|
||||
$this->assertTrue(empty($data12['locks']));
|
||||
$this->assertEquals(1, count($data12['page']['comments']));
|
||||
|
||||
// Page 1-3 was exported same way as in the previous test and it has two comments.
|
||||
$data13 = $data[$this->pagepaths[1][3]];
|
||||
$this->assertNotEmpty($data13['page']['timecreated']);
|
||||
$this->assertEquals(2, count($data13['revisions']));
|
||||
$this->assertEquals(1, count($data13['locks']));
|
||||
$this->assertEquals(2, count($data13['page']['comments']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
provider::delete_data_for_all_users_in_context($this->contexts[1]);
|
||||
|
||||
$appctx = new approved_contextlist($this->users[1], 'mod_wiki',
|
||||
[$this->contexts[1]->id, $this->contexts[2]->id]);
|
||||
provider::export_user_data($appctx);
|
||||
$this->assertFalse(writer::with_context($this->contexts[1])->has_any_data());
|
||||
$this->assertTrue(writer::with_context($this->contexts[2])->has_any_data());
|
||||
|
||||
writer::reset();
|
||||
$appctx = new approved_contextlist($this->users[2], 'mod_wiki', [$this->contexts[1]->id]);
|
||||
provider::export_user_data($appctx);
|
||||
$this->assertFalse(writer::with_context($this->contexts[1])->has_any_data());
|
||||
|
||||
writer::reset();
|
||||
$appctx = new approved_contextlist($this->users[3], 'mod_wiki', [$this->contexts[1]->id]);
|
||||
provider::export_user_data($appctx);
|
||||
$this->assertFalse(writer::with_context($this->contexts[1])->has_any_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
$appctx = new approved_contextlist($this->users[1], 'mod_wiki',
|
||||
[$this->contexts[1]->id, $this->contexts[1]->id]);
|
||||
provider::delete_data_for_user($appctx);
|
||||
|
||||
provider::export_user_data($appctx);
|
||||
$this->assertTrue(writer::with_context($this->contexts[1])->has_any_data());
|
||||
$this->assertFalse(writer::with_context($this->contexts[2])->has_any_data());
|
||||
}
|
||||
}
|
@ -74,28 +74,41 @@ class content_writer implements \core_privacy\local\request\content_writer {
|
||||
/**
|
||||
* Whether any data has been exported at all within the current context.
|
||||
*
|
||||
* @param array $subcontext The location within the current context that this data belongs -
|
||||
* in this method it can be partial subcontext path (or none at all to check presence of any data anywhere).
|
||||
* User preferences never have subcontext, if $subcontext is specified, user preferences are not checked.
|
||||
* @return bool
|
||||
*/
|
||||
public function has_any_data() {
|
||||
$hasdata = !empty($this->data->{$this->context->id});
|
||||
$hasrelateddata = !empty($this->relateddata->{$this->context->id});
|
||||
$hasmetadata = !empty($this->metadata->{$this->context->id});
|
||||
$hasfiles = !empty($this->files->{$this->context->id});
|
||||
$hascustomfiles = !empty($this->customfiles->{$this->context->id});
|
||||
$hasuserprefs = !empty($this->userprefs->{$this->context->id});
|
||||
public function has_any_data($subcontext = []) {
|
||||
if (empty($subcontext)) {
|
||||
// When subcontext is not specified check presence of user preferences in this context and in system context.
|
||||
$hasuserprefs = !empty($this->userprefs->{$this->context->id});
|
||||
$systemcontext = \context_system::instance();
|
||||
$hasglobaluserprefs = !empty($this->userprefs->{$systemcontext->id});
|
||||
if ($hasuserprefs || $hasglobaluserprefs) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$systemcontext = \context_system::instance();
|
||||
$hasglobaluserprefs = !empty($this->userprefs->{$systemcontext->id});
|
||||
|
||||
$hasanydata = $hasdata;
|
||||
$hasanydata = $hasanydata || $hasrelateddata;
|
||||
$hasanydata = $hasanydata || $hasmetadata;
|
||||
$hasanydata = $hasanydata || $hasfiles;
|
||||
$hasanydata = $hasanydata || $hascustomfiles;
|
||||
$hasanydata = $hasanydata || $hasuserprefs;
|
||||
$hasanydata = $hasanydata || $hasglobaluserprefs;
|
||||
|
||||
return $hasanydata;
|
||||
foreach (['data', 'relateddata', 'metadata', 'files', 'customfiles'] as $datatype) {
|
||||
if (!property_exists($this->$datatype, $this->context->id)) {
|
||||
// No data of this type for this context at all. Continue to the next data type.
|
||||
continue;
|
||||
}
|
||||
$basepath = $this->$datatype->{$this->context->id};
|
||||
foreach ($subcontext as $subpath) {
|
||||
if (!isset($basepath->children->$subpath)) {
|
||||
// No data of this type is present for this path. Continue to the next data type.
|
||||
continue 2;
|
||||
}
|
||||
$basepath = $basepath->children->$subpath;
|
||||
}
|
||||
if (!empty($basepath)) {
|
||||
// Some data found for this type for this subcontext.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,6 +372,8 @@ class content_writer implements \core_privacy\local\request\content_writer {
|
||||
* actual writer exports files so it can be reliably tested only in real writers such as
|
||||
* {@link core_privacy\local\request\moodle_content_writer}.
|
||||
*
|
||||
* However we have to remove @@PLUGINFILE@@ since otherwise {@link format_text()} shows debugging messages
|
||||
*
|
||||
* @param array $subcontext The location within the current context that this data belongs.
|
||||
* @param string $component The name of the component that the files belong to.
|
||||
* @param string $filearea The filearea within that component.
|
||||
@ -367,7 +382,7 @@ class content_writer implements \core_privacy\local\request\content_writer {
|
||||
* @return string The processed string
|
||||
*/
|
||||
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text) : string {
|
||||
return $text;
|
||||
return str_replace('@@PLUGINFILE@@/', 'files/', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +87,9 @@ class tests_content_writer_test extends advanced_testcase {
|
||||
$writer->set_context($context);
|
||||
$data = $writer->get_data(['data']);
|
||||
$this->assertSame($dataa, $data);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$this->assertTrue($writer->has_any_data(['data']));
|
||||
$this->assertFalse($writer->has_any_data(['somepath']));
|
||||
|
||||
$writer->set_context($usercontext);
|
||||
$data = $writer->get_data(['data']);
|
||||
@ -181,6 +184,9 @@ class tests_content_writer_test extends advanced_testcase {
|
||||
$this->assertEquals('value2', $metadata->value);
|
||||
$this->assertEquals('description2', $metadata->description);
|
||||
$this->assertEquals('value2', $writer->get_metadata(['metadata'], 'somekey', true));
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$this->assertTrue($writer->has_any_data(['metadata']));
|
||||
$this->assertFalse($writer->has_any_data(['somepath']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,6 +332,8 @@ class tests_content_writer_test extends advanced_testcase {
|
||||
$files = $writer->get_files([]);
|
||||
$this->assertCount(1, $files);
|
||||
$this->assertEquals($fileb, $files['foo/foo.txt']);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$this->assertFalse($writer->has_any_data(['somepath']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,6 +383,10 @@ class tests_content_writer_test extends advanced_testcase {
|
||||
|
||||
$data = $writer->get_related_data(['file', 'data'], 'file');
|
||||
$this->assertEquals('data1', $data);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$this->assertTrue($writer->has_any_data(['file']));
|
||||
$this->assertTrue($writer->has_any_data(['file', 'data']));
|
||||
$this->assertFalse($writer->has_any_data(['somepath']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -440,6 +452,9 @@ class tests_content_writer_test extends advanced_testcase {
|
||||
$this->assertEquals('Content 1', $files['file.txt']);
|
||||
$file = $writer->get_custom_file(['file.txt'], 'file.txt');
|
||||
$this->assertEquals('Content 1', $file);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$this->assertTrue($writer->has_any_data(['file.txt']));
|
||||
$this->assertFalse($writer->has_any_data(['somepath']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user