MDL-61899 tool_dataprivacy: Addition of plugin compliance registry.

Includes MDL-61489
This commit is contained in:
Adrian Greeve 2018-03-26 15:45:37 +08:00 committed by Eloy Lafuente (stronk7)
parent e95f0def95
commit d6ff9edd4f
9 changed files with 631 additions and 0 deletions

View File

@ -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());
}
}

View File

@ -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;
}
}

View 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.
*

View File

@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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();

View 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';

View 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')
);

View File

@ -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}}

View File

@ -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}}

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/>.
/**
* 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']);
}
}