From ff0e4387806caf7e2acd5ea790b0ecc5216e1cab Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Fri, 16 Mar 2018 12:03:34 +0800 Subject: [PATCH] MDL-61663 core_portfolio: add subsystem privacy provider Added provider for portfolio plugins, and legacy polyfill for plugins. --- lang/en/portfolio.php | 1 + portfolio/classes/privacy/legacy_polyfill.php | 65 +++++++ .../classes/privacy/portfolio_provider.php | 63 +++++++ portfolio/classes/privacy/provider.php | 121 +++++++++++++ .../tests/privacy_legacy_polyfill_test.php | 163 ++++++++++++++++++ portfolio/tests/privacy_provider_test.php | 46 +++++ 6 files changed, 459 insertions(+) create mode 100644 portfolio/classes/privacy/legacy_polyfill.php create mode 100644 portfolio/classes/privacy/portfolio_provider.php create mode 100644 portfolio/classes/privacy/provider.php create mode 100644 portfolio/tests/privacy_legacy_polyfill_test.php create mode 100644 portfolio/tests/privacy_provider_test.php diff --git a/lang/en/portfolio.php b/lang/en/portfolio.php index 701eb036ee02b..7e18f50f7f50c 100644 --- a/lang/en/portfolio.php +++ b/lang/en/portfolio.php @@ -166,6 +166,7 @@ $string['pluginismisconfigured'] = 'Portfolio plugin is misconfigured, skipping. Error was: {$a}'; $string['portfolio'] = 'Portfolio'; $string['portfolios'] = 'Portfolios'; +$string['privacy:metadata'] = 'The portfolio subsystem acts as a channel, passing requests from plugins to the various portfolio plugins.'; $string['queuesummary'] = 'Currently queued transfers'; $string['returntowhereyouwere'] = 'Return to where you were'; $string['save'] = 'Save'; diff --git a/portfolio/classes/privacy/legacy_polyfill.php b/portfolio/classes/privacy/legacy_polyfill.php new file mode 100644 index 0000000000000..532ebdba51c21 --- /dev/null +++ b/portfolio/classes/privacy/legacy_polyfill.php @@ -0,0 +1,65 @@ +. + +/** + * This file contains the polyfill to allow a plugin to operate with Moodle 3.3 up. + * + * @package core_portfolio + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_portfolio\privacy; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The trait used to provide a backwards compatibility for third-party plugins. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +trait legacy_polyfill { + /** + * Export all portfolio data from each portfolio plugin for the specified userid and context. + * + * @param int $userid The user to export. + * @param \context $context The context to export. + * @param array $subcontext The subcontext within the context to export this information to. + * @param array $linkarray The weird and wonderful link array used to display information for a specific item + */ + public static function export_portfolio_user_data($userid, \context $context, array $subcontext, array $linkarray) { + static::_export_portfolio_user_data($userid, $context, $subcontext, $linkarray); + } + + /** + * Delete all user information for the provided context. + * + * @param \context $context The context to delete user data for. + */ + public static function delete_portfolio_for_context(\context $context) { + static::_delete_portfolio_for_context($context); + } + + /** + * Delete all user information for the provided user and context. + * + * @param int $userid The user to delete + * @param \context $context The context to refine the deletion. + */ + public static function delete_portfolio_for_user($userid, \context $context) { + static::_delete_portfolio_for_user($userid, $context); + } +} diff --git a/portfolio/classes/privacy/portfolio_provider.php b/portfolio/classes/privacy/portfolio_provider.php new file mode 100644 index 0000000000000..ec96187518066 --- /dev/null +++ b/portfolio/classes/privacy/portfolio_provider.php @@ -0,0 +1,63 @@ +. + +/** + * Privacy class for requesting user data. + * + * @package core_portfolio + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_portfolio\privacy; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Provider for the portfolio API. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface portfolio_provider extends + // The portfolio_provider should be implemented by plugins which only provide information to a subsystem. + \core_privacy\local\request\plugin\subsystem_provider { + + /** + * Export all portfolio data from each portfolio plugin for the specified userid and context. + * + * @param int $userid The user to export. + * @param \context $context The context to export. + * @param array $subcontext The subcontext within the context to export this information to. + * @param array $linkarray The weird and wonderful link array used to display information for a specific item + */ + public static function export_portfolio_user_data($userid, \context $context, array $subcontext, array $linkarray); + + /** + * Delete all user information for the provided context. + * + * @param \context $context The context to delete user data for. + */ + public static function delete_portfolio_for_context(\context $context); + + /** + * Delete all user information for the provided user and context. + * + * @param int $userid The user to delete + * @param \context $context The context to refine the deletion. + */ + public static function delete_portfolio_for_user($userid, \context $context); +} diff --git a/portfolio/classes/privacy/provider.php b/portfolio/classes/privacy/provider.php new file mode 100644 index 0000000000000..2313b9575234d --- /dev/null +++ b/portfolio/classes/privacy/provider.php @@ -0,0 +1,121 @@ +. + +/** + * Privacy class for requesting user data. + * + * @package core_portfolio + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_portfolio\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\context; + +/** + * Provider for the portfolio API. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + // The Portfolio subsystem does not store any data itself. + // It has no database tables, and it purely acts as a conduit to the various portfolio plugins. + \core_privacy\local\metadata\provider, + + // The portfolio subsystem will be called by other components. + \core_privacy\local\request\subsystem\plugin_provider { + + /** + * Returns meta data about this system. + * + * @param collection $collection The initialised collection to add items to. + * @return collection A listing of user data stored through this system. + */ + public static function get_metadata(collection $collection) { + return $collection->add_plugintype_link('portfolio', [], 'privacy:metadata'); + } + + /** + * Export all portfolio data from each portfolio plugin for the specified userid and context. + * + * @param int $userid The user to export. + * @param \context $context The context to export. + * @param array $subcontext The subcontext within the context to export this information to. + * @param array $linkarray The weird and wonderful link array used to display information for a specific item + */ + public static function export_portfolio_user_data($userid, \context $context, array $subcontext, array $linkarray) { + static::call_plugin_method('export_portfolio_user_data', [$userid, $context, $subcontext, $linkarray]); + } + + /** + * Deletes all user content for a context in all portfolio plugins. + * + * @param \context $context The context to delete user data for. + */ + public static function delete_portfolio_for_context(\context $context) { + static::call_plugin_method('delete_portfolio_for_context', [$context]); + } + + /** + * Deletes all user content for a user in a context in all portfolio plugins. + * + * @param int $userid The user to delete + * @param \context $context The context to refine the deletion. + */ + public static function delete_portfolio_for_user($userid, \context $context) { + static::call_plugin_method('delete_portfolio_for_user', [$userid, $context]); + } + + /** + * 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 $providerclass the provider class name. + * @param string $interface the name of the interface we want to check. + * @return bool True if an implementation was found, false otherwise. + */ + protected static function component_implements($providerclass, $interface) { + if (class_exists($providerclass)) { + $rc = new \ReflectionClass($providerclass); + return $rc->implementsInterface($interface); + } + + return false; + } + + /** + * Internal method for looping through all of the portfolio plugins and calling a method. + * + * @param string $methodname Name of the method to call on the plugins. + * @param array $params The parameters that go with the method being called. + */ + protected static function call_plugin_method($methodname, $params) { + // Note: Even if portfolio is _now_ disabled, there may be legacy data to export. + $plugins = \core_component::get_plugin_list('portfolio'); + foreach (array_keys($plugins) as $plugin) { + $component = "portfolio_{$plugin}"; + $classname = manager::get_provider_classname_for_component($component); + if (static::component_implements($classname, portfolio_provider::class)) { + // This portfolio plugin implements the portfolio_provider. + component_class_callback($classname, $methodname, $params); + } + } + } +} diff --git a/portfolio/tests/privacy_legacy_polyfill_test.php b/portfolio/tests/privacy_legacy_polyfill_test.php new file mode 100644 index 0000000000000..c58d2ee00bf5c --- /dev/null +++ b/portfolio/tests/privacy_legacy_polyfill_test.php @@ -0,0 +1,163 @@ +. + +/** + * Unit tests for the privacy legacy polyfill for portfolio. + * + * @package core_privacy + * @category test + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Unit tests for the Portfolio API's privacy legacy_polyfill. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_portfolio_privacy_legacy_polyfill_test extends advanced_testcase { + /** + * Test that the core_portfolio\privacy\legacy_polyfill works and that the static _export_portfolio_user_data can be called. + */ + public function test_export_portfolio_user_data() { + $userid = 476; + $context = context_system::instance(); + + $mock = $this->createMock(test_portfolio_legacy_polyfill_mock_wrapper::class); + $mock->expects($this->once()) + ->method('get_return_value') + ->with('_export_portfolio_user_data', [$userid, $context, [], []]); + + test_legacy_polyfill_portfolio_provider::$mock = $mock; + test_legacy_polyfill_portfolio_provider::export_portfolio_user_data($userid, $context, [], []); + } + + /** + * Test for _get_metadata shim. + */ + public function test_get_metadata() { + $collection = new \core_privacy\local\metadata\collection('core_portfolio'); + $this->assertSame($collection, test_legacy_polyfill_portfolio_provider::get_metadata($collection)); + } + + /** + * Test the _delete_portfolio_for_context shim. + */ + public function test_delete_portfolio_for_context() { + $context = context_system::instance(); + + $mock = $this->createMock(test_portfolio_legacy_polyfill_mock_wrapper::class); + $mock->expects($this->once()) + ->method('get_return_value') + ->with('_delete_portfolio_for_context', [$context]); + + test_legacy_polyfill_portfolio_provider::$mock = $mock; + test_legacy_polyfill_portfolio_provider::delete_portfolio_for_context($context); + } + + /** + * Test the _delete_portfolio_for_context shim. + */ + public function test_delete_portfolio_for_user() { + $userid = 696; + $context = \context_system::instance(); + + $mock = $this->createMock(test_portfolio_legacy_polyfill_mock_wrapper::class); + $mock->expects($this->once()) + ->method('get_return_value') + ->with('_delete_portfolio_for_user', [$userid, $context]); + + test_legacy_polyfill_portfolio_provider::$mock = $mock; + test_legacy_polyfill_portfolio_provider::delete_portfolio_for_user($userid, $context); + } +} + +/** + * Legacy polyfill test class for the portfolio_provider. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class test_legacy_polyfill_portfolio_provider implements + \core_privacy\local\metadata\provider, + \core_portfolio\privacy\portfolio_provider { + + use \core_portfolio\privacy\legacy_polyfill; + use \core_privacy\local\legacy_polyfill; + + /** + * @var test_legacy_polyfill_portfolio_provider $mock. + */ + public static $mock = null; + + /** + * Export all user data for the portfolio plugin. + * + * @param int $userid + * @param context $context + * @param array $subcontext + * @param array $linkarray + */ + protected static function _export_portfolio_user_data($userid, \context $context, array $subcontext, array $linkarray) { + static::$mock->get_return_value(__FUNCTION__, func_get_args()); + } + + /** + * Deletes all user data for the given context. + * + * @param context $context + */ + protected static function _delete_portfolio_for_context(\context $context) { + static::$mock->get_return_value(__FUNCTION__, func_get_args()); + } + + /** + * Delete personal data for the given user and context. + * + * @param int $userid + * @param context $context + */ + protected static function _delete_portfolio_for_user($userid, \context $context) { + static::$mock->get_return_value(__FUNCTION__, func_get_args()); + } + + /** + * Returns metadata about this plugin. + * + * @param \core_privacy\local\metadata\collection $collection The initialised collection to add items to. + * @return \core_privacy\local\metadata\collection A listing of user data stored through this system. + */ + protected static function _get_metadata(\core_privacy\local\metadata\collection $collection) { + return $collection; + } +} + +/** + * Called inside the polyfill methods in the test polyfill provider, allowing us to ensure these are called with correct params. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class test_portfolio_legacy_polyfill_mock_wrapper { + /** + * Get the return value for the specified item. + */ + public function get_return_value() { + } +} diff --git a/portfolio/tests/privacy_provider_test.php b/portfolio/tests/privacy_provider_test.php new file mode 100644 index 0000000000000..bc237966246d8 --- /dev/null +++ b/portfolio/tests/privacy_provider_test.php @@ -0,0 +1,46 @@ +. + +/** + * Privacy provider tests. + * + * @package core_portfolio + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Privacy provider tests class. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testcase { + + /** + * Verify that a collection of metadata is returned for this component and that it just links to the plugintype 'portfolio'. + */ + public function test_get_metadata() { + $collection = new \core_privacy\local\metadata\collection('core_portfolio'); + $collection = \core_portfolio\privacy\provider::get_metadata($collection); + $this->assertNotEmpty($collection); + $items = $collection->get_collection(); + $this->assertEquals(1, count($items)); + $this->assertInstanceOf(\core_privacy\local\metadata\types\plugintype_link::class, $items[0]); + } +}