MDL-61899 tool_dataprivacy: Addition of plugin compliance registry.
authorAdrian Greeve <adrian@moodle.com>
Mon, 26 Mar 2018 07:45:37 +0000 (15:45 +0800)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 18 Apr 2018 16:15:40 +0000 (18:15 +0200)
Includes MDL-61489

admin/tool/dataprivacy/classes/metadata_registry.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/output/data_registry_compliance_page.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/output/renderer.php
admin/tool/dataprivacy/dataregistry2.php [new file with mode: 0644]
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/settings.php
admin/tool/dataprivacy/templates/component_status.mustache [new file with mode: 0644]
admin/tool/dataprivacy/templates/data_registry_compliance.mustache [new file with mode: 0644]
admin/tool/dataprivacy/tests/metadata_registry_test.php [new file with mode: 0644]

diff --git a/admin/tool/dataprivacy/classes/metadata_registry.php b/admin/tool/dataprivacy/classes/metadata_registry.php
new file mode 100644 (file)
index 0000000..caa8bb4
--- /dev/null
@@ -0,0 +1,145 @@
+<?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/>.
+
+/**
+ * Class containing helper methods for processing data requests.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Adrian Greeve
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_dataprivacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class containing helper methods for processing data requests.
+ *
+ * @copyright  2018 Adrian Greeve
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class metadata_registry {
+
+    /**
+     * Returns plugin types / plugins and the user data that it stores in a format that can be sent to a template.
+     *
+     * @return array An array with all of the plugin types / plugins and the user data they store.
+     */
+    public function get_registry_metadata() {
+        $manager = new \core_privacy\manager();
+        $pluginman = \core_plugin_manager::instance();
+        $contributedplugins = $this->get_contrib_list();
+        $metadata = $manager->get_metadata_for_components();
+        $fullyrichtree = $this->get_full_component_list();
+        foreach ($fullyrichtree as $branch => $leaves) {
+            $plugintype = $leaves['plugin_type'];
+            $plugins = array_map(function($component) use ($manager, $metadata, $contributedplugins, $plugintype, $pluginman) {
+                // Use the plugin name for the plugins, ignore for core subsystems.
+                $internaldata = ($plugintype == 'core') ? ['component' => $component] :
+                        ['component' => $pluginman->plugin_name($component)];
+                $internaldata['raw_component'] = $component;
+                if ($manager->component_is_compliant($component)) {
+                    $internaldata['compliant'] = true;
+                    if (isset($metadata[$component])) {
+                        $collection = $metadata[$component]->get_collection();
+                        $internaldata = $this->format_metadata($collection, $component, $internaldata);
+                    } else {
+                        // Call get_reason for null provider.
+                        $internaldata['nullprovider'] = get_string($manager->get_null_provider_reason($component), $component);
+                    }
+                } else {
+                    $internaldata['compliant'] = false;
+                }
+                // Check to see if we are an external plugin.
+                $componentshortname = explode('_', $component);
+                $shortname = array_pop($componentshortname);
+                if (isset($contributedplugins[$plugintype][$shortname])) {
+                    $internaldata['external'] = true;
+                }
+                return $internaldata;
+            }, $leaves['plugins']);
+            $fullyrichtree[$branch]['plugin_type_raw'] = $plugintype;
+            // We're done using the plugin type. Convert it to a readable string.
+            $fullyrichtree[$branch]['plugin_type'] = $pluginman->plugintype_name($plugintype);
+            $fullyrichtree[$branch]['plugins'] = $plugins;
+        }
+        return $fullyrichtree;
+    }
+
+    /**
+     * Formats the metadata for use with a template.
+     *
+     * @param  array $collection The collection associated with the component that we want to expand and format.
+     * @param  string $component The component that we are dealing in
+     * @param  array $internaldata The array to add the formatted metadata to.
+     * @return array The internal data array with the formatted metadata.
+     */
+    protected function format_metadata($collection, $component, $internaldata) {
+        foreach ($collection as $collectioninfo) {
+            $privacyfields = $collectioninfo->get_privacy_fields();
+            $fields = '';
+            if (!empty($privacyfields)) {
+                $fields = array_map(function($key, $field) use ($component) {
+                    return [
+                        'field_name' => $key,
+                        'field_summary' => get_string($field, $component)
+                    ];
+                }, array_keys($privacyfields), $privacyfields);
+            }
+            // Can the metadata types be located somewhere else besides core?
+            $items = explode('\\', get_class($collectioninfo));
+            $type = array_pop($items);
+            $typedata = [
+                'name' => $collectioninfo->get_name(),
+                'type' => $type,
+                'fields' => $fields,
+                'summary' => get_string($collectioninfo->get_summary(), $component)
+            ];
+            if (strpos($type, 'subsystem_link') === 0 || strpos($type, 'plugintype_link') === 0) {
+                $typedata['link'] = true;
+            }
+            $internaldata['metadata'][] = $typedata;
+        }
+        return $internaldata;
+    }
+
+    /**
+     * Return the full list of components.
+     *
+     * @return array An array of plugin types which contain plugin data.
+     */
+    protected function get_full_component_list() {
+        $list = \core_component::get_component_list();
+        $formattedlist = [];
+        foreach ($list as $plugintype => $plugin) {
+            $formattedlist[] = ['plugin_type' => $plugintype, 'plugins' => array_keys($plugin)];
+        }
+        return $formattedlist;
+    }
+
+    /**
+     * Returns a list of contributed plugins installed on the system.
+     *
+     * @return array A list of contributed plugins installed.
+     */
+    protected function get_contrib_list() {
+        return array_map(function($plugins) {
+            return array_filter($plugins, function($plugindata) {
+                return !$plugindata->is_standard();
+            });    
+        }, \core_plugin_manager::instance()->get_plugins());
+    }
+}
diff --git a/admin/tool/dataprivacy/classes/output/data_registry_compliance_page.php b/admin/tool/dataprivacy/classes/output/data_registry_compliance_page.php
new file mode 100644 (file)
index 0000000..9d7c225
--- /dev/null
@@ -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/>.
+
+/**
+ * Data registry renderable.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 David Monllao
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_dataprivacy\output;
+defined('MOODLE_INTERNAL') || die();
+
+use renderable;
+use renderer_base;
+use stdClass;
+use templatable;
+
+require_once($CFG->libdir . '/coursecatlib.php');
+require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
+require_once($CFG->libdir . '/blocklib.php');
+
+/**
+ * Class containing the data registry compliance renderable
+ *
+ * @copyright  2018 Adrian Greeve
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class data_registry_compliance_page implements renderable, templatable {
+
+    protected $metadata;
+
+    /**
+     * Constructor.
+     */
+    public function __construct($metadata) {
+        $this->metadata = $metadata;
+    }
+
+    /**
+     * Export this data so it can be used as the context for a mustache template.
+     *
+     * @param renderer_base $output
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+        global $PAGE;
+
+        $data = ['types' => $this->metadata];
+        // print_object($data);
+
+        return $data;
+    }
+
+   
+}
index c08f07f..3c857bb 100644 (file)
@@ -91,6 +91,18 @@ class renderer extends plugin_renderer_base {
         return parent::render_from_template('tool_dataprivacy/data_registry', $data);
     }
 
+    /**
+     * Render the data compliance registry.
+     *
+     * @param data_registry_page $page
+     * @return string html for the page
+     * @throws moodle_exception
+     */
+    public function render_data_registry_compliance_page(data_registry_compliance_page $page) {
+        $data = $page->export_for_template($this);
+        return parent::render_from_template('tool_dataprivacy/data_registry_compliance', $data);
+    }
+
     /**
      * Render the purposes management page.
      *
diff --git a/admin/tool/dataprivacy/dataregistry2.php b/admin/tool/dataprivacy/dataregistry2.php
new file mode 100644 (file)
index 0000000..e3b4f6c
--- /dev/null
@@ -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/>.
+
+/**
+ * Prints the compliance data registry main page.
+ *
+ * @copyright 2018 onwards Adrian Greeve <adriangreeve.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package tool_dataprivacy
+ */
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
+
+$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
+$contextid = optional_param('contextid', 0, PARAM_INT);
+
+$url = new moodle_url('/admin/tool/dataprivacy/dataregistry2.php');
+$title = get_string('dataregistry2', 'tool_dataprivacy');
+
+\tool_dataprivacy\page_helper::setup($url, $title);
+
+$output = $PAGE->get_renderer('tool_dataprivacy');
+echo $output->header();
+
+// Get data!
+$metadatatool = new \tool_dataprivacy\metadata_registry();
+$metadata = $metadatatool->get_registry_metadata();
+
+$dataregistry = new tool_dataprivacy\output\data_registry_compliance_page($metadata);
+
+echo $output->render($dataregistry);
+echo $OUTPUT->footer();
index 6bef6b8..410a5c2 100644 (file)
@@ -41,6 +41,7 @@ $string['categorycreated'] = 'Category created';
 $string['categorieslist'] = 'List of data categories';
 $string['categoryupdated'] = 'Category updated';
 $string['close'] = 'Close';
