From 4fca5dabb2b2e8b53bf43695089d26cfa7f3f3a7 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 10 Sep 2018 09:18:28 +0800 Subject: [PATCH 01/13] MDL-63658 core_favourites: introduce the favourites subsystem to core --- lang/en/favourites.php | 22 ++++++++++++++++++++++ lib/classes/component.php | 1 + lib/tests/component_test.php | 2 +- phpunit.xml.dist | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 lang/en/favourites.php diff --git a/lang/en/favourites.php b/lang/en/favourites.php new file mode 100644 index 00000000000..b5d0a7e3942 --- /dev/null +++ b/lang/en/favourites.php @@ -0,0 +1,22 @@ +. +/** + * Strings for component 'favourites', language 'en', branch 'master' + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ diff --git a/lib/classes/component.php b/lib/classes/component.php index f7debb8ed81..9284f7d5a01 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -442,6 +442,7 @@ $cache = '.var_export($cache, true).'; 'edufields' => null, 'enrol' => $CFG->dirroot.'/enrol', 'error' => null, + 'favourites' => $CFG->dirroot . '/favourites', 'filepicker' => null, 'fileconverter' => $CFG->dirroot.'/files/converter', 'files' => $CFG->dirroot.'/files', diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 3e3bd9640f2..625279d1797 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase { * this is defined here to annoy devs that try to add more without any thinking, * always verify that it does not collide with any existing add-on modules and subplugins!!! */ - const SUBSYSTEMCOUNT = 66; + const SUBSYSTEMCOUNT = 67; public function setUp() { $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces'); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8cc36edb6b3..60af5a0d630 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -47,6 +47,9 @@ lib/tests lib/ajax/tests + + favourites/tests + lib/form/tests From 1cb94eabb42d5d12a714547a823799a730aac0b8 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 10 Sep 2018 11:06:35 +0800 Subject: [PATCH 02/13] MDL-63658 core_favourites: add tables to the subsystem --- lib/db/install.xml | 22 ++++++++++++++++++++++ lib/db/upgrade.php | 37 +++++++++++++++++++++++++++++++++++++ version.php | 2 +- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/db/install.xml b/lib/db/install.xml index 455b29c7944..76c20f5bc39 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -3898,5 +3898,27 @@ + + + + + + + + + + + + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index a6087d3da1d..b64923fa34a 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2524,5 +2524,42 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2018092800.03); } + if ($oldversion < 2018101600.01) { + // Define table 'favourite' to be created. + $table = new xmldb_table('favourite'); + + // Adding fields to table 'favourite'. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); + $table->add_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); + $table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('ordering', XMLDB_TYPE_INTEGER, 10, null, null, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table 'favourite'. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id')); + $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); + + // Conditionally launch create table for 'favourite'. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Add composite index 'uniqueuserfavouriteitem' to the table 'favourite'. + $index = new xmldb_index('uniqueuserfavouriteitem', XMLDB_INDEX_UNIQUE, + ['component', 'itemtype', 'itemid', 'contextid', 'userid']); + + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2018101600.01); + } + return true; } diff --git a/version.php b/version.php index 7ae9209295b..65c1ddb1bc3 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018101600.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018101600.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. From d4e98ee580f4d7cd4d01a036983b3c5ed6c6097e Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 17 Sep 2018 08:38:50 +0800 Subject: [PATCH 03/13] MDL-63658 core_favourites: add a user favourites repository --- favourites/classes/local/crud_repository.php | 100 ++++ .../classes/local/favourites_repository.php | 290 ++++++++++++ .../classes/local/ifavourites_repository.php | 45 ++ favourites/tests/repository_test.php | 439 ++++++++++++++++++ 4 files changed, 874 insertions(+) create mode 100644 favourites/classes/local/crud_repository.php create mode 100644 favourites/classes/local/favourites_repository.php create mode 100644 favourites/classes/local/ifavourites_repository.php create mode 100644 favourites/tests/repository_test.php diff --git a/favourites/classes/local/crud_repository.php b/favourites/classes/local/crud_repository.php new file mode 100644 index 00000000000..8dab4e0dbaf --- /dev/null +++ b/favourites/classes/local/crud_repository.php @@ -0,0 +1,100 @@ +. +/** + * Contains the crud_repository interface. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites\local; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The crud_repository interface, defining the basic CRUD operations for any repository types within core_favourites. + */ +interface crud_repository { + /** + * Add one item to this repository. + * + * @param \stdClass $item the item to add. + * @return \stdClass the item which was added. + */ + public function add(\stdClass $item) : \stdClass; + + /** + * Add all the items in the list to this repository. + * + * @param array $items the list of items to add. + * @return array the list of items added to this repository. + */ + public function add_all(array $items) : array; + + /** + * Find an item in this repository based on its id. + * + * @param int $id the id of the item. + * @return \stdClass the item. + */ + public function find(int $id) : \stdClass; + + /** + * Find all items in this repository. + * + * @return array list of all items in this repository. + */ + public function find_all() : array; + + /** + * Find all items with attributes matching certain values. + * + * @param array $criteria the array of attribute/value pairs. + * @return array the list of items matching the criteria. + */ + public function find_by(array $criteria) : array; + + /** + * Check whether an item exists in this repository, based on its id. + * + * @param int $id the id to search for. + * @return bool true if the item could be found, false otherwise. + */ + public function exists(int $id) : bool; + + /** + * Return the total number of items in this repository. + * + * @return int the total number of items. + */ + public function count() : int; + + /** + * Update an item within this repository. + * + * @param \stdClass $item the item to update. + * @return \stdClass the updated item. + */ + public function update(\stdClass $item) : \stdClass; + + /** + * Delete an item by id. + * + * @param int $id the id of the item to delete. + * @return void + */ + public function delete(int $id); +} diff --git a/favourites/classes/local/favourites_repository.php b/favourites/classes/local/favourites_repository.php new file mode 100644 index 00000000000..3e69f2ca1e8 --- /dev/null +++ b/favourites/classes/local/favourites_repository.php @@ -0,0 +1,290 @@ +. +/** + * Contains the user_favourites_repository class, responsible for CRUD operations for user favourites. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites\local; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class favourites_repository. + * + * This class handles persistence of favourites. Favourites from all areas are supported by this repository. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class favourites_repository implements ifavourites_repository { + + /** + * @var string the name of the table which favourites are stored in. + */ + protected $favouritetable = 'favourite'; + + /** + * The favourites_repository constructor. + */ + public function __construct() { + } + + /** + * Add a favourite to the repository. + * + * @param \stdClass $favourite the favourite to add. + * @return \stdClass the favourite which has been stored. + * @throws \dml_exception if any database errors are encountered. + * @throws \moodle_exception if the favourite has missing or invalid properties. + */ + public function add(\stdClass $favourite) : \stdClass { + global $DB; + $this->validate($favourite); + $favourite = (array)$favourite; + $time = time(); + $favourite['timecreated'] = $time; + $favourite['timemodified'] = $time; + $id = $DB->insert_record($this->favouritetable, $favourite); + return $this->find($id); + } + + /** + * Add a collection of favourites to the repository. + * + * @param array $items the list of favourites to add. + * @return array the list of favourites which have been stored. + * @throws \dml_exception if any database errors are encountered. + * @throws \moodle_exception if any of the favourites have missing or invalid properties. + */ + public function add_all(array $items) : array { + global $DB; + $time = time(); + foreach ($items as $item) { + $this->validate($item); + $favourite = (array)$item; + $favourite['timecreated'] = $time; + $favourite['timemodified'] = $time; + $ids[] = $DB->insert_record($this->favouritetable, $favourite); + } + list($insql, $params) = $DB->get_in_or_equal($ids); + return $DB->get_records_select($this->favouritetable, "id $insql", $params); + } + + /** + * Find a favourite by id. + * + * @param int $id the id of the favourite. + * @return \stdClass the favourite. + * @throws \dml_exception if any database errors are encountered. + */ + public function find(int $id) : \stdClass { + global $DB; + return $DB->get_record($this->favouritetable, ['id' => $id], '*', MUST_EXIST); + } + + /** + * Return all items matching the supplied criteria (a [key => value,..] list). + * + * @param array $criteria the list of key/value criteria pairs. + * @return array the list of favourites matching the criteria. + * @throws \dml_exception if any database errors are encountered. + */ + public function find_by(array $criteria) : array { + global $DB; + return $DB->get_records($this->favouritetable, $criteria); + } + + /** + * Return all items in this repository, as an array, indexed by id. + * + * @return array the list of all favourites stored within this repository. + * @throws \dml_exception if any database errors are encountered. + */ + public function find_all() : array { + global $DB; + return $DB->get_records($this->favouritetable); + } + + /** + * Find a specific favourite, based on the properties known to identify it. + * + * Used if we don't know its id. + * + * @param int $userid the id of the user to which the favourite belongs. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param int $contextid the contextid of the item which was favourited. + * @return \stdClass the favourite. + * @throws \dml_exception if any database errors are encountered or if the record could not be found. + */ + public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : \stdClass { + global $DB; + // Favourites model: We know that only one favourite can exist based on these properties. + return $DB->get_record($this->favouritetable, [ + 'userid' => $userid, + 'component' => $component, + 'itemtype' => $itemtype, + 'itemid' => $itemid, + 'contextid' => $contextid + ], '*', MUST_EXIST); + } + + /** + * Check whether a favourite exists in this repository, based on its id. + * + * @param int $id the id to search for. + * @return bool true if the favourite exists, false otherwise. + * @throws \dml_exception if any database errors are encountered. + */ + public function exists(int $id) : bool { + global $DB; + return $DB->record_exists($this->favouritetable, ['id' => $id]); + } + + /** + * Update a favourite. + * + * @param \stdClass $favourite the favourite to update. + * @return \stdClass the updated favourite. + * @throws \dml_exception if any database errors are encountered. + */ + public function update(\stdClass $favourite) : \stdClass { + global $DB; + $time = time(); + $favourite->timemodified = $time; + $DB->update_record($this->favouritetable, $favourite); + return $this->find($favourite->id); + } + + /** + * Delete a favourite, by id. + * + * @param int $id the id of the favourite to delete. + * @throws \dml_exception if any database errors are encountered. + */ + public function delete(int $id) { + global $DB; + $DB->delete_records($this->favouritetable, ['id' => $id]); + } + + /** + * Return the total number of favourites in this repository. + * + * @return int the total number of items. + * @throws \dml_exception if any database errors are encountered. + */ + public function count() : int { + global $DB; + return $DB->count_records($this->favouritetable); + } + + /** + * Check for the existence of a favourite item in the specified area. + * + * A favourite item is identified by the itemid/contextid pair. + * An area is identified by the component/itemtype pair. + * + * @param int $userid the id of user to whom the favourite belongs. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param int $contextid the contextid of the item which was favourited. + * @return bool true if the favourited item exists, false otherwise. + * @throws \dml_exception if any database errors are encountered. + */ + public function exists_by_area(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : bool { + global $DB; + return $DB->record_exists($this->favouritetable, + [ + 'userid' => $userid, + 'component' => $component, + 'itemtype' => $itemtype, + 'itemid' => $itemid, + 'contextid' => $contextid + ] + ); + } + + /** + * Delete all favourites within the component/itemtype. + * + * @param int $userid the id of the user to whom the favourite belongs. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @throws \dml_exception if any database errors are encountered. + */ + public function delete_by_area(int $userid, string $component, string $itemtype) { + global $DB; + $DB->delete_records($this->favouritetable, + [ + 'userid' => $userid, + 'component' => $component, + 'itemtype' => $itemtype + ] + ); + } + + /** + * Return the number of user favourites matching the specified criteria. + * + * @param array $criteria the list of key/value criteria pairs. + * @return int the number of favourites matching the criteria. + * @throws \dml_exception if any database errors are encountered. + */ + public function count_by(array $criteria) : int { + global $DB; + return $DB->count_records($this->favouritetable, $criteria); + } + + /** + * Basic validation, confirming we have the minimum field set needed to save a record to the store. + * + * @param \stdClass $favourite the favourite record to validate. + * @throws \moodle_exception if the supplied favourite has missing or unsupported fields. + */ + protected function validate(\stdClass $favourite) { + + $favourite = (array)$favourite; + + // The allowed fields, and whether or not each is required. + // The timecreated field is generated during create/update, and cannot be specified either. + $allowedfields = [ + 'userid' => true, + 'component' => true, + 'itemtype' => true, + 'itemid' => true, + 'contextid' => true, + 'ordering' => false + ]; + + $requiredfields = array_filter($allowedfields, function($field) { + return $field; + }); + + if ($missingfields = array_keys(array_diff_key($requiredfields, $favourite))) { + throw new \moodle_exception("Missing object property(s) '" . join(', ', $missingfields) . "'."); + } + + // If the record contains fields we don't allow, throw an exception. + if ($unsupportedfields = array_keys(array_diff_key($favourite, $allowedfields))) { + throw new \moodle_exception("Unexpected object property(s) '" . join(', ', $unsupportedfields) . "'."); + } + } +} diff --git a/favourites/classes/local/ifavourites_repository.php b/favourites/classes/local/ifavourites_repository.php new file mode 100644 index 00000000000..493934baaaf --- /dev/null +++ b/favourites/classes/local/ifavourites_repository.php @@ -0,0 +1,45 @@ +. +/** + * Contains the favourites_repository interface. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites\local; + +defined('MOODLE_INTERNAL') || die(); + +/** + * The favourites_repository interface, defining additional operations useful to favourites type repositories. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface ifavourites_repository extends crud_repository { + /** + * Find a single favourite, based on it's unique identifiers. + * + * @param int $userid the id of the user to which the favourite belongs. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param int $contextid the contextid of the item which was favourited. + * @return \stdClass the favourite. + */ + public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : \stdClass; +} diff --git a/favourites/tests/repository_test.php b/favourites/tests/repository_test.php new file mode 100644 index 00000000000..f7c713dc908 --- /dev/null +++ b/favourites/tests/repository_test.php @@ -0,0 +1,439 @@ +. + +/** + * Testing the repository objects within core_favourites. + * + * @package core_favourites + * @category test + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \core_favourites\local\favourites_repository; + +/** + * Test class covering the favourites_repository. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class favourites_repository_testcase extends advanced_testcase { + + public function setUp() { + $this->resetAfterTest(); + } + + // Basic setup stuff to be reused in most tests. + protected function setup_users_and_courses() { + $user1 = self::getDataGenerator()->create_user(); + $user1context = \context_user::instance($user1->id); + $user2 = self::getDataGenerator()->create_user(); + $user2context = \context_user::instance($user2->id); + $course1 = self::getDataGenerator()->create_course(); + $course2 = self::getDataGenerator()->create_course(); + $course1context = context_course::instance($course1->id); + $course2context = context_course::instance($course2->id); + return [$user1context, $user2context, $course1context, $course2context]; + } + + /** + * Verify the basic create operation can create records, and is validated. + */ + public function test_add() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + + $favcourse = (object)[ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id, + ]; + $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this. + $favourite = $favouritesrepo->add($favcourse); + + // Verify we get the record back. + $this->assertInstanceOf(\stdClass::class, $favourite); + $this->assertEquals('core_course', $favourite->component); + $this->assertEquals('course', $favourite->itemtype); + + // Verify the returned object has additional properties, created as part of the add. + $this->assertObjectHasAttribute('ordering', $favourite); + $this->assertObjectHasAttribute('timecreated', $favourite); + $this->assertGreaterThanOrEqual($timenow, $favourite->timecreated); + + // Try to save the same record again and confirm the store throws an exception. + $this->expectException('dml_write_exception'); + $favouritesrepo->add($favcourse); + } + + /** + * Tests that malformed favourites cannot be saved. + */ + public function test_add_malformed_favourite() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + + $favcourse = (object)[ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id, + 'anotherfield' => 'cat' + ]; + + $this->expectException('moodle_exception'); + $favouritesrepo->add($favcourse); + } + + /** + * Tests that incomplete favourites cannot be saved. + */ + public function test_add_incomplete_favourite() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + + $favcourse = (object)[ + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid + ]; + + $this->expectException('moodle_exception'); + $favouritesrepo->add($favcourse); + } + + public function test_add_all_basic() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite several courses. + $favouritesrepo = new favourites_repository($user1context); + $favcourses = []; + + $favcourses[] = (object)[ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id, + ]; + $favcourses[] = (object)[ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course2context->instanceid, + 'contextid' => $course2context->id, + ]; + $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this. + $favourites = $favouritesrepo->add_all($favcourses); + + $this->assertInternalType('array', $favourites); + $this->assertCount(2, $favourites); + foreach ($favourites as $favourite) { + // Verify we get the record back. + $this->assertEquals('core_course', $favourite->component); + $this->assertEquals('course', $favourite->itemtype); + + // Verify the returned object has additional properties, created as part of the add. + $this->assertObjectHasAttribute('ordering', $favourite); + $this->assertObjectHasAttribute('timecreated', $favourite); + $this->assertGreaterThanOrEqual($timenow, $favourite->timecreated); + } + + // Try to save the same record again and confirm the store throws an exception. + $this->expectException('dml_write_exception'); + $favouritesrepo->add_all($favcourses); + } + + /** + * Tests reading from the repository by instance id. + */ + public function test_find() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite = $favouritesrepo->add($favourite); + + // Now, from the repo, get the single favourite we just created, by id. + $userfavourite = $favouritesrepo->find($favourite->id); + $this->assertInstanceOf(\stdClass::class, $userfavourite); + $this->assertObjectHasAttribute('timecreated', $userfavourite); + + // Try to get a favourite we know doesn't exist. + // We expect an exception in this case. + $this->expectException(dml_exception::class); + $favouritesrepo->find(1); + } + + /** + * Test verifying that find_all() returns all favourites, or an empty array. + */ + public function test_find_all() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + $favouritesrepo = new favourites_repository($user1context); + + // Verify that for an empty repository, find_all returns an empty array. + $this->assertEquals([], $favouritesrepo->find_all()); + + // Save a favourite for 2 courses, in different areas. + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite2 = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'anothertype', + 'itemid' => $course2context->instanceid, + 'contextid' => $course2context->id + ]; + $favouritesrepo->add($favourite); + $favouritesrepo->add($favourite2); + + // Verify that find_all returns both of our favourites. + $favourites = $favouritesrepo->find_all(); + $this->assertCount(2, $favourites); + foreach ($favourites as $fav) { + $this->assertObjectHasAttribute('id', $fav); + $this->assertObjectHasAttribute('timecreated', $fav); + } + } + + /** + * Test retrieval of a user's favourites for a given criteria, in this case, area. + */ + public function test_find_by() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favouritesrepo->add($favourite); + + // From the repo, get the list of favourites for the 'core_course/course' area. + $userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => 'course']); + $this->assertInternalType('array', $userfavourites); + $this->assertCount(1, $userfavourites); + + // Try to get a list of favourites for a non-existent area. + $userfavourites = $favouritesrepo->find_by(['component' => 'core_cannibalism', 'itemtype' => 'course']); + $this->assertInternalType('array', $userfavourites); + $this->assertCount(0, $userfavourites); + } + + /** + * Test the count_by() method. + */ + public function test_count_by() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and add 2 favourites in different areas. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite2 = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'anothertype', + 'itemid' => $course2context->instanceid, + 'contextid' => $course2context->id + ]; + $favouritesrepo->add($favourite); + $favouritesrepo->add($favourite2); + + // Verify counts can be restricted by criteria. + $this->assertEquals(1, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course', + 'itemtype' => 'course'])); + $this->assertEquals(1, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course', + 'itemtype' => 'anothertype'])); + $this->assertEquals(0, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course', + 'itemtype' => 'nonexistenttype'])); + } + + public function test_exists() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $createdfavourite = $favouritesrepo->add($favourite); + + // Verify the existence of the favourite in the repo. + $this->assertTrue($favouritesrepo->exists($createdfavourite->id)); + + // Verify exists returns false for non-existent favourite. + $this->assertFalse($favouritesrepo->exists(1)); + } + + public function test_exists_by_area() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite two courses, in different areas. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite2 = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'anothertype', + 'itemid' => $course2context->instanceid, + 'contextid' => $course2context->id + ]; + $favourite1 = $favouritesrepo->add($favourite); + $favourite2 = $favouritesrepo->add($favourite2); + + // Verify the existence of the favourites. + $this->assertTrue($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'course', $favourite1->itemid, + $favourite1->contextid)); + $this->assertTrue($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype', + $favourite2->itemid, $favourite2->contextid)); + + // Verify that we can't find a favourite from one area, in another. + $this->assertFalse($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype', + $favourite1->itemid, $favourite1->contextid)); + } + + /** + * Test the update() method, by simulating a user changing the ordering of a favourite. + */ + public function test_update() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite1 = $favouritesrepo->add($favourite); + + // Verify we can update the ordering for 2 favourites. + $favourite1->ordering = 1; + $favourite1 = $favouritesrepo->update($favourite1); + $this->assertInstanceOf(stdClass::class, $favourite1); + $this->assertAttributeEquals('1', 'ordering', $favourite1); + } + + public function test_delete() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite a course. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite = $favouritesrepo->add($favourite); + + // Verify the existence of the favourite in the repo. + $this->assertTrue($favouritesrepo->exists($favourite->id)); + + // Now, delete the favourite and confirm it's not retrievable. + $favouritesrepo->delete($favourite->id); + $this->assertFalse($favouritesrepo->exists($favourite->id)); + } + + public function test_delete_by_area() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Create a favourites repository and favourite two courses, in different areas. + $favouritesrepo = new favourites_repository($user1context); + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $course1context->instanceid, + 'contextid' => $course1context->id + ]; + $favourite2 = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'anothertype', + 'itemid' => $course2context->instanceid, + 'contextid' => $course2context->id + ]; + $favourite1 = $favouritesrepo->add($favourite); + $favourite2 = $favouritesrepo->add($favourite2); + + // Verify we have 2 items in the repo. + $this->assertEquals(2, $favouritesrepo->count()); + + // Try to delete by a non-existent area, and confirm it doesn't remove anything. + $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'donaldduck'); + $this->assertEquals(2, $favouritesrepo->count()); + + // Try to delete by a non-existent area, and confirm it doesn't remove anything. + $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'cat'); + $this->assertEquals(2, $favouritesrepo->count()); + + // Delete by area, and confirm we have one record left, from the 'core_course/anothertype' area. + $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'course'); + $this->assertEquals(1, $favouritesrepo->count()); + $this->assertFalse($favouritesrepo->exists($favourite1->id)); + $this->assertTrue($favouritesrepo->exists($favourite2->id)); + } +} From 771051325b5eba906f99b9ec4e1933b13de9b7bb Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Mon, 17 Sep 2018 08:40:13 +0800 Subject: [PATCH 04/13] MDL-63658 core_favourites: add business logic aware service layer --- .../classes/local/user_favourites_service.php | 143 +++++++++ favourites/classes/services.php | 49 ++++ favourites/tests/service_test.php | 272 ++++++++++++++++++ 3 files changed, 464 insertions(+) create mode 100644 favourites/classes/local/user_favourites_service.php create mode 100644 favourites/classes/services.php create mode 100644 favourites/tests/service_test.php diff --git a/favourites/classes/local/user_favourites_service.php b/favourites/classes/local/user_favourites_service.php new file mode 100644 index 00000000000..41eb97e57b8 --- /dev/null +++ b/favourites/classes/local/user_favourites_service.php @@ -0,0 +1,143 @@ +. + +/** + * Contains the user_favourites_service class, part of the service layer for the favourites subsystem. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites\local; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class service, providing an single API for interacting with the favourites subsystem for a SINGLE USER. + * + * This class is responsible for exposing key operations (add, remove, find) and enforces any business logic necessary to validate + * authorization/data integrity for these operations. + * + * All object persistence is delegated to the ifavourites_repository. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_favourites_service { + + /** @var user_favourites_repository $repo the user favourites repository object. */ + protected $repo; + + /** @var int $userid the id of the user to which this favourites service is scoped. */ + protected $userid; + + /** + * Helper, returning a flat list of component names. + * + * @return array the array of component names. + */ + protected function get_component_list() { + return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) { + return array_merge($carry, $item); + }, [])); + } + + /** + * The user_favourites_service constructor. + * + * @param \context_user $usercontext The context of the user to which this service operations are scoped. + * @param ifavourites_repository $repository a user favourites repository. + */ + public function __construct(\context_user $usercontext, ifavourites_repository $repository) { + $this->repo = $repository; + $this->userid = $usercontext->instanceid; + } + + /** + * Favourite an item defined by itemid/context, in the area defined by component/itemtype. + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the item being favourited. + * @param int $itemid the id of the item which is to be favourited. + * @param \context $context the context in which the item is to be favourited. + * @param int|null $ordering optional ordering integer used for sorting the favourites in an area. + * @return \stdClass the favourite, once created. + * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors. + */ + public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, + int $ordering = null) : \stdClass { + // Access: Any component can ask to favourite something, we can't verify access to that 'something' here though. + + // Validate the component name. + if (!in_array($component, $this->get_component_list())) { + throw new \moodle_exception("Invalid component name '$component'"); + } + + $favourite = (object) [ + 'userid' => $this->userid, + 'component' => $component, + 'itemtype' => $itemtype, + 'itemid' => $itemid, + 'contextid' => $context->id, + 'ordering' => $ordering > 0 ? $ordering : null + ]; + return $this->repo->add($favourite); + } + + /** + * Find a list of favourites, by type, where type is the component/itemtype pair. + * + * E.g. "Find all favourite courses" might result in: + * $favcourses = find_favourites_by_type('core_course', 'course'); + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @return array the list of favourites found. + * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors. + */ + public function find_favourites_by_type(string $component, string $itemtype) : array { + if (!in_array($component, $this->get_component_list())) { + throw new \moodle_exception("Invalid component name '$component'"); + } + return $this->repo->find_by(['userid' => $this->userid, 'component' => $component, 'itemtype' => $itemtype]); + } + + /** + * Delete a favourite item from an area and from within a context. + * + * E.g. delete a favourite course from the area 'core_course', 'course' with itemid 3 and from within the CONTEXT_USER context. + * + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param \context $context the context of the item which was favourited. + * @throws \moodle_exception if the user does not control the favourite, or it doesn't exist. + */ + public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context) { + if (!in_array($component, $this->get_component_list())) { + throw new \moodle_exception("Invalid component name '$component'"); + } + + // Business logic: check the user owns the favourite. + try { + $favourite = $this->repo->find_favourite($this->userid, $component, $itemtype, $itemid, $context->id); + } catch (\moodle_exception $e) { + throw new \moodle_exception("Favourite does not exist for the user. Cannot delete."); + } + + $this->repo->delete($favourite->id); + } +} diff --git a/favourites/classes/services.php b/favourites/classes/services.php new file mode 100644 index 00000000000..65eecaccd21 --- /dev/null +++ b/favourites/classes/services.php @@ -0,0 +1,49 @@ +. +/** + * Contains the service locators for the favourites subsystem. + * + * Services encapsulate the business logic, and any data manipulation code, and are what clients should interact with. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class services, providing functions for location of service objects for the favourites subsystem. + * + * This class is responsible for providing service objects to clients only. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class services { + + /** + * Returns a basic service object providing operations for user favourites. + * + * @param \context_user $context the context of the user to which the service should be scoped. + * @return user_favourites_service the service object. + */ + public static function get_service_for_user_context(\context_user $context) : local\user_favourites_service { + return new local\user_favourites_service($context, new local\favourites_repository()); + } +} + diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php new file mode 100644 index 00000000000..5c47e6b8ed3 --- /dev/null +++ b/favourites/tests/service_test.php @@ -0,0 +1,272 @@ +. + +/** + * Testing the service layer within core_favourites. + * + * @package core_favourites + * @category test + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Test class covering the user_favourites_service within the service layer of favourites. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_favourites_service_testcase extends advanced_testcase { + + public function setUp() { + $this->resetAfterTest(); + } + + // Basic setup stuff to be reused in most tests. + protected function setup_users_and_courses() { + $user1 = self::getDataGenerator()->create_user(); + $user1context = \context_user::instance($user1->id); + $user2 = self::getDataGenerator()->create_user(); + $user2context = \context_user::instance($user2->id); + $course1 = self::getDataGenerator()->create_course(); + $course2 = self::getDataGenerator()->create_course(); + $course1context = context_course::instance($course1->id); + $course2context = context_course::instance($course2->id); + return [$user1context, $user2context, $course1context, $course2context]; + } + + /** + * Generates an in-memory repository for testing, using an array store for CRUD stuff. + * + * @param array $mockstore + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function get_mock_repository(array $mockstore) { + // This mock will just store data in an array. + $mockrepo = $this->getMockBuilder(\core_favourites\local\ifavourites_repository::class) + ->setMethods([]) + ->getMock(); + $mockrepo->expects($this->any()) + ->method('add') + ->will($this->returnCallback(function(\stdclass $favourite) use (&$mockstore) { + // Mock implementation of repository->add(), where an array is used instead of the DB. + // Duplicates are confirmed via the unique key, and exceptions thrown just like a real repo. + $key = $favourite->userid . $favourite->component . $favourite->itemtype . $favourite->itemid + . $favourite->contextid; + + // Check the objects for the unique key. + foreach ($mockstore as $item) { + if ($item->uniquekey == $key) { + throw new \moodle_exception('Favourite already exists'); + } + } + $index = count($mockstore); // Integer index. + $favourite->uniquekey = $key; // Simulate the unique key constraint. + $favourite->id = $index; + $mockstore[$index] = $favourite; + return $mockstore[$index]; + }) + ); + $mockrepo->expects($this->any()) + ->method('find_by') + ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { + // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. + foreach ($mockstore as $index => $mockrow) { + $mockrowarr = (array)$mockrow; + if (array_diff($criteria, $mockrowarr) == []) { + $returns[$index] = $mockrow; + } + } + return $returns; + }) + ); + $mockrepo->expects($this->any()) + ->method('find_favourite') + ->will($this->returnCallback(function(int $userid, string $comp, string $type, int $id, int $ctxid) use (&$mockstore) { + // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. + $crit = ['userid' => $userid, 'component' => $comp, 'itemtype' => $type, 'itemid' => $id, 'contextid' => $ctxid]; + foreach ($mockstore as $fakerow) { + $fakerowarr = (array)$fakerow; + if (array_diff($crit, $fakerowarr) == []) { + return $fakerow; + } + } + throw new \moodle_exception("Item not found"); + }) + ); + $mockrepo->expects($this->any()) + ->method('find') + ->will($this->returnCallback(function(int $id) use (&$mockstore) { + return $mockstore[$id]; + }) + ); + $mockrepo->expects($this->any()) + ->method('exists') + ->will($this->returnCallback(function(int $id) use (&$mockstore) { + return array_key_exists($id, $mockstore); + }) + ); + $mockrepo->expects($this->any()) + ->method('delete') + ->will($this->returnCallback(function(int $id) use (&$mockstore) { + foreach ($mockstore as $mockrow) { + if ($mockrow->id == $id) { + unset($mockstore[$id]); + } + } + }) + ); + return $mockrepo; + } + + /** + * Test getting a user_favourites_service from the static locator. + */ + public function test_get_service_for_user_context() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + $userservice = \core_favourites\services::get_service_for_user_context($user1context); + $this->assertInstanceOf(\core_favourites\local\user_favourites_service::class, $userservice); + } + + /** + * Test confirming an item can be favourited only once. + */ + public function test_create_favourite_basic() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for a user. + $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. + $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); + + // Favourite a course. + $favourite1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + $this->assertObjectHasAttribute('id', $favourite1); + + // Try to favourite the same course again. + $this->expectException('moodle_exception'); + $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + } + + /** + * Test confirming that an exception is thrown if trying to favourite an item for a non-existent component. + */ + public function test_create_favourite_nonexistent_component() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for the user. + $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. + $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); + + // Try to favourite something in a non-existent component. + $this->expectException('moodle_exception'); + $user1service->create_favourite('core_cccourse', 'my_area', $course1context->instanceid, $course1context); + } + + /** + * Test fetching favourites for single user, by area. + */ + public function test_find_favourites_by_type_single_user() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for the user. + $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. + $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + + // Favourite 2 courses, in separate areas. + $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + $fav2 = $service->create_favourite('core_course', 'anothertype', $course2context->instanceid, $course2context); + + // Verify we can get favourites by area. + $favourites = $service->find_favourites_by_type('core_course', 'course'); + $this->assertInternalType('array', $favourites); + $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area. + $this->assertAttributeEquals($fav1->id, 'id', $favourites[$fav1->id]); + + $favourites = $service->find_favourites_by_type('core_course', 'anothertype'); + $this->assertInternalType('array', $favourites); + $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area. + $this->assertAttributeEquals($fav2->id, 'id', $favourites[$fav2->id]); + } + + /** + * Make sure the find_favourites_by_type() method only returns favourites for the scoped user. + */ + public function test_find_favourites_by_type_multiple_users() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for 2 users. + $repo = $this->get_mock_repository([]); + $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $user2service = new \core_favourites\local\user_favourites_service($user2context, $repo); + + // Now, as each user, favourite the same course. + $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + $fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + + // Verify find_favourites_by_type only returns results for the user to which the service is scoped. + $user1favourites = $user1service->find_favourites_by_type('core_course', 'course'); + $this->assertInternalType('array', $user1favourites); + $this->assertCount(1, $user1favourites); // We only get favourites for the 'core_course/course' area for $user1. + $this->assertAttributeEquals($fav1->id, 'id', $user1favourites[$fav1->id]); + + $user2favourites = $user2service->find_favourites_by_type('core_course', 'course'); + $this->assertInternalType('array', $user2favourites); + $this->assertCount(1, $user2favourites); // We only get favourites for the 'core_course/course' area for $user2. + $this->assertAttributeEquals($fav2->id, 'id', $user2favourites[$fav2->id]); + } + + /** + * Test confirming that an exception is thrown if trying to get favourites for a non-existent component. + */ + public function test_find_favourites_by_type_nonexistent_component() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for the user. + $repo = $this->get_mock_repository([]); + $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + + // Verify we get an exception if we try to search for favourites in an invalid component. + $this->expectException('moodle_exception'); + $service->find_favourites_by_type('cccore_notreal', 'something'); + } + + /** + * Test confirming the basic deletion behaviour. + */ + public function test_delete_favourite_basic() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for the user. + $repo = $this->get_mock_repository([]); + $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + + // Favourite a course. + $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); + $this->assertTrue($repo->exists($fav1->id)); + + // Delete the favourite. + $service->delete_favourite('core_course', 'course', $course1context->instanceid, $course1context); + + // Verify the favourite doesn't exist. + $this->assertFalse($repo->exists($fav1->id)); + + // Try to delete a favourite which we know doesn't exist. + $this->expectException(\moodle_exception::class); + $service->delete_favourite('core_course', 'course', $course1context->instanceid, $course1context); + } +} From 4a02aae5f5aeda0e3c15d57bb22f60964536c5d0 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 2 Oct 2018 08:25:25 +0800 Subject: [PATCH 05/13] MDL-63658 core_favourites: namespace the repositories and services --- .../local/{ => repository}/crud_repository.php | 2 +- .../{ => repository}/favourites_repository.php | 2 +- .../ifavourites_repository.php | 2 +- .../{ => service}/user_favourites_service.php | 8 ++++---- favourites/classes/services.php | 4 ++-- favourites/tests/repository_test.php | 2 +- favourites/tests/service_test.php | 18 +++++++++--------- 7 files changed, 19 insertions(+), 19 deletions(-) rename favourites/classes/local/{ => repository}/crud_repository.php (98%) rename favourites/classes/local/{ => repository}/favourites_repository.php (99%) rename favourites/classes/local/{ => repository}/ifavourites_repository.php (97%) rename favourites/classes/local/{ => service}/user_favourites_service.php (94%) diff --git a/favourites/classes/local/crud_repository.php b/favourites/classes/local/repository/crud_repository.php similarity index 98% rename from favourites/classes/local/crud_repository.php rename to favourites/classes/local/repository/crud_repository.php index 8dab4e0dbaf..503a3213dd8 100644 --- a/favourites/classes/local/crud_repository.php +++ b/favourites/classes/local/repository/crud_repository.php @@ -20,7 +20,7 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace core_favourites\local; +namespace core_favourites\local\repository; defined('MOODLE_INTERNAL') || die(); diff --git a/favourites/classes/local/favourites_repository.php b/favourites/classes/local/repository/favourites_repository.php similarity index 99% rename from favourites/classes/local/favourites_repository.php rename to favourites/classes/local/repository/favourites_repository.php index 3e69f2ca1e8..799b8e9346b 100644 --- a/favourites/classes/local/favourites_repository.php +++ b/favourites/classes/local/repository/favourites_repository.php @@ -20,7 +20,7 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace core_favourites\local; +namespace core_favourites\local\repository; defined('MOODLE_INTERNAL') || die(); diff --git a/favourites/classes/local/ifavourites_repository.php b/favourites/classes/local/repository/ifavourites_repository.php similarity index 97% rename from favourites/classes/local/ifavourites_repository.php rename to favourites/classes/local/repository/ifavourites_repository.php index 493934baaaf..332240a1f61 100644 --- a/favourites/classes/local/ifavourites_repository.php +++ b/favourites/classes/local/repository/ifavourites_repository.php @@ -20,7 +20,7 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace core_favourites\local; +namespace core_favourites\local\repository; defined('MOODLE_INTERNAL') || die(); diff --git a/favourites/classes/local/user_favourites_service.php b/favourites/classes/local/service/user_favourites_service.php similarity index 94% rename from favourites/classes/local/user_favourites_service.php rename to favourites/classes/local/service/user_favourites_service.php index 41eb97e57b8..37da62349e0 100644 --- a/favourites/classes/local/user_favourites_service.php +++ b/favourites/classes/local/service/user_favourites_service.php @@ -21,7 +21,7 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace core_favourites\local; +namespace core_favourites\local\service; defined('MOODLE_INTERNAL') || die(); @@ -38,7 +38,7 @@ defined('MOODLE_INTERNAL') || die(); */ class user_favourites_service { - /** @var user_favourites_repository $repo the user favourites repository object. */ + /** @var ifavourites_repository $repo the user favourites repository object. */ protected $repo; /** @var int $userid the id of the user to which this favourites service is scoped. */ @@ -59,9 +59,9 @@ class user_favourites_service { * The user_favourites_service constructor. * * @param \context_user $usercontext The context of the user to which this service operations are scoped. - * @param ifavourites_repository $repository a user favourites repository. + * @param \core_favourites\local\repository\ifavourites_repository $repository a user favourites repository. */ - public function __construct(\context_user $usercontext, ifavourites_repository $repository) { + public function __construct(\context_user $usercontext, \core_favourites\local\repository\ifavourites_repository $repository) { $this->repo = $repository; $this->userid = $usercontext->instanceid; } diff --git a/favourites/classes/services.php b/favourites/classes/services.php index 65eecaccd21..7d335c7d00f 100644 --- a/favourites/classes/services.php +++ b/favourites/classes/services.php @@ -42,8 +42,8 @@ class services { * @param \context_user $context the context of the user to which the service should be scoped. * @return user_favourites_service the service object. */ - public static function get_service_for_user_context(\context_user $context) : local\user_favourites_service { - return new local\user_favourites_service($context, new local\favourites_repository()); + public static function get_service_for_user_context(\context_user $context) : local\service\user_favourites_service { + return new local\service\user_favourites_service($context, new local\repository\favourites_repository()); } } diff --git a/favourites/tests/repository_test.php b/favourites/tests/repository_test.php index f7c713dc908..e815cf95379 100644 --- a/favourites/tests/repository_test.php +++ b/favourites/tests/repository_test.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -use \core_favourites\local\favourites_repository; +use \core_favourites\local\repository\favourites_repository; /** * Test class covering the favourites_repository. diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 5c47e6b8ed3..60f8fab768b 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -58,7 +58,7 @@ class user_favourites_service_testcase extends advanced_testcase { */ protected function get_mock_repository(array $mockstore) { // This mock will just store data in an array. - $mockrepo = $this->getMockBuilder(\core_favourites\local\ifavourites_repository::class) + $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\ifavourites_repository::class) ->setMethods([]) ->getMock(); $mockrepo->expects($this->any()) @@ -140,7 +140,7 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_get_service_for_user_context() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); $userservice = \core_favourites\services::get_service_for_user_context($user1context); - $this->assertInstanceOf(\core_favourites\local\user_favourites_service::class, $userservice); + $this->assertInstanceOf(\core_favourites\local\service\user_favourites_service::class, $userservice); } /** @@ -151,7 +151,7 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for a user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); // Favourite a course. $favourite1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -170,7 +170,7 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for the user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); // Try to favourite something in a non-existent component. $this->expectException('moodle_exception'); @@ -185,7 +185,7 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for the user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); // Favourite 2 courses, in separate areas. $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -211,8 +211,8 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for 2 users. $repo = $this->get_mock_repository([]); - $user1service = new \core_favourites\local\user_favourites_service($user1context, $repo); - $user2service = new \core_favourites\local\user_favourites_service($user2context, $repo); + $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $user2service = new \core_favourites\local\service\user_favourites_service($user2context, $repo); // Now, as each user, favourite the same course. $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -238,7 +238,7 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for the user. $repo = $this->get_mock_repository([]); - $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); // Verify we get an exception if we try to search for favourites in an invalid component. $this->expectException('moodle_exception'); @@ -253,7 +253,7 @@ class user_favourites_service_testcase extends advanced_testcase { // Get a user_favourites_service for the user. $repo = $this->get_mock_repository([]); - $service = new \core_favourites\local\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); // Favourite a course. $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); From ac9138db1cd4fce59357849ea60305899db85f48 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 2 Oct 2018 12:31:03 +0800 Subject: [PATCH 06/13] MDL-63658 core_favourites: implement privacy API for favourites --- favourites/classes/privacy/provider.php | 143 ++++++++++++++++++++++++ favourites/tests/privacy_test.php | 137 +++++++++++++++++++++++ lang/en/favourites.php | 8 ++ 3 files changed, 288 insertions(+) create mode 100644 favourites/classes/privacy/provider.php create mode 100644 favourites/tests/privacy_test.php diff --git a/favourites/classes/privacy/provider.php b/favourites/classes/privacy/provider.php new file mode 100644 index 00000000000..74c9b28fe02 --- /dev/null +++ b/favourites/classes/privacy/provider.php @@ -0,0 +1,143 @@ +. + +/** + * Privacy class for requesting user data for the favourites subsystem. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_favourites\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use \core_privacy\local\metadata\collection; +use \core_privacy\local\request\context; +use \core_privacy\local\request\approved_contextlist; + +/** + * Privacy class for requesting user data. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\subsystem\plugin_provider { + + /** + * Returns metadata 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 { + return $collection->add_database_table('favourite', [ + 'userid' => 'privacy:metadata:favourite:userid', + 'component' => 'privacy:metadata:favourite:component', + 'itemtype' => 'privacy:metadata:favourite:itemtype', + 'itemid' => 'privacy:metadata:favourite:itemid', + 'ordering' => 'privacy:metadata:favourite:ordering', + 'timecreated' => 'privacy:metadata:favourite:timecreated', + 'timemodified' => 'privacy:metadata:favourite:timemodified', + ], 'privacy:metadata:favourite'); + } + + /** + * Provide a list of contexts which have favourites for the user, in the respective area (component/itemtype combination). + * + * This method is to be called by consumers of the favourites subsystem (plugins), in their get_contexts_for_userid() method, + * to add the contexts for items which may have been favourited, but would normally not be reported as having user data by the + * plugin responsible for them. + * + * Consider an example: Favourite courses. + * Favourite courses will be handled by the core_course subsystem and courses can be favourited at site context. + * + * Now normally, the course provider method get_contexts_for_userid() would report the context of any courses the user is in. + * Then, we'd export data for those contexts. This won't include courses the user has favourited, but is not a member of. + * + * To report the full list, the course provider needs to be made aware of the contexts of any courses the user may have marked + * as favourites. Course will need to ask th favourites subsystem for this - a call to add_contexts_for_userid($userid). + * + * Once called, if a course has been marked as a favourite, at site context, then we'd return the site context. During export, + * the consumer (course), just looks at all contexts and decides whether to export favourite courses for each one. + * + * @param \core_privacy\local\request\contextlist $contextlist + * @param int $userid The id of the user in scope. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited items. + */ + public static function add_contexts_for_userid(\core_privacy\local\request\contextlist $contextlist, int $userid, + string $component, string $itemtype = null) { + $sql = "SELECT contextid + FROM {favourite} f + WHERE userid = :userid + AND component = :component"; + if (!is_null($itemtype)) { + $sql .= "AND itemtype = :itemtype"; + } + $params = ['userid' => $userid, 'component' => $component, 'itemtype' => $itemtype]; + $contextlist->add_from_sql($sql, $params); + } + + /** + * Delete all favourites for all users in the specified contexts, and component area. + * + * @param \context $context The context to which deletion is scoped. + * @param string $component The favourite's component name. + * @param string $itemtype The favourite's itemtype. + * @throws \dml_exception if any errors are encountered during deletion. + */ + public static function delete_favourites_for_all_users(\context $context, string $component, string $itemtype) { + global $DB; + + $params = [ + 'component' => $component, + 'itemtype' => $itemtype, + 'contextid' => $context->id + ]; + + $select = "component = :component AND itemtype =:itemtype AND contextid = :contextid"; + $DB->delete_records_select('favourite', $select, $params); + } + + /** + * Delete all favourites for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + * @param string $component + * @param string $itemtype + * @throws \coding_exception + * @throws \dml_exception + */ + public static function delete_favourites_for_user(approved_contextlist $contextlist, string $component, string $itemtype) { + global $DB; + + $userid = $contextlist->get_user()->id; + + list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); + + $params = [ + 'userid' => $userid, + 'component' => $component, + 'itemtype' => $itemtype, + ]; + $params += $inparams; + + $select = "userid = :userid AND component = :component AND itemtype =:itemtype AND contextid $insql"; + $DB->delete_records_select('favourite', $select, $params); + } +} diff --git a/favourites/tests/privacy_test.php b/favourites/tests/privacy_test.php new file mode 100644 index 00000000000..1d8c85b29bd --- /dev/null +++ b/favourites/tests/privacy_test.php @@ -0,0 +1,137 @@ +. + +/** + * Privacy tests for core_favourites. + * + * @package core_favourites + * @category test + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \core_privacy\tests\provider_testcase; +use \core_favourites\privacy\provider; + +/** + * Unit tests for favourites/classes/privacy/provider + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class privacy_test extends provider_testcase { + + public function setUp() { + $this->resetAfterTest(true); + } + + /** + * Helper to set up some sample users and courses. + */ + protected function set_up_courses_and_users() { + $user1 = self::getDataGenerator()->create_user(); + $user1context = \context_user::instance($user1->id); + $user2 = self::getDataGenerator()->create_user(); + $user2context = \context_user::instance($user2->id); + $course1 = self::getDataGenerator()->create_course(); + $course2 = self::getDataGenerator()->create_course(); + $course1context = context_course::instance($course1->id); + $course2context = context_course::instance($course2->id); + return [$user1, $user2, $user1context, $user2context, $course1context, $course2context]; + } + + /** + * Test confirming that contexts of favourited items can be added to the contextlist. + */ + public function test_add_contexts_for_userid() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 courses for user1 and 1 course for user2, all at the site context. + $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $systemcontext = context_system::instance(); + $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $systemcontext); + $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext); + $ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + + // Now, just for variety, let's assume you can favourite a course at user context, and do so for user1. + $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context); + + // Now, ask the favourites privacy api to export contexts for favourites of the type we just created, for user1. + $contextlist = new \core_privacy\local\request\contextlist(); + \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user1->id, 'core_course', 'course'); + + // Verify we have two contexts in the list for user1. + $this->assertCount(2, $contextlist->get_contextids()); + + // And verify we only have the system context returned for user2. + $contextlist = new \core_privacy\local\request\contextlist(); + \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user2->id, 'core_course', 'course'); + $this->assertCount(1, $contextlist->get_contextids()); + } + + /** + * Test deletion of user favourites based on an approved_contextlist and component area. + */ + public function test_delete_favourites_for_user() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 courses for user1 and 1 course for user2, all at the user context. + $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context); + $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $user1context); + $ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $user2context); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + + // Now, delete the favourites for user1 only. + $approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_course', [$user1context->id]); + provider::delete_favourites_for_user($approvedcontextlist, 'core_course', 'course'); + + // Verify that we have no favourite courses for user1 but that the records are in tact for user2. + $this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'course')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + } + + public function test_delete_favourites_for_all_users() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 course modules for user1 and 1 course module for user2 all in course 1 context. + $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $ufservice1->create_favourite('core_course', 'modules', 1, $course1context); + $ufservice1->create_favourite('core_course', 'modules', 2, $course1context); + $ufservice2->create_favourite('core_course', 'modules', 3, $course1context); + + // Now, favourite a different course module for user2 in course 2. + $ufservice2->create_favourite('core_course', 'modules', 5, $course2context); + + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'modules')); + $this->assertCount(2, $ufservice2->find_favourites_by_type('core_course', 'modules')); + + // Now, delete all course module favourites in the 'course1' context only. + provider::delete_favourites_for_all_users($course1context, 'core_course', 'modules'); + + // Verify that only a single favourite for user1 in course 1 remains. + $this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'modules')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'modules')); + } +} diff --git a/lang/en/favourites.php b/lang/en/favourites.php index b5d0a7e3942..ea13dd2f1af 100644 --- a/lang/en/favourites.php +++ b/lang/en/favourites.php @@ -20,3 +20,11 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['privacy:metadata:favourite'] = 'Stores references to items which have been marked as favourites.'; +$string['privacy:metadata:favourite:component'] = 'The component to which the favourite belongs to. E.g. core_user.'; +$string['privacy:metadata:favourite:itemid'] = 'The identifier for the item being marked as a favourite.'; +$string['privacy:metadata:favourite:itemtype'] = 'The type of the favourite item. E.g. course.'; +$string['privacy:metadata:favourite:ordering'] = 'A number used to order the favourites of the same type.'; +$string['privacy:metadata:favourite:timecreated'] = 'The time at which the item was marked as a favourite.'; +$string['privacy:metadata:favourite:timemodified'] = 'The time at which favourite was last modified.'; +$string['privacy:metadata:favourite:userid'] = 'The user who created the favourite.'; From 8ffbe9c1634ba832da9a812f7c67221ac83f5575 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 9 Oct 2018 11:10:21 +0800 Subject: [PATCH 07/13] MDL-63658 core_favourites: adding paging support to the service layer --- .../local/repository/crud_repository.php | 24 ++--- .../repository/favourites_repository.php | 16 ++-- .../local/service/user_favourites_service.php | 36 +++++--- favourites/tests/repository_test.php | 88 +++++++++++++++++++ favourites/tests/service_test.php | 38 +++++++- 5 files changed, 172 insertions(+), 30 deletions(-) diff --git a/favourites/classes/local/repository/crud_repository.php b/favourites/classes/local/repository/crud_repository.php index 503a3213dd8..3a515d908d7 100644 --- a/favourites/classes/local/repository/crud_repository.php +++ b/favourites/classes/local/repository/crud_repository.php @@ -31,10 +31,10 @@ interface crud_repository { /** * Add one item to this repository. * - * @param \stdClass $item the item to add. - * @return \stdClass the item which was added. + * @param object $item the item to add. + * @return object the item which was added. */ - public function add(\stdClass $item) : \stdClass; + public function add($item); /** * Add all the items in the list to this repository. @@ -48,24 +48,28 @@ interface crud_repository { * Find an item in this repository based on its id. * * @param int $id the id of the item. - * @return \stdClass the item. + * @return object the item. */ - public function find(int $id) : \stdClass; + public function find(int $id); /** * Find all items in this repository. * + * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point. + * @param int $limitnum optional pagination control for returning a subset comprising this many records. * @return array list of all items in this repository. */ - public function find_all() : array; + public function find_all(int $limitfrom = 0, int $limitnum = 0) : array; /** * Find all items with attributes matching certain values. * * @param array $criteria the array of attribute/value pairs. + * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point. + * @param int $limitnum optional pagination control for returning a subset comprising this many records. * @return array the list of items matching the criteria. */ - public function find_by(array $criteria) : array; + public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array; /** * Check whether an item exists in this repository, based on its id. @@ -85,10 +89,10 @@ interface crud_repository { /** * Update an item within this repository. * - * @param \stdClass $item the item to update. - * @return \stdClass the updated item. + * @param object $item the item to update. + * @return object the updated item. */ - public function update(\stdClass $item) : \stdClass; + public function update($item); /** * Delete an item by id. diff --git a/favourites/classes/local/repository/favourites_repository.php b/favourites/classes/local/repository/favourites_repository.php index 799b8e9346b..9e0fbdbe237 100644 --- a/favourites/classes/local/repository/favourites_repository.php +++ b/favourites/classes/local/repository/favourites_repository.php @@ -53,7 +53,7 @@ class favourites_repository implements ifavourites_repository { * @throws \dml_exception if any database errors are encountered. * @throws \moodle_exception if the favourite has missing or invalid properties. */ - public function add(\stdClass $favourite) : \stdClass { + public function add($favourite) : \stdClass { global $DB; $this->validate($favourite); $favourite = (array)$favourite; @@ -102,23 +102,27 @@ class favourites_repository implements ifavourites_repository { * Return all items matching the supplied criteria (a [key => value,..] list). * * @param array $criteria the list of key/value criteria pairs. + * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point. + * @param int $limitnum optional pagination control for returning a subset comprising this many records. * @return array the list of favourites matching the criteria. * @throws \dml_exception if any database errors are encountered. */ - public function find_by(array $criteria) : array { + public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array { global $DB; - return $DB->get_records($this->favouritetable, $criteria); + return $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum); } /** * Return all items in this repository, as an array, indexed by id. * + * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point. + * @param int $limitnum optional pagination control for returning a subset comprising this many records. * @return array the list of all favourites stored within this repository. * @throws \dml_exception if any database errors are encountered. */ - public function find_all() : array { + public function find_all(int $limitfrom = 0, int $limitnum = 0) : array { global $DB; - return $DB->get_records($this->favouritetable); + return $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum); } /** @@ -165,7 +169,7 @@ class favourites_repository implements ifavourites_repository { * @return \stdClass the updated favourite. * @throws \dml_exception if any database errors are encountered. */ - public function update(\stdClass $favourite) : \stdClass { + public function update($favourite) : \stdClass { global $DB; $time = time(); $favourite->timemodified = $time; diff --git a/favourites/classes/local/service/user_favourites_service.php b/favourites/classes/local/service/user_favourites_service.php index 37da62349e0..c9026aa535f 100644 --- a/favourites/classes/local/service/user_favourites_service.php +++ b/favourites/classes/local/service/user_favourites_service.php @@ -44,17 +44,6 @@ class user_favourites_service { /** @var int $userid the id of the user to which this favourites service is scoped. */ protected $userid; - /** - * Helper, returning a flat list of component names. - * - * @return array the array of component names. - */ - protected function get_component_list() { - return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) { - return array_merge($carry, $item); - }, [])); - } - /** * The user_favourites_service constructor. * @@ -66,6 +55,17 @@ class user_favourites_service { $this->userid = $usercontext->instanceid; } + /** + * Helper, returning a flat list of component names. + * + * @return array the array of component names. + */ + protected function get_component_list() { + return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) { + return array_merge($carry, $item); + }, [])); + } + /** * Favourite an item defined by itemid/context, in the area defined by component/itemtype. * @@ -105,14 +105,24 @@ class user_favourites_service { * * @param string $component the frankenstyle component name. * @param string $itemtype the type of the favourited item. + * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point. + * @param int $limitnum optional pagination control for returning a subset comprising this many records. * @return array the list of favourites found. * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors. */ - public function find_favourites_by_type(string $component, string $itemtype) : array { + public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array { if (!in_array($component, $this->get_component_list())) { throw new \moodle_exception("Invalid component name '$component'"); } - return $this->repo->find_by(['userid' => $this->userid, 'component' => $component, 'itemtype' => $itemtype]); + return $this->repo->find_by( + [ + 'userid' => $this->userid, + 'component' => $component, + 'itemtype' => $itemtype + ], + $limitfrom, + $limitnum + ); } /** diff --git a/favourites/tests/repository_test.php b/favourites/tests/repository_test.php index e815cf95379..1910931e19a 100644 --- a/favourites/tests/repository_test.php +++ b/favourites/tests/repository_test.php @@ -235,6 +235,48 @@ class favourites_repository_testcase extends advanced_testcase { } } + /** + * Testing the pagination of the find_all method. + */ + public function test_find_all_pagination() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + $favouritesrepo = new favourites_repository($user1context); + + // Verify that for an empty repository, find_all with any combination of page options returns an empty array. + $this->assertEquals([], $favouritesrepo->find_all(0, 0)); + $this->assertEquals([], $favouritesrepo->find_all(0, 10)); + $this->assertEquals([], $favouritesrepo->find_all(1, 0)); + $this->assertEquals([], $favouritesrepo->find_all(1, 10)); + + // Save 10 arbitrary favourites to the repo. + foreach (range(1, 10) as $i) { + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $i, + 'contextid' => $course1context->id + ]; + $favouritesrepo->add($favourite); + } + + // Verify we have 10 favourites. + $this->assertEquals(10, $favouritesrepo->count()); + + // Verify we can fetch the first page of 5 records. + $favourites = $favouritesrepo->find_all(0, 5); + $this->assertCount(5, $favourites); + + // Verify we can fetch the second page. + $favourites = $favouritesrepo->find_all(5, 5); + $this->assertCount(5, $favourites); + + // Verify the third page request ends with an empty array. + $favourites = $favouritesrepo->find_all(10, 5); + $this->assertCount(0, $favourites); + } + /** * Test retrieval of a user's favourites for a given criteria, in this case, area. */ @@ -263,6 +305,52 @@ class favourites_repository_testcase extends advanced_testcase { $this->assertCount(0, $userfavourites); } + /** + * Testing the pagination of the find_by method. + */ + public function test_find_by_pagination() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + $favouritesrepo = new favourites_repository($user1context); + + // Verify that for an empty repository, find_all with any combination of page options returns an empty array. + $this->assertEquals([], $favouritesrepo->find_by([], 0, 0)); + $this->assertEquals([], $favouritesrepo->find_by([], 0, 10)); + $this->assertEquals([], $favouritesrepo->find_by([], 1, 0)); + $this->assertEquals([], $favouritesrepo->find_by([], 1, 10)); + + // Save 10 arbitrary favourites to the repo. + foreach (range(1, 10) as $i) { + $favourite = (object) [ + 'userid' => $user1context->instanceid, + 'component' => 'core_course', + 'itemtype' => 'course', + 'itemid' => $i, + 'contextid' => $course1context->id + ]; + $favouritesrepo->add($favourite); + } + + // Verify we have 10 favourites. + $this->assertEquals(10, $favouritesrepo->count()); + + // Verify a request for a page, when no criteria match, results in an empty array. + $favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5); + $this->assertCount(0, $favourites); + + // Verify we can fetch a the first page of 5 records. + $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 0, 5); + $this->assertCount(5, $favourites); + + // Verify we can fetch the second page. + $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 5, 5); + $this->assertCount(5, $favourites); + + // Verify the third page request ends with an empty array. + $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 10, 5); + $this->assertCount(0, $favourites); + } + /** * Test the count_by() method. */ diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 60f8fab768b..d02b353f508 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -84,7 +84,7 @@ class user_favourites_service_testcase extends advanced_testcase { ); $mockrepo->expects($this->any()) ->method('find_by') - ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { + ->will($this->returnCallback(function(array $criteria, int $limitfrom = 0, int $limitnum = 0) use (&$mockstore) { // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. foreach ($mockstore as $index => $mockrow) { $mockrowarr = (array)$mockrow; @@ -92,6 +92,11 @@ class user_favourites_service_testcase extends advanced_testcase { $returns[$index] = $mockrow; } } + // Return a subset of the records, according to the paging options, if set. + if ($limitnum != 0) { + return array_slice($returns, $limitfrom, $limitnum); + } + // Otherwise, just return the full set. return $returns; }) ); @@ -245,6 +250,37 @@ class user_favourites_service_testcase extends advanced_testcase { $service->find_favourites_by_type('cccore_notreal', 'something'); } + /** + * Test confirming the pagination support for the find_favourites_by_type() method. + */ + public function test_find_favourites_by_type_pagination() { + list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); + + // Get a user_favourites_service for the user. + $repo = $this->get_mock_repository([]); + $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + + // Favourite 10 arbitrary items. + foreach (range(1, 10) as $i) { + $service->create_favourite('core_course', 'course', $i, $course1context); + } + + // Verify we have 10 favourites. + $this->assertCount(10, $service->find_favourites_by_type('core_course', 'course')); + + // Verify we get back 5 favourites for page 1. + $favourites = $service->find_favourites_by_type('core_course', 'course', 0, 5); + $this->assertCount(5, $favourites); + + // Verify we get back 5 favourites for page 2. + $favourites = $service->find_favourites_by_type('core_course', 'course', 5, 5); + $this->assertCount(5, $favourites); + + // Verify we get back an empty array if querying page 3. + $favourites = $service->find_favourites_by_type('core_course', 'course', 10, 5); + $this->assertCount(0, $favourites); + } + /** * Test confirming the basic deletion behaviour. */ From cfaf86b059ca6054b3c4e35653816b9037a08fe4 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 16 Oct 2018 17:32:37 +0800 Subject: [PATCH 08/13] MDL-63658 core_favourites: make favourite a first class object This patch adds several things: - favourite entity class under local/entity - refactoring of repository and service to use the new class instead of stdClass. - update repository unit tests to use the object instead of stdClass. - update service_test so that the mock repo requires the object for the add operation. - remove unnecessary constructor from favourites_repository class. --- favourites/classes/local/entity/favourite.php | 77 +++++ .../local/repository/crud_repository.php | 17 +- .../repository/favourites_repository.php | 83 +++-- .../repository/ifavourites_repository.php | 7 +- .../local/service/user_favourites_service.php | 15 +- favourites/classes/services.php | 2 +- favourites/tests/repository_test.php | 297 +++++++++--------- favourites/tests/service_test.php | 4 +- 8 files changed, 311 insertions(+), 191 deletions(-) create mode 100644 favourites/classes/local/entity/favourite.php diff --git a/favourites/classes/local/entity/favourite.php b/favourites/classes/local/entity/favourite.php new file mode 100644 index 00000000000..a10a8f27014 --- /dev/null +++ b/favourites/classes/local/entity/favourite.php @@ -0,0 +1,77 @@ +. + +/** + * Contains the favourite class, each instance being a representation of a DB row for the 'favourite' table. + * + * @package core_favourites + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_favourites\local\entity; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Contains the favourite class, each instance being a representation of a DB row for the 'favourite' table. + * + * @copyright 2018 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class favourite { + /** @var int $id the id of the favourite.*/ + public $id; + + /** @var string $component the frankenstyle name of the component containing the favourited item. E.g. 'core_course'.*/ + public $component; + + /** @var string $itemtype the type of the item being marked as a favourite. E.g. 'course', 'conversation', etc.*/ + public $itemtype; + + /** @var int $itemid the id of the item that is being marked as a favourite. e.g course->id, conversation->id, etc.*/ + public $itemid; + + /** @var int $contextid the id of the context in which this favourite was created.*/ + public $contextid; + + /** @var int $userid the id of user who owns this favourite.*/ + public $userid; + + /** @var int $ordering the ordering of the favourite within it's favourite area.*/ + public $ordering; + + /** @var int $timecreated the time at which the favourite was created.*/ + public $timecreated; + + /** @var int $timemodified the time at which the last modification of the favourite took place.*/ + public $timemodified; + + /** + * Favourite constructor. + * @param string $component the frankenstyle name of the component containing the favourited item. E.g. 'core_course'. + * @param string $itemtype the type of the item being marked as a favourite. E.g. 'course', 'conversation', etc. + * @param int $itemid the id of the item that is being marked as a favourite. e.g course->id, conversation->id, etc. + * @param int $contextid the id of the context in which this favourite was created. + * @param int $userid the id of user who owns this favourite. + */ + public function __construct(string $component, string $itemtype, int $itemid, int $contextid, int $userid) { + $this->component = $component; + $this->itemtype = $itemtype; + $this->itemid = $itemid; + $this->contextid = $contextid; + $this->userid = $userid; + } +} diff --git a/favourites/classes/local/repository/crud_repository.php b/favourites/classes/local/repository/crud_repository.php index 3a515d908d7..06033f5b3cc 100644 --- a/favourites/classes/local/repository/crud_repository.php +++ b/favourites/classes/local/repository/crud_repository.php @@ -21,6 +21,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_favourites\local\repository; +use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); @@ -31,10 +32,10 @@ interface crud_repository { /** * Add one item to this repository. * - * @param object $item the item to add. - * @return object the item which was added. + * @param favourite $item the item to add. + * @return favourite the item which was added. */ - public function add($item); + public function add(favourite $item) : favourite; /** * Add all the items in the list to this repository. @@ -48,9 +49,9 @@ interface crud_repository { * Find an item in this repository based on its id. * * @param int $id the id of the item. - * @return object the item. + * @return favourite the item. */ - public function find(int $id); + public function find(int $id) : favourite; /** * Find all items in this repository. @@ -89,10 +90,10 @@ interface crud_repository { /** * Update an item within this repository. * - * @param object $item the item to update. - * @return object the updated item. + * @param favourite $item the item to update. + * @return favourite the updated item. */ - public function update($item); + public function update(favourite $item) : favourite; /** * Delete an item by id. diff --git a/favourites/classes/local/repository/favourites_repository.php b/favourites/classes/local/repository/favourites_repository.php index 9e0fbdbe237..edb5f10ce26 100644 --- a/favourites/classes/local/repository/favourites_repository.php +++ b/favourites/classes/local/repository/favourites_repository.php @@ -14,13 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * Contains the user_favourites_repository class, responsible for CRUD operations for user favourites. + * Contains the favourites_repository class, responsible for CRUD operations for favourites. * * @package core_favourites * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_favourites\local\repository; +use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); @@ -40,20 +41,48 @@ class favourites_repository implements ifavourites_repository { protected $favouritetable = 'favourite'; /** - * The favourites_repository constructor. + * Get a favourite object, based on a full record. + * @param \stdClass $record the record we wish to hydrate. + * @return favourite the favourite record. */ - public function __construct() { + protected function get_favourite_from_record(\stdClass $record) : favourite { + $favourite = new favourite( + $record->component, + $record->itemtype, + $record->itemid, + $record->contextid, + $record->userid + ); + $favourite->id = $record->id; + $favourite->ordering = $record->ordering ?? null; + $favourite->timecreated = $record->timecreated ?? null; + $favourite->timemodified = $record->timemodified ?? null; + + return $favourite; + } + + /** + * Get a list of favourite objects, based on a list of records. + * @param array $records the record we wish to hydrate. + * @return array the list of favourites. + */ + protected function get_list_of_favourites_from_records(array $records) { + $list = []; + foreach ($records as $index => $record) { + $list[$index] = $this->get_favourite_from_record($record); + } + return $list; } /** * Add a favourite to the repository. * - * @param \stdClass $favourite the favourite to add. - * @return \stdClass the favourite which has been stored. + * @param favourite $favourite the favourite to add. + * @return favourite the favourite which has been stored. * @throws \dml_exception if any database errors are encountered. * @throws \moodle_exception if the favourite has missing or invalid properties. */ - public function add($favourite) : \stdClass { + public function add(favourite $favourite) : favourite { global $DB; $this->validate($favourite); $favourite = (array)$favourite; @@ -83,19 +112,21 @@ class favourites_repository implements ifavourites_repository { $ids[] = $DB->insert_record($this->favouritetable, $favourite); } list($insql, $params) = $DB->get_in_or_equal($ids); - return $DB->get_records_select($this->favouritetable, "id $insql", $params); + $records = $DB->get_records_select($this->favouritetable, "id $insql", $params); + return $this->get_list_of_favourites_from_records($records); } /** * Find a favourite by id. * * @param int $id the id of the favourite. - * @return \stdClass the favourite. + * @return favourite the favourite. * @throws \dml_exception if any database errors are encountered. */ - public function find(int $id) : \stdClass { + public function find(int $id) : favourite { global $DB; - return $DB->get_record($this->favouritetable, ['id' => $id], '*', MUST_EXIST); + $record = $DB->get_record($this->favouritetable, ['id' => $id], '*', MUST_EXIST); + return $this->get_favourite_from_record($record); } /** @@ -109,7 +140,8 @@ class favourites_repository implements ifavourites_repository { */ public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array { global $DB; - return $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum); + $records = $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum); + return $this->get_list_of_favourites_from_records($records); } /** @@ -122,7 +154,8 @@ class favourites_repository implements ifavourites_repository { */ public function find_all(int $limitfrom = 0, int $limitnum = 0) : array { global $DB; - return $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum); + $records = $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum); + return $this->get_list_of_favourites_from_records($records); } /** @@ -135,19 +168,20 @@ class favourites_repository implements ifavourites_repository { * @param string $itemtype the type of the favourited item. * @param int $itemid the id of the item which was favourited (not the favourite's id). * @param int $contextid the contextid of the item which was favourited. - * @return \stdClass the favourite. + * @return favourite the favourite. * @throws \dml_exception if any database errors are encountered or if the record could not be found. */ - public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : \stdClass { + public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : favourite { global $DB; // Favourites model: We know that only one favourite can exist based on these properties. - return $DB->get_record($this->favouritetable, [ + $record = $DB->get_record($this->favouritetable, [ 'userid' => $userid, 'component' => $component, 'itemtype' => $itemtype, 'itemid' => $itemid, 'contextid' => $contextid ], '*', MUST_EXIST); + return $this->get_favourite_from_record($record); } /** @@ -165,11 +199,11 @@ class favourites_repository implements ifavourites_repository { /** * Update a favourite. * - * @param \stdClass $favourite the favourite to update. - * @return \stdClass the updated favourite. + * @param favourite $favourite the favourite to update. + * @return favourite the updated favourite. * @throws \dml_exception if any database errors are encountered. */ - public function update($favourite) : \stdClass { + public function update(favourite $favourite) : favourite { global $DB; $time = time(); $favourite->timemodified = $time; @@ -260,22 +294,25 @@ class favourites_repository implements ifavourites_repository { /** * Basic validation, confirming we have the minimum field set needed to save a record to the store. * - * @param \stdClass $favourite the favourite record to validate. + * @param favourite $favourite the favourite record to validate. * @throws \moodle_exception if the supplied favourite has missing or unsupported fields. */ - protected function validate(\stdClass $favourite) { + protected function validate(favourite $favourite) { $favourite = (array)$favourite; - // The allowed fields, and whether or not each is required. - // The timecreated field is generated during create/update, and cannot be specified either. + // The allowed fields, and whether or not each is required to create a record. + // The timecreated, timemodified and id fields are generated during create/update. $allowedfields = [ 'userid' => true, 'component' => true, 'itemtype' => true, 'itemid' => true, 'contextid' => true, - 'ordering' => false + 'ordering' => false, + 'timecreated' => false, + 'timemodified' => false, + 'id' => false ]; $requiredfields = array_filter($allowedfields, function($field) { diff --git a/favourites/classes/local/repository/ifavourites_repository.php b/favourites/classes/local/repository/ifavourites_repository.php index 332240a1f61..f058e1bb3a0 100644 --- a/favourites/classes/local/repository/ifavourites_repository.php +++ b/favourites/classes/local/repository/ifavourites_repository.php @@ -21,11 +21,12 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_favourites\local\repository; +use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** - * The favourites_repository interface, defining additional operations useful to favourites type repositories. + * The favourites_repository interface, defining additional operations useful to favourite type repositories. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -39,7 +40,7 @@ interface ifavourites_repository extends crud_repository { * @param string $itemtype the type of the favourited item. * @param int $itemid the id of the item which was favourited (not the favourite's id). * @param int $contextid the contextid of the item which was favourited. - * @return \stdClass the favourite. + * @return favourite the favourite. */ - public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : \stdClass; + public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : favourite; } diff --git a/favourites/classes/local/service/user_favourites_service.php b/favourites/classes/local/service/user_favourites_service.php index c9026aa535f..9fa806f43e0 100644 --- a/favourites/classes/local/service/user_favourites_service.php +++ b/favourites/classes/local/service/user_favourites_service.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_favourites\local\service; +use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); @@ -74,11 +75,11 @@ class user_favourites_service { * @param int $itemid the id of the item which is to be favourited. * @param \context $context the context in which the item is to be favourited. * @param int|null $ordering optional ordering integer used for sorting the favourites in an area. - * @return \stdClass the favourite, once created. + * @return favourite the favourite, once created. * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors. */ public function create_favourite(string $component, string $itemtype, int $itemid, \context $context, - int $ordering = null) : \stdClass { + int $ordering = null) : favourite { // Access: Any component can ask to favourite something, we can't verify access to that 'something' here though. // Validate the component name. @@ -86,14 +87,8 @@ class user_favourites_service { throw new \moodle_exception("Invalid component name '$component'"); } - $favourite = (object) [ - 'userid' => $this->userid, - 'component' => $component, - 'itemtype' => $itemtype, - 'itemid' => $itemid, - 'contextid' => $context->id, - 'ordering' => $ordering > 0 ? $ordering : null - ]; + $favourite = new favourite($component, $itemtype, $itemid, $context->id, $this->userid); + $favourite->ordering = $ordering > 0 ? $ordering : null; return $this->repo->add($favourite); } diff --git a/favourites/classes/services.php b/favourites/classes/services.php index 7d335c7d00f..88c54b4948c 100644 --- a/favourites/classes/services.php +++ b/favourites/classes/services.php @@ -40,7 +40,7 @@ class services { * Returns a basic service object providing operations for user favourites. * * @param \context_user $context the context of the user to which the service should be scoped. - * @return user_favourites_service the service object. + * @return \core_favourites\local\service\user_favourites_service the service object. */ public static function get_service_for_user_context(\context_user $context) : local\service\user_favourites_service { return new local\service\user_favourites_service($context, new local\repository\favourites_repository()); diff --git a/favourites/tests/repository_test.php b/favourites/tests/repository_test.php index 1910931e19a..951dfa404c0 100644 --- a/favourites/tests/repository_test.php +++ b/favourites/tests/repository_test.php @@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die(); use \core_favourites\local\repository\favourites_repository; +use \core_favourites\local\entity\favourite; /** * Test class covering the favourites_repository. @@ -61,18 +62,19 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favcourse = (object)[ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id, - ]; + $favcourse = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this. $favourite = $favouritesrepo->add($favcourse); // Verify we get the record back. - $this->assertInstanceOf(\stdClass::class, $favourite); + $this->assertInstanceOf(favourite::class, $favourite); + $this->assertObjectHasAttribute('id', $favourite); $this->assertEquals('core_course', $favourite->component); $this->assertEquals('course', $favourite->itemtype); @@ -95,14 +97,14 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favcourse = (object)[ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id, - 'anotherfield' => 'cat' - ]; + $favcourse = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favcourse->something = 'something'; $this->expectException('moodle_exception'); $favouritesrepo->add($favcourse); @@ -114,14 +116,17 @@ class favourites_repository_testcase extends advanced_testcase { public function test_add_incomplete_favourite() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Create a favourites repository and favourite a course. + // Create a favourites repository and try to favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favcourse = (object)[ - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid - ]; + $favcourse = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + unset($favcourse->userid); $this->expectException('moodle_exception'); $favouritesrepo->add($favcourse); @@ -134,27 +139,29 @@ class favourites_repository_testcase extends advanced_testcase { $favouritesrepo = new favourites_repository($user1context); $favcourses = []; - $favcourses[] = (object)[ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id, - ]; - $favcourses[] = (object)[ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course2context->instanceid, - 'contextid' => $course2context->id, - ]; + $favcourses[] = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favcourses[] = new favourite( + 'core_course', + 'course', + $course2context->instanceid, + $course2context->id, + $user1context->instanceid + ); + $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this. $favourites = $favouritesrepo->add_all($favcourses); $this->assertInternalType('array', $favourites); $this->assertCount(2, $favourites); foreach ($favourites as $favourite) { - // Verify we get the record back. + // Verify we get the favourite back. + $this->assertInstanceOf(favourite::class, $favourite); $this->assertEquals('core_course', $favourite->component); $this->assertEquals('course', $favourite->itemtype); @@ -177,18 +184,18 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $favourite = $favouritesrepo->add($favourite); // Now, from the repo, get the single favourite we just created, by id. $userfavourite = $favouritesrepo->find($favourite->id); - $this->assertInstanceOf(\stdClass::class, $userfavourite); + $this->assertInstanceOf(favourite::class, $userfavourite); $this->assertObjectHasAttribute('timecreated', $userfavourite); // Try to get a favourite we know doesn't exist. @@ -209,20 +216,20 @@ class favourites_repository_testcase extends advanced_testcase { $this->assertEquals([], $favouritesrepo->find_all()); // Save a favourite for 2 courses, in different areas. - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; - $favourite2 = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'anothertype', - 'itemid' => $course2context->instanceid, - 'contextid' => $course2context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favourite2 = new favourite( + 'core_course', + 'course', + $course2context->instanceid, + $course2context->id, + $user1context->instanceid + ); $favouritesrepo->add($favourite); $favouritesrepo->add($favourite2); @@ -230,6 +237,7 @@ class favourites_repository_testcase extends advanced_testcase { $favourites = $favouritesrepo->find_all(); $this->assertCount(2, $favourites); foreach ($favourites as $fav) { + $this->assertInstanceOf(favourite::class, $fav); $this->assertObjectHasAttribute('id', $fav); $this->assertObjectHasAttribute('timecreated', $fav); } @@ -251,13 +259,13 @@ class favourites_repository_testcase extends advanced_testcase { // Save 10 arbitrary favourites to the repo. foreach (range(1, 10) as $i) { - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $i, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $i, + $course1context->id, + $user1context->instanceid + ); $favouritesrepo->add($favourite); } @@ -285,13 +293,13 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $favouritesrepo->add($favourite); // From the repo, get the list of favourites for the 'core_course/course' area. @@ -321,13 +329,13 @@ class favourites_repository_testcase extends advanced_testcase { // Save 10 arbitrary favourites to the repo. foreach (range(1, 10) as $i) { - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $i, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $i, + $course1context->id, + $user1context->instanceid + ); $favouritesrepo->add($favourite); } @@ -359,20 +367,20 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and add 2 favourites in different areas. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; - $favourite2 = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'anothertype', - 'itemid' => $course2context->instanceid, - 'contextid' => $course2context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favourite2 = new favourite( + 'core_course', + 'anothertype', + $course2context->instanceid, + $course2context->id, + $user1context->instanceid + ); $favouritesrepo->add($favourite); $favouritesrepo->add($favourite2); @@ -390,13 +398,13 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $createdfavourite = $favouritesrepo->add($favourite); // Verify the existence of the favourite in the repo. @@ -411,20 +419,20 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite two courses, in different areas. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; - $favourite2 = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'anothertype', - 'itemid' => $course2context->instanceid, - 'contextid' => $course2context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favourite2 = new favourite( + 'core_course', + 'anothertype', + $course2context->instanceid, + $course2context->id, + $user1context->instanceid + ); $favourite1 = $favouritesrepo->add($favourite); $favourite2 = $favouritesrepo->add($favourite2); @@ -447,19 +455,20 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $favourite1 = $favouritesrepo->add($favourite); + $this->assertNull($favourite1->ordering); // Verify we can update the ordering for 2 favourites. $favourite1->ordering = 1; $favourite1 = $favouritesrepo->update($favourite1); - $this->assertInstanceOf(stdClass::class, $favourite1); + $this->assertInstanceOf(favourite::class, $favourite1); $this->assertAttributeEquals('1', 'ordering', $favourite1); } @@ -468,13 +477,13 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite a course. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $favourite = $favouritesrepo->add($favourite); // Verify the existence of the favourite in the repo. @@ -490,20 +499,20 @@ class favourites_repository_testcase extends advanced_testcase { // Create a favourites repository and favourite two courses, in different areas. $favouritesrepo = new favourites_repository($user1context); - $favourite = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'course', - 'itemid' => $course1context->instanceid, - 'contextid' => $course1context->id - ]; - $favourite2 = (object) [ - 'userid' => $user1context->instanceid, - 'component' => 'core_course', - 'itemtype' => 'anothertype', - 'itemid' => $course2context->instanceid, - 'contextid' => $course2context->id - ]; + $favourite = new favourite( + 'core_course', + 'course', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); + $favourite2 = new favourite( + 'core_course', + 'anothertype', + $course1context->instanceid, + $course1context->id, + $user1context->instanceid + ); $favourite1 = $favouritesrepo->add($favourite); $favourite2 = $favouritesrepo->add($favourite2); diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index d02b353f508..7f0a0945cd3 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -22,7 +22,7 @@ * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - +use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** @@ -63,7 +63,7 @@ class user_favourites_service_testcase extends advanced_testcase { ->getMock(); $mockrepo->expects($this->any()) ->method('add') - ->will($this->returnCallback(function(\stdclass $favourite) use (&$mockstore) { + ->will($this->returnCallback(function(favourite $favourite) use (&$mockstore) { // Mock implementation of repository->add(), where an array is used instead of the DB. // Duplicates are confirmed via the unique key, and exceptions thrown just like a real repo. $key = $favourite->userid . $favourite->component . $favourite->itemtype . $favourite->itemid From cb90b549b65c5bfd8544aa247b90db12163a15a1 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 16 Oct 2018 17:46:26 +0800 Subject: [PATCH 09/13] MDL-63658 core_favourites: singularise the repository class types Instead of favourites_repository we now have favourite_repository, which is more in line with other implementations of this pattern. --- ...epository.php => favourite_repository.php} | 6 ++-- ...pository.php => ifavourite_repository.php} | 6 ++-- .../local/service/user_favourites_service.php | 8 ++--- favourites/classes/services.php | 2 +- favourites/tests/repository_test.php | 36 +++++++++---------- favourites/tests/service_test.php | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) rename favourites/classes/local/repository/{favourites_repository.php => favourite_repository.php} (98%) rename favourites/classes/local/repository/{ifavourites_repository.php => ifavourite_repository.php} (88%) diff --git a/favourites/classes/local/repository/favourites_repository.php b/favourites/classes/local/repository/favourite_repository.php similarity index 98% rename from favourites/classes/local/repository/favourites_repository.php rename to favourites/classes/local/repository/favourite_repository.php index edb5f10ce26..3b8e012ccca 100644 --- a/favourites/classes/local/repository/favourites_repository.php +++ b/favourites/classes/local/repository/favourite_repository.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * Contains the favourites_repository class, responsible for CRUD operations for favourites. + * Contains the favourite_repository class, responsible for CRUD operations for favourites. * * @package core_favourites * @copyright 2018 Jake Dallimore @@ -26,14 +26,14 @@ use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** - * Class favourites_repository. + * Class favourite_repository. * * This class handles persistence of favourites. Favourites from all areas are supported by this repository. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class favourites_repository implements ifavourites_repository { +class favourite_repository implements ifavourite_repository { /** * @var string the name of the table which favourites are stored in. diff --git a/favourites/classes/local/repository/ifavourites_repository.php b/favourites/classes/local/repository/ifavourite_repository.php similarity index 88% rename from favourites/classes/local/repository/ifavourites_repository.php rename to favourites/classes/local/repository/ifavourite_repository.php index f058e1bb3a0..bbabf6625db 100644 --- a/favourites/classes/local/repository/ifavourites_repository.php +++ b/favourites/classes/local/repository/ifavourite_repository.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * Contains the favourites_repository interface. + * Contains the favourite_repository interface. * * @package core_favourites * @copyright 2018 Jake Dallimore @@ -26,12 +26,12 @@ use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** - * The favourites_repository interface, defining additional operations useful to favourite type repositories. + * The favourite_repository interface, defining additional operations useful to favourite type repositories. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -interface ifavourites_repository extends crud_repository { +interface ifavourite_repository extends crud_repository { /** * Find a single favourite, based on it's unique identifiers. * diff --git a/favourites/classes/local/service/user_favourites_service.php b/favourites/classes/local/service/user_favourites_service.php index 9fa806f43e0..485f119a54f 100644 --- a/favourites/classes/local/service/user_favourites_service.php +++ b/favourites/classes/local/service/user_favourites_service.php @@ -32,14 +32,14 @@ defined('MOODLE_INTERNAL') || die(); * This class is responsible for exposing key operations (add, remove, find) and enforces any business logic necessary to validate * authorization/data integrity for these operations. * - * All object persistence is delegated to the ifavourites_repository. + * All object persistence is delegated to the ifavourite_repository. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_favourites_service { - /** @var ifavourites_repository $repo the user favourites repository object. */ + /** @var ifavourite_repository $repo the user favourites repository object. */ protected $repo; /** @var int $userid the id of the user to which this favourites service is scoped. */ @@ -49,9 +49,9 @@ class user_favourites_service { * The user_favourites_service constructor. * * @param \context_user $usercontext The context of the user to which this service operations are scoped. - * @param \core_favourites\local\repository\ifavourites_repository $repository a user favourites repository. + * @param \core_favourites\local\repository\ifavourite_repository $repository a user favourites repository. */ - public function __construct(\context_user $usercontext, \core_favourites\local\repository\ifavourites_repository $repository) { + public function __construct(\context_user $usercontext, \core_favourites\local\repository\ifavourite_repository $repository) { $this->repo = $repository; $this->userid = $usercontext->instanceid; } diff --git a/favourites/classes/services.php b/favourites/classes/services.php index 88c54b4948c..4b4d3569d9d 100644 --- a/favourites/classes/services.php +++ b/favourites/classes/services.php @@ -43,7 +43,7 @@ class services { * @return \core_favourites\local\service\user_favourites_service the service object. */ public static function get_service_for_user_context(\context_user $context) : local\service\user_favourites_service { - return new local\service\user_favourites_service($context, new local\repository\favourites_repository()); + return new local\service\user_favourites_service($context, new local\repository\favourite_repository()); } } diff --git a/favourites/tests/repository_test.php b/favourites/tests/repository_test.php index 951dfa404c0..60c8ed31fd4 100644 --- a/favourites/tests/repository_test.php +++ b/favourites/tests/repository_test.php @@ -25,16 +25,16 @@ defined('MOODLE_INTERNAL') || die(); -use \core_favourites\local\repository\favourites_repository; +use \core_favourites\local\repository\favourite_repository; use \core_favourites\local\entity\favourite; /** - * Test class covering the favourites_repository. + * Test class covering the favourite_repository. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class favourites_repository_testcase extends advanced_testcase { +class favourite_repository_testcase extends advanced_testcase { public function setUp() { $this->resetAfterTest(); @@ -60,7 +60,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favcourse = new favourite( 'core_course', @@ -95,7 +95,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favcourse = new favourite( 'core_course', @@ -117,7 +117,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and try to favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favcourse = new favourite( 'core_course', @@ -136,7 +136,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite several courses. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favcourses = []; $favcourses[] = new favourite( @@ -183,7 +183,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -210,7 +210,7 @@ class favourites_repository_testcase extends advanced_testcase { public function test_find_all() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); // Verify that for an empty repository, find_all returns an empty array. $this->assertEquals([], $favouritesrepo->find_all()); @@ -249,7 +249,7 @@ class favourites_repository_testcase extends advanced_testcase { public function test_find_all_pagination() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); // Verify that for an empty repository, find_all with any combination of page options returns an empty array. $this->assertEquals([], $favouritesrepo->find_all(0, 0)); @@ -292,7 +292,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -319,7 +319,7 @@ class favourites_repository_testcase extends advanced_testcase { public function test_find_by_pagination() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); // Verify that for an empty repository, find_all with any combination of page options returns an empty array. $this->assertEquals([], $favouritesrepo->find_by([], 0, 0)); @@ -366,7 +366,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and add 2 favourites in different areas. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -397,7 +397,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -418,7 +418,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite two courses, in different areas. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -454,7 +454,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -476,7 +476,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite a course. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', @@ -498,7 +498,7 @@ class favourites_repository_testcase extends advanced_testcase { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); // Create a favourites repository and favourite two courses, in different areas. - $favouritesrepo = new favourites_repository($user1context); + $favouritesrepo = new favourite_repository($user1context); $favourite = new favourite( 'core_course', 'course', diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 7f0a0945cd3..b73398ecca6 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -58,7 +58,7 @@ class user_favourites_service_testcase extends advanced_testcase { */ protected function get_mock_repository(array $mockstore) { // This mock will just store data in an array. - $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\ifavourites_repository::class) + $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\ifavourite_repository::class) ->setMethods([]) ->getMock(); $mockrepo->expects($this->any()) From 0551ed3569b7e13ce4af5935cdfcc906431bcdb0 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Tue, 16 Oct 2018 17:53:42 +0800 Subject: [PATCH 10/13] MDL-63658 core_favourites: rename user_favourites_service and services Now: user_favourite_service (singular) and service_factory --- ...service.php => user_favourite_service.php} | 6 +-- .../{services.php => service_factory.php} | 12 +++--- favourites/tests/privacy_test.php | 12 +++--- favourites/tests/service_test.php | 40 +++++++++---------- 4 files changed, 35 insertions(+), 35 deletions(-) rename favourites/classes/local/service/{user_favourites_service.php => user_favourite_service.php} (97%) rename favourites/classes/{services.php => service_factory.php} (76%) diff --git a/favourites/classes/local/service/user_favourites_service.php b/favourites/classes/local/service/user_favourite_service.php similarity index 97% rename from favourites/classes/local/service/user_favourites_service.php rename to favourites/classes/local/service/user_favourite_service.php index 485f119a54f..9364a94dd1e 100644 --- a/favourites/classes/local/service/user_favourites_service.php +++ b/favourites/classes/local/service/user_favourite_service.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Contains the user_favourites_service class, part of the service layer for the favourites subsystem. + * Contains the user_favourite_service class, part of the service layer for the favourites subsystem. * * @package core_favourites * @copyright 2018 Jake Dallimore @@ -37,7 +37,7 @@ defined('MOODLE_INTERNAL') || die(); * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class user_favourites_service { +class user_favourite_service { /** @var ifavourite_repository $repo the user favourites repository object. */ protected $repo; @@ -46,7 +46,7 @@ class user_favourites_service { protected $userid; /** - * The user_favourites_service constructor. + * The user_favourite_service constructor. * * @param \context_user $usercontext The context of the user to which this service operations are scoped. * @param \core_favourites\local\repository\ifavourite_repository $repository a user favourites repository. diff --git a/favourites/classes/services.php b/favourites/classes/service_factory.php similarity index 76% rename from favourites/classes/services.php rename to favourites/classes/service_factory.php index 4b4d3569d9d..f1580033331 100644 --- a/favourites/classes/services.php +++ b/favourites/classes/service_factory.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * Contains the service locators for the favourites subsystem. + * Contains the service_factory, a locator for services for the favourites subsystem. * * Services encapsulate the business logic, and any data manipulation code, and are what clients should interact with. * @@ -27,23 +27,23 @@ namespace core_favourites; defined('MOODLE_INTERNAL') || die(); /** - * Class services, providing functions for location of service objects for the favourites subsystem. + * Class service_factory, providing functions for location of service objects for the favourites subsystem. * * This class is responsible for providing service objects to clients only. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class services { +class service_factory { /** * Returns a basic service object providing operations for user favourites. * * @param \context_user $context the context of the user to which the service should be scoped. - * @return \core_favourites\local\service\user_favourites_service the service object. + * @return \core_favourites\local\service\user_favourite_service the service object. */ - public static function get_service_for_user_context(\context_user $context) : local\service\user_favourites_service { - return new local\service\user_favourites_service($context, new local\repository\favourite_repository()); + public static function get_service_for_user_context(\context_user $context) : local\service\user_favourite_service { + return new local\service\user_favourite_service($context, new local\repository\favourite_repository()); } } diff --git a/favourites/tests/privacy_test.php b/favourites/tests/privacy_test.php index 1d8c85b29bd..409282bae80 100644 --- a/favourites/tests/privacy_test.php +++ b/favourites/tests/privacy_test.php @@ -62,8 +62,8 @@ class privacy_test extends provider_testcase { list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); // Favourite 2 courses for user1 and 1 course for user2, all at the site context. - $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); - $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); $systemcontext = context_system::instance(); $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $systemcontext); $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext); @@ -94,8 +94,8 @@ class privacy_test extends provider_testcase { list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); // Favourite 2 courses for user1 and 1 course for user2, all at the user context. - $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); - $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context); $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $user1context); $ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $user2context); @@ -115,8 +115,8 @@ class privacy_test extends provider_testcase { list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); // Favourite 2 course modules for user1 and 1 course module for user2 all in course 1 context. - $ufservice1 = \core_favourites\services::get_service_for_user_context($user1context); - $ufservice2 = \core_favourites\services::get_service_for_user_context($user2context); + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); $ufservice1->create_favourite('core_course', 'modules', 1, $course1context); $ufservice1->create_favourite('core_course', 'modules', 2, $course1context); $ufservice2->create_favourite('core_course', 'modules', 3, $course1context); diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index b73398ecca6..277b8482b92 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -26,12 +26,12 @@ use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** - * Test class covering the user_favourites_service within the service layer of favourites. + * Test class covering the user_favourite_service within the service layer of favourites. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class user_favourites_service_testcase extends advanced_testcase { +class user_favourite_service_testcase extends advanced_testcase { public function setUp() { $this->resetAfterTest(); @@ -140,12 +140,12 @@ class user_favourites_service_testcase extends advanced_testcase { } /** - * Test getting a user_favourites_service from the static locator. + * Test getting a user_favourite_service from the static locator. */ public function test_get_service_for_user_context() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - $userservice = \core_favourites\services::get_service_for_user_context($user1context); - $this->assertInstanceOf(\core_favourites\local\service\user_favourites_service::class, $userservice); + $userservice = \core_favourites\service_factory::get_service_for_user_context($user1context); + $this->assertInstanceOf(\core_favourites\local\service\user_favourite_service::class, $userservice); } /** @@ -154,9 +154,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_create_favourite_basic() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for a user. + // Get a user_favourite_service for a user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Favourite a course. $favourite1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -173,9 +173,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_create_favourite_nonexistent_component() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for the user. + // Get a user_favourite_service for the user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Try to favourite something in a non-existent component. $this->expectException('moodle_exception'); @@ -188,9 +188,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_find_favourites_by_type_single_user() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for the user. + // Get a user_favourite_service for the user. $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. - $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Favourite 2 courses, in separate areas. $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -214,10 +214,10 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_find_favourites_by_type_multiple_users() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for 2 users. + // Get a user_favourite_service for 2 users. $repo = $this->get_mock_repository([]); - $user1service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); - $user2service = new \core_favourites\local\service\user_favourites_service($user2context, $repo); + $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); + $user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo); // Now, as each user, favourite the same course. $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); @@ -241,9 +241,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_find_favourites_by_type_nonexistent_component() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for the user. + // Get a user_favourite_service for the user. $repo = $this->get_mock_repository([]); - $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Verify we get an exception if we try to search for favourites in an invalid component. $this->expectException('moodle_exception'); @@ -256,9 +256,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_find_favourites_by_type_pagination() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for the user. + // Get a user_favourite_service for the user. $repo = $this->get_mock_repository([]); - $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Favourite 10 arbitrary items. foreach (range(1, 10) as $i) { @@ -287,9 +287,9 @@ class user_favourites_service_testcase extends advanced_testcase { public function test_delete_favourite_basic() { list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); - // Get a user_favourites_service for the user. + // Get a user_favourite_service for the user. $repo = $this->get_mock_repository([]); - $service = new \core_favourites\local\service\user_favourites_service($user1context, $repo); + $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); // Favourite a course. $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); From b7a3ec6f8dbe2f0ace9a3cb5ec6eaf4fbfac7bd9 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Wed, 17 Oct 2018 08:59:53 +0800 Subject: [PATCH 11/13] MDL-63658 core_favourites: consolidate interfaces and rename Let's worry about reuse of the crud code later, when that requirement arises, so moved that into the ifavourite_repository interface, and then renamed it favourite_repository_interface. --- .../local/repository/favourite_repository.php | 2 +- ...php => favourite_repository_interface.php} | 18 ++++++-- .../repository/ifavourite_repository.php | 46 ------------------- .../local/service/user_favourite_service.php | 9 ++-- favourites/tests/service_test.php | 2 +- 5 files changed, 22 insertions(+), 55 deletions(-) rename favourites/classes/local/repository/{crud_repository.php => favourite_repository_interface.php} (80%) delete mode 100644 favourites/classes/local/repository/ifavourite_repository.php diff --git a/favourites/classes/local/repository/favourite_repository.php b/favourites/classes/local/repository/favourite_repository.php index 3b8e012ccca..2b94f91caec 100644 --- a/favourites/classes/local/repository/favourite_repository.php +++ b/favourites/classes/local/repository/favourite_repository.php @@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die(); * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class favourite_repository implements ifavourite_repository { +class favourite_repository implements favourite_repository_interface { /** * @var string the name of the table which favourites are stored in. diff --git a/favourites/classes/local/repository/crud_repository.php b/favourites/classes/local/repository/favourite_repository_interface.php similarity index 80% rename from favourites/classes/local/repository/crud_repository.php rename to favourites/classes/local/repository/favourite_repository_interface.php index 06033f5b3cc..138872a974a 100644 --- a/favourites/classes/local/repository/crud_repository.php +++ b/favourites/classes/local/repository/favourite_repository_interface.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * Contains the crud_repository interface. + * Contains the favourite_repository interface. * * @package core_favourites * @copyright 2018 Jake Dallimore @@ -26,9 +26,9 @@ use \core_favourites\local\entity\favourite; defined('MOODLE_INTERNAL') || die(); /** - * The crud_repository interface, defining the basic CRUD operations for any repository types within core_favourites. + * The favourite_repository interface, defining the basic CRUD operations for favourite type items within core_favourites. */ -interface crud_repository { +interface favourite_repository_interface { /** * Add one item to this repository. * @@ -102,4 +102,16 @@ interface crud_repository { * @return void */ public function delete(int $id); + + /** + * Find a single favourite, based on it's unique identifiers. + * + * @param int $userid the id of the user to which the favourite belongs. + * @param string $component the frankenstyle component name. + * @param string $itemtype the type of the favourited item. + * @param int $itemid the id of the item which was favourited (not the favourite's id). + * @param int $contextid the contextid of the item which was favourited. + * @return favourite the favourite. + */ + public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : favourite; } diff --git a/favourites/classes/local/repository/ifavourite_repository.php b/favourites/classes/local/repository/ifavourite_repository.php deleted file mode 100644 index bbabf6625db..00000000000 --- a/favourites/classes/local/repository/ifavourite_repository.php +++ /dev/null @@ -1,46 +0,0 @@ -. -/** - * Contains the favourite_repository interface. - * - * @package core_favourites - * @copyright 2018 Jake Dallimore - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace core_favourites\local\repository; -use \core_favourites\local\entity\favourite; - -defined('MOODLE_INTERNAL') || die(); - -/** - * The favourite_repository interface, defining additional operations useful to favourite type repositories. - * - * @copyright 2018 Jake Dallimore - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -interface ifavourite_repository extends crud_repository { - /** - * Find a single favourite, based on it's unique identifiers. - * - * @param int $userid the id of the user to which the favourite belongs. - * @param string $component the frankenstyle component name. - * @param string $itemtype the type of the favourited item. - * @param int $itemid the id of the item which was favourited (not the favourite's id). - * @param int $contextid the contextid of the item which was favourited. - * @return favourite the favourite. - */ - public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : favourite; -} diff --git a/favourites/classes/local/service/user_favourite_service.php b/favourites/classes/local/service/user_favourite_service.php index 9364a94dd1e..15dc06b3f24 100644 --- a/favourites/classes/local/service/user_favourite_service.php +++ b/favourites/classes/local/service/user_favourite_service.php @@ -23,6 +23,7 @@ */ namespace core_favourites\local\service; use \core_favourites\local\entity\favourite; +use \core_favourites\local\repository\favourite_repository_interface; defined('MOODLE_INTERNAL') || die(); @@ -32,14 +33,14 @@ defined('MOODLE_INTERNAL') || die(); * This class is responsible for exposing key operations (add, remove, find) and enforces any business logic necessary to validate * authorization/data integrity for these operations. * - * All object persistence is delegated to the ifavourite_repository. + * All object persistence is delegated to the favourite_repository_interface object. * * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_favourite_service { - /** @var ifavourite_repository $repo the user favourites repository object. */ + /** @var favourite_repository_interface $repo the favourite repository object. */ protected $repo; /** @var int $userid the id of the user to which this favourites service is scoped. */ @@ -49,9 +50,9 @@ class user_favourite_service { * The user_favourite_service constructor. * * @param \context_user $usercontext The context of the user to which this service operations are scoped. - * @param \core_favourites\local\repository\ifavourite_repository $repository a user favourites repository. + * @param \core_favourites\local\repository\favourite_repository_interface $repository a favourites repository. */ - public function __construct(\context_user $usercontext, \core_favourites\local\repository\ifavourite_repository $repository) { + public function __construct(\context_user $usercontext, favourite_repository_interface $repository) { $this->repo = $repository; $this->userid = $usercontext->instanceid; } diff --git a/favourites/tests/service_test.php b/favourites/tests/service_test.php index 277b8482b92..6600c2dccc6 100644 --- a/favourites/tests/service_test.php +++ b/favourites/tests/service_test.php @@ -58,7 +58,7 @@ class user_favourite_service_testcase extends advanced_testcase { */ protected function get_mock_repository(array $mockstore) { // This mock will just store data in an array. - $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\ifavourite_repository::class) + $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\favourite_repository_interface::class) ->setMethods([]) ->getMock(); $mockrepo->expects($this->any()) From 3a1ece149fd37b6c62dbfeb14ef509de740f7be5 Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Wed, 17 Oct 2018 17:12:27 +0800 Subject: [PATCH 12/13] MDL-63658 core: new method get_component_names() added to core --- lib/classes/component.php | 27 +++++++++++++++++++++++++++ lib/tests/component_test.php | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/lib/classes/component.php b/lib/classes/component.php index 9284f7d5a01..11ce5a6d552 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -1278,4 +1278,31 @@ $cache = '.var_export($cache, true).'; } return $components; } + + /** + * Returns a list of frankenstyle component names. + * + * E.g. + * [ + * 'core_course', + * 'core_message', + * 'mod_assign', + * ... + * ] + * @return array the list of frankenstyle component names. + */ + public static function get_component_names() : array { + $componentnames = []; + // Get all plugins. + foreach (self::get_plugin_types() as $plugintype => $typedir) { + foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) { + $componentnames[] = $plugintype . '_' . $pluginname; + } + } + // Get all subsystems. + foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) { + $componentnames[] = 'core_' . $subsystemname; + } + return $componentnames; + } } diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 625279d1797..2b79591082b 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -805,4 +805,31 @@ class core_component_testcase extends advanced_testcase { $this->assertEquals($componentslist['mod']['mod_forum'], $CFG->dirroot . '/mod/forum'); $this->assertEquals($componentslist['tool']['tool_usertours'], $CFG->dirroot . '/' . $CFG->admin . '/tool/usertours'); } + + /** + * Test the get_component_names() method. + */ + public function test_get_component_names() { + global $CFG; + $componentnames = \core_component::get_component_names(); + + // We should have an entry for each plugin type. + $plugintypes = \core_component::get_plugin_types(); + $numplugintypes = 0; + foreach ($plugintypes as $type => $typedir) { + foreach (\core_component::get_plugin_list($type) as $plugin) { + $numplugintypes++; + } + } + // And an entry for each core subsystem. + $numcomponents = $numplugintypes + count(\core_component::get_core_subsystems()); + + $this->assertEquals($numcomponents, count($componentnames)); + + // Check a few of the known plugin types to confirm their presence at their respective type index. + $this->assertContains('core_comment', $componentnames); + $this->assertContains('mod_forum', $componentnames); + $this->assertContains('tool_usertours', $componentnames); + $this->assertContains('core_favourites', $componentnames); + } } From 8d9cd27b02384b1c62ea1f5e2077850562ce6cec Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Wed, 17 Oct 2018 17:12:56 +0800 Subject: [PATCH 13/13] MDL-63658 core_favourites: let service object use get_component_names This was using a helper, but now we have the function in core to achieve the same thing, so use this instead. --- .../local/service/user_favourite_service.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/favourites/classes/local/service/user_favourite_service.php b/favourites/classes/local/service/user_favourite_service.php index 15dc06b3f24..d3b6f6d99b9 100644 --- a/favourites/classes/local/service/user_favourite_service.php +++ b/favourites/classes/local/service/user_favourite_service.php @@ -57,17 +57,6 @@ class user_favourite_service { $this->userid = $usercontext->instanceid; } - /** - * Helper, returning a flat list of component names. - * - * @return array the array of component names. - */ - protected function get_component_list() { - return array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) { - return array_merge($carry, $item); - }, [])); - } - /** * Favourite an item defined by itemid/context, in the area defined by component/itemtype. * @@ -84,7 +73,7 @@ class user_favourite_service { // Access: Any component can ask to favourite something, we can't verify access to that 'something' here though. // Validate the component name. - if (!in_array($component, $this->get_component_list())) { + if (!in_array($component, \core_component::get_component_names())) { throw new \moodle_exception("Invalid component name '$component'"); } @@ -107,7 +96,7 @@ class user_favourite_service { * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors. */ public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array { - if (!in_array($component, $this->get_component_list())) { + if (!in_array($component, \core_component::get_component_names())) { throw new \moodle_exception("Invalid component name '$component'"); } return $this->repo->find_by( @@ -133,7 +122,7 @@ class user_favourite_service { * @throws \moodle_exception if the user does not control the favourite, or it doesn't exist. */ public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context) { - if (!in_array($component, $this->get_component_list())) { + if (!in_array($component, \core_component::get_component_names())) { throw new \moodle_exception("Invalid component name '$component'"); }