first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the favourite class, each instance being a representation of a DB row for the 'favourite' table.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @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 <jrhdallimore@gmail.com>
* @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;
/** @var string $uniquekey favourite unique key.*/
public $uniquekey;
/**
* 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;
}
}
@@ -0,0 +1,324 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the favourite_repository class, responsible for CRUD operations for favourites.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @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();
/**
* Class favourite_repository.
*
* This class handles persistence of favourites. Favourites from all areas are supported by this repository.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class favourite_repository implements favourite_repository_interface {
/**
* @var string the name of the table which favourites are stored in.
*/
protected $favouritetable = 'favourite';
/**
* Get a favourite object, based on a full record.
* @param \stdClass $record the record we wish to hydrate.
* @return favourite the favourite record.
*/
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;
}
/**
* Basic validation, confirming we have the minimum field set needed to save a record to the store.
*
* @param favourite $favourite the favourite record to validate.
* @throws \moodle_exception if the supplied favourite has missing or unsupported fields.
*/
protected function validate(favourite $favourite) {
$favourite = (array)$favourite;
// 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,
'timecreated' => false,
'timemodified' => false,
'id' => false,
'uniquekey' => 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) . "'.");
}
}
/**
* Add a favourite to the repository.
*
* @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 $favourite): favourite {
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);
$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 favourite the favourite.
* @throws \dml_exception if any database errors are encountered.
*/
public function find(int $id): favourite {
global $DB;
$record = $DB->get_record($this->favouritetable, ['id' => $id], '*', MUST_EXIST);
return $this->get_favourite_from_record($record);
}
/**
* 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(int $limitfrom = 0, int $limitnum = 0): array {
global $DB;
$records = $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum);
return $this->get_list_of_favourites_from_records($records);
}
/**
* Return all items matching the supplied criteria (a [key => value,..] list).
*
* @param array $criteria the list of key/value(s) 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, int $limitfrom = 0, int $limitnum = 0): array {
global $DB;
$conditions = [];
$params = [];
foreach ($criteria as $field => $value) {
if (is_array($value) && count($value)) {
list($insql, $inparams) = $DB->get_in_or_equal($value, SQL_PARAMS_NAMED);
$conditions[] = "$field $insql";
$params = array_merge($params, $inparams);
} else {
$conditions[] = "$field = :$field";
$params = array_merge($params, [$field => $value]);
}
}
$records = $DB->get_records_select($this->favouritetable, implode(' AND ', $conditions), $params,
'', '*', $limitfrom, $limitnum);
return $this->get_list_of_favourites_from_records($records);
}
/**
* 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 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): favourite {
global $DB;
// Favourites model: We know that only one favourite can exist based on these properties.
$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);
}
/**
* 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]);
}
/**
* Check whether an item exists in this repository, based on the specified criteria.
*
* @param array $criteria the list of key/value criteria pairs.
* @return bool true if the favourite exists, false otherwise.
* @throws \dml_exception if any database errors are encountered.
*/
public function exists_by(array $criteria): bool {
global $DB;
return $DB->record_exists($this->favouritetable, $criteria);
}
/**
* Update a 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 $favourite): favourite {
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]);
}
/**
* Delete all favourites matching the specified criteria.
*
* @param array $criteria the list of key/value criteria pairs.
* @throws \dml_exception if any database errors are encountered.
*/
public function delete_by(array $criteria) {
global $DB;
$DB->delete_records($this->favouritetable, $criteria);
}
/**
* 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);
}
/**
* 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);
}
}
@@ -0,0 +1,141 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the favourite_repository interface.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @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 the basic CRUD operations for favourite type items within core_favourites.
*/
interface favourite_repository_interface {
/**
* Add one item to this repository.
*
* @param favourite $item the item to add.
* @return favourite the item which was added.
*/
public function add(favourite $item): favourite;
/**
* 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 favourite the item.
*/
public function find(int $id): favourite;
/**
* 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(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, int $limitfrom = 0, int $limitnum = 0): 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;
/**
* Check whether an item exists in this repository, based on the specified criteria.
*
* @param array $criteria the list of key/value criteria pairs.
* @return bool true if the favourite exists, false otherwise.
*/
public function exists_by(array $criteria): bool;
/**
* Return the total number of items in this repository.
*
* @return int the total number of items.
*/
public function count(): int;
/**
* Return the number of favourites matching the specified criteria.
*
* @param array $criteria the list of key/value criteria pairs.
* @return int the number of favourites matching the criteria.
*/
public function count_by(array $criteria): int;
/**
* Update an item within this repository.
*
* @param favourite $item the item to update.
* @return favourite the updated item.
*/
public function update(favourite $item): favourite;
/**
* Delete an item by id.
*
* @param int $id the id of the item to delete.
* @return void
*/
public function delete(int $id);
/**
* Delete all favourites matching the specified criteria.
*
* @param array $criteria the list of key/value criteria pairs.
* @return void.
*/
public function delete_by(array $criteria);
/**
* 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;
}
@@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the component_favourite_service class, part of the service layer for the favourites subsystem.
*
* @package core_favourites
* @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_favourites\local\service;
use \core_favourites\local\repository\favourite_repository_interface;
defined('MOODLE_INTERNAL') || die();
/**
* Class service, providing an single API for interacting with the favourites subsystem, for all favourites of a specific component.
*
* This class provides operations which can be applied to favourites within a component, based on type and context identifiers.
*
* All object persistence is delegated to the favourite_repository_interface object.
*
* @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class component_favourite_service {
/** @var favourite_repository_interface $repo the favourite repository object. */
protected $repo;
/** @var int $component the frankenstyle component name to which this favourites service is scoped. */
protected $component;
/**
* The component_favourite_service constructor.
*
* @param string $component The frankenstyle name of the component to which this service operations are scoped.
* @param \core_favourites\local\repository\favourite_repository_interface $repository a favourites repository.
* @throws \moodle_exception if the component name is invalid.
*/
public function __construct(string $component, favourite_repository_interface $repository) {
if (!in_array($component, \core_component::get_component_names())) {
throw new \moodle_exception("Invalid component name '$component'");
}
$this->repo = $repository;
$this->component = $component;
}
/**
* Delete a collection of favourites by type and item, and optionally for a given context.
*
* E.g. delete all favourites of type 'message_conversations' for the conversation '11' and in the CONTEXT_COURSE context.
*
* @param string $itemtype the type of the favourited items.
* @param int $itemid the id of the item to which the favourites relate
* @param \context $context the context of the items which were favourited.
*/
public function delete_favourites_by_type_and_item(string $itemtype, int $itemid, \context $context = null) {
$criteria = ['component' => $this->component, 'itemtype' => $itemtype, 'itemid' => $itemid] +
($context ? ['contextid' => $context->id] : []);
$this->repo->delete_by($criteria);
}
}
@@ -0,0 +1,281 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the user_favourite_service class, part of the service layer for the favourites subsystem.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_favourites\local\service;
use \core_favourites\local\entity\favourite;
use \core_favourites\local\repository\favourite_repository_interface;
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 favourite_repository_interface object.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_favourite_service {
/** @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. */
protected $userid;
/**
* 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\favourite_repository_interface $repository a favourites repository.
*/
public function __construct(\context_user $usercontext, favourite_repository_interface $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 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): favourite {
// 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, \core_component::get_component_names())) {
throw new \moodle_exception("Invalid component name '$component'");
}
$favourite = new favourite($component, $itemtype, $itemid, $context->id, $this->userid);
$favourite->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.
* @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, int $limitfrom = 0, int $limitnum = 0): array {
if (!in_array($component, \core_component::get_component_names())) {
throw new \moodle_exception("Invalid component name '$component'");
}
return $this->repo->find_by(
[
'userid' => $this->userid,
'component' => $component,
'itemtype' => $itemtype
],
$limitfrom,
$limitnum
);
}
/**
* Find a list of favourites, by multiple types within a component.
*
* E.g. "Find all favourites in the activity chooser" might result in:
* $favcourses = find_all_favourites('core_course', ['contentitem_mod_assign');
*
* @param string $component the frankenstyle component name.
* @param array $itemtypes optional 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_all_favourites(string $component, array $itemtypes = [], int $limitfrom = 0, int $limitnum = 0): array {
if (!in_array($component, \core_component::get_component_names())) {
throw new \moodle_exception("Invalid component name '$component'");
}
$params = [
'userid' => $this->userid,
'component' => $component,
];
if ($itemtypes) {
$params['itemtype'] = $itemtypes;
}
return $this->repo->find_by(
$params,
$limitfrom,
$limitnum
);
}
/**
* Returns the SQL required to include favourite information for a given component/itemtype combination.
*
* Generally, find_favourites_by_type() is the recommended way to fetch favourites.
*
* This method is used to include favourite information in external queries, for items identified by their
* component and itemtype, matching itemid to the $joinitemid, and for the user to which this service is scoped.
*
* It uses a LEFT JOIN to preserve the original records. If you wish to restrict your records, please consider using a
* "WHERE {$tablealias}.id IS NOT NULL" in your query.
*
* Example usage:
*
* list($sql, $params) = $service->get_join_sql_by_type('core_message', 'message_conversations', 'myfavouritetablealias',
* 'conv.id');
* Results in $sql:
* "LEFT JOIN {favourite} fav
* ON fav.component = :favouritecomponent
* AND fav.itemtype = :favouriteitemtype
* AND fav.userid = 1234
* AND fav.itemid = conv.id"
* and $params:
* ['favouritecomponent' => 'core_message', 'favouriteitemtype' => 'message_conversations']
*
* @param string $component the frankenstyle component name.
* @param string $itemtype the type of the favourited item.
* @param string $tablealias the desired alias for the favourites table.
* @param string $joinitemid the table and column identifier which the itemid is joined to. E.g. conversation.id.
* @return array the list of sql and params, in the format [$sql, $params].
*/
public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid): array {
$sql = " LEFT JOIN {favourite} {$tablealias}
ON {$tablealias}.component = :favouritecomponent
AND {$tablealias}.itemtype = :favouriteitemtype
AND {$tablealias}.userid = {$this->userid}
AND {$tablealias}.itemid = {$joinitemid} ";
$params = [
'favouritecomponent' => $component,
'favouriteitemtype' => $itemtype,
];
return [$sql, $params];
}
/**
* 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, \core_component::get_component_names())) {
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);
}
/**
* Check whether an item has been marked as a favourite in the respective area.
*
* @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.
* @return bool true if the item is favourited, false otherwise.
*/
public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context): bool {
return $this->repo->exists_by(
[
'userid' => $this->userid,
'component' => $component,
'itemtype' => $itemtype,
'itemid' => $itemid,
'contextid' => $context->id
]
);
}
/**
* Get the favourite.
*
* @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.
* @return favourite|null
*/
public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) {
try {
return $this->repo->find_favourite(
$this->userid,
$component,
$itemtype,
$itemid,
$context->id
);
} catch (\dml_missing_record_exception $e) {
return null;
}
}
/**
* Count the favourite by item type.
*
* @param string $component the frankenstyle component name.
* @param string $itemtype the type of the favourited item.
* @param \context|null $context the context of the item which was favourited.
* @return int
*/
public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) {
$criteria = [
'userid' => $this->userid,
'component' => $component,
'itemtype' => $itemtype
];
if ($context) {
$criteria['contextid'] = $context->id;
}
return $this->repo->count_by($criteria);
}
}
+270
View File
@@ -0,0 +1,270 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy class for requesting user data for the favourites subsystem.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @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;
use \core_privacy\local\request\transform;
/**
* Privacy class for requesting user data.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @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,
\core_privacy\local\request\shared_userlist_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";
$params = ['userid' => $userid, 'component' => $component];
if (!is_null($itemtype)) {
$sql .= " AND itemtype = :itemtype";
$params['itemtype'] = $itemtype;
}
$contextlist->add_from_sql($sql, $params);
}
/**
* Add users to a userlist who have favourites within the specified context.
*
* @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
* @param string $itemtype the type of the favourited items.
* @return void
*/
public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist,
string $itemtype = null) {
if (empty($userlist)) {
return;
}
$params = [
'contextid' => $userlist->get_context()->id,
'component' => $userlist->get_component()
];
$sql = "SELECT userid
FROM {favourite}
WHERE contextid = :contextid
AND component = :component";
if (!is_null($itemtype)) {
$sql .= " AND itemtype = :itemtype";
$params['itemtype'] = $itemtype;
}
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Get favourites data for the specified user in the specified component, item type and item ID.
*
* @param int $userid The id of the user in scope.
* @param \context $context The context to which data is scoped.
* @param string $component The favourite's component name.
* @param string $itemtype The favourite's item type.
* @param int $itemid The favourite's item ID.
* @return array|null
*/
public static function get_favourites_info_for_user(int $userid, \context $context,
string $component, string $itemtype, int $itemid) {
global $DB;
$params = [
'userid' => $userid,
'component' => $component,
'itemtype' => $itemtype,
'itemid' => $itemid,
'contextid' => $context->id
];
if (!$favourited = $DB->get_record('favourite', $params)) {
return;
}
return [
'starred' => transform::yesno(true),
'ordering' => $favourited->ordering,
'timecreated' => transform::datetime($favourited->timecreated),
'timemodified' => transform::datetime($favourited->timemodified)
];
}
/**
* 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.
* @param int $itemid Optional itemid associated with component.
* @throws \dml_exception if any errors are encountered during deletion.
*/
public static function delete_favourites_for_all_users(\context $context, string $component, string $itemtype,
int $itemid = 0) {
global $DB;
$params = [
'component' => $component,
'itemtype' => $itemtype,
'contextid' => $context->id
];
$select = "component = :component AND itemtype =:itemtype AND contextid = :contextid";
if (!empty($itemid)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $itemid;
}
$DB->delete_records_select('favourite', $select, $params);
}
/**
* Delete all favourites for the specified users in the specified context, component area and item type.
*
* @param \core_privacy\local\request\approved_userlist $userlist The approved contexts and user information
* to delete information for.
* @param string $itemtype The favourite's itemtype.
* @param int $itemid Optional itemid associated with component.
* @throws \dml_exception if any errors are encountered during deletion.
*/
public static function delete_favourites_for_userlist(\core_privacy\local\request\approved_userlist $userlist,
string $itemtype, int $itemid = 0) {
global $DB;
$userids = $userlist->get_userids();
if (empty($userids)) {
return;
}
$context = $userlist->get_context();
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$params = [
'component' => $userlist->get_component(),
'itemtype' => $itemtype,
'contextid' => $context->id
];
$params += $userparams;
$select = "component = :component AND itemtype = :itemtype AND contextid = :contextid AND userid $usersql";
if (!empty($itemid)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $itemid;
}
$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 The favourite's component name.
* @param string $itemtype The favourite's itemtype.
* @param int $itemid Optional itemid associated with component.
* @throws \coding_exception
* @throws \dml_exception
*/
public static function delete_favourites_for_user(approved_contextlist $contextlist, string $component, string $itemtype,
int $itemid = 0) {
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";
if (!empty($itemid)) {
$select .= " AND itemid = :itemid";
$params['itemid'] = $itemid;
}
$DB->delete_records_select('favourite', $select, $params);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the 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.
*
* @package core_favourites
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_favourites;
defined('MOODLE_INTERNAL') || die();
/**
* 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 <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
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_favourite_service the service object.
*/
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());
}
/**
* Returns a basic service object providing operations for favourites belonging to a given component.
*
* @param string $component frankenstyle component name.
* @return local\service\component_favourite_service the service object.
*/
public static function get_service_for_component(string $component): local\service\component_favourite_service {
return new local\service\component_favourite_service($component, new local\repository\favourite_repository());
}
}
@@ -0,0 +1,283 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_favourites;
use core_favourites\local\entity\favourite;
/**
* Test class covering the component_favourite_service within the service layer of favourites.
*
* @package core_favourites
* @category test
* @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class component_favourite_service_test extends \advanced_testcase {
public function setUp(): void {
$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\repository\favourite_repository_interface::class)
->onlyMethods([])
->getMock();
$mockrepo->expects($this->any())
->method('add')
->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
. $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, 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;
if (array_diff_assoc($criteria, $mockrowarr) == []) {
$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;
})
);
$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_assoc($crit, $fakerowarr) == []) {
return $fakerow;
}
}
throw new \dml_missing_record_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('count_by')
->will($this->returnCallback(function(array $criteria) use (&$mockstore) {
$count = 0;
// 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_assoc($criteria, $mockrowarr) == []) {
$count++;
}
}
return $count;
})
);
$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]);
}
}
})
);
$mockrepo->expects($this->any())
->method('delete_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_assoc($criteria, $mockrowarr) == []) {
unset($mockstore[$index]);
}
}
})
);
$mockrepo->expects($this->any())
->method('exists_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;
echo "Here";
if (array_diff_assoc($criteria, $mockrowarr) == []) {
return true;
}
}
return false;
})
);
return $mockrepo;
}
/**
* Test confirming the deletion of favourites by type and item, but with no optional context filter provided.
*/
public function test_delete_favourites_by_type_and_item(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for each user.
$repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB.
$user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
$user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo);
// Favourite both courses for both users.
$fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$fav3 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context);
$fav4 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context);
$this->assertTrue($repo->exists($fav1->id));
$this->assertTrue($repo->exists($fav2->id));
$this->assertTrue($repo->exists($fav3->id));
$this->assertTrue($repo->exists($fav4->id));
// Favourite something else arbitrarily.
$fav5 = $user2service->create_favourite('core_user', 'course', $course2context->instanceid, $course2context);
$fav6 = $user2service->create_favourite('core_course', 'whatnow', $course2context->instanceid, $course2context);
// Get a component_favourite_service to perform the type based deletion.
$service = new \core_favourites\local\service\component_favourite_service('core_course', $repo);
// Delete all 'course' type favourites (for all users who have favourited course1).
$service->delete_favourites_by_type_and_item('course', $course1context->instanceid);
// Delete all 'course' type favourites (for all users who have favourited course2).
$service->delete_favourites_by_type_and_item('course', $course2context->instanceid);
// Verify the favourites don't exist.
$this->assertFalse($repo->exists($fav1->id));
$this->assertFalse($repo->exists($fav2->id));
$this->assertFalse($repo->exists($fav3->id));
$this->assertFalse($repo->exists($fav4->id));
// Verify favourites of other types or for other components are not affected.
$this->assertTrue($repo->exists($fav5->id));
$this->assertTrue($repo->exists($fav6->id));
// Try to delete favourites for a type which we know doesn't exist. Verify no exception.
$this->assertNull($service->delete_favourites_by_type_and_item('course', $course1context->instanceid));
}
/**
* Test confirming the deletion of favourites by type and item and with the optional context filter provided.
*/
public function test_delete_favourites_by_type_and_item_with_context(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for each user.
$repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB.
$user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
$user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo);
// Favourite both courses for both users.
$fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$fav3 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context);
$fav4 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context);
$this->assertTrue($repo->exists($fav1->id));
$this->assertTrue($repo->exists($fav2->id));
$this->assertTrue($repo->exists($fav3->id));
$this->assertTrue($repo->exists($fav4->id));
// Favourite something else arbitrarily.
$fav5 = $user2service->create_favourite('core_user', 'course', $course1context->instanceid, $course1context);
$fav6 = $user2service->create_favourite('core_course', 'whatnow', $course1context->instanceid, $course1context);
// Favourite the courses again, but this time in another context.
$fav7 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, \context_system::instance());
$fav8 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, \context_system::instance());
$fav9 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, \context_system::instance());
$fav10 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, \context_system::instance());
// Get a component_favourite_service to perform the type based deletion.
$service = new \core_favourites\local\service\component_favourite_service('core_course', $repo);
// Delete all 'course' type favourites (for all users at ONLY the course 1 context).
$service->delete_favourites_by_type_and_item('course', $course1context->instanceid, $course1context);
// Verify the favourites for course 1 context don't exist.
$this->assertFalse($repo->exists($fav1->id));
$this->assertFalse($repo->exists($fav2->id));
// Verify the favourites for the same component and type, but NOT for the same contextid and unaffected.
$this->assertTrue($repo->exists($fav3->id));
$this->assertTrue($repo->exists($fav4->id));
// Verify favourites of other types or for other components are not affected.
$this->assertTrue($repo->exists($fav5->id));
$this->assertTrue($repo->exists($fav6->id));
// Verify the course favourite at the system context are unaffected.
$this->assertTrue($repo->exists($fav7->id));
$this->assertTrue($repo->exists($fav8->id));
$this->assertTrue($repo->exists($fav9->id));
$this->assertTrue($repo->exists($fav10->id));
// Try to delete favourites for a type which we know doesn't exist. Verify no exception.
$this->assertNull($service->delete_favourites_by_type_and_item('course', $course1context->instanceid, $course1context));
}
}
+305
View File
@@ -0,0 +1,305 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy tests for core_favourites.
*
* @package core_favourites
* @category test
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_favourites\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\tests\provider_testcase;
use core_favourites\privacy\provider;
use core_privacy\local\request\transform;
/**
* Unit tests for favourites/classes/privacy/provider
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
public function setUp(): void {
$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(): void {
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\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', 'courses', $course1context->instanceid, $systemcontext);
$ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext);
$ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext);
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
// 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', 'courses', $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', 'courses');
// 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', 'courses');
$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(): void {
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\service_factory::get_service_for_user_context($user1context);
$ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context);
$ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $user1context);
$ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $user1context);
$ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $user2context);
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
// 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', 'courses');
// 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', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
}
public function test_delete_favourites_for_all_users(): void {
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\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);
// 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'));
}
/**
* Test confirming that user ID's of favourited items can be added to the userlist.
*/
public function test_add_userids_for_context(): void {
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\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', 'courses', $course1context->instanceid, $systemcontext);
$ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext);
$ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext);
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
// 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', 'courses', $course1context->instanceid, $user1context);
// Now, ask the favourites privacy api to export userids for favourites of the type we just created, in the system context.
$userlist = new \core_privacy\local\request\userlist($systemcontext, 'core_course');
provider::add_userids_for_context($userlist, 'courses');
// Verify we have two userids in the list for system context.
$this->assertCount(2, $userlist->get_userids());
$expected = [
$user1->id,
$user2->id
];
$this->assertEqualsCanonicalizing($expected, $userlist->get_userids());
// Ask the favourites privacy api to export userids for favourites of the type we just created, in the user1 context.
$userlist = new \core_privacy\local\request\userlist($user1context, 'core_course');
provider::add_userids_for_context($userlist, 'courses');
// Verify we have one userid in the list for user1 context.
$this->assertCount(1, $userlist->get_userids());
$expected = [$user1->id];
$this->assertEquals($expected, $userlist->get_userids());
// Ask the favourites privacy api to export userids for favourites of the type we just created, in the user2 context.
$userlist = new \core_privacy\local\request\userlist($user2context, 'core_favourites');
provider::add_userids_for_context($userlist, 'core_course', 'courses');
// Verify we do not have any userids in the list for user2 context.
$this->assertCount(0, $userlist->get_userids());
}
/**
* Test deletion of user favourites based on an approved_userlist, component area and item type.
*/
public function test_delete_favourites_for_userlist(): void {
list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users();
// Favourite 2 courses for user1 and 1 course for user2.
$systemcontext = \context_system::instance();
$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', 'courses', $course1context->instanceid, $systemcontext);
$ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $user1context);
$ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext);
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
// Ask the favourites privacy api to export userids for favourites of the type we just created, in the system context.
$userlist1 = new \core_privacy\local\request\userlist($systemcontext, 'core_course');
provider::add_userids_for_context($userlist1, 'courses');
// Verify we have two userids in the list for system context.
$this->assertCount(2, $userlist1->get_userids());
// Ask the favourites privacy api to export userids for favourites of the type we just created, in the user1 context.
$userlist2 = new \core_privacy\local\request\userlist($user1context, 'core_course');
provider::add_userids_for_context($userlist2, 'courses');
// Verify we have one userid in the list for user1 context.
$this->assertCount(1, $userlist2->get_userids());
// Now, delete the favourites for user1 only in the system context.
$approveduserlist = new \core_privacy\local\request\approved_userlist($systemcontext, 'core_course',
[$user1->id]);
provider::delete_favourites_for_userlist($approveduserlist, 'courses');
// Ensure user1's data was deleted and user2 is still returned for system context.
$userlist1 = new \core_privacy\local\request\userlist($systemcontext, 'core_course');
provider::add_userids_for_context($userlist1, 'courses');
$this->assertCount(1, $userlist1->get_userids());
// Verify that user2 is still in the list for system context.
$expected = [$user2->id];
$this->assertEquals($expected, $userlist1->get_userids());
// Verify that the data of user1 was not deleted in the user1context.
$userlist2 = new \core_privacy\local\request\userlist($user1context, 'core_course');
provider::add_userids_for_context($userlist2, 'courses');
$expected = [$user1->id];
$this->assertEquals($expected, $userlist2->get_userids());
// Now, delete the favourites for user2 only in the user1 context.
// Make sure favourites are only being deleted in the right context.
$approveduserlist = new \core_privacy\local\request\approved_userlist($user1context, 'core_course',
[$user2->id]);
provider::delete_favourites_for_userlist($approveduserlist, 'courses');
// Verify we have one userid in the list for system context.
$userlist2 = new \core_privacy\local\request\userlist($systemcontext, 'core_course');
provider::add_userids_for_context($userlist2, 'courses');
$this->assertCount(1, $userlist2->get_userids());
// Verify that user2 is still in the list for system context.
$expected = [$user2->id];
$this->assertEquals($expected, $userlist2->get_userids());
// Verify that user1 is still present in the list for user1 context.
$userlist3 = new \core_privacy\local\request\userlist($user1context, 'core_course');
provider::add_userids_for_context($userlist3, 'courses');
$this->assertCount(1, $userlist3->get_userids());
// Verify that user1 is still in the list for user1 context.
$expected = [$user1->id];
$this->assertEquals($expected, $userlist3->get_userids());
}
/**
* Test fetching the favourites data for a specified user in a specified component, item type and item ID.
*/
public function test_get_favourites_info_for_user(): void {
list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users();
// Favourite 2 courses for user1 and 1 course for user2.
$ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context);
$ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context);
$coursefavourite1 = $ufservice1->create_favourite('core_course', 'courses',
$course1context->instanceid, $course1context);
$this->waitForSecond();
$coursefavourite2 = $ufservice1->create_favourite('core_course', 'courses',
$course2context->instanceid, $course2context);
$this->waitForSecond();
$coursefavourite3 = $ufservice2->create_favourite('core_course', 'courses',
$course2context->instanceid, $course2context);
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses'));
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses'));
// Get the favourites info for user1 in the course1 context.
$favouriteinfo1 = (object) provider::get_favourites_info_for_user($user1->id, $course1context,
'core_course', 'courses', $course1context->instanceid);
// Ensure the correct data has been returned.
$this->assertEquals(transform::yesno(true), $favouriteinfo1->starred);
$this->assertEquals('', $favouriteinfo1->ordering);
$this->assertEquals(transform::datetime($coursefavourite1->timecreated), $favouriteinfo1->timecreated);
$this->assertEquals(transform::datetime($coursefavourite1->timemodified), $favouriteinfo1->timemodified);
// Get the favourites info for user1 in the course2 context.
$favouriteinfo2 = (object) provider::get_favourites_info_for_user($user1->id, $course2context,
'core_course', 'courses', $course2context->instanceid);
// Ensure the correct data has been returned.
$this->assertEquals(transform::yesno(true), $favouriteinfo2->starred);
$this->assertEquals('', $favouriteinfo2->ordering);
$this->assertEquals(transform::datetime($coursefavourite2->timecreated), $favouriteinfo2->timecreated);
$this->assertEquals(transform::datetime($coursefavourite2->timemodified), $favouriteinfo2->timemodified);
// Get the favourites info for user2 in the course2 context.
$favouriteinfo3 = (object) provider::get_favourites_info_for_user($user2->id, $course2context,
'core_course', 'courses', $course2context->instanceid);
// Ensure the correct data has been returned.
$this->assertEquals(transform::yesno(true), $favouriteinfo3->starred);
$this->assertEquals('', $favouriteinfo3->ordering);
$this->assertEquals(transform::datetime($coursefavourite3->timecreated), $favouriteinfo3->timecreated);
$this->assertEquals(transform::datetime($coursefavourite3->timemodified), $favouriteinfo3->timemodified);
// Get the favourites info for user2 in the course1 context (user2 has not favourited course1).
$favouriteinfo4 = provider::get_favourites_info_for_user($user2->id, $course1context,
'core_course', 'courses', $course1context->instanceid);
// Ensure that data has not been returned.
$this->assertEmpty($favouriteinfo4);
}
}
+617
View File
@@ -0,0 +1,617 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_favourites;
use core_favourites\local\repository\favourite_repository;
use core_favourites\local\entity\favourite;
/**
* Test class covering the favourite_repository.
*
* @package core_favourites
* @category test
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repository_test extends \advanced_testcase {
public function setUp(): void {
$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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$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(favourite::class, $favourite);
$this->assertObjectHasProperty('id', $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->assertObjectHasProperty('ordering', $favourite);
$this->assertObjectHasProperty('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 incomplete favourites cannot be saved.
*/
public function test_add_incomplete_favourite(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and try to favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$favcourse = new favourite(
'core_course',
'course',
$course1context->instanceid,
$course1context->id,
$user1context->instanceid
);
unset($favcourse->userid);
$this->expectException('moodle_exception');
$favouritesrepo->add($favcourse);
}
public function test_add_all_basic(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite several courses.
$favouritesrepo = new favourite_repository($user1context);
$favcourses = [];
$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->assertIsArray($favourites);
$this->assertCount(2, $favourites);
foreach ($favourites as $favourite) {
// Verify we get the favourite back.
$this->assertInstanceOf(favourite::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->assertObjectHasProperty('ordering', $favourite);
$this->assertObjectHasProperty('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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$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(favourite::class, $userfavourite);
$this->assertObjectHasProperty('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(0);
}
/**
* Test verifying that find_all() returns all favourites, or an empty array.
*/
public function test_find_all(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
$favouritesrepo = new favourite_repository($user1context);
// Verify that only two self-conversations are found.
$this->assertCount(2, $favouritesrepo->find_all());
// Save a favourite for 2 courses, in different areas.
$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);
// Verify that find_all returns both of our favourites + two self-conversations.
$favourites = $favouritesrepo->find_all();
$this->assertCount(4, $favourites);
foreach ($favourites as $fav) {
$this->assertInstanceOf(favourite::class, $fav);
$this->assertObjectHasProperty('id', $fav);
$this->assertObjectHasProperty('timecreated', $fav);
}
}
/**
* Testing the pagination of the find_all method.
*/
public function test_find_all_pagination(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
$favouritesrepo = new favourite_repository($user1context);
// Verify that for an empty repository, find_all with any combination of page options returns only self-conversations.
$this->assertCount(2, $favouritesrepo->find_all(0, 0));
$this->assertCount(2, $favouritesrepo->find_all(0, 10));
$this->assertCount(1, $favouritesrepo->find_all(1, 0));
$this->assertCount(1, $favouritesrepo->find_all(1, 10));
// Save 10 arbitrary favourites to the repo.
foreach (range(1, 10) as $i) {
$favourite = new favourite(
'core_course',
'course',
$i,
$course1context->id,
$user1context->instanceid
);
$favouritesrepo->add($favourite);
}
// Verify we have 10 favourites + 2 self-conversations.
$this->assertEquals(12, $favouritesrepo->count());
// Verify we can fetch the first page of 5 records+ 2 self-conversations.
$favourites = $favouritesrepo->find_all(0, 6);
$this->assertCount(6, $favourites);
// Verify we can fetch the second page.
$favourites = $favouritesrepo->find_all(6, 6);
$this->assertCount(6, $favourites);
// Verify the third page request ends with an empty array.
$favourites = $favouritesrepo->find_all(12, 6);
$this->assertCount(0, $favourites);
}
/**
* Test retrieval of a user's favourites for a given criteria, in this case, area.
*/
public function test_find_by(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$favourite = new favourite(
'core_course',
'course',
$course1context->instanceid,
$course1context->id,
$user1context->instanceid
);
$favouritesrepo->add($favourite);
// Add another favourite.
$favourite = new favourite(
'core_course',
'course_item',
$course1context->instanceid,
$course1context->id,
$user1context->instanceid
);
$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->assertIsArray($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->assertIsArray($userfavourites);
$this->assertCount(0, $userfavourites);
// From the repo, get the list of favourites for the 'core_course/course' area when passed as an array.
$userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => ['course']]);
$this->assertIsArray($userfavourites);
$this->assertCount(1, $userfavourites);
// From the repo, get the list of favourites for the 'core_course' area given multiple item_types.
$userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => ['course', 'course_item']]);
$this->assertIsArray($userfavourites);
$this->assertCount(2, $userfavourites);
}
/**
* Testing the pagination of the find_by method.
*/
public function test_find_by_pagination(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
$favouritesrepo = new favourite_repository($user1context);
// Verify that by default, find_all with any combination of page options returns only self-conversations.
$this->assertCount(2, $favouritesrepo->find_by([], 0, 0));
$this->assertCount(2, $favouritesrepo->find_by([], 0, 10));
$this->assertCount(1, $favouritesrepo->find_by([], 1, 0));
$this->assertCount(1, $favouritesrepo->find_by([], 1, 10));
// Save 10 arbitrary favourites to the repo.
foreach (range(1, 10) as $i) {
$favourite = new favourite(
'core_course',
'course',
$i,
$course1context->id,
$user1context->instanceid
);
$favouritesrepo->add($favourite);
}
// Verify we have 10 favourites + 2 self-conversations.
$this->assertEquals(12, $favouritesrepo->count());
// Verify a request for a page, when no criteria match, results in 2 self-conversations array.
$favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5);
$this->assertCount(2, $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.
*/
public function test_count_by(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and add 2 favourites in different areas.
$favouritesrepo = new favourite_repository($user1context);
$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);
// 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']));
}
/**
* Test the exists() function.
*/
public function test_exists(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$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.
$this->assertTrue($favouritesrepo->exists($createdfavourite->id));
// Verify exists returns false for non-existent favourite.
$this->assertFalse($favouritesrepo->exists(0));
}
/**
* Test the exists_by() method.
*/
public function test_exists_by(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite two courses, in different areas.
$favouritesrepo = new favourite_repository($user1context);
$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);
// Verify the existence of the favourites.
$this->assertTrue($favouritesrepo->exists_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'course',
'itemid' => $favourite1->itemid,
'contextid' => $favourite1->contextid
]
));
$this->assertTrue($favouritesrepo->exists_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'anothertype',
'itemid' => $favourite2->itemid,
'contextid' => $favourite2->contextid
]
));
// Verify that we can't find a favourite from one area, in another.
$this->assertFalse($favouritesrepo->exists_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'anothertype',
'itemid' => $favourite1->itemid,
'contextid' => $favourite1->contextid
]
));
}
/**
* Test the update() method, by simulating a user changing the ordering of a favourite.
*/
public function test_update(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$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(favourite::class, $favourite1);
$this->assertEquals('1', $favourite1->ordering);
}
public function test_delete(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite a course.
$favouritesrepo = new favourite_repository($user1context);
$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.
$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));
}
/**
* Test the delete_by() method.
*/
public function test_delete_by(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite two courses, in different areas.
$favouritesrepo = new favourite_repository($user1context);
$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);
// Verify we have 2 items in the repo + 2 self-conversations.
$this->assertEquals(4, $favouritesrepo->count());
// Try to delete by a non-existent area, and confirm it doesn't remove anything.
$favouritesrepo->delete_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'donaldduck'
]
);
$this->assertEquals(4, $favouritesrepo->count());
// Try to delete by a non-existent area, and confirm it doesn't remove anything.
$favouritesrepo->delete_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'cat'
]
);
$this->assertEquals(4, $favouritesrepo->count());
// Delete by area, and confirm we have one record left, from the 'core_course/anothertype' area.
$favouritesrepo->delete_by(
[
'userid' => $user1context->instanceid,
'component' => 'core_course',
'itemtype' => 'course'
]
);
$this->assertEquals(3, $favouritesrepo->count());
$this->assertFalse($favouritesrepo->exists($favourite1->id));
$this->assertTrue($favouritesrepo->exists($favourite2->id));
}
/**
* Test the find_favourite() method for an existing favourite.
*/
public function test_find_favourite_basic(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Create a favourites repository and favourite two courses, in different areas.
$favouritesrepo = new favourite_repository($user1context);
$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);
$fav = $favouritesrepo->find_favourite($user1context->instanceid, 'core_course', 'course', $course1context->instanceid,
$course1context->id);
$this->assertInstanceOf(\core_favourites\local\entity\favourite::class, $fav);
}
/**
* Test confirming the repository throws an exception in find_favourite if the favourite can't be found.
*/
public function test_find_favourite_nonexistent_favourite(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Confirm we get an exception.
$favouritesrepo = new favourite_repository($user1context);
$this->expectException(\dml_exception::class);
$favouritesrepo->find_favourite($user1context->instanceid, 'core_course', 'course', 0, $course1context->id);
}
}
@@ -0,0 +1,512 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_favourites;
use core_favourites\local\entity\favourite;
/**
* Test class covering the user_favourite_service within the service layer of favourites.
*
* @package core_favourites
* @category test
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_favourite_service_test extends \advanced_testcase {
public function setUp(): void {
$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\repository\favourite_repository_interface::class)
->onlyMethods([])
->getMock();
$mockrepo->expects($this->any())
->method('add')
->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
. $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, int $limitfrom = 0, int $limitnum = 0) use (&$mockstore) {
// Check for single value key pair vs multiple.
$multipleconditions = [];
foreach ($criteria as $key => $value) {
if (is_array($value)) {
$multipleconditions[$key] = $value;
unset($criteria[$key]);
}
}
// 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_assoc($criteria, $mockrowarr) == []) {
$found = true;
foreach ($multipleconditions as $key => $value) {
if (!in_array($mockrowarr[$key], $value)) {
$found = false;
break;
}
}
if ($found) {
$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;
})
);
$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_assoc($crit, $fakerowarr) == []) {
return $fakerow;
}
}
throw new \dml_missing_record_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('count_by')
->will($this->returnCallback(function(array $criteria) use (&$mockstore) {
$count = 0;
// 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_assoc($criteria, $mockrowarr) == []) {
$count++;
}
}
return $count;
})
);
$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]);
}
}
})
);
$mockrepo->expects($this->any())
->method('exists_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_assoc($criteria, $mockrowarr) == []) {
return true;
}
}
return false;
})
);
return $mockrepo;
}
/**
* Test getting a user_favourite_service from the static locator.
*/
public function test_get_service_for_user_context(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
$userservice = \core_favourites\service_factory::get_service_for_user_context($user1context);
$this->assertInstanceOf(\core_favourites\local\service\user_favourite_service::class, $userservice);
}
/**
* Test confirming an item can be favourited only once.
*/
public function test_create_favourite_basic(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// 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_favourite_service($user1context, $repo);
// Favourite a course.
$favourite1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$this->assertObjectHasProperty('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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// 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_favourite_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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// 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_favourite_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->assertIsArray($favourites);
$this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
$this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
$favourites = $service->find_favourites_by_type('core_course', 'anothertype');
$this->assertIsArray($favourites);
$this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
$this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
}
/**
* Test fetching favourites for single user, by area.
*/
public function test_find_all_favourites(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// 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_favourite_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);
$fav3 = $service->create_favourite('core_course', 'yetanothertype', $course2context->instanceid, $course2context);
// Verify we can get favourites by area.
$favourites = $service->find_all_favourites('core_course', ['course']);
$this->assertIsArray($favourites);
$this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
$this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
$favourites = $service->find_all_favourites('core_course', ['course', 'anothertype']);
$this->assertIsArray($favourites);
// We only get favourites for the 'core_course/course' and 'core_course/anothertype area.
$this->assertCount(2, $favourites);
$this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
$this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
$favourites = $service->find_all_favourites('core_course');
$this->assertIsArray($favourites);
$this->assertCount(3, $favourites); // We only get favourites for the 'core_cours' area.
$this->assertEquals($fav2->id, $favourites[$fav2->id]->id);
$this->assertEquals($fav1->id, $favourites[$fav1->id]->id);
$this->assertEquals($fav3->id, $favourites[$fav3->id]->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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for 2 users.
$repo = $this->get_mock_repository([]);
$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);
$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->assertIsArray($user1favourites);
$this->assertCount(1, $user1favourites); // We only get favourites for the 'core_course/course' area for $user1.
$this->assertEquals($fav1->id, $user1favourites[$fav1->id]->id);
$user2favourites = $user2service->find_favourites_by_type('core_course', 'course');
$this->assertIsArray($user2favourites);
$this->assertCount(1, $user2favourites); // We only get favourites for the 'core_course/course' area for $user2.
$this->assertEquals($fav2->id, $user2favourites[$fav2->id]->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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$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');
$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(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$service = new \core_favourites\local\service\user_favourite_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.
*/
public function test_delete_favourite_basic(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$service = new \core_favourites\local\service\user_favourite_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);
}
/**
* Test confirming the behaviour of the favourite_exists() method.
*/
public function test_favourite_exists(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
// Favourite a course.
$fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
// Verify we can check existence of the favourite.
$this->assertTrue(
$service->favourite_exists(
'core_course',
'course',
$course1context->instanceid,
$course1context
)
);
// And one that we know doesn't exist.
$this->assertFalse(
$service->favourite_exists(
'core_course',
'someothertype',
$course1context->instanceid,
$course1context
)
);
}
/**
* Test confirming the behaviour of the get_favourite() method.
*/
public function test_get_favourite(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
// Favourite a course.
$fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$result = $service->get_favourite(
'core_course',
'course',
$course1context->instanceid,
$course1context
);
// Verify we can get the favourite.
$this->assertEquals($fav1->id, $result->id);
// And one that we know doesn't exist.
$this->assertNull(
$service->get_favourite(
'core_course',
'someothertype',
$course1context->instanceid,
$course1context
)
);
}
/**
* Test confirming the behaviour of the count_favourites_by_type() method.
*/
public function test_count_favourites_by_type(): void {
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
$repo = $this->get_mock_repository([]);
$service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
$this->assertEquals(0, $service->count_favourites_by_type('core_course', 'course', $course1context));
// Favourite a course.
$service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
$this->assertEquals(1, $service->count_favourites_by_type('core_course', 'course', $course1context));
// Favourite another course.
$service->create_favourite('core_course', 'course', $course2context->instanceid, $course1context);
$this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context));
// Favourite a course in another context.
$service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context);
// Doesn't affect original context.
$this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context));
// Gets counted if we include all contexts.
$this->assertEquals(3, $service->count_favourites_by_type('core_course', 'course'));
}
/**
* Verify that the join sql generated by get_join_sql_by_type is valid and can be used to include favourite information.
*/
public function test_get_join_sql_by_type(): void {
global $DB;
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
// Get a user_favourite_service for the user.
// We need to use a real (DB) repository, as we want to run the SQL.
$repo = new \core_favourites\local\repository\favourite_repository();
$service = new \core_favourites\local\service\user_favourite_service($user1context, $repo);
// Favourite the first course only.
$service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
// Generate the join snippet.
list($favsql, $favparams) = $service->get_join_sql_by_type('core_course', 'course', 'favalias', 'c.id');
// Join against a simple select, including the 2 courses only.
$params = ['courseid1' => $course1context->instanceid, 'courseid2' => $course2context->instanceid];
$params = $params + $favparams;
$records = $DB->get_records_sql("SELECT c.id, favalias.component
FROM {course} c $favsql
WHERE c.id = :courseid1 OR c.id = :courseid2", $params);
// Verify the favourite information is returned, but only for the favourited course.
$this->assertCount(2, $records);
$this->assertEquals('core_course', $records[$course1context->instanceid]->component);
$this->assertEmpty($records[$course2context->instanceid]->component);
}
}