+$string['compliant'] = 'Compliant';
 $string['confirmapproval'] = 'Do you really want to approve this data request?';
 $string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
 $string['confirmdenial'] = 'Do you really want deny this data request?';
@@ -61,7 +62,10 @@ $string['datadeletionpagehelp'] = 'This page lists the contexts that are already
 $string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
 $string['dataprivacy:managedatarequests'] = 'Manage data requests';
 $string['dataprivacy:managedataregistry'] = 'Manage data registry';
+$string['dataprivacysettings'] = 'Data privacy settings';
 $string['dataregistry'] = 'Data registry';
+$string['dataregistry2'] = 'Plugin privacy registry';
+$string['dataregistrysetup'] = 'Settings';
 $string['datarequestemailsubject'] = 'Data request: {$a}';
 $string['datarequests'] = 'Data requests';
 $string['daterequested'] = 'Date requested';
@@ -97,6 +101,12 @@ $string['errorsendingmessagetodpo'] = 'An error was encountered while trying to
 $string['expiredretentionperiodtask'] = 'Expired retention period';
 $string['expiry'] = 'Expiry';
 $string['frontpagecourse'] = 'Front page course';
+$string['expandplugin'] = 'Expand and collapse plugin.';
+$string['expandplugintype'] = 'Expand and collapse plugin type.';
+$string['explanationtitle'] = 'Icons used on this page and what they mean.';
+$string['external'] = 'External';
+$string['externalexplanation'] = 'An additional plugin installed on this site.';
+$string['hide'] = 'Collapse all';
 $string['inherit'] = 'Inherit';
 $string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
 $string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
