first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
<?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/>.
/**
* File containing a class for deprecated interfaces.
*
* Plugins should implement this if their interface is now deprecated.
*
* @package core_privacy
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local;
defined('MOODLE_INTERNAL') || die();
/**
* The deprecated interface.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface deprecated {
}
+105
View File
@@ -0,0 +1,105 @@
<?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 polyfill to allow a plugin to operate with Moodle 3.3 up.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
defined('MOODLE_INTERNAL') || die();
/**
* The trait used to provide a backwards compatability for third-party plugins.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait legacy_polyfill {
/**
* 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 static::_get_reason();
}
/**
* Get the list of items.
*
* @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 {
return static::_get_metadata($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) {
return static::_export_user_preferences($userid);
}
/**
* 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 {
return static::_get_contexts_for_userid($userid);
}
/**
* 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) {
return static::_export_user_data($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) {
return static::_delete_data_for_all_users_in_context($context);
}
/**
* 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) {
return static::_delete_data_for_user($contextlist);
}
}
@@ -0,0 +1,209 @@
<?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 defines the core_privacy\local\metadata\collection class object.
*
* The collection class is used to organize a collection of types
* objects, which contains the privacy field details of a component.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
use core_privacy\local\metadata\types\type;
defined('MOODLE_INTERNAL') || die();
/**
* A collection of metadata items.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class collection {
/**
* @var string The component that the items in the collection belong to.
*/
protected $component;
/**
* @var type[] The collection of metadata items.
*/
protected $collection = [];
/**
* Constructor for a component's privacy collection class.
*
* @param string $component component name.
*/
public function __construct($component) {
$this->component = $component;
}
/**
* Function to add an object that implements type interface to the current collection.
*
* @param type $type to add to collection.
* @return $this
*/
public function add_type(type $type) {
$this->collection[] = $type;
return $this;
}
/**
* Function to add a database table which contains user data to this collection.
*
* @param string $name the name of the database table.
* @param array $privacyfields An associative array of fieldname to description.
* @param string $summary A description of what the table is used for.
* @return $this
*/
public function add_database_table($name, array $privacyfields, $summary = '') {
$this->add_type(new types\database_table($name, $privacyfields, $summary));
return $this;
}
/**
* Function to link a subsystem to the component.
*
* @param string $name the name of the subsystem to link.
* @param array $privacyfields An optional associative array of fieldname to description.
* @param string $summary A description of what is stored within this subsystem.
* @return $this
*/
public function add_subsystem_link($name, array $privacyfields = [], $summary = '') {
$this->add_type(new types\subsystem_link($name, $privacyfields, $summary));
return $this;
}
/**
* Old function to link a subsystem to the component.
*
* This function is legacy and is not recommended. Please use add_subsystem_link() instead.
*
* @param string $name the name of the subsystem to link.
* @param string $summary A description of what is stored within this subsystem.
* @return $this
*/
public function link_subsystem($name, $summary = '') {
$this->add_type(new types\subsystem_link($name, [], $summary));
return $this;
}
/**
* Function to link a plugin to the component.
*
* @param string $name the name of the plugin to link.
* @param array $privacyfields An optional associative array of fieldname to description.
* @param string $summary A description of what is stored within this plugin.
* @return $this
*/
public function add_plugintype_link($name, array $privacyfields = [], $summary = '') {
$this->add_type(new types\plugintype_link($name, $privacyfields, $summary));
return $this;
}
/**
* Old function to link a plugin to the component.
*
* This function is legacy and is not recommended. Please use add_plugintype_link() instead.
*
* @param string $name the name of the plugin to link.
* @param string $summary A description of what is stored within this plugin.
* @return $this
*/
public function link_plugintype($name, $summary = '') {
$this->add_type(new types\plugintype_link($name, [], $summary));
return $this;
}
/**
* Function to indicate that data may be exported to an external location.
*
* @param string $name A name for the type of data exported.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for. This is a language string identifier
* within the component.
* @return $this
*/
public function add_external_location_link($name, array $privacyfields, $summary = '') {
$this->add_type(new types\external_location($name, $privacyfields, $summary));
return $this;
}
/**
* Old function to indicate that data may be exported to an external location.
*
* This function is legacy and is not recommended. Please use add_external_location_link() instead.
*
* @param string $name A name for the type of data exported.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for. This is a language string identifier
* within the component.
* @return $this
*/
public function link_external_location($name, array $privacyfields, $summary = '') {
$this->add_type(new types\external_location($name, $privacyfields, $summary));
return $this;
}
/**
* Add a type of user preference to the collection.
*
* Typically this is a single user preference, but in some cases the
* name of a user preference fits a particular format.
*
* @param string $name The name of the user preference.
* @param string $summary A description of what the preference is used for.
* @return $this
*/
public function add_user_preference($name, $summary = '') {
$this->add_type(new types\user_preference($name, $summary));
return $this;
}
/**
* Function to return the current component name.
*
* @return string
*/
public function get_component() {
return $this->component;
}
/**
* The content of this collection.
*
* @return type[]
*/
public function get_collection() {
return $this->collection;
}
}
@@ -0,0 +1,40 @@
<?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 core_privacy\nodata interface.
*
* Plugins implement this interface to declare that they don't store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
defined('MOODLE_INTERNAL') || die();
interface 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 ;
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* INterface for main metadata provider interface.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
defined('MOODLE_INTERNAL') || die();
/**
* INterface for main metadata provider interface.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface 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 ;
}
@@ -0,0 +1,110 @@
<?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 defines an item of metadata which encapsulates a database table.
*
* @package core_privacy
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The database_table type.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class database_table implements type {
/**
* @var string Database table name.
*/
protected $name;
/**
* @var array Fields which contain user information within the table.
*/
protected $privacyfields;
/**
* @var string A description of what this table is used for.
*/
protected $summary;
/**
* Constructor to create a new database_table type.
*
* @param string $name The name of the database table being described.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for.
*/
public function __construct($name, array $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
if (empty($privacyfields)) {
debugging("Table '{$name}' was supplied without any fields.", DEBUG_DEVELOPER);
}
foreach ($privacyfields as $key => $field) {
$teststring = clean_param($field, PARAM_STRINGID);
if ($teststring !== $field) {
debugging("Field '{$key}' passed for table '{$name}' has an invalid langstring identifier: '{$field}'",
DEBUG_DEVELOPER);
}
}
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for the '{$name}' table has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* The name of the database table.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* The list of fields within the table which contain user data, with a description of each field.
*
* @return array
*/
public function get_privacy_fields() {
return $this->privacyfields;
}
/**
* A summary of what this table is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,112 @@
<?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 defines an item of metadata which encapsulates data which is exported to an external location.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The external_location type.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_location implements type {
/**
* @var string The name to describe the type of information exported.
*/
protected $name;
/**
* @var array The list of data names and descriptions exported.
*/
protected $privacyfields;
/**
* @var string A description of what this table is used for.
* This is a language string identifier.
*/
protected $summary;
/**
* Constructor to create a new external_location type.
*
* @param string $name A name for the type of data exported.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for. This is a language string identifier
* within the component.
*/
public function __construct($name, array $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
if (empty($privacyfields)) {
debugging("Location '{$name}' was supplied without any fields.", DEBUG_DEVELOPER);
}
foreach ($privacyfields as $key => $field) {
$teststring = clean_param($field, PARAM_STRINGID);
if ($teststring !== $field) {
debugging("Field '{$key}' passed for location '{$name}' has an invalid langstring identifier: '{$field}'",
DEBUG_DEVELOPER);
}
}
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for the '{$name}' location has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* The name to describe the type of information exported.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Get the list of fields which contain user data, with a description of each field.
*
* @return array
*/
public function get_privacy_fields() {
return $this->privacyfields;
}
/**
* A summary of what this type of exported data is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,98 @@
<?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 defines a link to another Moodle plugin.
*
* @package core_privacy
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The plugintype link.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugintype_link implements type {
/**
* @var The name of the core plugintype to link.
*/
protected $name;
/**
* @var array The list of data names and descriptions.
*/
protected $privacyfields;
/**
* @var string A description of what this plugintype is used to store.
*/
protected $summary;
/**
* Constructor for the plugintype_link.
*
* @param string $name The name of the plugintype to link.
* @param string $summary A description of what is stored within this plugintype.
*/
public function __construct($name, $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' plugintype " .
"has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* Function to return the name of this plugintype_link type.
*
* @return string $name
*/
public function get_name() {
return $this->name;
}
/**
* A plugintype link does not define any fields itself.
*
* @return array
*/
public function get_privacy_fields(): array {
return $this->privacyfields;
}
/**
* A summary of what this plugintype is used for.
*
* @return string $summary
*/
public function get_summary() {
return $this->summary;
}
}
@@ -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/>.
/**
* This file defines a link to another Moodle subsystem.
*
* @package core_privacy
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The subsystem link type.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class subsystem_link implements type {
/**
* @var The name of the core subsystem to link.
*/
protected $name;
/**
* @var array The list of data names and descriptions.
*/
protected $privacyfields;
/**
* @var string A description of what this subsystem is used to store.
*/
protected $summary;
/**
* Constructor for the subsystem_link.
*
* @param string $name The name of the subsystem to link.
* @param array $privacyfields An optional array of fields and their descriptions.
* @param string $summary A description of what is stored within this subsystem.
*/
public function __construct($name, array $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' subsystem " .
"has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* Function to return the name of this subsystem_link type.
*
* @return string $name
*/
public function get_name() {
return $this->name;
}
/**
* A subsystem link does not define any fields itself.
*
* @return array
*/
public function get_privacy_fields(): array {
return $this->privacyfields;
}
/**
* A summary of what this subsystem is used for.
*
* @return string $summary
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,56 @@
<?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 base type interface which encapsulates a set of data held by a component with Moodle.
*
* @package core_privacy
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The base type interface which all metadata types must implement.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @package core_privacy
*/
interface type {
/**
* Get the name describing this type.
*
* @return string
*/
public function get_name();
/**
* A list of the fields and their usage description.
*
* @return array
*/
public function get_privacy_fields();
/**
* A summary of what the metalink type is used for.
*
* @return string $summary
*/
public function get_summary();
}
@@ -0,0 +1,93 @@
<?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 defines an item of metadata which encapsulates a user's preferences.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The user_preference type.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_preference implements type {
/**
* @var The name of this user preference.
*/
protected $name;
/**
* @var A description of what this user preference means.
*/
protected $summary;
/**
* Constructor to create a new user_preference types.
*
* @param string $name The name of the user preference.
* @param string $summary A description of what the preference is used for.
*/
public function __construct($name, $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' subsystem " .
" has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->summary = $summary;
}
/**
* The name of the user preference.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* A user preference encapsulates a single field and has no sub-fields.
*
* @return array
*/
public function get_privacy_fields() {
return null;
}
/**
* A summary of what this user preference is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,75 @@
<?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/>.
/**
* An implementation of a contextlist which has been filtered and approved.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of a contextlist which has been filtered and approved.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_contextlist extends contextlist_base {
/**
* @var \stdClass The user this contextlist belongs to.
*/
protected $user;
/**
* Create a new approved contextlist.
*
* @param \stdClass $user The user record.
* @param string $component the frankenstyle component name.
* @param \int[] $contextids The list of contextids present in this list.
*/
public function __construct(\stdClass $user, string $component, array $contextids) {
$this->set_user($user);
$this->set_component($component);
$this->set_contextids($contextids);
}
/**
* Specify the user which owns this request.
*
* @param \stdClass $user The user record.
* @return $this
*/
protected function set_user(\stdClass $user): approved_contextlist {
$this->user = $user;
return $this;
}
/**
* Get the user which requested their data.
*
* @return \stdClass
*/
public function get_user(): \stdClass {
return $this->user;
}
}
@@ -0,0 +1,61 @@
<?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/>.
/**
* An implementation of a userlist which has been filtered and approved.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of a userlist which has been filtered and approved.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_userlist extends userlist_base {
/**
* Create a new approved userlist.
*
* @param \context $context The context.
* @param string $component the frankenstyle component name.
* @param \int[] $userids The list of userids present in this list.
*/
public function __construct(\context $context, string $component, array $userids) {
parent::__construct($context, $component);
$this->set_userids($userids);
}
/**
* Create an approved userlist from a userlist.
*
* @param userlist $userlist The source list
* @return approved_userlist The newly created approved userlist.
*/
public static function create_from_userlist(userlist $userlist): approved_userlist {
$newlist = new static($userlist->get_context(), $userlist->get_component(), $userlist->get_userids());
return $newlist;
}
}
@@ -0,0 +1,143 @@
<?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 interface required to implmeent a content writer.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The interface for a Moodle content writer.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*/
interface content_writer {
/**
* Constructor for the content writer.
*
* Note: The writer_factory must be passed.
* @param writer $writer The factory.
*/
public function __construct(writer $writer);
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
* @return content_writer
*/
public function set_context(\context $context): content_writer ;
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
* @return content_writer
*/
public function export_data(array $subcontext, \stdClass $data): content_writer ;
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
* @return content_writer
*/
public function export_metadata(array $subcontext, string $name, $value, string $description): content_writer ;
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
* @return content_writer
*/
public function export_related_data(array $subcontext, $name, $data): content_writer ;
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
* @return content_writer
*/
public function export_custom_file(array $subcontext, $filename, $filecontent): content_writer ;
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* @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.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text): string;
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @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.
* @param string $itemid Which item those files belong to.
* @return content_writer
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid): content_writer ;
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
* @return content_writer
*/
public function export_file(array $subcontext, \stored_file $file): content_writer ;
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public function export_user_preference(string $component, string $key, string $value, string $description): content_writer ;
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content(): string ;
}
@@ -0,0 +1,49 @@
<?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/>.
/**
* File containing the provider interface for plugins needing access to all approved contexts to fill in relevant contextual data.
*
* Plugins should implement this if they need access to all approved contexts.
*
* @package core_privacy
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The provider interface for plugins which need access to all approved contexts to fill in relevant contextual data.
*
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface context_aware_provider extends \core_privacy\local\request\core_data_provider {
/**
* Give the component a chance to include any contextual information deemed relevant to any child contexts which are
* exporting personal data.
*
* By giving the component access to the full list of contexts being exported across all components, it can determine whether a
* descendant context is being exported, and decide whether to add relevant contextual information about itself. Having access
* to the full list of contexts being exported is what makes this component a context aware provider.
*
* @param \core_privacy\local\request\contextlist_collection $contextcollection The collection of approved context lists.
*/
public static function export_context_data(\core_privacy\local\request\contextlist_collection $contextcollection);
}
@@ -0,0 +1,190 @@
<?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 Fetch Result Set.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Fetch Result Set.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist extends contextlist_base {
/**
* Add a set of contexts from SQL.
*
* The SQL should only return a list of context IDs.
*
* @param string $sql The SQL which will fetch the list of * context IDs
* @param array $params The set of SQL parameters
* @return $this
*/
public function add_from_sql(string $sql, array $params): contextlist {
global $DB;
$fields = \context_helper::get_preload_record_columns_sql('ctx');
if ($fieldname = $this->guess_id_field_from_sql($sql)) {
if (is_numeric($fieldname)) {
$wrapper = "
SELECT {$fields}
FROM {context} ctx
WHERE ctx.id = :fieldvalue";
$params = ['fieldvalue' => $fieldname];
} else {
// Able to guess a field name.
$wrapper = "
SELECT {$fields}
FROM {context} ctx
JOIN ({$sql}) target ON ctx.id = target.{$fieldname}";
}
} else {
// No field name available. Fall back on a potentially slower version.
$wrapper = "
SELECT {$fields}
FROM {context} ctx
WHERE ctx.id IN ({$sql})";
}
$contexts = $DB->get_recordset_sql($wrapper, $params);
$contextids = [];
foreach ($contexts as $context) {
$contextids[] = $context->ctxid;
\context_helper::preload_from_record($context);
}
$contexts->close();
$this->set_contextids(array_merge($this->get_contextids(), $contextids));
return $this;
}
/**
* Adds the system context.
*
* @return $this
*/
public function add_system_context(): contextlist {
return $this->add_from_sql(SYSCONTEXTID, []);
}
/**
* Adds the user context for a given user.
*
* @param int $userid
* @return $this
*/
public function add_user_context(int $userid): contextlist {
$sql = "SELECT DISTINCT ctx.id
FROM {context} ctx
WHERE ctx.contextlevel = :contextlevel
AND ctx.instanceid = :instanceid";
return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER, 'instanceid' => $userid]);
}
/**
* Adds the user contexts for given users.
*
* @param array $userids
* @return $this
*/
public function add_user_contexts(array $userids): contextlist {
global $DB;
list($useridsql, $useridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$sql = "SELECT DISTINCT ctx.id
FROM {context} ctx
WHERE ctx.contextlevel = :contextlevel
AND ctx.instanceid $useridsql";
return $this->add_from_sql($sql, ['contextlevel' => CONTEXT_USER] + $useridparams);
}
/**
* Sets the component for this contextlist.
*
* @param string $component the frankenstyle component name.
*/
public function set_component($component) {
parent::set_component($component);
}
/**
* Guess the name of the contextid field from the supplied SQL.
*
* @param string $sql The SQL to guess from
* @return string The field name or a numeric value representing the context id
*/
protected function guess_id_field_from_sql(string $sql): string {
// We are not interested in any subquery/view/conditions for the purpose of this method, so
// let's reduce the query to the interesting parts by recursively cleaning all
// contents within parenthesis. If there are problems (null), we keep the text unmodified.
// So just top-level sql will remain after the reduction.
$recursiveregexp = '/\((([^()]*|(?R))*)\)/';
$sql = (preg_replace($recursiveregexp, '', $sql) ?: $sql);
// Get the list of relevant words from the SQL Query.
// We explode the SQL by the space character, then trim any extra whitespace (e.g. newlines), before we filter
// empty value, and finally we re-index the array.
$sql = rtrim($sql, ';');
$words = array_map('trim', preg_split('/\s+/', $sql));
$words = array_filter($words, function($word) {
return $word !== '';
});
$words = array_values($words);
$uwords = array_map('strtoupper', $words); // Uppercase all them.
// If the query has boolean operators (UNION, it is the only one we support cross-db)
// then we cannot guarantee whats coming after the first query, it can be anything.
if (array_search('UNION', $uwords)) {
return '';
}
if ($firstfrom = array_search('FROM', $uwords)) {
// Found a FROM keyword.
// Select the previous word.
$fieldname = $words[$firstfrom - 1];
if (is_numeric($fieldname)) {
return $fieldname;
}
if ($hasdot = strpos($fieldname, '.')) {
// This field is against a table alias. Take off the alias.
$fieldname = substr($fieldname, $hasdot + 1);
}
return $fieldname;
} else if ((count($words) == 1) && (is_numeric($words[0]))) {
// Not a real SQL, just a single numerical value - such as one returned by {@link self::add_system_context()}.
return $words[0];
} else if ((count($words) == 2) && (strtoupper($words[0]) === 'SELECT') && (is_numeric($words[1]))) {
// SQL returning a constant numerical value.
return $words[1];
}
return '';
}
}
@@ -0,0 +1,189 @@
<?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/>.
/**
* Base implementation of a contextlist.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Base implementation of a contextlist used to store a set of contexts.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class contextlist_base implements
// Implement an Iterator to fetch the Context objects.
\Iterator,
// Implement the Countable interface to allow the number of returned results to be queried easily.
\Countable {
/**
* @var array List of context IDs.
*
* Note: this must not be updated using set_contextids only as this
* ensures uniqueness.
*/
private $contextids = [];
/**
* @var string component the frankenstyle component name.
*/
protected $component = '';
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/**
* Set the contextids.
*
* @param array $contextids The list of contexts.
*/
protected function set_contextids(array $contextids) {
$this->contextids = array_unique($contextids);
}
/**
* Get the list of context IDs that relate to this request.
*
* @return int[]
*/
public function get_contextids(): array {
return $this->contextids;
}
/**
* Get the complete list of context objects that relate to this
* request.
*
* @return \context[]
*/
public function get_contexts(): array {
$contexts = [];
foreach ($this->contextids as $contextid) {
// It is possible that this context has been deleted and we now have subsequent calls being made with this
// contextlist. Exceptions here will stop the further processing of this component and that is why we are
// doing a try catch.
try {
$contexts[] = \context::instance_by_id($contextid);
} catch (\Exception $e) {
// Remove this context.
unset($this->contextids[$this->iteratorposition]);
}
}
return $contexts;
}
/**
* Sets the component for this contextlist.
*
* @param string $component the frankenstyle component name.
*/
protected function set_component($component) {
$this->component = $component;
}
/**
* Get the name of the component to which this contextlist belongs.
*
* @return string the component name associated with this contextlist.
*/
public function get_component(): string {
return $this->component;
}
/**
* Return the current context.
*
* @return \context
*/
#[\ReturnTypeWillChange]
public function current() {
// It is possible that this context has been deleted and we now have subsequent calls being made with this
// contextlist. Exceptions here will stop the further processing of this component and that is why we are
// doing a try catch.
try {
$context = \context::instance_by_id($this->contextids[$this->iteratorposition]);
} catch (\Exception $e) {
// Remove this context.
unset($this->contextids[$this->iteratorposition]);
// Check to see if there are any more contexts left.
if ($this->count()) {
// Move the pointer to the next record and try again.
$this->next();
$context = $this->current();
} else {
// There are no more context ids left.
return null;
}
}
return $context;
}
/**
* Return the key of the current element.
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
return $this->iteratorposition;
}
/**
* Move to the next context in the list.
*/
public function next(): void {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid(): bool {
return isset($this->contextids[$this->iteratorposition]);
}
/**
* Rewind to the first found context.
*
* The list of contexts is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind(): void {
$this->iteratorposition = 0;
}
/**
* Return the number of contexts.
*/
public function count(): int {
return count($this->contextids);
}
}
@@ -0,0 +1,182 @@
<?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 defines the contextlist_collection class object.
*
* The contextlist_collection is used to organize a collection of contextlists.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* A collection of contextlist items.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_collection implements \Iterator, \Countable {
/**
* @var int $userid The ID of the user that the contextlist collection belongs to.
*/
protected $userid = null;
/**
* @var array $contextlists the internal array of contextlist objects.
*/
protected $contextlists = [];
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/**
* Constructor to create a new contextlist_collection.
*
* @param int $userid The userid to which this collection belongs.
*/
public function __construct($userid) {
$this->userid = $userid;
}
/**
* Return the ID of the user whose collection this is.
*
* @return int
*/
public function get_userid(): int {
return $this->userid;
}
/**
* Add a contextlist to this collection.
*
* @param contextlist_base $contextlist the contextlist to export.
* @return $this
*/
public function add_contextlist(contextlist_base $contextlist) {
$component = $contextlist->get_component();
if (empty($component)) {
throw new \moodle_exception("The contextlist must have a component set");
}
if (isset($this->contextlists[$component])) {
throw new \moodle_exception("A contextlist has already been added for the '{$component}' component");
}
$this->contextlists[$component] = $contextlist;
return $this;
}
/**
* Get the contextlists in this collection.
*
* @return array the associative array of contextlists in this collection, indexed by component name.
* E.g. mod_assign => contextlist, core_comment => contextlist.
*/
public function get_contextlists(): array {
return $this->contextlists;
}
/**
* Get the contextlist for the specified component.
*
* @param string $component the frankenstyle name of the component to fetch for.
* @return contextlist_base|null
*/
public function get_contextlist_for_component(string $component) {
if (isset($this->contextlists[$component])) {
return $this->contextlists[$component];
}
return null;
}
/**
* Return the current contexlist.
*
* @return \context
*/
#[\ReturnTypeWillChange]
public function current() {
$key = $this->get_key_from_position();
return $this->contextlists[$key];
}
/**
* Return the key of the current element.
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
return $this->get_key_from_position();
}
/**
* Move to the next context in the list.
*/
public function next(): void {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid(): bool {
return ($this->iteratorposition < count($this->contextlists));
}
/**
* Rewind to the first found context.
*
* The list of contexts is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind(): void {
$this->iteratorposition = 0;
}
/**
* Get the key for the current iterator position.
*
* @return string
*/
protected function get_key_from_position() {
$keylist = array_keys($this->contextlists);
if (isset($keylist[$this->iteratorposition])) {
return $keylist[$this->iteratorposition];
}
return null;
}
/**
* Return the number of contexts.
*/
public function count(): int {
return count($this->contextlists);
}
}
@@ -0,0 +1,44 @@
<?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 \core_privacy\local\request\core_data_provider interface to describe
* classes which provide data in some form to core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The core_data_provider interface is used to describe a provider which
* services user requests between components and core.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*/
interface core_data_provider extends data_provider {
}
@@ -0,0 +1,69 @@
<?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 \core_privacy\local\request\core_user_data_provider interface to describe
* classes which provide user data in some form to core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The core_user_data_provider interface is used to describe a provider
* which services user requests between components and core.
*
* It describes data how these requests are serviced in a specific format.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*/
interface core_user_data_provider extends core_data_provider {
/**
* 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;
/**
* 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);
/**
* 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);
/**
* 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);
}
@@ -0,0 +1,51 @@
<?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 an interface to describe classes which provide user data in some form to core.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The interface is used to describe a provider which is capable of identifying the users who have data within it.
*
* It describes data how these requests are serviced in a specific format.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*/
interface core_userlist_provider extends userlist_provider {
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist);
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist);
}
@@ -0,0 +1,49 @@
<?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 \core_privacy\local\request\data_provider interface to describe
* a class which provides data in some form.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The data_provider interface is used to describe a provider
* which services user requests in any fashion. This includes both
* -- component <-> core; and
* -- component <-> component.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* This is the base interface for any component which stores any form of
* user data.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*/
interface data_provider {
}
+308
View File
@@ -0,0 +1,308 @@
<?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 core_privacy\local\request helper.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
use \core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/modinfolib.php');
require_once($CFG->dirroot . '/course/modlib.php');
/**
* The core_privacy\local\request\helper class with useful shared functionality.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Add core-controlled contexts which are related to a component but that component may know about.
*
* For example, most activities are not aware of activity completion, but the course implements it for them.
* These should be included.
*
* @param int $userid The user being added for.
* @param contextlist $contextlist The contextlist being appended to.
* @return contextlist The final contextlist
*/
public static function add_shared_contexts_to_contextlist_for(int $userid, contextlist $contextlist): contextlist {
if (strpos($contextlist->get_component(), 'mod_') === 0) {
// Activity modules support data stored by core about them - for example, activity completion.
$contextlist = static::add_shared_contexts_to_contextlist_for_course_module($userid, $contextlist);
}
return $contextlist;
}
/**
* Add core-controlled contexts which are related to a component but that component may know about.
*
* For example, most activities are not aware of activity completion, but the course implements it for them.
* These should be included.
*
* @param \core_privacy\local\request\userlist $userlist
* @return contextlist The final contextlist
*/
public static function add_shared_users_to_userlist(\core_privacy\local\request\userlist $userlist) {
}
/**
* Handle export of standard data for a plugin which implements the null provider and does not normally store data
* of its own.
*
* This is used in cases such as activities like mod_resource, which do not store their own data, but may still have
* data on them (like Activity Completion).
*
* Any context provided in a contextlist should have base data exported as a minimum.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_data_for_null_provider(approved_contextlist $contextlist) {
$user = $contextlist->get_user();
foreach ($contextlist as $context) {
$data = static::get_context_data($context, $user);
static::export_context_files($context, $user);
writer::with_context($context)->export_data([], $data);
}
}
/**
* Handle removal of 'standard' data for any plugin.
*
* This will handle deletion for things such as activity completion.
*
* @param string $component The component being deleted for.
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(string $component, \context $context) {
// Activity modules support data stored by core about them - for example, activity completion.
static::delete_data_for_all_users_in_context_course_module($component, $context);
}
/**
* Delete all 'standard' user data for the specified user, in the specified contexts.
*
* This will handle deletion for things such as activity completion.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
$component = $contextlist->get_component();
// Activity modules support data stored by core about them - for example, activity completion.
static::delete_data_for_user_in_course_module($contextlist);
}
/**
* Get all general data for this context.
*
* @param \context $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
public static function get_context_data(\context $context, \stdClass $user): \stdClass {
global $DB;
$basedata = (object) [];
if ($context instanceof \context_module) {
return static::get_context_module_data($context, $user);
}
if ($context instanceof \context_block) {
return static::get_context_block_data($context, $user);
}
return $basedata;
}
/**
* Export all files for this context.
*
* @param \context $context The context to export files for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
public static function export_context_files(\context $context, \stdClass $user) {
if ($context instanceof \context_module) {
return static::export_context_module_files($context, $user);
}
}
/**
* Add core-controlled contexts which are related to a component but that component may know about.
*
* For example, most activities are not aware of activity completion, but the course implements it for them.
* These should be included.
*
* @param int $userid The user being added for.
* @param contextlist $contextlist The contextlist being appended to.
* @return contextlist The final contextlist
*/
protected static function add_shared_contexts_to_contextlist_for_course_module(int $userid, contextlist $contextlist): contextlist {
// Fetch all contexts where the user has activity completion enabled.
$sql = "SELECT
c.id
FROM {course_modules_completion} cmp
INNER JOIN {course_modules} cm ON cm.id = cmp.coursemoduleid
INNER JOIN {modules} m ON m.id = cm.module
INNER JOIN {context} c ON c.instanceid = cm.id AND c.contextlevel = :contextlevel
WHERE cmp.userid = :userid
AND m.name = :modname";
$params = [
'userid' => $userid,
// Strip the mod_ from the name.
'modname' => substr($contextlist->get_component(), 4),
'contextlevel' => CONTEXT_MODULE,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get all general data for the activity module at this context.
*
* @param \context_module $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
protected static function get_context_module_data(\context_module $context, \stdClass $user): \stdClass {
global $DB;
$coursecontext = $context->get_course_context();
$modinfo = get_fast_modinfo($coursecontext->instanceid);
$cm = $modinfo->cms[$context->instanceid];
$component = "mod_{$cm->modname}";
$course = $cm->get_course();
$moduledata = $DB->get_record($cm->modname, ['id' => $cm->instance]);
$basedata = (object) [
'name' => $cm->get_formatted_name(),
];
if (plugin_supports('mod', $cm->modname, FEATURE_MOD_INTRO, true)) {
$intro = $moduledata->intro;
$intro = writer::with_context($context)
->rewrite_pluginfile_urls([], $component, 'intro', 0, $intro);
$options = [
'noclean' => true,
'para' => false,
'context' => $context,
'overflowdiv' => true,
];
$basedata->intro = format_text($intro, $moduledata->introformat, $options);
}
// Completion tracking.
$completiondata = \core_completion\privacy\provider::get_activity_completion_info($user, $course, $cm);
if (isset($completiondata->completionstate)) {
$basedata->completion = (object) [
'state' => $completiondata->completionstate,
];
}
return $basedata;
}
/**
* Get all general data for the block at this context.
*
* @param \context_block $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass General data about this block instance.
*/
protected static function get_context_block_data(\context_block $context, \stdClass $user): \stdClass {
global $DB;
$block = $DB->get_record('block_instances', ['id' => $context->instanceid]);
$basedata = (object) [
'blocktype' => get_string('pluginname', 'block_' . $block->blockname)
];
return $basedata;
}
/**
* Get all general data for the activity module at this context.
*
* @param \context_module $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
protected static function export_context_module_files(\context_module $context, \stdClass $user) {
$coursecontext = $context->get_course_context();
$modinfo = get_fast_modinfo($coursecontext->instanceid);
$cm = $modinfo->cms[$context->instanceid];
$component = "mod_{$cm->modname}";
writer::with_context($context)
// Export the files for the intro.
->export_area_files([], $component, 'intro', 0);
}
/**
* Handle removal of 'standard' data for course modules.
*
* This will handle deletion for things such as activity completion.
*
* @param string $component The component being deleted for.
* @param \context $context The context to delete all data for.
*/
public static function delete_data_for_all_users_in_context_course_module(string $component, \context $context) {
global $DB;
if ($context instanceof \context_module) {
// Delete course completion data for this context.
\core_completion\privacy\provider::delete_completion(null, null, $context->instanceid);
}
}
/**
* Delete all 'standard' user data for the specified user in course modules.
*
* This will handle deletion for things such as activity completion.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
protected static function delete_data_for_user_in_course_module(approved_contextlist $contextlist) {
global $DB;
foreach ($contextlist as $context) {
if ($context instanceof \context_module) {
// Delete course completion data for this context.
\core_completion\privacy\provider::delete_completion($contextlist->get_user(), null, $context->instanceid);
}
}
}
}
@@ -0,0 +1,727 @@
<?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/>.
namespace core_privacy\local\request;
/**
* The moodle_content_writer is the default Moodle implementation of a content writer.
*
* It exports data to a rich tree structure using Moodle's context system,
* and produces a single zip file with all content.
*
* Objects of data are stored as JSON.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodle_content_writer implements content_writer {
/**
* Maximum context name char size.
*/
const MAX_CONTEXT_NAME_LENGTH = 32;
/**
* @var string The base path on disk for this instance.
*/
protected $path = null;
/**
* @var \context The current context of the writer.
*/
protected $context = null;
/**
* @var \stored_file[] The list of files to be exported.
*/
protected $files = [];
/**
* @var array The list of plugins that have been checked to see if they are installed.
*/
protected $checkedplugins = [];
/**
* Constructor for the content writer.
*
* Note: The writer factory must be passed.
*
* @param writer $writer The factory.
*/
public function __construct(writer $writer) {
$this->path = make_request_directory();
}
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
*/
public function set_context(\context $context): content_writer {
$this->context = $context;
return $this;
}
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
* @return content_writer
*/
public function export_data(array $subcontext, \stdClass $data): content_writer {
$path = $this->get_path($subcontext, 'data.json');
$this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
return $this;
}
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
* @return content_writer
*/
public function export_metadata(array $subcontext, string $key, $value, string $description): content_writer {
$path = $this->get_full_path($subcontext, 'metadata.json');
if (file_exists($path)) {
$data = json_decode(file_get_contents($path));
} else {
$data = (object) [];
}
$data->$key = (object) [
'value' => $value,
'description' => $description,
];
$path = $this->get_path($subcontext, 'metadata.json');
$this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
return $this;
}
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
* @return content_writer
*/
public function export_related_data(array $subcontext, $name, $data): content_writer {
return $this->export_custom_file($subcontext, "{$name}.json",
json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
*/
public function export_custom_file(array $subcontext, $filename, $filecontent): content_writer {
$filename = clean_param($filename, PARAM_FILE);
$path = $this->get_path($subcontext, $filename);
$this->write_data($path, $filecontent);
return $this;
}
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* @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.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text): string {
if ($text === null || $text === '') {
return '';
}
// Need to take into consideration the subcontext to provide the full path to this file.
$subcontextpath = '';
if (!empty($subcontext)) {
$subcontextpath = DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $subcontext);
}
$path = $this->get_context_path();
$path = implode(DIRECTORY_SEPARATOR, $path) . $subcontextpath;
$returnstring = $path . DIRECTORY_SEPARATOR . $this->get_files_target_url($component, $filearea, $itemid) . '/';
$returnstring = clean_param($returnstring, PARAM_PATH);
return str_replace('@@PLUGINFILE@@/', $returnstring, $text);
}
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @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.
* @param string $itemid Which item those files belong to.
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid): content_writer {
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid);
foreach ($files as $file) {
$this->export_file($subcontext, $file);
}
return $this;
}
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
*/
public function export_file(array $subcontext, \stored_file $file): content_writer {
if (!$file->is_directory()) {
$pathitems = array_merge(
$subcontext,
[$this->get_files_target_path($file->get_component(), $file->get_filearea(), $file->get_itemid())],
[$file->get_filepath()]
);
$path = $this->get_path($pathitems, $file->get_filename());
$fullpath = $this->get_full_path($pathitems, $file->get_filename());
check_dir_exists(dirname($fullpath), true, true);
$this->files[$path] = $file;
}
return $this;
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public function export_user_preference(string $component, string $key, string $value, string $description): content_writer {
$subcontext = [
get_string('userpreferences'),
];
$fullpath = $this->get_full_path($subcontext, "{$component}.json");
$path = $this->get_path($subcontext, "{$component}.json");
if (file_exists($fullpath)) {
$data = json_decode(file_get_contents($fullpath));
} else {
$data = (object) [];
}
$data->$key = (object) [
'value' => $value,
'description' => $description,
];
$this->write_data($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
return $this;
}
/**
* Determine the path for the current context.
*
* @return array The context path.
* @throws \coding_exception
*/
protected function get_context_path(): array {
$path = [];
$contexts = array_reverse($this->context->get_parent_contexts(true));
foreach ($contexts as $context) {
$name = $context->get_context_name();
$id = ' _.' . $context->id;
$path[] = shorten_text(clean_param($name, PARAM_FILE),
self::MAX_CONTEXT_NAME_LENGTH, true, json_decode('"' . '\u2026' . '"')) . $id;
}
return $path;
}
/**
* Get the relative file path within the current context, and subcontext, using the specified filename.
*
* @param string[] $subcontext The location within the current context to export this data.
* @param string $name The intended filename, including any extensions.
* @return string The fully-qualfiied file path.
*/
protected function get_path(array $subcontext, string $name): string {
$subcontext = shorten_filenames($subcontext, MAX_FILENAME_SIZE, true);
$name = shorten_filename($name, MAX_FILENAME_SIZE, true);
// This weird code is to look for a subcontext that contains a number and append an '_' to the front.
// This is because there seems to be some weird problem with array_merge_recursive used in finalise_content().
$subcontext = array_map(function($data) {
if (stripos($data, DIRECTORY_SEPARATOR) !== false) {
$newpath = explode(DIRECTORY_SEPARATOR, $data);
$newpath = array_map(function($value) {
if (is_numeric($value)) {
return '_' . $value;
}
return $value;
}, $newpath);
$data = implode(DIRECTORY_SEPARATOR, $newpath);
} else if (is_numeric($data)) {
$data = '_' . $data;
}
// Because clean_param() normalises separators to forward-slashes
// and because there is code DIRECTORY_SEPARATOR dependent after
// this array_map(), we ensure we get the original separator.
// Note that maybe we could leave the clean_param() alone, but
// surely that means that the DIRECTORY_SEPARATOR dependent
// code is not needed at all. So better keep existing behavior
// until this is revisited.
return str_replace('/', DIRECTORY_SEPARATOR, clean_param($data, PARAM_PATH));
}, $subcontext);
// Combine the context path, and the subcontext data.
$path = array_merge(
$this->get_context_path(),
$subcontext
);
// Join the directory together with the name.
$filepath = implode(DIRECTORY_SEPARATOR, $path) . DIRECTORY_SEPARATOR . $name;
// To use backslash, it must be doubled ("\\\\" PHP string).
$separator = str_replace('\\', '\\\\', DIRECTORY_SEPARATOR);
return preg_replace('@(' . $separator . '|/)+@', $separator, $filepath);
}
/**
* Get the fully-qualified file path within the current context, and subcontext, using the specified filename.
*
* @param string[] $subcontext The location within the current context to export this data.
* @param string $name The intended filename, including any extensions.
* @return string The fully-qualfiied file path.
*/
protected function get_full_path(array $subcontext, string $name): string {
$path = array_merge(
[$this->path],
[$this->get_path($subcontext, $name)]
);
// Join the directory together with the name.
$filepath = implode(DIRECTORY_SEPARATOR, $path);
// To use backslash, it must be doubled ("\\\\" PHP string).
$separator = str_replace('\\', '\\\\', DIRECTORY_SEPARATOR);
return preg_replace('@(' . $separator . '|/)+@', $separator, $filepath);
}
/**
* Get a path within a subcontext where exported files should be written to.
*
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @return string The path
*/
protected function get_files_target_path($component, $filearea, $itemid): string {
// We do not need to include the component because we organise things by context.
$parts = ['_files', $filearea];
if (!empty($itemid)) {
$parts[] = $itemid;
}
return implode(DIRECTORY_SEPARATOR, $parts);
}
/**
* Get a relative url to the directory of the exported files within a subcontext.
*
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @return string The url
*/
protected function get_files_target_url($component, $filearea, $itemid): string {
// We do not need to include the component because we organise things by context.
$parts = ['_files', $filearea];
if (!empty($itemid)) {
$parts[] = '_' . $itemid;
}
return implode('/', $parts);
}
/**
* Write the data to the specified path.
*
* @param string $path The path to export the data at.
* @param string $data The data to be exported.
* @throws \moodle_exception If the file cannot be written for some reason.
*/
protected function write_data(string $path, string $data) {
$targetpath = $this->path . DIRECTORY_SEPARATOR . $path;
check_dir_exists(dirname($targetpath), true, true);
if (file_put_contents($targetpath, $data) === false) {
throw new \moodle_exception('cannotsavefile', 'error', '', $targetpath);
}
$this->files[$path] = $targetpath;
}
/**
* Copy a file to the specified path.
*
* @param array $path Current location of the file.
* @param array $destination Destination path to copy the file to.
*/
protected function copy_data(array $path, array $destination) {
global $CFG;
$filename = array_pop($destination);
$destdirectory = implode(DIRECTORY_SEPARATOR, $destination);
$fulldestination = $this->path . DIRECTORY_SEPARATOR . $destdirectory;
check_dir_exists($fulldestination, true, true);
$fulldestination .= $filename;
$currentpath = $CFG->dirroot . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $path);
copy($currentpath, $fulldestination);
$this->files[$destdirectory . DIRECTORY_SEPARATOR . $filename] = $fulldestination;
}
/**
* This creates three different bits of data from all of the files that will be
* exported.
* $tree - A multidimensional array of the navigation tree structure.
* $treekey - An array with the short path of the file and element data for
* html (data_file_{number} or 'No var')
* $allfiles - All *.json files that need to be added as an index to be referenced
* by the js files to display the user data.
*
* @return array returns a tree, tree key, and a list of all files.
*/
protected function prepare_for_export(): Array {
$tree = [];
$treekey = [];
$allfiles = [];
$i = 1;
foreach ($this->files as $shortpath => $fullfile) {
// Generate directory tree as an associative array.
$items = explode(DIRECTORY_SEPARATOR, $shortpath);
$newitems = $this->condense_array($items);
$tree = array_merge_recursive($tree, $newitems);
if (is_string($fullfile)) {
$filearray = explode(DIRECTORY_SEPARATOR, $shortpath);
$filename = array_pop($filearray);
$filenamearray = explode('.', $filename);
// Don't process files that are not json files.
if (end($filenamearray) !== 'json') {
continue;
}
// Chop the last two characters of the extension. json => js.
$filename = substr($filename, 0, -2);
array_push($filearray, $filename);
$newshortpath = implode(DIRECTORY_SEPARATOR, $filearray);
$varname = 'data_file_' . $i;
$i++;
$quicktemp = clean_param($shortpath, PARAM_PATH);
$treekey[$quicktemp] = $varname;
$allfiles[$varname] = clean_param($newshortpath, PARAM_PATH);
// Need to load up the current json file and add a variable (varname mentioned above) at the start.
// Then save it as a js file.
$content = $this->get_file_content($fullfile);
$jsondecodedcontent = json_decode($content);
$jsonencodedcontent = json_encode($jsondecodedcontent, JSON_PRETTY_PRINT);
$variablecontent = 'var ' . $varname . ' = ' . $jsonencodedcontent;
$this->write_data($newshortpath, $variablecontent);
} else {
$treekey[clean_param($shortpath, PARAM_PATH)] = 'No var';
}
}
return [$tree, $treekey, $allfiles];
}
/**
* Add more detail to the tree to help with sorting and display in the renderer.
*
* @param array $tree The file structure currently as a multidimensional array.
* @param array $treekey An array of the current file paths.
* @param array $currentkey The current short path of the tree.
* @return array An array of objects that has additional data.
*/
protected function make_tree_object(array $tree, array $treekey, array $currentkey = []): Array {
$newtree = [];
// Try to extract the context id and then add the context object.
$addcontext = function($index, $object) {
if (stripos($index, '_.') !== false) {
$namearray = explode('_.', $index);
$contextid = array_pop($namearray);
if (is_numeric($contextid)) {
$object[$index]->name = implode('_.', $namearray);
$object[$index]->context = \context::instance_by_id($contextid);
}
} else {
$object[$index]->name = $index;
}
};
// Just add the final data to the tree object.
$addfinalfile = function($directory, $treeleaf, $file) use ($treekey) {
$url = implode(DIRECTORY_SEPARATOR, $directory);
$url = clean_param($url, PARAM_PATH);
$treeleaf->name = $file;
$treeleaf->itemtype = 'item';
$gokey = clean_param($url . '/' . $file, PARAM_PATH);
if (isset($treekey[$gokey]) && $treekey[$gokey] !== 'No var') {
$treeleaf->datavar = $treekey[$gokey];
} else {
$treeleaf->url = new \moodle_url($url . '/' . $file);
}
};
foreach ($tree as $key => $value) {
$newtree[$key] = new \stdClass();
if (is_array($value)) {
$newtree[$key]->itemtype = 'treeitem';
// The array merge recursive adds a numeric index, and so we only add to the current
// key if it is now numeric.
$currentkey = is_numeric($key) ? $currentkey : array_merge($currentkey, [$key]);
// Try to extract the context id and then add the context object.
$addcontext($key, $newtree);
$newtree[$key]->children = $this->make_tree_object($value, $treekey, $currentkey);
if (!is_numeric($key)) {
// We're heading back down the tree, so remove the last key.
array_pop($currentkey);
}
} else {
// If the key is not numeric then we want to add a directory and put the file under that.
if (!is_numeric($key)) {
$newtree[$key]->itemtype = 'treeitem';
// Try to extract the context id and then add the context object.
$addcontext($key, $newtree);
array_push($currentkey, $key);
$newtree[$key]->children[$value] = new \stdClass();
$addfinalfile($currentkey, $newtree[$key]->children[$value], $value);
array_pop($currentkey);
} else {
// If the key is just a number then we just want to show the file instead.
$addfinalfile($currentkey, $newtree[$key], $value);
}
}
}
return $newtree;
}
/**
* Sorts the tree list into an order that makes more sense.
* Order is:
* 1 - Items with a context first, the lower the number the higher up the tree.
* 2 - Items that are directories.
* 3 - Items that are log directories.
* 4 - Links to a page.
*
* @param array $tree The tree structure to order.
*/
protected function sort_my_list(array &$tree) {
uasort($tree, function($a, $b) {
if (isset($a->context) && isset($b->context)) {
return $a->context->contextlevel <=> $b->context->contextlevel;
}
if (isset($a->context) && !isset($b->context)) {
return -1;
}
if (isset($b->context) && !isset($a->context)) {
return 1;
}
if ($a->itemtype == 'treeitem' && $b->itemtype == 'treeitem') {
// Ugh need to check that this plugin has not been uninstalled.
if ($this->check_plugin_is_installed('tool_log')) {
if (trim($a->name) == get_string('privacy:path:logs', 'tool_log')) {
return 1;
} else if (trim($b->name) == get_string('privacy:path:logs', 'tool_log')) {
return -1;
}
return 0;
}
}
if ($a->itemtype == 'treeitem' && $b->itemtype == 'item') {
return -1;
}
if ($b->itemtype == 'treeitem' && $a->itemtype == 'item') {
return 1;
}
return 0;
});
foreach ($tree as $treeobject) {
if (isset($treeobject->children)) {
$this->sort_my_list($treeobject->children);
}
}
}
/**
* Check to see if a specified plugin is installed.
*
* @param string $component The component name e.g. tool_log
* @return bool Whether this component is installed.
*/
protected function check_plugin_is_installed(string $component): Bool {
if (!isset($this->checkedplugins[$component])) {
$pluginmanager = \core_plugin_manager::instance();
$plugin = $pluginmanager->get_plugin_info($component);
$this->checkedplugins[$component] = !is_null($plugin);
}
return $this->checkedplugins[$component];
}
/**
* Writes the appropriate files for creating an HTML index page for human navigation of the user data export.
*/
protected function write_html_data() {
global $PAGE, $SITE, $USER, $CFG;
// Do this first before adding more files to $this->files.
list($tree, $treekey, $allfiles) = $this->prepare_for_export();
// Add more detail to the tree such as contexts.
$richtree = $this->make_tree_object($tree, $treekey);
// Now that we have more detail we can use that to sort it.
$this->sort_my_list($richtree);
// Copy over the JavaScript required to display the html page.
$jspath = ['privacy', 'export_files', 'general.js'];
$targetpath = ['js', 'general.js'];
$this->copy_data($jspath, $targetpath);
$jquery = ['lib', 'jquery', 'jquery-3.7.1.min.js'];
$jquerydestination = ['js', 'jquery-3.7.1.min.js'];
$this->copy_data($jquery, $jquerydestination);
$requirecurrentpath = ['lib', 'requirejs', 'require.min.js'];
$destination = ['js', 'require.min.js'];
$this->copy_data($requirecurrentpath, $destination);
$treepath = ['lib', 'amd', 'build', 'tree.min.js'];
$destination = ['js', 'tree.min.js'];
$this->copy_data($treepath, $destination);
// Icons to be used.
$expandediconpath = ['pix', 't', 'expanded.svg'];
$this->copy_data($expandediconpath, ['pix', 'expanded.svg']);
$collapsediconpath = ['pix', 't', 'collapsed.svg'];
$this->copy_data($collapsediconpath, ['pix', 'collapsed.svg']);
$naviconpath = ['pix', 'i', 'navigationitem.svg'];
$this->copy_data($naviconpath, ['pix', 'navigationitem.svg']);
$moodleimgpath = ['pix', 'moodlelogo.svg'];
$this->copy_data($moodleimgpath, ['pix', 'moodlelogo.svg']);
// Additional required css.
$csspath = ['theme', 'boost', 'style', 'moodle.css'];
$destination = ['moodle.css'];
$this->copy_data($csspath, $destination);
$csspath = ['privacy', 'export_files', 'general.css'];
$destination = ['general.css'];
$this->copy_data($csspath, $destination);
// Create an index file that lists all, to be newly created, js files.
$encoded = json_encode($allfiles, JSON_PRETTY_PRINT);
$encoded = 'var user_data_index = ' . $encoded;
$path = 'js' . DIRECTORY_SEPARATOR . 'data_index.js';
$this->write_data($path, $encoded);
$output = $PAGE->get_renderer('core_privacy');
$navigationpage = new \core_privacy\output\exported_navigation_page(current($richtree));
$navigationhtml = $output->render_navigation($navigationpage);
$systemname = format_string($SITE->fullname, true, ['context' => \context_system::instance()]);
$fullusername = fullname($USER);
$siteurl = $CFG->wwwroot;
// Create custom index.html file.
$rtl = right_to_left();
$htmlpage = new \core_privacy\output\exported_html_page($navigationhtml, $systemname, $fullusername, $rtl, $siteurl);
$outputpage = $output->render_html_page($htmlpage);
$this->write_data('index.html', $outputpage);
}
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content(): string {
$this->write_html_data();
$exportfile = make_request_directory() . '/export.zip';
$fp = get_file_packer();
$fp->archive_to_pathname($this->files, $exportfile);
// Reset the writer to prevent any further writes.
writer::reset();
return $exportfile;
}
/**
* Creates a multidimensional array out of array elements.
*
* @param array $array Array which items are to be condensed into a multidimensional array.
* @return array The multidimensional array.
*/
protected function condense_array(array $array): Array {
if (count($array) === 2) {
return [$array[0] => $array[1]];
}
if (isset($array[0])) {
return [$array[0] => $this->condense_array(array_slice($array, 1))];
}
return [];
}
/**
* Get the contents of a file.
*
* @param string $filepath The file path.
* @return string contents of the file.
* @throws \moodle_exception If the file cannot be opened.
*/
protected function get_file_content(string $filepath): String {
$content = file_get_contents($filepath);
if ($content === false) {
throw new \moodle_exception('cannotopenfile', 'error', '', $filepath);
}
return $content;
}
}
@@ -0,0 +1,39 @@
<?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 \core_privacy\local\request\plugin\provider interface to describe
* a class which provides data in some form for a plugin.
*
* Plugins should implement this if they store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The provider interface for plugins which provide data from a plugin
* directly to the Privacy subsystem.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface provider extends \core_privacy\local\request\core_user_data_provider {
}
@@ -0,0 +1,42 @@
<?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 \core_privacy\local\request\plugin\subplugin_provider
* interface to describe a class which provides data in some form for the
* subplugin of another plugin.
*
* It should not be implemented directly, but should be extended by the
* plugin providing a subplugin.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The subplugin_provider interface is for plugins which are sub-plugins of
* a plugin. They do not provide data directly to the core Privacy
* subsystem, but will be accessed and called via the plugin itself.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface subplugin_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,54 @@
<?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 \core_privacy\local\request\plugin\subsystem_provider
* interface to describe a class which provides data in some form for a
* subsystem.
*
* It should not be implemented directly, but should be extended by the
* subsystem responsible for the plugintype.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The subsystem_provider interface is for plugins which may not
* necessarily be called directly, but instead via a subsystem.
*
* One example of this is the questiontype plugintype. These are
* intrinsically linked against the question subsystem and the question
* subsystem should define an interface extending this one through which it
* can query and retrieve specific data from each questiontype as required.
*
* Each questiontype may additionally respond directly to the privacy API
* if it also impleents the \core_privacay\local\request\plugin\provider
* interface directly.
*
* Care should be taken when extending this provider to not conflict with
* the \core_privacay\local\request\plugin\provider interface.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface subsystem_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,48 @@
<?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 \core_privacy\local\request\shared_data_provider interface to describe
* a class which provides data in some form.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The shared_data_provider interface is used to describe a provider which
* services user requests between components and and other components.
*
* This includes communication between subplugin, subsystems, and plugins
* which are designed to interact closely with subsystems.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
*/
interface shared_data_provider extends data_provider {
}
@@ -0,0 +1,37 @@
<?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 an interface to describe classes which provide user data in some form for shared providers.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The interface is used to describe a provider which is capable of identifying the users who have data within it.
*
* It describes data how these requests are serviced in a specific format.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*/
interface shared_userlist_provider extends userlist_provider {
}
@@ -0,0 +1,36 @@
<?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 \core_privacy\local\request\subsystem\plugin_provider interface to describe
* a class which provides data in some form for a subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\subsystem;
defined('MOODLE_INTERNAL') || die();
/**
* The plugin_provider interface for subsystems which provide data directly to a plugin.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*/
interface plugin_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,39 @@
<?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 \core_privacy\local\request\subsystem\provider interface to describe
* a class which provides data in some form for a subsystem.
*
* Plugins should implement this if they directly store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\subsystem;
defined('MOODLE_INTERNAL') || die();
/**
* The provider interface for plugins which provide data from a subsystem
* directly to the Privacy subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*/
interface provider extends \core_privacy\local\request\core_user_data_provider {
}
@@ -0,0 +1,97 @@
<?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 core_privacy\local\request helper.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* A class containing a set of data transformations for core data types.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class transform {
/**
* Translate a userid into the standard user format for exports.
*
* We have not determined if we will do this or not, but we provide the functionality and encourgae people to use
* it so that it can be retrospectively fitted if required.
*
* @param int $userid the userid to translate
* @return mixed
*/
public static function user(int $userid) {
// For the moment we do not think we should transform as this reveals information about other users.
// However this function is implemented should the need arise in the future.
return $userid;
}
/**
* Translate a unix timestamp into a datetime string.
*
* @param int $datetime the unixtimestamp to translate.
* @return string The translated string.
*/
public static function datetime($datetime) {
return userdate($datetime, get_string('strftimedaydatetime', 'langconfig'));
}
/**
* Translate a unix timestamp into a date string.
*
* @param int $date the unixtimestamp to translate.
* @return string The translated string.
*/
public static function date($date) {
return userdate($date, get_string('strftimedate', 'langconfig'));
}
/**
* Translate a bool or int (0/1) value into a translated yes/no string.
*
* @param bool $value The value to translate
* @return string
*/
public static function yesno($value) {
if ($value) {
return get_string('yes');
} else {
return get_string('no');
}
}
/**
* Translate a float value which should be between 0.0 and 1.0 into percentage.
*
* @param float $value The value between 0.0 and 1.0.
* @return float|string
*/
public static function percentage(float $value) {
if (is_float($value)) {
return (100 * $value) . '%';
} else {
return $value;
}
}
}
@@ -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/>.
/**
* This file contains the \core_privacy\local\request\user_preference_provider interface to describe
* a class which provides preference data in some form to core.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The user_preference_provider interface is an interface designed to be
* implemented by components directly to describe a case where that
* component is responsible for storing some form of system-wide user
* preference.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface user_preference_provider extends core_data_provider {
/**
* 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);
}
+104
View File
@@ -0,0 +1,104 @@
<?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/>.
/**
* List of users from the Privacy API Search functions.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* List of users from the Privacy API Search functions.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userlist extends userlist_base {
/**
* Add a set of users from SQL.
*
* The SQL should only return a list of user IDs.
*
* @param string $fieldname The name of the field which holds the user id
* @param string $sql The SQL which will fetch the list of * user IDs
* @param array $params The set of SQL parameters
* @return $this
*/
public function add_from_sql(string $fieldname, string $sql, array $params): userlist {
global $DB;
// Able to guess a field name.
$wrapper = "
SELECT DISTINCT u.id
FROM {user} u
JOIN ({$sql}) target ON u.id = target.{$fieldname}";
$users = $DB->get_records_sql($wrapper, $params);
$this->add_userids(array_keys($users));
return $this;
}
/**
* Adds the user user for a given user.
*
* @param int $userid
* @return $this
*/
public function add_user(int $userid): userlist {
$this->add_users([$userid]);
return $this;
}
/**
* Adds the user users for given users.
*
* @param int[] $userids
* @return $this
*/
public function add_users(array $userids): userlist {
global $DB;
if (!empty($userids)) {
list($useridsql, $useridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$sql = "SELECT DISTINCT u.id
FROM {user} u
WHERE u.id {$useridsql}";
$this->add_from_sql('id', $sql, $useridparams);
}
return $this;
}
/**
* Sets the component for this userlist.
*
* @param string $component the frankenstyle component name.
* @return $this
*/
public function set_component($component): userlist_base {
parent::set_component($component);
return $this;
}
}
@@ -0,0 +1,222 @@
<?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/>.
/**
* Base implementation of a userlist.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Base implementation of a userlist used to store a set of users.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class userlist_base implements
// Implement an Iterator to fetch the Context objects.
\Iterator,
// Implement the Countable interface to allow the number of returned results to be queried easily.
\Countable {
/**
* @var array List of user IDs.
*
* Note: this must not be updated using set_userids only as this
* ensures uniqueness.
*/
private $userids = [];
/**
* @var string component the frankenstyle component name.
*/
protected $component = '';
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/** @var \context The context that this userlist belongs to */
protected $context;
/**
* Constructor to create a new userlist.
*
* @param \context $context
* @param string $component
*/
public function __construct(\context $context, string $component) {
$this->context = $context;
$this->set_component($component);
}
/**
* Set the userids.
*
* @param array $userids The list of users.
* @return $this
*/
protected function set_userids(array $userids): userlist_base {
$this->userids = array_values(array_unique($userids));
return $this;
}
/**
* Add a set of additional userids.
*
* @param array $userids The list of users.
* @return $this
*/
protected function add_userids(array $userids): userlist_base {
$this->set_userids(array_merge($this->get_userids(), $userids));
return $this;
}
/**
* Get the list of user IDs that relate to this request.
*
* @return int[]
*/
public function get_userids(): array {
return $this->userids;
}
/**
* Get the complete list of user objects that relate to this request.
*
* @return \stdClass[]
*/
public function get_users(): array {
$users = [];
foreach ($this->userids as $userid) {
if ($user = \core_user::get_user($userid)) {
$users[] = $user;
}
}
return $users;
}
/**
* Sets the component for this userlist.
*
* @param string $component the frankenstyle component name.
* @return $this
*/
protected function set_component($component): userlist_base {
$this->component = $component;
return $this;
}
/**
* Get the name of the component to which this userlist belongs.
*
* @return string the component name associated with this userlist.
*/
public function get_component(): string {
return $this->component;
}
/**
* Return the current user.
*
* @return \user
*/
#[\ReturnTypeWillChange]
public function current() {
$user = \core_user::get_user($this->userids[$this->iteratorposition]);
if (false === $user) {
// This user was not found.
unset($this->userids[$this->iteratorposition]);
// Check to see if there are any more users left.
if ($this->count()) {
// Move the pointer to the next record and try again.
$this->next();
$user = $this->current();
} else {
// There are no more context ids left.
return null;
}
}
return $user;
}
/**
* Return the key of the current element.
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
return $this->iteratorposition;
}
/**
* Move to the next user in the list.
*/
public function next(): void {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid(): bool {
return isset($this->userids[$this->iteratorposition]) && $this->current();
}
/**
* Rewind to the first found user.
*
* The list of users is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind(): void {
$this->iteratorposition = 0;
}
/**
* Return the number of users.
*/
public function count(): int {
return count($this->userids);
}
/**
* Get the context for this userlist
*
* @return \context
*/
public function get_context(): \context {
return $this->context;
}
}
@@ -0,0 +1,179 @@
<?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 defines the userlist_collection class object.
*
* The userlist_collection is used to organize a collection of userlists.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* A collection of userlist items.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class userlist_collection implements \Iterator, \Countable {
/**
* @var \context $context The context that the userlist collection belongs to.
*/
protected $context = null;
/**
* @var array $userlists the internal array of userlist objects.
*/
protected $userlists = [];
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/**
* Constructor to create a new userlist_collection.
*
* @param \context $context The context to which this collection belongs.
*/
public function __construct(\context $context) {
$this->context = $context;
}
/**
* Return the context that this collection relates to.
*
* @return int
*/
public function get_context(): \context {
return $this->context;
}
/**
* Add a userlist to this collection.
*
* @param userlist_base $userlist the userlist to export.
* @return $this
*/
public function add_userlist(userlist_base $userlist): userlist_collection {
$component = $userlist->get_component();
if (isset($this->userlists[$component])) {
throw new \moodle_exception("A userlist has already been added for the '{$component}' component");
}
$this->userlists[$component] = $userlist;
return $this;
}
/**
* Get the userlists in this collection.
*
* @return array the associative array of userlists in this collection, indexed by component name.
* E.g. mod_assign => userlist, core_comment => userlist.
*/
public function get_userlists(): array {
return $this->userlists;
}
/**
* Get the userlist for the specified component.
*
* @param string $component the frankenstyle name of the component to fetch for.
* @return userlist_base|null
*/
public function get_userlist_for_component(string $component) {
if (isset($this->userlists[$component])) {
return $this->userlists[$component];
}
return null;
}
/**
* Return the current contexlist.
*
* @return \user
*/
#[\ReturnTypeWillChange]
public function current() {
$key = $this->get_key_from_position();
return $this->userlists[$key];
}
/**
* Return the key of the current element.
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
return $this->get_key_from_position();
}
/**
* Move to the next user in the list.
*/
public function next(): void {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid(): bool {
return ($this->iteratorposition < count($this->userlists));
}
/**
* Rewind to the first found user.
*
* The list of users is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind(): void {
$this->iteratorposition = 0;
}
/**
* Get the key for the current iterator position.
*
* @return string
*/
protected function get_key_from_position() {
$keylist = array_keys($this->userlists);
if (isset($keylist[$this->iteratorposition])) {
return $keylist[$this->iteratorposition];
}
return null;
}
/**
* Return the number of users.
*/
public function count(): int {
return count($this->userlists);
}
}
@@ -0,0 +1,37 @@
<?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 an interface to describe classes which userlist support.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The interface is used to describe a provider which is capable of identifying the users who have data within it.
*
* It describes data how these requests are serviced in a specific format.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
*/
interface userlist_provider {
}
+136
View File
@@ -0,0 +1,136 @@
<?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 interface required to implmeent a content writer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The writer factory class used to fetch and work with the content_writer.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class writer {
/**
* @var writer The singleton instance of this writer.
*/
protected static $instance = null;
/**
* @var content_writer The current content_writer instance.
*/
protected $realwriter = null;
/**
* Constructor for the content writer.
*
* Protected to prevent direct instantiation.
*/
protected function __construct() {
}
/**
* Singleton to return or create and return a copy of a content_writer.
*
* @return content_writer
*/
protected function get_writer_instance(): content_writer {
if (null === $this->realwriter) {
if (PHPUNIT_TEST) {
$this->realwriter = new \core_privacy\tests\request\content_writer(static::instance());
} else {
$this->realwriter = new moodle_content_writer(static::instance());
}
}
return $this->realwriter;
}
/**
* Create a real content_writer for use by PHPUnit tests,
* where a mock writer will not suffice.
*
* @return content_writer
*/
public static function setup_real_writer_instance() {
if (!PHPUNIT_TEST) {
throw new \coding_exception('setup_real_writer_instance() is only for use with PHPUnit tests.');
}
$instance = static::instance();
if (null === $instance->realwriter) {
$instance->realwriter = new moodle_content_writer(static::instance());
}
}
/**
* Return an instance of
*/
final protected static function instance() {
if (null === self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
/**
* Reset the writer and content_writer.
*/
final public static function reset() {
static::$instance = null;
}
/**
* Provide an instance of the writer with the specified context applied.
*
* @param \context $context The context to apply
* @return content_writer The content_writer
*/
public static function with_context(\context $context): content_writer {
return static::instance()
->get_writer_instance()
->set_context($context);
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public static function export_user_preference(
string $component,
string $key,
string $value,
string $description
): content_writer {
return static::with_context(\context_system::instance())
->export_user_preference($component, $key, $value, $description);
}
}
@@ -0,0 +1,96 @@
<?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/>.
/**
* Default (core) handler for site policies.
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\sitepolicy;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
/**
* Default (core) handler for site policies.
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class default_handler extends handler {
/**
* Checks if the site has site policy defined
*
* @param bool $forguests
* @return bool
*/
public static function is_defined($forguests = false) {
global $CFG;
if (!empty($CFG->sitepolicyhandler)) {
// This handler can also be used as a fallback in case of invalid $CFG->sitepolicyhandler,
// in this case assume that no site policy is set.
return false;
}
if (!$forguests) {
return !empty($CFG->sitepolicy);
} else {
return !empty($CFG->sitepolicyguest);
}
}
/**
* Returns URL to redirect user to when user needs to agree to site policy
*
* This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
* allow user to view policies and accept them.
*
* @param bool $forguests
* @return moodle_url|null (returns null if site policy is not defined)
*/
public static function get_redirect_url($forguests = false) {
return static::is_defined($forguests) ? new moodle_url('/user/policy.php') : null;
}
/**
* Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
*
* This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
* the "Accept" button and call {@link self::accept()} on completion.
*
* @param bool $forguests
* @return moodle_url|null
*/
public static function get_embed_url($forguests = false) {
global $CFG;
if (!empty($CFG->sitepolicyhandler)) {
// This handler can also be used as a fallback in case of invalid $CFG->sitepolicyhandler,
// in this case assume that no site policy is set.
return null;
}
if ($forguests && !empty($CFG->sitepolicyguest)) {
return new moodle_url($CFG->sitepolicyguest);
} else if (!$forguests && !empty($CFG->sitepolicy)) {
return new moodle_url($CFG->sitepolicy);
}
return null;
}
}
@@ -0,0 +1,113 @@
<?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/>.
/**
* Base class for site policy handlers.
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\sitepolicy;
defined('MOODLE_INTERNAL') || die();
/**
* Base class for site policy handlers.
*
* If a plugin wants to act as a site policy handler it has to define class
* PLUGINNAME\privacy\sitepolicy\handler that extends \core_privacy\sitepolicy\handler
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class handler {
/**
* Checks if the site has site policy defined
*
* @param bool $forguests
* @return bool
*/
public static function is_defined($forguests = false) {
$url = static::get_redirect_url($forguests);
return !empty($url);
}
/**
* Returns URL to redirect user to when user needs to agree to site policy
*
* This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
* allow user to view policies and accept them.
*
* @param bool $forguests
* @return moodle_url|null (returns null if site policy is not defined)
*/
abstract public static function get_redirect_url($forguests = false);
/**
* Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
*
* This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
* the "Accept" button and call {@link self::accept()} on completion.
*
* @param bool $forguests
* @return moodle_url|null
*/
abstract public static function get_embed_url($forguests = false);
/**
* Accept site policy for the current user
*
* @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
* true - if we have successfully marked the user as agreed to the site policy
*/
public static function accept() {
global $USER, $DB;
if (!isloggedin()) {
return false;
}
if ($USER->policyagreed || !static::is_defined(isguestuser())) {
return false;
}
if (!isguestuser()) {
// For the guests agreement in stored in session only, for other users - in DB.
$DB->set_field('user', 'policyagreed', 1, array('id' => $USER->id));
}
$USER->policyagreed = 1;
return true;
}
/**
* Adds "Agree to site policy" checkbox to the signup form.
*
* Sitepolicy handlers can override the simple checkbox with their own controls.
*
* @param \MoodleQuickForm $mform
*/
public static function signup_form($mform) {
if ($url = static::get_embed_url()) {
$mform->addElement('header', 'policyagreement', get_string('policyagreement'), '');
$mform->setExpanded('policyagreement');
$mform->addElement('static', 'policylink', '', '<a href="' . $url .
'" onclick="this.target=\'_blank\'">' . get_string('policyagreementclick') . '</a>');
$mform->addElement('checkbox', 'policyagreed', get_string('policyaccept'));
$mform->addRule('policyagreed', get_string('policyagree'), 'required', null, 'client');
}
}
}
@@ -0,0 +1,144 @@
<?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/>.
/**
* Site policy management class.
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\sitepolicy;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
/**
* Site policy management class.
*
* @package core_privacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* Returns the list of plugins that can work as sitepolicy handlers (have class PLUGINNAME\privacy\sitepolicy\handler)
* @return array
*/
public function get_all_handlers() {
$sitepolicyhandlers = [];
foreach (\core_component::get_plugin_types() as $ptype => $unused) {
$plugins = \core_component::get_plugin_list_with_class($ptype, 'privacy\local\sitepolicy\handler') +
\core_component::get_plugin_list_with_class($ptype, 'privacy_local_sitepolicy_handler');
// Allow plugins to have the class either with namespace or without (useful for unittest).
foreach ($plugins as $pname => $class) {
$sitepolicyhandlers[$pname] = $class;
}
}
return $sitepolicyhandlers;
}
/**
* Returns the current site policy handler
*
* @return handler
*/
public function get_handler_classname() {
global $CFG;
if (!empty($CFG->sitepolicyhandler)) {
$sitepolicyhandlers = $this->get_all_handlers();
if (!isset($sitepolicyhandlers[$CFG->sitepolicyhandler])) {
return default_handler::class;
} else {
return $sitepolicyhandlers[$CFG->sitepolicyhandler];
}
} else {
return default_handler::class;
}
}
/**
* Checks if the site has site policy defined
*
* @param bool $forguests
* @return bool
*/
public function is_defined($forguests = false) {
return component_class_callback($this->get_handler_classname(), 'is_defined', [$forguests]);
}
/**
* Returns URL to redirect user to when user needs to agree to site policy
*
* This is a regular interactive page for web users. It should have normal Moodle header/footers, it should
* allow user to view policies and accept them.
*
* @param bool $forguests
* @return moodle_url|null (returns null if site policy is not defined)
*/
public function get_redirect_url($forguests = false) {
$url = component_class_callback($this->get_handler_classname(), 'get_redirect_url', [$forguests]);
if ($url && !($url instanceof moodle_url)) {
$url = new moodle_url($url);
}
return $url;
}
/**
* Returns URL of the site policy that needs to be displayed to the user (inside iframe or to use in WS such as mobile app)
*
* This page should not have any header/footer, it does not also have any buttons/checkboxes. The caller needs to implement
* the "Accept" button and call {@link self::accept()} on completion.
*
* @param bool $forguests
* @return moodle_url|null
*/
public function get_embed_url($forguests = false) {
$url = component_class_callback($this->get_handler_classname(), 'get_embed_url', [$forguests]);
if ($url && !($url instanceof moodle_url)) {
$url = new moodle_url($url);
}
return $url;
}
/**
* Accept site policy for the current user
*
* @return bool - false if sitepolicy not defined, user is not logged in or user has already agreed to site policy;
* true - if we have successfully marked the user as agreed to the site policy
*/
public function accept() {
return component_class_callback($this->get_handler_classname(), 'accept', []);
}
/**
* Adds "Agree to site policy" checkbox to the signup form.
*
* Sitepolicy handlers can override the simple checkbox with their own controls.
*
* @param \MoodleQuickForm $mform
*/
public function signup_form($mform) {
component_class_callback($this->get_handler_classname(), 'signup_form', [$mform]);
}
}
+635
View File
@@ -0,0 +1,635 @@
<?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 core_privacy\manager class.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\metadata\null_provider;
use core_privacy\local\request\context_aware_provider;
use core_privacy\local\request\contextlist_collection;
use core_privacy\local\request\core_user_data_provider;
use core_privacy\local\request\core_userlist_provider;
use core_privacy\local\request\data_provider;
use core_privacy\local\request\user_preference_provider;
use \core_privacy\local\metadata\provider as metadata_provider;
defined('MOODLE_INTERNAL') || die();
/**
* The core_privacy\manager class, providing a facade to describe, export and delete personal data across Moodle and its components.
*
* This class is responsible for communicating with and collating privacy data from all relevant components, where relevance is
* determined through implementations of specific marker interfaces. These marker interfaces describe the responsibilities (in terms
* of personal data storage) as well as the relationship between the component and the core_privacy subsystem.
*
* The interface hierarchy is as follows:
* ├── local\metadata\null_provider
* ├── local\metadata\provider
* ├── local\request\data_provider
* └── local\request\core_data_provider
* └── local\request\core_user_data_provider
* └── local\request\plugin\provider
* └── local\request\subsystem\provider
* └── local\request\user_preference_provider
* └── local\request\shared_data_provider
* └── local\request\plugin\subsystem_provider
* └── local\request\plugin\subplugin_provider
* └── local\request\subsystem\plugin_provider
*
* Describing personal data:
* -------------------------
* All components must state whether they store personal data (and DESCRIBE it) by implementing one of the metadata providers:
* - local\metadata\null_provider (indicating they don't store personal data)
* - local\metadata\provider (indicating they do store personal data, and describing it)
*
* The manager requests metadata for all Moodle components implementing the local\metadata\provider interface.
*
* Export and deletion of personal data:
* -------------------------------------
* Those components storing personal data need to provide EXPORT and DELETION of this data by implementing a request provider.
* Which provider implementation depends on the nature of the component; whether it's a sub-component and which components it
* stores data for.
*
* Export and deletion for sub-components (or any component storing data on behalf of another component) is managed by the parent
* component. If a component contains sub-components, it must ask those sub-components to provide the relevant data. Only certain
* 'core provider' components are called directly from the manager and these must provide the personal data stored by both
* themselves, and by all sub-components. Because of this hierarchical structure, the core_privacy\manager needs to know which
* components are to be called directly by core: these are called core data providers. The providers implemented by sub-components
* are called shared data providers.
*
* The following are interfaces are not implemented directly, but are marker interfaces uses to classify components by nature:
* - local\request\data_provider:
* Not implemented directly. Used to classify components storing personal data of some kind. Includes both components storing
* personal data for themselves and on behalf of other components.
* Include: local\request\core_data_provider and local\request\shared_data_provider.
* - local\request\core_data_provider:
* Not implemented directly. Used to classify components storing personal data for themselves and which are to be called by the
* core_privacy subsystem directly.
* Includes: local\request\core_user_data_provider and local\request\user_preference_provider.
* - local\request\core_user_data_provider:
* Not implemented directly. Used to classify components storing personal data for themselves, which are either a plugin or
* subsystem and which are to be called by the core_privacy subsystem directly.
* Includes: local\request\plugin\provider and local\request\subsystem\provider.
* - local\request\shared_data_provider:
* Not implemented directly. Used to classify components storing personal data on behalf of other components and which are
* called by the owning component directly.
* Includes: local\request\plugin\subsystem_provider, local\request\plugin\subplugin_provider and local\request\subsystem\plugin_provider
*
* The manager only requests the export or deletion of personal data for components implementing the local\request\core_data_provider
* interface or one of its descendants; local\request\plugin\provider, local\request\subsystem\provider or local\request\user_preference_provider.
* Implementing one of these signals to the core_privacy subsystem that the component must be queried directly from the manager.
*
* Any component using another component to store personal data on its behalf, is responsible for making the relevant call to
* that component's relevant shared_data_provider class.
*
* For example:
* The manager calls a core_data_provider component (e.g. mod_assign) which, in turn, calls relevant subplugins or subsystems
* (which assign uses to store personal data) to get that data. All data for assign and its sub-components is aggregated by assign
* and returned to the core_privacy subsystem.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* @var manager_observer Observer.
*/
protected $observer;
/**
* Set the failure handler.
*
* @param manager_observer $observer
*/
public function set_observer(manager_observer $observer) {
$this->observer = $observer;
}
/**
* Checks whether the given component is compliant with the core_privacy API.
* To be considered compliant, a component must declare whether (and where) it stores personal data.
*
* Components which do store personal data must:
* - Have implemented the core_privacy\local\metadata\provider interface (to describe the data it stores) and;
* - Have implemented the core_privacy\local\request\data_provider interface (to facilitate export of personal data)
* - Have implemented the core_privacy\local\request\deleter interface
*
* Components which do not store personal data must:
* - Have implemented the core_privacy\local\metadata\null_provider interface to signal that they don't store personal data.
*
* @param string $component frankenstyle component name, e.g. 'mod_assign'
* @return bool true if the component is compliant, false otherwise.
*/
public function component_is_compliant(string $component): bool {
// Components which don't store user data need only implement the null_provider.
if ($this->component_implements($component, null_provider::class)) {
return true;
}
if (static::is_empty_subsystem($component)) {
return true;
}
// Components which store user data must implement the local\metadata\provider and the local\request\data_provider.
if ($this->component_implements($component, metadata_provider::class) &&
$this->component_implements($component, data_provider::class)) {
return true;
}
return false;
}
/**
* Retrieve the reason for implementing the null provider interface.
*
* @param string $component Frankenstyle component name.
* @return string The key to retrieve the language string for the null provider reason.
*/
public function get_null_provider_reason(string $component): string {
if ($this->component_implements($component, null_provider::class)) {
$reason = $this->handled_component_class_callback($component, null_provider::class, 'get_reason', []);
return empty($reason) ? 'privacy:reason' : $reason;
} else {
throw new \coding_exception('Call to undefined method', 'Please only call this method on a null provider.');
}
}
/**
* Return whether this is an 'empty' subsystem - that is, a subsystem without a directory.
*
* @param string $component Frankenstyle component name.
* @return string The key to retrieve the language string for the null provider reason.
*/
public static function is_empty_subsystem($component) {
if (strpos($component, 'core_') === 0) {
if (null === \core_component::get_subsystem_directory(substr($component, 5))) {
// This is a subsystem without a directory.
return true;
}
}
return false;
}
/**
* Get the privacy metadata for all components.
*
* @return collection[] The array of collection objects, indexed by frankenstyle component name.
*/
public function get_metadata_for_components(): array {
// Get the metadata, and put into an assoc array indexed by component name.
$metadata = [];
foreach ($this->get_component_list() as $component) {
$componentmetadata = $this->handled_component_class_callback($component, metadata_provider::class,
'get_metadata', [new collection($component)]);
if ($componentmetadata !== null) {
$metadata[$component] = $componentmetadata;
}
}
return $metadata;
}
/**
* Gets a collection of resultset objects for all components.
*
*
* @param int $userid the id of the user we're fetching contexts for.
* @return contextlist_collection the collection of contextlist items for the respective components.
*/
public function get_contexts_for_userid(int $userid): contextlist_collection {
$progress = static::get_log_tracer();
$components = $this->get_component_list();
$a = (object) [
'total' => count($components),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
$clcollection = new contextlist_collection($userid);
$progress->output(get_string('trace:fetchcomponents', 'core_privacy', $a), 1);
foreach ($components as $component) {
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
$contextlist = $this->handled_component_class_callback($component, core_user_data_provider::class,
'get_contexts_for_userid', [$userid]);
if ($contextlist === null) {
$contextlist = new local\request\contextlist();
}
// Each contextlist is tied to its respective component.
$contextlist->set_component($component);
// Add contexts that the component may not know about.
// Example of these include activity completion which modules do not know about themselves.
$contextlist = local\request\helper::add_shared_contexts_to_contextlist_for($userid, $contextlist);
if (count($contextlist)) {
$clcollection->add_contextlist($contextlist);
}
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
return $clcollection;
}
/**
* Gets a collection of users for all components in the specified context.
*
* @param \context $context The context to search
* @return userlist_collection the collection of userlist items for the respective components.
*/
public function get_users_in_context(\context $context): \core_privacy\local\request\userlist_collection {
$progress = static::get_log_tracer();
$components = $this->get_component_list();
$a = (object) [
'total' => count($components),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
$collection = new \core_privacy\local\request\userlist_collection($context);
$progress->output(get_string('trace:fetchcomponents', 'core_privacy', $a), 1);
foreach ($components as $component) {
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:preprocessingcomponent', 'core_privacy', $a), 2);
$userlist = new local\request\userlist($context, $component);
$this->handled_component_class_callback($component, core_userlist_provider::class, 'get_users_in_context', [$userlist]);
// Add contexts that the component may not know about.
\core_privacy\local\request\helper::add_shared_users_to_userlist($userlist);
if (count($userlist)) {
$collection->add_userlist($userlist);
}
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
return $collection;
}
/**
* Export all user data for the specified approved_contextlist items.
*
* Note: userid and component are stored in each respective approved_contextlist.
*
* @param contextlist_collection $contextlistcollection the collection of contextlists for all components.
* @return string the location of the exported data.
* @throws \moodle_exception if the contextlist_collection does not contain all approved_contextlist items or if one of the
* approved_contextlists' components is not a core_data_provider.
*/
public function export_user_data(contextlist_collection $contextlistcollection) {
$progress = static::get_log_tracer();
$a = (object) [
'total' => count($contextlistcollection),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
// Export for the various components/contexts.
$progress->output(get_string('trace:exportingapproved', 'core_privacy', $a), 1);
foreach ($contextlistcollection as $approvedcontextlist) {
if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
throw new \moodle_exception('Contextlist must be an approved_contextlist');
}
$component = $approvedcontextlist->get_component();
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
// Core user data providers.
if ($this->component_implements($component, core_user_data_provider::class)) {
if (count($approvedcontextlist)) {
// This plugin has data it knows about. It is responsible for storing basic data about anything it is
// told to export.
$this->handled_component_class_callback($component, core_user_data_provider::class,
'export_user_data', [$approvedcontextlist]);
}
} else if (!$this->component_implements($component, context_aware_provider::class)) {
// This plugin does not know that it has data - export the shared data it doesn't know about.
local\request\helper::export_data_for_null_provider($approvedcontextlist);
}
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
// Check each component for non contextlist items too.
$components = $this->get_component_list();
$a->total = count($components);
$a->progress = 0;
$a->datetime = userdate(time());
$progress->output(get_string('trace:exportingrelated', 'core_privacy', $a), 1);
foreach ($components as $component) {
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
// Core user preference providers.
$this->handled_component_class_callback($component, user_preference_provider::class,
'export_user_preferences', [$contextlistcollection->get_userid()]);
// Contextual information providers. Give each component a chance to include context information based on the
// existence of a child context in the contextlist_collection.
$this->handled_component_class_callback($component, context_aware_provider::class,
'export_context_data', [$contextlistcollection]);
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
$progress->output(get_string('trace:finalisingexport', 'core_privacy'), 1);
$location = local\request\writer::with_context(\context_system::instance())->finalise_content();
$progress->output(get_string('trace:exportcomplete', 'core_privacy'), 1);
return $location;
}
/**
* Delete all user data for approved contexts lists provided in the collection.
*
* This call relates to the forgetting of an entire user.
*
* Note: userid and component are stored in each respective approved_contextlist.
*
* @param contextlist_collection $contextlistcollection the collections of approved_contextlist items on which to call deletion.
* @throws \moodle_exception if the contextlist_collection doesn't contain all approved_contextlist items, or if the component
* for an approved_contextlist isn't a core provider.
*/
public function delete_data_for_user(contextlist_collection $contextlistcollection) {
$progress = static::get_log_tracer();
$a = (object) [
'total' => count($contextlistcollection),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
// Delete the data.
$progress->output(get_string('trace:deletingapproved', 'core_privacy', $a), 1);
foreach ($contextlistcollection as $approvedcontextlist) {
if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
throw new \moodle_exception('Contextlist must be an approved_contextlist');
}
$component = $approvedcontextlist->get_component();
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
if (count($approvedcontextlist)) {
// The component knows about data that it has.
// Have it delete its own data.
$this->handled_component_class_callback($approvedcontextlist->get_component(), core_user_data_provider::class,
'delete_data_for_user', [$approvedcontextlist]);
}
// Delete any shared user data it doesn't know about.
local\request\helper::delete_data_for_user($approvedcontextlist);
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
}
/**
* Delete all user data for all specified users in a context.
*
* @param \core_privacy\local\request\userlist_collection $collection
*/
public function delete_data_for_users_in_context(\core_privacy\local\request\userlist_collection $collection) {
$progress = static::get_log_tracer();
$a = (object) [
'contextid' => $collection->get_context()->id,
'total' => count($collection),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
// Delete the data.
$progress->output(get_string('trace:deletingapprovedusers', 'core_privacy', $a), 1);
foreach ($collection as $userlist) {
if (!$userlist instanceof \core_privacy\local\request\approved_userlist) {
throw new \moodle_exception('The supplied userlist must be an approved_userlist');
}
$component = $userlist->get_component();
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
if (empty($userlist)) {
// This really shouldn't happen!
continue;
}
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
$this->handled_component_class_callback($component, core_userlist_provider::class,
'delete_data_for_users', [$userlist]);
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
}
/**
* Delete all use data which matches the specified deletion criteria.
*
* @param \context $context The specific context to delete data for.
*/
public function delete_data_for_all_users_in_context(\context $context) {
$progress = static::get_log_tracer();
$components = $this->get_component_list();
$a = (object) [
'total' => count($components),
'progress' => 0,
'component' => '',
'datetime' => userdate(time()),
];
$progress->output(get_string('trace:deletingcontext', 'core_privacy', $a), 1);
foreach ($this->get_component_list() as $component) {
$a->component = $component;
$a->progress++;
$a->datetime = userdate(time());
$progress->output(get_string('trace:processingcomponent', 'core_privacy', $a), 2);
// If this component knows about specific data that it owns,
// have it delete all of that user data for the context.
$this->handled_component_class_callback($component, core_user_data_provider::class,
'delete_data_for_all_users_in_context', [$context]);
// Delete any shared user data it doesn't know about.
local\request\helper::delete_data_for_all_users_in_context($component, $context);
}
$progress->output(get_string('trace:done', 'core_privacy'), 1);
}
/**
* Returns a list of frankenstyle names of core components (plugins and subsystems).
*
* @return array the array of frankenstyle component names.
*/
protected function get_component_list() {
$components = array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) {
return array_merge($carry, $item);
}, []));
$components[] = 'core';
return $components;
}
/**
* Return the fully qualified provider classname for the component.
*
* @param string $component the frankenstyle component name.
* @return string the fully qualified provider classname.
*/
protected function get_provider_classname($component) {
return static::get_provider_classname_for_component($component);
}
/**
* Return the fully qualified provider classname for the component.
*
* @param string $component the frankenstyle component name.
* @return string the fully qualified provider classname.
*/
public static function get_provider_classname_for_component(string $component) {
return "$component\\privacy\\provider";
}
/**
* Checks whether the component's provider class implements the specified interface.
* This can either be implemented directly, or by implementing a descendant (extension) of the specified interface.
*
* @param string $component the frankenstyle component name.
* @param string $interface the name of the interface we want to check.
* @return bool True if an implementation was found, false otherwise.
*/
protected function component_implements(string $component, string $interface): bool {
$providerclass = $this->get_provider_classname($component);
if (class_exists($providerclass)) {
$rc = new \ReflectionClass($providerclass);
return $rc->implementsInterface($interface);
}
return false;
}
/**
* Call the named method with the specified params on any plugintype implementing the relevant interface.
*
* @param string $plugintype The plugingtype to check
* @param string $interface The interface to implement
* @param string $methodname The method to call
* @param array $params The params to call
*/
public static function plugintype_class_callback(string $plugintype, string $interface, string $methodname, array $params) {
$components = \core_component::get_plugin_list($plugintype);
foreach (array_keys($components) as $component) {
static::component_class_callback("{$plugintype}_{$component}", $interface, $methodname, $params);
}
}
/**
* Call the named method with the specified params on the supplied component if it implements the relevant interface on its provider.
*
* @param string $component The component to call
* @param string $interface The interface to implement
* @param string $methodname The method to call
* @param array $params The params to call
* @return mixed
*/
public static function component_class_callback(string $component, string $interface, string $methodname, array $params) {
$classname = static::get_provider_classname_for_component($component);
if (class_exists($classname) && is_subclass_of($classname, $interface)) {
return component_class_callback($classname, $methodname, $params);
}
return null;
}
/**
* Get the tracer used for logging.
*
* The text tracer is used except for unit tests.
*
* @return \progress_trace
*/
protected static function get_log_tracer() {
if (PHPUNIT_TEST) {
return new \null_progress_trace();
}
return new \text_progress_trace();
}
/**
* Call the named method with the specified params on the supplied component if it implements the relevant interface
* on its provider.
*
* @param string $component The component to call
* @param string $interface The interface to implement
* @param string $methodname The method to call
* @param array $params The params to call
* @return mixed
*/
protected function handled_component_class_callback(string $component, string $interface, string $methodname, array $params) {
try {
return static::component_class_callback($component, $interface, $methodname, $params);
} catch (\Throwable $e) {
debugging($e->getMessage(), DEBUG_DEVELOPER, $e->getTrace());
$this->component_class_callback_failed($e, $component, $interface, $methodname, $params);
return null;
}
}
/**
* Notifies the observer of any failure.
*
* @param \Throwable $e
* @param string $component
* @param string $interface
* @param string $methodname
* @param array $params
*/
protected function component_class_callback_failed(\Throwable $e, string $component, string $interface,
string $methodname, array $params) {
if ($this->observer) {
call_user_func_array([$this->observer, 'handle_component_failure'], func_get_args());
}
}
}
+47
View File
@@ -0,0 +1,47 @@
<?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 interface required to observe failures in the manager.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy;
defined('MOODLE_INTERNAL') || die();
/**
* The interface for a Manager observer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface manager_observer {
/**
* Handle failure of a component.
*
* @param \Throwable $e
* @param string $component
* @param string $interface
* @param string $methodname
* @param array $params
*/
public function handle_component_failure($e, $component, $interface, $methodname, array $params);
}
@@ -0,0 +1,87 @@
<?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/>.
/**
* Contains the navigation renderable for user data exports.
*
* @package core_privacy
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
/**
* Class containing the navigation renderable
*
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class exported_html_page implements renderable, templatable {
/** @var string $navigationdata navigation html to be displayed about the system. */
protected $navigationdata;
/** @var string $systemname systemname for the page. */
protected $systemname;
/** @var string $username The full name of the user. */
protected $username;
/** @var bool $rtl The direction to show the page (right to left) */
protected $rtl;
/** @var string $siteurl The url back to the site that created this export. */
protected $siteurl;
/**
* Constructor.
*
* @param string $navigationdata Navigation html to be displayed about the system.
* @param string $systemname systemname for the page.
* @param string $username The full name of the user.
* @param bool $righttoleft Is the language used right to left?
* @param string $siteurl The url to the site that created this export.
*/
public function __construct(string $navigationdata, string $systemname, string $username, bool $righttoleft, string $siteurl) {
$this->navigationdata = $navigationdata;
$this->systemname = $systemname;
$this->username = $username;
$this->rtl = $righttoleft;
$this->siteurl = $siteurl;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): Array {
return [
'navigation' => $this->navigationdata,
'systemname' => $this->systemname,
'timegenerated' => time(),
'username' => $this->username,
'righttoleft' => $this->rtl,
'siteurl' => $this->siteurl
];
}
}
@@ -0,0 +1,98 @@
<?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/>.
/**
* Contains the navigation renderable for user data exports.
*
* @package core_privacy
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
/**
* Class containing the navigation renderable
*
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class exported_navigation_page implements renderable, templatable {
/** @var array $tree Full tree in multidimensional form. */
protected $tree;
/** @var boolean $firstelement This is used to create unique classes for the first elements in the navigation tree. */
protected $firstelement = true;
/**
* Constructor
*
* @param \stdClass $tree Full tree to create navigation out of.
*/
public function __construct(\stdClass $tree) {
$this->tree = $tree;
}
/**
* Creates the navigation list html. Why this and not a template? My attempts at trying to get a recursive template
* working failed.
*
* @param \stdClass $tree Full tree to create navigation out of.
* @return string navigation html.
*/
protected function create_navigation(\stdClass $tree) {
if ($this->firstelement) {
$html = \html_writer::start_tag('ul', ['class' => 'treeview parent block_tree list', 'id' => 'my-tree']);
$this->firstelement = false;
} else {
$html = \html_writer::start_tag('ul', ['class' => 'parent', 'role' => 'group']);
}
foreach ($tree->children as $child) {
if (isset($child->children)) {
$html .= \html_writer::start_tag('li', ['class' => 'menu-item', 'role' => 'treeitem', 'aria-expanded' => 'false']);
$html .= $child->name;
$html .= $this->create_navigation($child);
} else {
$html .= \html_writer::start_tag('li', ['class' => 'item', 'role' => 'treeitem', 'aria-expanded' => 'false']);
// Normal display.
if (isset($child->datavar)) {
$html .= \html_writer::link('#', $child->name, ['data-var' => $child->datavar]);
} else {
$html .= \html_writer::link($child->url, $child->name, ['target' => '_blank']);
}
}
$html .= \html_writer::end_tag('li');
}
$html .= \html_writer::end_tag('ul');
return $html;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return array navigation data for the template.
*/
public function export_for_template(renderer_base $output): Array {
$data = $this->create_navigation($this->tree);
return ['navigation' => $data];
}
}
+58
View File
@@ -0,0 +1,58 @@
<?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 renderer.
*
* @package core_privacy
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\output;
defined('MOODLE_INTERNAL') || die;
/**
* Privacy renderer's for privacy stuff.
*
* @since Moodle 3.6
* @package core_privacy
* @copyright 2018 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Render the whole tree.
*
* @param navigation_page $page
* @return string
*/
public function render_navigation(exported_navigation_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('core_privacy/navigation', $data);
}
/**
* Render the html page.
*
* @param html_page $page
* @return string
*/
public function render_html_page(exported_html_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('core_privacy/htmlpage', $data);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?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 the Privacy Subsystem (how very meta).
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* The privacy subsystem does not store any data of it's own.
* It merely serves as a conduit to allow other components to describe, export, and delete data.
*
* @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';
}
}
+120
View File
@@ -0,0 +1,120 @@
<?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/>.
/**
* Testcase for providers implementing parts of the core_privacy subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests;
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Testcase for providers implementing parts of the core_privacy subsystem.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class provider_testcase extends \advanced_testcase {
/**
* Test tearDown.
*/
public function tearDown(): void {
\core_privacy\local\request\writer::reset();
}
/**
* Export all data for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param string $component The component to get context data for.
* @return \core_privacy\local\request\contextlist
*/
public function get_contexts_for_userid(int $userid, string $component) {
$classname = $this->get_provider_classname($component);
return $classname::get_contexts_for_userid($userid);
}
/**
* Export all data for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param string $component The component to get export data for.
*/
public function export_all_data_for_user(int $userid, string $component) {
$contextlist = $this->get_contexts_for_userid($userid, $component);
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($userid),
$component,
$contextlist->get_contextids()
);
$classname = $this->get_provider_classname($component);
$classname::export_user_data($approvedcontextlist);
}
/**
* Export all daa within a context for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param \context $context The context to export data for.
* @param string $component The component to get export data for.
*/
public function export_context_data_for_user(int $userid, \context $context, string $component) {
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($userid),
$component,
[$context->id]
);
$classname = $this->get_provider_classname($component);
$classname::export_user_data($contextlist);
}
/**
* Determine the classname and ensure that it is a provider.
*
* @param string $component The classname.
* @return string
*/
protected function get_provider_classname($component) {
$classname = "\\{$component}\\privacy\\provider";
if (!class_exists($classname)) {
throw new \coding_exception("{$component} does not implement any provider");
}
$rc = new \ReflectionClass($classname);
if (!$rc->implementsInterface(\core_privacy\local\metadata\provider::class)) {
throw new \coding_exception("{$component} does not implement metadata provider");
}
if (!$rc->implementsInterface(\core_privacy\local\request\core_user_data_provider::class)) {
throw new \coding_exception("{$component} does not declare that it provides any user data");
}
return $classname;
}
}
@@ -0,0 +1,79 @@
<?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/>.
/**
* Approved result set for unit testing.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests\request;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Fetch Result Set.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_contextlist extends \core_privacy\local\request\approved_contextlist {
/**
* Add a single context to this approved_contextlist.
*
* @param \context $context The context to be added.
* @return $this
*/
public function add_context(\context $context) {
return $this->add_context_by_id($context->id);
}
/**
* Add a single context to this approved_contextlist by it's ID.
*
* @param int $contextid The context to be added.
* @return $this
*/
public function add_context_by_id($contextid) {
return $this->set_contextids(array_merge($this->get_contextids(), [$contextid]));
}
/**
* Add a set of contexts to this approved_contextlist.
*
* @param \context[] $contexts The contexts to be added.
* @return $this
*/
public function add_contexts(array $contexts) {
foreach ($contexts as $context) {
$this->add_context($context);
}
}
/**
* Add a set of contexts to this approved_contextlist by ID.
*
* @param int[] $contexts The contexts to be added.
* @return $this
*/
public function add_contexts_by_id(array $contexts) {
foreach ($contexts as $contextid) {
$this->add_context_by_id($contextid);
}
}
}
@@ -0,0 +1,554 @@
<?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 moodle format implementation of the content writer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests\request;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of the content_writer for use in unit tests.
*
* This implementation does not export any data but instead stores it in
* structures within the instance which can be easily queried for use
* during unit tests.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content_writer implements \core_privacy\local\request\content_writer {
/**
* @var \context The context currently being exported.
*/
protected $context;
/**
* @var \stdClass The collection of metadata which has been exported.
*/
protected $metadata;
/**
* @var \stdClass The data which has been exported.
*/
protected $data;
/**
* @var \stdClass The related data which has been exported.
*/
protected $relateddata;
/**
* @var \stdClass The list of stored files which have been exported.
*/
protected $files;
/**
* @var \stdClass The custom files which have been exported.
*/
protected $customfiles;
/**
* @var \stdClass The user preferences which have been exported.
*/
protected $userprefs;
/**
* 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($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;
}
}
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;
}
/**
* 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.
*
* Note: The writer_factory must be passed.
* @param \core_privacy\local\request\writer $writer The writer factory.
*/
public function __construct(\core_privacy\local\request\writer $writer) {
$this->data = (object) [];
$this->relateddata = (object) [];
$this->metadata = (object) [];
$this->files = (object) [];
$this->customfiles = (object) [];
$this->userprefs = (object) [];
}
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
*/
public function set_context(\context $context): \core_privacy\local\request\content_writer {
$this->context = $context;
if (isset($this->data->{$this->context->id}) && empty((array) $this->data->{$this->context->id})) {
$this->data->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
if (isset($this->relateddata->{$this->context->id}) && empty((array) $this->relateddata->{$this->context->id})) {
$this->relateddata->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
if (isset($this->metadata->{$this->context->id}) && empty((array) $this->metadata->{$this->context->id})) {
$this->metadata->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
if (isset($this->files->{$this->context->id}) && empty((array) $this->files->{$this->context->id})) {
$this->files->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
if (isset($this->customfiles->{$this->context->id}) && empty((array) $this->customfiles->{$this->context->id})) {
$this->customfiles->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
if (isset($this->userprefs->{$this->context->id}) && empty((array) $this->userprefs->{$this->context->id})) {
$this->userprefs->{$this->context->id} = (object) [
'children' => (object) [],
'data' => [],
];
}
return $this;
}
/**
* Return the current context.
*
* @return \context
*/
public function get_current_context(): \context {
return $this->context;
}
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
*/
public function export_data(array $subcontext, \stdClass $data): \core_privacy\local\request\content_writer {
$current = $this->fetch_root($this->data, $subcontext);
$current->data = $data;
return $this;
}
/**
* Get all data within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return \stdClass|array The metadata as a series of keys to value + description objects.
*/
public function get_data(array $subcontext = []) {
return $this->fetch_data_root($this->data, $subcontext);
}
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
* @return $this
*/
public function export_metadata(
array $subcontext,
string $key,
$value,
string $description
): \core_privacy\local\request\content_writer {
$current = $this->fetch_root($this->metadata, $subcontext);
$current->data[$key] = (object) [
'value' => $value,
'description' => $description,
];
return $this;
}
/**
* Get all metadata within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return \stdClass|array The metadata as a series of keys to value + description objects.
*/
public function get_all_metadata(array $subcontext = []) {
return $this->fetch_data_root($this->metadata, $subcontext);
}
/**
* Get the specified metadata within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata to be fetched within the context + subcontext.
* @param boolean $valueonly Whether to fetch only the value, rather than the value + description.
* @return \stdClass|array|null The metadata as a series of keys to value + description objects.
*/
public function get_metadata(array $subcontext, $key, $valueonly = true) {
$keys = $this->get_all_metadata($subcontext);
if (isset($keys[$key])) {
$metadata = $keys[$key];
} else {
return null;
}
if ($valueonly) {
return $metadata->value;
} else {
return $metadata;
}
}
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
*/
public function export_related_data(array $subcontext, $name, $data): \core_privacy\local\request\content_writer {
$current = $this->fetch_root($this->relateddata, $subcontext);
$current->data[$name] = $data;
return $this;
}
/**
* Get all data within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the intended filename.
* @return \stdClass|array The metadata as a series of keys to value + description objects.
*/
public function get_related_data(array $subcontext = [], $filename = null) {
$current = $this->fetch_data_root($this->relateddata, $subcontext);
if (null === $filename) {
return $current;
}
if (isset($current[$filename])) {
return $current[$filename];
}
return [];
}
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
*/
public function export_custom_file(array $subcontext, $filename, $filecontent): \core_privacy\local\request\content_writer {
$filename = clean_param($filename, PARAM_FILE);
$current = $this->fetch_root($this->customfiles, $subcontext);
$current->data[$filename] = $filecontent;
return $this;
}
/**
* Get the specified custom file within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be fetched within the context + subcontext.
* @return string The content of the file.
*/
public function get_custom_file(array $subcontext = [], $filename = null) {
$current = $this->fetch_data_root($this->customfiles, $subcontext);
if (null === $filename) {
return $current;
}
if (isset($current[$filename])) {
return $current[$filename];
}
return null;
}
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* Note that this method does not implement the pluginfile URL rewriting. Such a job tightly depends on how the
* 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.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text): string {
return str_replace('@@PLUGINFILE@@/', 'files/', $text ?? '');
}
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @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.
* @param string $itemid Which item those files belong to.
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid): \core_privacy\local\request\content_writer {
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid);
foreach ($files as $file) {
$this->export_file($subcontext, $file);
}
return $this;
}
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
*/
public function export_file(array $subcontext, \stored_file $file): \core_privacy\local\request\content_writer {
if (!$file->is_directory()) {
$filepath = $file->get_filepath();
// Directory separator in the stored_file class should always be '/'. The following line is just a fail safe.
$filepath = str_replace(DIRECTORY_SEPARATOR, '/', $filepath);
$filepath = explode('/', $filepath);
$filepath[] = $file->get_filename();
$filepath = array_filter($filepath);
$filepath = implode('/', $filepath);
$current = $this->fetch_root($this->files, $subcontext);
$current->data[$filepath] = $file;
}
return $this;
}
/**
* Get all files in the specfied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return \stored_file[] The list of stored_files in this context + subcontext.
*/
public function get_files(array $subcontext = []) {
return $this->fetch_data_root($this->files, $subcontext);
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return \core_privacy\local\request\content_writer
*/
public function export_user_preference(
string $component,
string $key,
string $value,
string $description
): \core_privacy\local\request\content_writer {
$prefs = $this->fetch_root($this->userprefs, []);
if (!isset($prefs->{$component})) {
$prefs->{$component} = (object) [];
}
$prefs->{$component}->$key = (object) [
'value' => $value,
'description' => $description,
];
return $this;
}
/**
* Get all user preferences for the specified component.
*
* @param string $component The name of the component.
* @return \stdClass
*/
public function get_user_preferences(string $component) {
$context = \context_system::instance();
$prefs = $this->fetch_root($this->userprefs, [], $context->id);
if (isset($prefs->{$component})) {
return $prefs->{$component};
} else {
return (object) [];
}
}
/**
* Get all user preferences for the specified component.
*
* @param string $component The name of the component.
* @return \stdClass
*/
public function get_user_context_preferences(string $component) {
$prefs = $this->fetch_root($this->userprefs, []);
if (isset($prefs->{$component})) {
return $prefs->{$component};
} else {
return (object) [];
}
}
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content(): string {
return 'mock_path';
}
/**
* Fetch the entire root record at the specified location type, creating it if required.
*
* @param \stdClass $base The base to use - e.g. $this->data
* @param array $subcontext The subcontext to fetch
* @param int $temporarycontextid A temporary context ID to use for the fetch.
* @return \stdClass|array
*/
protected function fetch_root($base, $subcontext, $temporarycontextid = null) {
$contextid = !empty($temporarycontextid) ? $temporarycontextid : $this->context->id;
if (!isset($base->{$contextid})) {
$base->{$contextid} = (object) [
'children' => (object) [],
'data' => [],
];
}
$current = $base->{$contextid};
foreach ($subcontext as $node) {
if (!isset($current->children->{$node})) {
$current->children->{$node} = (object) [
'children' => (object) [],
'data' => [],
];
}
$current = $current->children->{$node};
}
return $current;
}
/**
* Fetch the data region of the specified root.
*
* @param \stdClass $base The base to use - e.g. $this->data
* @param array $subcontext The subcontext to fetch
* @return \stdClass|array
*/
protected function fetch_data_root($base, $subcontext) {
$root = $this->fetch_root($base, $subcontext);
return $root->data;
}
}