Merge branch 'MDL-63658-master-2' of https://github.com/snake/moodle
This commit is contained in:
@@ -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 favourite class, each instance being a representation of a DB row for the 'favourite' table.
|
||||
*
|
||||
* @package core_favourites
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class favourite {
|
||||
/** @var int $id the id of the favourite.*/
|
||||
public $id;
|
||||
|
||||
/** @var string $component the frankenstyle name of the component containing the favourited item. E.g. 'core_course'.*/
|
||||
public $component;
|
||||
|
||||
/** @var string $itemtype the type of the item being marked as a favourite. E.g. 'course', 'conversation', etc.*/
|
||||
public $itemtype;
|
||||
|
||||
/** @var int $itemid the id of the item that is being marked as a favourite. e.g course->id, conversation->id, etc.*/
|
||||
public $itemid;
|
||||
|
||||
/** @var int $contextid the id of the context in which this favourite was created.*/
|
||||
public $contextid;
|
||||
|
||||
/** @var int $userid the id of user who owns this favourite.*/
|
||||
public $userid;
|
||||
|
||||
/** @var int $ordering the ordering of the favourite within it's favourite area.*/
|
||||
public $ordering;
|
||||
|
||||
/** @var int $timecreated the time at which the favourite was created.*/
|
||||
public $timecreated;
|
||||
|
||||
/** @var int $timemodified the time at which the last modification of the favourite took place.*/
|
||||
public $timemodified;
|
||||
|
||||
/**
|
||||
* Favourite constructor.
|
||||
* @param string $component the frankenstyle name of the component containing the favourited item. E.g. 'core_course'.
|
||||
* @param string $itemtype the type of the item being marked as a favourite. E.g. 'course', 'conversation', etc.
|
||||
* @param int $itemid the id of the item that is being marked as a favourite. e.g course->id, conversation->id, etc.
|
||||
* @param int $contextid the id of the context in which this favourite was created.
|
||||
* @param int $userid the id of user who owns this favourite.
|
||||
*/
|
||||
public function __construct(string $component, string $itemtype, int $itemid, int $contextid, int $userid) {
|
||||
$this->component = $component;
|
||||
$this->itemtype = $itemtype;
|
||||
$this->itemid = $itemid;
|
||||
$this->contextid = $contextid;
|
||||
$this->userid = $userid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
<?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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 matching the supplied criteria (a [key => value,..] list).
|
||||
*
|
||||
* @param array $criteria the list of key/value criteria pairs.
|
||||
* @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
|
||||
* @param int $limitnum optional pagination control for returning a subset comprising this many records.
|
||||
* @return array the list of favourites matching the criteria.
|
||||
* @throws \dml_exception if any database errors are encountered.
|
||||
*/
|
||||
public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array {
|
||||
global $DB;
|
||||
$records = $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum);
|
||||
return $this->get_list_of_favourites_from_records($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of favourites in this repository.
|
||||
*
|
||||
* @return int the total number of items.
|
||||
* @throws \dml_exception if any database errors are encountered.
|
||||
*/
|
||||
public function count() : int {
|
||||
global $DB;
|
||||
return $DB->count_records($this->favouritetable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for the existence of a favourite item in the specified area.
|
||||
*
|
||||
* A favourite item is identified by the itemid/contextid pair.
|
||||
* An area is identified by the component/itemtype pair.
|
||||
*
|
||||
* @param int $userid the id of user to whom the favourite belongs.
|
||||
* @param string $component the frankenstyle component name.
|
||||
* @param string $itemtype the type of the favourited item.
|
||||
* @param int $itemid the id of the item which was favourited (not the favourite's id).
|
||||
* @param int $contextid the contextid of the item which was favourited.
|
||||
* @return bool true if the favourited item exists, false otherwise.
|
||||
* @throws \dml_exception if any database errors are encountered.
|
||||
*/
|
||||
public function exists_by_area(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : bool {
|
||||
global $DB;
|
||||
return $DB->record_exists($this->favouritetable,
|
||||
[
|
||||
'userid' => $userid,
|
||||
'component' => $component,
|
||||
'itemtype' => $itemtype,
|
||||
'itemid' => $itemid,
|
||||
'contextid' => $contextid
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all favourites within the component/itemtype.
|
||||
*
|
||||
* @param int $userid the id of the user to whom the favourite belongs.
|
||||
* @param string $component the frankenstyle component name.
|
||||
* @param string $itemtype the type of the favourited item.
|
||||
* @throws \dml_exception if any database errors are encountered.
|
||||
*/
|
||||
public function delete_by_area(int $userid, string $component, string $itemtype) {
|
||||
global $DB;
|
||||
$DB->delete_records($this->favouritetable,
|
||||
[
|
||||
'userid' => $userid,
|
||||
'component' => $component,
|
||||
'itemtype' => $itemtype
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of user favourites matching the specified criteria.
|
||||
*
|
||||
* @param array $criteria the list of key/value criteria pairs.
|
||||
* @return int the number of favourites matching the criteria.
|
||||
* @throws \dml_exception if any database errors are encountered.
|
||||
*/
|
||||
public function count_by(array $criteria) : int {
|
||||
global $DB;
|
||||
return $DB->count_records($this->favouritetable, $criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic validation, confirming we have the minimum field set needed to save a record to the store.
|
||||
*
|
||||
* @param 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
|
||||
];
|
||||
|
||||
$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) . "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?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 <[email protected]>
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Return the total number of items in this repository.
|
||||
*
|
||||
* @return int the total number of items.
|
||||
*/
|
||||
public function count() : int;
|
||||
|
||||
/**
|
||||
* Update an item within this repository.
|
||||
*
|
||||
* @param 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);
|
||||
|
||||
/**
|
||||
* 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,138 @@
|
||||
<?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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?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 <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_favourites\privacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\context;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
|
||||
/**
|
||||
* Privacy class for requesting user data.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\subsystem\plugin_provider {
|
||||
|
||||
/**
|
||||
* Returns metadata about this system.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
return $collection->add_database_table('favourite', [
|
||||
'userid' => 'privacy:metadata:favourite:userid',
|
||||
'component' => 'privacy:metadata:favourite:component',
|
||||
'itemtype' => 'privacy:metadata:favourite:itemtype',
|
||||
'itemid' => 'privacy:metadata:favourite:itemid',
|
||||
'ordering' => 'privacy:metadata:favourite:ordering',
|
||||
'timecreated' => 'privacy:metadata:favourite:timecreated',
|
||||
'timemodified' => 'privacy:metadata:favourite:timemodified',
|
||||
], 'privacy:metadata:favourite');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of contexts which have favourites for the user, in the respective area (component/itemtype combination).
|
||||
*
|
||||
* This method is to be called by consumers of the favourites subsystem (plugins), in their get_contexts_for_userid() method,
|
||||
* to add the contexts for items which may have been favourited, but would normally not be reported as having user data by the
|
||||
* plugin responsible for them.
|
||||
*
|
||||
* Consider an example: Favourite courses.
|
||||
* Favourite courses will be handled by the core_course subsystem and courses can be favourited at site context.
|
||||
*
|
||||
* Now normally, the course provider method get_contexts_for_userid() would report the context of any courses the user is in.
|
||||
* Then, we'd export data for those contexts. This won't include courses the user has favourited, but is not a member of.
|
||||
*
|
||||
* To report the full list, the course provider needs to be made aware of the contexts of any courses the user may have marked
|
||||
* as favourites. Course will need to ask th favourites subsystem for this - a call to add_contexts_for_userid($userid).
|
||||
*
|
||||
* Once called, if a course has been marked as a favourite, at site context, then we'd return the site context. During export,
|
||||
* the consumer (course), just looks at all contexts and decides whether to export favourite courses for each one.
|
||||
*
|
||||
* @param \core_privacy\local\request\contextlist $contextlist
|
||||
* @param int $userid The id of the user in scope.
|
||||
* @param string $component the frankenstyle component name.
|
||||
* @param string $itemtype the type of the favourited items.
|
||||
*/
|
||||
public static function add_contexts_for_userid(\core_privacy\local\request\contextlist $contextlist, int $userid,
|
||||
string $component, string $itemtype = null) {
|
||||
$sql = "SELECT contextid
|
||||
FROM {favourite} f
|
||||
WHERE userid = :userid
|
||||
AND component = :component";
|
||||
if (!is_null($itemtype)) {
|
||||
$sql .= "AND itemtype = :itemtype";
|
||||
}
|
||||
$params = ['userid' => $userid, 'component' => $component, 'itemtype' => $itemtype];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all favourites for all users in the specified contexts, and component area.
|
||||
*
|
||||
* @param \context $context The context to which deletion is scoped.
|
||||
* @param string $component The favourite's component name.
|
||||
* @param string $itemtype The favourite's itemtype.
|
||||
* @throws \dml_exception if any errors are encountered during deletion.
|
||||
*/
|
||||
public static function delete_favourites_for_all_users(\context $context, string $component, string $itemtype) {
|
||||
global $DB;
|
||||
|
||||
$params = [
|
||||
'component' => $component,
|
||||
'itemtype' => $itemtype,
|
||||
'contextid' => $context->id
|
||||
];
|
||||
|
||||
$select = "component = :component AND itemtype =:itemtype AND contextid = :contextid";
|
||||
$DB->delete_records_select('favourite', $select, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all favourites for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
* @param string $component
|
||||
* @param string $itemtype
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function delete_favourites_for_user(approved_contextlist $contextlist, string $component, string $itemtype) {
|
||||
global $DB;
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'component' => $component,
|
||||
'itemtype' => $itemtype,
|
||||
];
|
||||
$params += $inparams;
|
||||
|
||||
$select = "userid = :userid AND component = :component AND itemtype =:itemtype AND contextid $insql";
|
||||
$DB->delete_records_select('favourite', $select, $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<?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 <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
use \core_favourites\privacy\provider;
|
||||
|
||||
/**
|
||||
* Unit tests for favourites/classes/privacy/provider
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class privacy_test extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set up some sample users and courses.
|
||||
*/
|
||||
protected function set_up_courses_and_users() {
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user1context = \context_user::instance($user1->id);
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user2context = \context_user::instance($user2->id);
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
$course2 = self::getDataGenerator()->create_course();
|
||||
$course1context = context_course::instance($course1->id);
|
||||
$course2context = context_course::instance($course2->id);
|
||||
return [$user1, $user2, $user1context, $user2context, $course1context, $course2context];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that contexts of favourited items can be added to the contextlist.
|
||||
*/
|
||||
public function test_add_contexts_for_userid() {
|
||||
list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users();
|
||||
|
||||
// Favourite 2 courses for user1 and 1 course for user2, all at the site context.
|
||||
$ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context);
|
||||
$ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context);
|
||||
$systemcontext = context_system::instance();
|
||||
$ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $systemcontext);
|
||||
$ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext);
|
||||
$ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext);
|
||||
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course'));
|
||||
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course'));
|
||||
|
||||
// Now, just for variety, let's assume you can favourite a course at user context, and do so for user1.
|
||||
$ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context);
|
||||
|
||||
// Now, ask the favourites privacy api to export contexts for favourites of the type we just created, for user1.
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
\core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user1->id, 'core_course', 'course');
|
||||
|
||||
// Verify we have two contexts in the list for user1.
|
||||
$this->assertCount(2, $contextlist->get_contextids());
|
||||
|
||||
// And verify we only have the system context returned for user2.
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
\core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user2->id, 'core_course', 'course');
|
||||
$this->assertCount(1, $contextlist->get_contextids());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deletion of user favourites based on an approved_contextlist and component area.
|
||||
*/
|
||||
public function test_delete_favourites_for_user() {
|
||||
list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users();
|
||||
|
||||
// Favourite 2 courses for user1 and 1 course for user2, all at the user context.
|
||||
$ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context);
|
||||
$ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context);
|
||||
$ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context);
|
||||
$ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $user1context);
|
||||
$ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $user2context);
|
||||
$this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course'));
|
||||
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course'));
|
||||
|
||||
// Now, delete the favourites for user1 only.
|
||||
$approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_course', [$user1context->id]);
|
||||
provider::delete_favourites_for_user($approvedcontextlist, 'core_course', 'course');
|
||||
|
||||
// Verify that we have no favourite courses for user1 but that the records are in tact for user2.
|
||||
$this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'course'));
|
||||
$this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course'));
|
||||
}
|
||||
|
||||
public function test_delete_favourites_for_all_users() {
|
||||
list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users();
|
||||
|
||||
// Favourite 2 course modules for user1 and 1 course module for user2 all in course 1 context.
|
||||
$ufservice1 = \core_favourites\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'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Testing the repository objects within core_favourites.
|
||||
*
|
||||
* @package core_favourites
|
||||
* @category test
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_favourites\local\repository\favourite_repository;
|
||||
use \core_favourites\local\entity\favourite;
|
||||
|
||||
/**
|
||||
* Test class covering the favourite_repository.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class favourite_repository_testcase extends advanced_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
// Basic setup stuff to be reused in most tests.
|
||||
protected function setup_users_and_courses() {
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user1context = \context_user::instance($user1->id);
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user2context = \context_user::instance($user2->id);
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
$course2 = self::getDataGenerator()->create_course();
|
||||
$course1context = context_course::instance($course1->id);
|
||||
$course2context = context_course::instance($course2->id);
|
||||
return [$user1context, $user2context, $course1context, $course2context];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the basic create operation can create records, and is validated.
|
||||
*/
|
||||
public function test_add() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new 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->assertObjectHasAttribute('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->assertObjectHasAttribute('ordering', $favourite);
|
||||
$this->assertObjectHasAttribute('timecreated', $favourite);
|
||||
$this->assertGreaterThanOrEqual($timenow, $favourite->timecreated);
|
||||
|
||||
// Try to save the same record again and confirm the store throws an exception.
|
||||
$this->expectException('dml_write_exception');
|
||||
$favouritesrepo->add($favcourse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that malformed favourites cannot be saved.
|
||||
*/
|
||||
public function test_add_malformed_favourite() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new favourite_repository($user1context);
|
||||
|
||||
$favcourse = new favourite(
|
||||
'core_course',
|
||||
'course',
|
||||
$course1context->instanceid,
|
||||
$course1context->id,
|
||||
$user1context->instanceid
|
||||
);
|
||||
$favcourse->something = 'something';
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
$favouritesrepo->add($favcourse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that incomplete favourites cannot be saved.
|
||||
*/
|
||||
public function test_add_incomplete_favourite() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and 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() {
|
||||
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->assertInternalType('array', $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->assertObjectHasAttribute('ordering', $favourite);
|
||||
$this->assertObjectHasAttribute('timecreated', $favourite);
|
||||
$this->assertGreaterThanOrEqual($timenow, $favourite->timecreated);
|
||||
}
|
||||
|
||||
// Try to save the same record again and confirm the store throws an exception.
|
||||
$this->expectException('dml_write_exception');
|
||||
$favouritesrepo->add_all($favcourses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reading from the repository by instance id.
|
||||
*/
|
||||
public function test_find() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new 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->assertObjectHasAttribute('timecreated', $userfavourite);
|
||||
|
||||
// Try to get a favourite we know doesn't exist.
|
||||
// We expect an exception in this case.
|
||||
$this->expectException(dml_exception::class);
|
||||
$favouritesrepo->find(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that find_all() returns all favourites, or an empty array.
|
||||
*/
|
||||
public function test_find_all() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
$favouritesrepo = new favourite_repository($user1context);
|
||||
|
||||
// Verify that for an empty repository, find_all returns an empty array.
|
||||
$this->assertEquals([], $favouritesrepo->find_all());
|
||||
|
||||
// Save a favourite for 2 courses, in different areas.
|
||||
$favourite = 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.
|
||||
$favourites = $favouritesrepo->find_all();
|
||||
$this->assertCount(2, $favourites);
|
||||
foreach ($favourites as $fav) {
|
||||
$this->assertInstanceOf(favourite::class, $fav);
|
||||
$this->assertObjectHasAttribute('id', $fav);
|
||||
$this->assertObjectHasAttribute('timecreated', $fav);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing the pagination of the find_all method.
|
||||
*/
|
||||
public function test_find_all_pagination() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
$favouritesrepo = new favourite_repository($user1context);
|
||||
|
||||
// Verify that for an empty repository, find_all with any combination of page options returns an empty array.
|
||||
$this->assertEquals([], $favouritesrepo->find_all(0, 0));
|
||||
$this->assertEquals([], $favouritesrepo->find_all(0, 10));
|
||||
$this->assertEquals([], $favouritesrepo->find_all(1, 0));
|
||||
$this->assertEquals([], $favouritesrepo->find_all(1, 10));
|
||||
|
||||
// Save 10 arbitrary favourites to the repo.
|
||||
foreach (range(1, 10) as $i) {
|
||||
$favourite = new favourite(
|
||||
'core_course',
|
||||
'course',
|
||||
$i,
|
||||
$course1context->id,
|
||||
$user1context->instanceid
|
||||
);
|
||||
$favouritesrepo->add($favourite);
|
||||
}
|
||||
|
||||
// Verify we have 10 favourites.
|
||||
$this->assertEquals(10, $favouritesrepo->count());
|
||||
|
||||
// Verify we can fetch the first page of 5 records.
|
||||
$favourites = $favouritesrepo->find_all(0, 5);
|
||||
$this->assertCount(5, $favourites);
|
||||
|
||||
// Verify we can fetch the second page.
|
||||
$favourites = $favouritesrepo->find_all(5, 5);
|
||||
$this->assertCount(5, $favourites);
|
||||
|
||||
// Verify the third page request ends with an empty array.
|
||||
$favourites = $favouritesrepo->find_all(10, 5);
|
||||
$this->assertCount(0, $favourites);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test retrieval of a user's favourites for a given criteria, in this case, area.
|
||||
*/
|
||||
public function test_find_by() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new favourite_repository($user1context);
|
||||
$favourite = new favourite(
|
||||
'core_course',
|
||||
'course',
|
||||
$course1context->instanceid,
|
||||
$course1context->id,
|
||||
$user1context->instanceid
|
||||
);
|
||||
$favouritesrepo->add($favourite);
|
||||
|
||||
// From the repo, get the list of favourites for the 'core_course/course' area.
|
||||
$userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => 'course']);
|
||||
$this->assertInternalType('array', $userfavourites);
|
||||
$this->assertCount(1, $userfavourites);
|
||||
|
||||
// Try to get a list of favourites for a non-existent area.
|
||||
$userfavourites = $favouritesrepo->find_by(['component' => 'core_cannibalism', 'itemtype' => 'course']);
|
||||
$this->assertInternalType('array', $userfavourites);
|
||||
$this->assertCount(0, $userfavourites);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing the pagination of the find_by method.
|
||||
*/
|
||||
public function test_find_by_pagination() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
$favouritesrepo = new favourite_repository($user1context);
|
||||
|
||||
// Verify that for an empty repository, find_all with any combination of page options returns an empty array.
|
||||
$this->assertEquals([], $favouritesrepo->find_by([], 0, 0));
|
||||
$this->assertEquals([], $favouritesrepo->find_by([], 0, 10));
|
||||
$this->assertEquals([], $favouritesrepo->find_by([], 1, 0));
|
||||
$this->assertEquals([], $favouritesrepo->find_by([], 1, 10));
|
||||
|
||||
// Save 10 arbitrary favourites to the repo.
|
||||
foreach (range(1, 10) as $i) {
|
||||
$favourite = new favourite(
|
||||
'core_course',
|
||||
'course',
|
||||
$i,
|
||||
$course1context->id,
|
||||
$user1context->instanceid
|
||||
);
|
||||
$favouritesrepo->add($favourite);
|
||||
}
|
||||
|
||||
// Verify we have 10 favourites.
|
||||
$this->assertEquals(10, $favouritesrepo->count());
|
||||
|
||||
// Verify a request for a page, when no criteria match, results in an empty array.
|
||||
$favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5);
|
||||
$this->assertCount(0, $favourites);
|
||||
|
||||
// Verify we can fetch a the first page of 5 records.
|
||||
$favourites = $favouritesrepo->find_by(['component' => 'core_course'], 0, 5);
|
||||
$this->assertCount(5, $favourites);
|
||||
|
||||
// Verify we can fetch the second page.
|
||||
$favourites = $favouritesrepo->find_by(['component' => 'core_course'], 5, 5);
|
||||
$this->assertCount(5, $favourites);
|
||||
|
||||
// Verify the third page request ends with an empty array.
|
||||
$favourites = $favouritesrepo->find_by(['component' => 'core_course'], 10, 5);
|
||||
$this->assertCount(0, $favourites);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the count_by() method.
|
||||
*/
|
||||
public function test_count_by() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and add 2 favourites in different areas.
|
||||
$favouritesrepo = new 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']));
|
||||
}
|
||||
|
||||
public function test_exists() {
|
||||
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(1));
|
||||
}
|
||||
|
||||
public function test_exists_by_area() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite two courses, in different areas.
|
||||
$favouritesrepo = new 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_area($user1context->instanceid, 'core_course', 'course', $favourite1->itemid,
|
||||
$favourite1->contextid));
|
||||
$this->assertTrue($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype',
|
||||
$favourite2->itemid, $favourite2->contextid));
|
||||
|
||||
// Verify that we can't find a favourite from one area, in another.
|
||||
$this->assertFalse($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype',
|
||||
$favourite1->itemid, $favourite1->contextid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the update() method, by simulating a user changing the ordering of a favourite.
|
||||
*/
|
||||
public function test_update() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new 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->assertAttributeEquals('1', 'ordering', $favourite1);
|
||||
}
|
||||
|
||||
public function test_delete() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite a course.
|
||||
$favouritesrepo = new 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));
|
||||
}
|
||||
|
||||
public function test_delete_by_area() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Create a favourites repository and favourite two courses, in different areas.
|
||||
$favouritesrepo = new 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.
|
||||
$this->assertEquals(2, $favouritesrepo->count());
|
||||
|
||||
// Try to delete by a non-existent area, and confirm it doesn't remove anything.
|
||||
$favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'donaldduck');
|
||||
$this->assertEquals(2, $favouritesrepo->count());
|
||||
|
||||
// Try to delete by a non-existent area, and confirm it doesn't remove anything.
|
||||
$favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'cat');
|
||||
$this->assertEquals(2, $favouritesrepo->count());
|
||||
|
||||
// Delete by area, and confirm we have one record left, from the 'core_course/anothertype' area.
|
||||
$favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'course');
|
||||
$this->assertEquals(1, $favouritesrepo->count());
|
||||
$this->assertFalse($favouritesrepo->exists($favourite1->id));
|
||||
$this->assertTrue($favouritesrepo->exists($favourite2->id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Testing the service layer within core_favourites.
|
||||
*
|
||||
* @package core_favourites
|
||||
* @category test
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
use \core_favourites\local\entity\favourite;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test class covering the user_favourite_service within the service layer of favourites.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_favourite_service_testcase extends advanced_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
// Basic setup stuff to be reused in most tests.
|
||||
protected function setup_users_and_courses() {
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user1context = \context_user::instance($user1->id);
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user2context = \context_user::instance($user2->id);
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
$course2 = self::getDataGenerator()->create_course();
|
||||
$course1context = context_course::instance($course1->id);
|
||||
$course2context = context_course::instance($course2->id);
|
||||
return [$user1context, $user2context, $course1context, $course2context];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an in-memory repository for testing, using an array store for CRUD stuff.
|
||||
*
|
||||
* @param array $mockstore
|
||||
* @return \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected function get_mock_repository(array $mockstore) {
|
||||
// This mock will just store data in an array.
|
||||
$mockrepo = $this->getMockBuilder(\core_favourites\local\repository\favourite_repository_interface::class)
|
||||
->setMethods([])
|
||||
->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($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($crit, $fakerowarr) == []) {
|
||||
return $fakerow;
|
||||
}
|
||||
}
|
||||
throw new \moodle_exception("Item not found");
|
||||
})
|
||||
);
|
||||
$mockrepo->expects($this->any())
|
||||
->method('find')
|
||||
->will($this->returnCallback(function(int $id) use (&$mockstore) {
|
||||
return $mockstore[$id];
|
||||
})
|
||||
);
|
||||
$mockrepo->expects($this->any())
|
||||
->method('exists')
|
||||
->will($this->returnCallback(function(int $id) use (&$mockstore) {
|
||||
return array_key_exists($id, $mockstore);
|
||||
})
|
||||
);
|
||||
$mockrepo->expects($this->any())
|
||||
->method('delete')
|
||||
->will($this->returnCallback(function(int $id) use (&$mockstore) {
|
||||
foreach ($mockstore as $mockrow) {
|
||||
if ($mockrow->id == $id) {
|
||||
unset($mockstore[$id]);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
return $mockrepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting a user_favourite_service from the static locator.
|
||||
*/
|
||||
public function test_get_service_for_user_context() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
$userservice = \core_favourites\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() {
|
||||
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->assertObjectHasAttribute('id', $favourite1);
|
||||
|
||||
// Try to favourite the same course again.
|
||||
$this->expectException('moodle_exception');
|
||||
$user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that an exception is thrown if trying to favourite an item for a non-existent component.
|
||||
*/
|
||||
public function test_create_favourite_nonexistent_component() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Get a user_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() {
|
||||
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->assertInternalType('array', $favourites);
|
||||
$this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
|
||||
$this->assertAttributeEquals($fav1->id, 'id', $favourites[$fav1->id]);
|
||||
|
||||
$favourites = $service->find_favourites_by_type('core_course', 'anothertype');
|
||||
$this->assertInternalType('array', $favourites);
|
||||
$this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area.
|
||||
$this->assertAttributeEquals($fav2->id, 'id', $favourites[$fav2->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the find_favourites_by_type() method only returns favourites for the scoped user.
|
||||
*/
|
||||
public function test_find_favourites_by_type_multiple_users() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Get a user_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->assertInternalType('array', $user1favourites);
|
||||
$this->assertCount(1, $user1favourites); // We only get favourites for the 'core_course/course' area for $user1.
|
||||
$this->assertAttributeEquals($fav1->id, 'id', $user1favourites[$fav1->id]);
|
||||
|
||||
$user2favourites = $user2service->find_favourites_by_type('core_course', 'course');
|
||||
$this->assertInternalType('array', $user2favourites);
|
||||
$this->assertCount(1, $user2favourites); // We only get favourites for the 'core_course/course' area for $user2.
|
||||
$this->assertAttributeEquals($fav2->id, 'id', $user2favourites[$fav2->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that an exception is thrown if trying to get favourites for a non-existent component.
|
||||
*/
|
||||
public function test_find_favourites_by_type_nonexistent_component() {
|
||||
list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
|
||||
|
||||
// Get a user_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() {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Strings for component 'favourites', language 'en', branch 'master'
|
||||
*
|
||||
* @package core_favourites
|
||||
* @copyright 2018 Jake Dallimore <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
$string['privacy:metadata:favourite'] = 'Stores references to items which have been marked as favourites.';
|
||||
$string['privacy:metadata:favourite:component'] = 'The component to which the favourite belongs to. E.g. core_user.';
|
||||
$string['privacy:metadata:favourite:itemid'] = 'The identifier for the item being marked as a favourite.';
|
||||
$string['privacy:metadata:favourite:itemtype'] = 'The type of the favourite item. E.g. course.';
|
||||
$string['privacy:metadata:favourite:ordering'] = 'A number used to order the favourites of the same type.';
|
||||
$string['privacy:metadata:favourite:timecreated'] = 'The time at which the item was marked as a favourite.';
|
||||
$string['privacy:metadata:favourite:timemodified'] = 'The time at which favourite was last modified.';
|
||||
$string['privacy:metadata:favourite:userid'] = 'The user who created the favourite.';
|
||||
@@ -442,6 +442,7 @@ $cache = '.var_export($cache, true).';
|
||||
'edufields' => null,
|
||||
'enrol' => $CFG->dirroot.'/enrol',
|
||||
'error' => null,
|
||||
'favourites' => $CFG->dirroot . '/favourites',
|
||||
'filepicker' => null,
|
||||
'fileconverter' => $CFG->dirroot.'/files/converter',
|
||||
'files' => $CFG->dirroot.'/files',
|
||||
@@ -1277,4 +1278,31 @@ $cache = '.var_export($cache, true).';
|
||||
}
|
||||
return $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of frankenstyle component names.
|
||||
*
|
||||
* E.g.
|
||||
* [
|
||||
* 'core_course',
|
||||
* 'core_message',
|
||||
* 'mod_assign',
|
||||
* ...
|
||||
* ]
|
||||
* @return array the list of frankenstyle component names.
|
||||
*/
|
||||
public static function get_component_names() : array {
|
||||
$componentnames = [];
|
||||
// Get all plugins.
|
||||
foreach (self::get_plugin_types() as $plugintype => $typedir) {
|
||||
foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) {
|
||||
$componentnames[] = $plugintype . '_' . $pluginname;
|
||||
}
|
||||
}
|
||||
// Get all subsystems.
|
||||
foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) {
|
||||
$componentnames[] = 'core_' . $subsystemname;
|
||||
}
|
||||
return $componentnames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3898,5 +3898,27 @@
|
||||
<INDEX NAME="indexprioritytimerequested" UNIQUE="false" FIELDS="indexpriority, timerequested"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
|
||||
<TABLE NAME="favourite" COMMENT="Stores the relationship between an arbitrary item (itemtype, itemid), and a context area (component, contextid) for a specific user. Used by the favourites subsystem.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Defines the Moodle component in which the favourite was created."/>
|
||||
<FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="The type of the item which is being favourited. Usually a table name, but doesn't have to be. E.g. 'messages' or 'message_conversations'."/>
|
||||
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The identifier of the item which is being favourited."/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The context id of the item being favourited"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The id of the user to whom the favourite belongs"/>
|
||||
<FIELD NAME="ordering" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Optional ordering of the favourite within its context area. Allows things like sorting favourite message conversations, for example."/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Creation time"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Last modification time"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="uniqueuserfavouriteitem" UNIQUE="true" FIELDS="component, itemtype, itemid, contextid, userid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
||||
@@ -2533,5 +2533,42 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2018101700.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2018101800.00) {
|
||||
// Define table 'favourite' to be created.
|
||||
$table = new xmldb_table('favourite');
|
||||
|
||||
// Adding fields to table 'favourite'.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('itemid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('ordering', XMLDB_TYPE_INTEGER, 10, null, null, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table 'favourite'.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id'));
|
||||
$table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
|
||||
|
||||
// Conditionally launch create table for 'favourite'.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Add composite index 'uniqueuserfavouriteitem' to the table 'favourite'.
|
||||
$index = new xmldb_index('uniqueuserfavouriteitem', XMLDB_INDEX_UNIQUE,
|
||||
['component', 'itemtype', 'itemid', 'contextid', 'userid']);
|
||||
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2018101800.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase {
|
||||
* this is defined here to annoy devs that try to add more without any thinking,
|
||||
* always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
*/
|
||||
const SUBSYSTEMCOUNT = 66;
|
||||
const SUBSYSTEMCOUNT = 67;
|
||||
|
||||
public function setUp() {
|
||||
$psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
|
||||
@@ -805,4 +805,31 @@ class core_component_testcase extends advanced_testcase {
|
||||
$this->assertEquals($componentslist['mod']['mod_forum'], $CFG->dirroot . '/mod/forum');
|
||||
$this->assertEquals($componentslist['tool']['tool_usertours'], $CFG->dirroot . '/' . $CFG->admin . '/tool/usertours');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_component_names() method.
|
||||
*/
|
||||
public function test_get_component_names() {
|
||||
global $CFG;
|
||||
$componentnames = \core_component::get_component_names();
|
||||
|
||||
// We should have an entry for each plugin type.
|
||||
$plugintypes = \core_component::get_plugin_types();
|
||||
$numplugintypes = 0;
|
||||
foreach ($plugintypes as $type => $typedir) {
|
||||
foreach (\core_component::get_plugin_list($type) as $plugin) {
|
||||
$numplugintypes++;
|
||||
}
|
||||
}
|
||||
// And an entry for each core subsystem.
|
||||
$numcomponents = $numplugintypes + count(\core_component::get_core_subsystems());
|
||||
|
||||
$this->assertEquals($numcomponents, count($componentnames));
|
||||
|
||||
// Check a few of the known plugin types to confirm their presence at their respective type index.
|
||||
$this->assertContains('core_comment', $componentnames);
|
||||
$this->assertContains('mod_forum', $componentnames);
|
||||
$this->assertContains('tool_usertours', $componentnames);
|
||||
$this->assertContains('core_favourites', $componentnames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<directory suffix="_test.php">lib/tests</directory>
|
||||
<directory suffix="_test.php">lib/ajax/tests</directory>
|
||||
</testsuite>
|
||||
<testsuite name="core_favourites_testsuite">
|
||||
<directory suffix="_test.php">favourites/tests</directory>
|
||||
</testsuite>
|
||||
<testsuite name="core_form_testsuite">
|
||||
<directory suffix="_test.php">lib/form/tests</directory>
|
||||
</testsuite>
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2018101700.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2018101800.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user