@@ -120,6 +130,7 @@ $string['nopurposes'] = 'There are no purposes yet';
 $string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
 $string['nosystemdefaults'] = 'Site purpose and category have not yet been defined.';
 $string['notset'] = 'Not set (use the default value)';
+$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
 $string['privacy'] = 'Privacy';
 $string['privacy:metadata:request'] = 'Information from personal data requests (subject access and deletion requests) made for this site.';
 $string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
@@ -151,6 +162,8 @@ $string['requesttypeexport'] = 'Export all of my personal data';
 $string['requesttypeexportshort'] = 'Export';
 $string['requesttypeothers'] = 'General inquiry';
 $string['requesttypeothersshort'] = 'Others';
+$string['requiresattention'] = 'Requires attention.';
+$string['requiresattentionexplanation'] = 'This plugin does not implement the Moodle privacy API. If this plugin stores any personal data it will not be able to be exported or deleted through Moodle\'s privacy system.';
 $string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
 $string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
 $string['reviewdata'] = 'Review data';
@@ -171,3 +184,4 @@ $string['statusrejected'] = 'Rejected';
 $string['subjectscope'] = 'Subject scope';
 $string['user'] = 'User';
 $string['viewrequest'] = 'View the request';
+$string['visible'] = 'Expand all';
index a94dfb2..5e2c398 100644 (file)
@@ -70,4 +70,8 @@ $ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('datare
 // Link that leads to the review page of expired contexts that are up for deletion.
 $ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
         new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
+
+// Link that leads to the other data registry management page.
+$ADMIN->add('dataprivacysettings', new admin_externalpage('dataregistry2', get_string('dataregistry2', 'tool_dataprivacy'),
+    new moodle_url('/admin/tool/dataprivacy/dataregistry2.php'), 'tool/dataprivacy:managedataregistry')
 );
diff --git a/admin/tool/dataprivacy/templates/component_status.mustache b/admin/tool/dataprivacy/templates/component_status.mustache
new file mode 100644 (file)
index 0000000..6a14683
--- /dev/null
@@ -0,0 +1,127 @@
+{{!
+    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 comments.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template tool_dataprivacy/component_status
+
+    Data registry main page.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * none
+
+    Example context (json):
+    {
+        "compliant" : "True",
+        "raw_component" : "core_comment",
+        "component" : "Core comment",
+        "external" : "True",
+        "metadata" : {
+            "name" : "comments",
+            "type" : "database_table",
+            "summary" : "Stores comments of users",
+            "fields" : {
+                "field_name" : "content",
+                "field_summary" : "Stores the text of the content."
+            }
+        }
+    }
+}}
+
+<div class="row">
+    <div class="col">
+        {{#compliant}}
+            <a class="expand" data-component="{{raw_component}}" href='#'>
+            <h4 class="d-inline p-r-1 p-l-1" id="{{raw_component}}">{{#pix}}t/collapsed, moodle, {{#str}}expandplugin, tool_dataprivacy{{/str}}{{/pix}}{{component}}</h4>
+            </a>
+            <!-- <span class="badge badge-pill badge-success">{{#str}}compliant, tool_dataprivacy{{/str}}</span> -->
+        {{/compliant}}
+        {{^compliant}}
+            <h4 class="d-inline p-r-1 p-l-1" id="{{raw_component}}">{{component}}</h4>
+            <span>{{#pix}}i/risk_xss, moodle, {{#str}}requiresattention, tool_dataprivacy{{/str}}{{/pix}}</span>
+        {{/compliant}}
+        {{#external}}
+            <span class="badge badge-pill badge-notice">{{#str}}external, tool_dataprivacy{{/str}}</span>
+        {{/external}}
+    </div>
+</div>
+
+    {{#compliant}}
+        <div class="hide" data-section="{{raw_component}}" aria-expanded="false">
+        {{#metadata}}
+                <hr />
+                <div class="row-fluid">
+                    <div class="span2 col-xs-3">
+                        {{#link}}
+                            <a href="#{{name}}"><h5>{{name}}</h5></a>
+                        {{/link}}
+                        {{^link}}
+                            <h5>{{name}}</h5>
+                        {{/link}}
+                        <div class="p-b-1 small text-muted">{{type}}</div>
+                    </div>
+                    <div class="span10 col-xs-9">{{summary}}</div>
+                </div>
+                <table class="table table-sm">
+                    <tbody>
+                    {{#fields}}
+                        <tr class="row">
+                            <td class="col-xs-3">{{field_name}}</td>
+                            <td class="col-xs-9">{{field_summary}}</td>
+                        </tr>
+                    {{/fields}}
+                    </tbody>
+                </table>
+        {{/metadata}}
+        {{#nullprovider}}
+            <hr />
+            <div>{{nullprovider}}</div>
+        {{/nullprovider}}
+        </div>
+    {{/compliant}}
+<hr />
+
+
+{{#js}}
+require(['jquery', 'core/url'], function($, url) {
+
+    var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
+    var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
+
+    $('.expand').click(function(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        e.stopImmediatePropagation();
+        var component = $(this).data('component');
+        var metadata = $('[data-section=\'' + component + '\']');
+        var metainfo = metadata.attr('class');
+        if (metadata.attr('class') === 'hide') {
+            metadata.attr('class', 'visible');
+            $(this).children('img').attr('src', expandedImage.attr('src'));
+            metadata.attr('aria-expanded', true);
+        } else {
+            metadata.attr('class', 'hide');
+            $(this).children('img').attr('src', collapsedImage.attr('src'));
+            metadata.attr('aria-expanded', false);
+        }
+    });
+});
+{{/js}}
diff --git a/admin/tool/dataprivacy/templates/data_registry_compliance.mustache b/admin/tool/dataprivacy/templates/data_registry_compliance.mustache
new file mode 100644 (file)
index 0000000..350b982
--- /dev/null
@@ -0,0 +1,110 @@
+{{!
+    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 comments.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template tool_dataprivacy/data_registry_compliance
+
+    Data registry main page.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * none
+
+    Example context (json):
+    {
+        "types" : {
+            "plugin_type_raw" : "mod",
+            "plugin_type" : "Activities and Modules"
+        }
+    }
+}}
+<div>
+    <h2>{{#str}}pluginregistrytitle, tool_dataprivacy{{/str}}</h2>
+    <hr />
+    <p><strong>{{#str}}explanationtitle, tool_dataprivacy{{/str}}</strong></p>
+    <dl>
+        <dt>{{#pix}}i/risk_xss, moodle, {{#str}}requiresattention, tool_dataprivacy{{/str}}{{/pix}}</dt>
+        <dd>{{#str}}requiresattentionexplanation, tool_dataprivacy{{/str}}</dd>
+        <dt><span class="badge badge-pill badge-notice">{{#str}}external, tool_dataprivacy{{/str}}</span></dt>
+        <dd>{{#str}}externalexplanation, tool_dataprivacy{{/str}}</dd>
+    </dl>
+    <hr />
+    <div><a class="tool_dataprivacy-expand-all pull-right" href="#" data-visibility-state='visible'>{{#str}}visible, tool_dataprivacy{{/str}}</a></div>
+    {{#types}}
+        <div class="container-fluid">
+            <div class="row">
+                <div class="col">
+                    <a class="other-expand" href='#' data-plugin="{{plugin_type_raw}}">
+                    <h3 id="{{plugin_type_raw}}">{{#pix}}t/collapsed, moodle, {{#str}}expandplugintype, tool_dataprivacy{{/str}}{{/pix}}{{plugin_type}}</h3>
+                    </a>
+                </div>
+            </div>
+            <div class="hide" data-plugintarget="{{plugin_type_raw}}" aria-expanded="false">
+                {{#plugins}}
+                    {{> tool_dataprivacy/component_status}}
+                {{/plugins}}
+            </div>
+        </div>
+    {{/types}}
+</div>
+{{#js}}
+require(['jquery', 'core/url', 'core/str'], function($, url, str) {
+
+    var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
+    var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
+
+    $('.other-expand').click(function(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        e.stopImmediatePropagation();
+        window.console.log(this);
+        var plugin = $(this).data('plugin');
+        var metadata = $('[data-plugintarget=\'' + plugin + '\']');
+        if (metadata.attr('class') === 'hide') {
+            metadata.attr('class', 'visible');
+            $(this).children('img').attr('src', expandedImage.attr('src'));
+            metadata.attr('aria-expanded', true);
+        } else {
+            metadata.attr('class', 'hide');
+            $(this).children('img').attr('src', collapsedImage.attr('src'));
+            metadata.attr('aria-expanded', false);
+        }
+    });
+
+    $('.tool_dataprivacy-expand-all').click(function(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        var nextstate = $(this).data('visibilityState');
+        var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
+        var ariaexpandedstate = (nextstate == 'visible') ? true : false;
+        $('.' + currentstate).each(function() {
+            $(this).attr('class', nextstate);
+            $(this).attr('aria-expanded', ariaexpandedstate);
+        });
+        $(this).data('visibilityState', currentstate);
+
+        str.get_string(currentstate, 'tool_dataprivacy').then(function(langString) {
+            var visibilitynode = $('.tool_dataprivacy-expand-all');
+            visibilitynode.html(langString);
+        }).catch(Notification.exception);
+    });
+});
+{{/js}}
diff --git a/admin/tool/dataprivacy/tests/metadata_registry_test.php b/admin/tool/dataprivacy/tests/metadata_registry_test.php
new file mode 100644 (file)
index 0000000..21f067b
--- /dev/null
@@ -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/>.
+
+/**
+ * Metadata registry tests.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Adrian Greeve <adriangreeve.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+/**
+ * Metadata registry tests.
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Adrian Greeve <adriangreeve.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_dataprivacy_metadata_registry_testcase extends advanced_testcase {
+
+    /**
+     * Fetch the meta data and return it in a form that we can easily unit test.
+     *
+     * @return array the meta data.
+     */
+    protected function get_meta_data() {
+        $metadataregistry = new \tool_dataprivacy\metadata_registry();
+        $data = $metadataregistry->get_registry_metadata();
+        $newdata = [];
+        foreach ($data as $value) {
+            $additional = [];
+            foreach ($value['plugins'] as $moredata) {
+                $additional[$moredata['raw_component']] = $moredata;
+            }
+            $newdata[$value['plugin_type_raw']] = $additional;
+        }
+        return $newdata;
+    }
+
+    /**
+     * Test that we can fetch metadata about users for the whole system and that it matches the system count.
+     */
+    public function test_get_registry_metadata_count() {
+        $data = $this->get_meta_data();
+
+        $plugintypes = \core_component::get_plugin_types();
+
+        // Check that we have the correct number of plugin types.
+        $plugincount = count($plugintypes) + 1; // Plus one for core.
+        $this->assertEquals($plugincount, count($data));
+
+        // Check that each plugin count matches.
+        foreach ($plugintypes as $plugintype => $notused) {
+            $plugins = \core_component::get_plugin_list($plugintype);
+            $this->assertEquals(count($plugins), count($data[$plugintype]));
+        }
+
+        // Let's check core subsystems.
+        $coresubsystems = \core_component::get_core_subsystems();
+        $this->assertEquals(count($coresubsystems), count($data['core']));
+    }
+
+    /**
+     * Check that the expected null provider information is returned.
+     */
+    public function test_get_registry_metadata_null_provider_details() {
+        $data = $this->get_meta_data();
+
+        // Check details of core privacy (a null privder) are correct.
+        $coreprivacy = $data['core']['core_privacy'];
+        $this->assertEquals(1, $coreprivacy['compliant']);
+        $this->assertNotEmpty($coreprivacy['nullprovider']);
+    }
+
+    /**
+     * Check that the expected privacy provider information is returned.
+     */
+    public function test_get_registry_metadata_provider_details() {
+        $data = $this->get_meta_data();
+
+        // Check details of core rating (a normal provider) are correct.
+        $corerating = $data['core']['core_rating'];
+        $this->assertEquals(1, $corerating['compliant']);
+        $this->assertNotEmpty($corerating['metadata']);
+        $this->assertEquals('database_table', $corerating['metadata'][0]['type']);
+        $this->assertNotEmpty('database_table', $corerating['metadata'][0]['fields']);
+    }
+}