Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 36 and 311] [Versions 37 and 311] [Versions 38 and 311]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * Contains the user_favourite_service class, part of the service layer for the favourites subsystem.
      19   *
      20   * @package   core_favourites
      21   * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
      22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      23   */
      24  namespace core_favourites\local\service;
      25  use \core_favourites\local\entity\favourite;
      26  use \core_favourites\local\repository\favourite_repository_interface;
      27  
      28  defined('MOODLE_INTERNAL') || die();
      29  
      30  /**
      31   * Class service, providing an single API for interacting with the favourites subsystem for a SINGLE USER.
      32   *
      33   * This class is responsible for exposing key operations (add, remove, find) and enforces any business logic necessary to validate
      34   * authorization/data integrity for these operations.
      35   *
      36   * All object persistence is delegated to the favourite_repository_interface object.
      37   *
      38   * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
      39   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      40   */
      41  class user_favourite_service {
      42  
      43      /** @var favourite_repository_interface $repo the favourite repository object. */
      44      protected $repo;
      45  
      46      /** @var int $userid the id of the user to which this favourites service is scoped. */
      47      protected $userid;
      48  
      49      /**
      50       * The user_favourite_service constructor.
      51       *
      52       * @param \context_user $usercontext The context of the user to which this service operations are scoped.
      53       * @param \core_favourites\local\repository\favourite_repository_interface $repository a favourites repository.
      54       */
      55      public function __construct(\context_user $usercontext, favourite_repository_interface $repository) {
      56          $this->repo = $repository;
      57          $this->userid = $usercontext->instanceid;
      58      }
      59  
      60      /**
      61       * Favourite an item defined by itemid/context, in the area defined by component/itemtype.
      62       *
      63       * @param string $component the frankenstyle component name.
      64       * @param string $itemtype the type of the item being favourited.
      65       * @param int $itemid the id of the item which is to be favourited.
      66       * @param \context $context the context in which the item is to be favourited.
      67       * @param int|null $ordering optional ordering integer used for sorting the favourites in an area.
      68       * @return favourite the favourite, once created.
      69       * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors.
      70       */
      71      public function create_favourite(string $component, string $itemtype, int $itemid, \context $context,
      72              int $ordering = null) : favourite {
      73          // Access: Any component can ask to favourite something, we can't verify access to that 'something' here though.
      74  
      75          // Validate the component name.
      76          if (!in_array($component, \core_component::get_component_names())) {
      77              throw new \moodle_exception("Invalid component name '$component'");
      78          }
      79  
      80          $favourite = new favourite($component, $itemtype, $itemid, $context->id, $this->userid);
      81          $favourite->ordering = $ordering > 0 ? $ordering : null;
      82          return $this->repo->add($favourite);
      83      }
      84  
      85      /**
      86       * Find a list of favourites, by type, where type is the component/itemtype pair.
      87       *
      88       * E.g. "Find all favourite courses" might result in:
      89       * $favcourses = find_favourites_by_type('core_course', 'course');
      90       *
      91       * @param string $component the frankenstyle component name.
      92       * @param string $itemtype the type of the favourited item.
      93       * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
      94       * @param int $limitnum optional pagination control for returning a subset comprising this many records.
      95       * @return array the list of favourites found.
      96       * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors.
      97       */
      98      public function find_favourites_by_type(string $component, string $itemtype, int $limitfrom = 0, int $limitnum = 0) : array {
      99          if (!in_array($component, \core_component::get_component_names())) {
     100              throw new \moodle_exception("Invalid component name '$component'");
     101          }
     102          return $this->repo->find_by(
     103              [
     104                  'userid' => $this->userid,
     105                  'component' => $component,
     106                  'itemtype' => $itemtype
     107              ],
     108              $limitfrom,
     109              $limitnum
     110          );
     111      }
     112  
     113      /**
     114       * Find a list of favourites, by multiple types within a component.
     115       *
     116       * E.g. "Find all favourites in the activity chooser" might result in:
     117       * $favcourses = find_all_favourites('core_course', ['contentitem_mod_assign','contentitem_mod_assignment');
     118       *
     119       * @param string $component the frankenstyle component name.
     120       * @param array $itemtypes optional the type of the favourited item.
     121       * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
     122       * @param int $limitnum optional pagination control for returning a subset comprising this many records.
     123       * @return array the list of favourites found.
     124       * @throws \moodle_exception if the component name is invalid, or if the repository encounters any errors.
     125       */
     126      public function find_all_favourites(string $component, array $itemtypes = [], int $limitfrom = 0, int $limitnum = 0) : array {
     127          if (!in_array($component, \core_component::get_component_names())) {
     128              throw new \moodle_exception("Invalid component name '$component'");
     129          }
     130          $params = [
     131              'userid' => $this->userid,
     132              'component' => $component,
     133          ];
     134          if ($itemtypes) {
     135              $params['itemtype'] = $itemtypes;
     136          }
     137  
     138          return $this->repo->find_by(
     139              $params,
     140              $limitfrom,
     141              $limitnum
     142          );
     143      }
     144  
     145      /**
     146       * Returns the SQL required to include favourite information for a given component/itemtype combination.
     147       *
     148       * Generally, find_favourites_by_type() is the recommended way to fetch favourites.
     149       *
     150       * This method is used to include favourite information in external queries, for items identified by their
     151       * component and itemtype, matching itemid to the $joinitemid, and for the user to which this service is scoped.
     152       *
     153       * It uses a LEFT JOIN to preserve the original records. If you wish to restrict your records, please consider using a
     154       * "WHERE {$tablealias}.id IS NOT NULL" in your query.
     155       *
     156       * Example usage:
     157       *
     158       * list($sql, $params) = $service->get_join_sql_by_type('core_message', 'message_conversations', 'myfavouritetablealias',
     159       *                                                      'conv.id');
     160       * Results in $sql:
     161       *     "LEFT JOIN {favourite} fav
     162       *             ON fav.component = :favouritecomponent
     163       *            AND fav.itemtype = :favouriteitemtype
     164       *            AND fav.userid = 1234
     165       *            AND fav.itemid = conv.id"
     166       * and $params:
     167       *     ['favouritecomponent' => 'core_message', 'favouriteitemtype' => 'message_conversations']
     168       *
     169       * @param string $component the frankenstyle component name.
     170       * @param string $itemtype the type of the favourited item.
     171       * @param string $tablealias the desired alias for the favourites table.
     172       * @param string $joinitemid the table and column identifier which the itemid is joined to. E.g. conversation.id.
     173       * @return array the list of sql and params, in the format [$sql, $params].
     174       */
     175      public function get_join_sql_by_type(string $component, string $itemtype, string $tablealias, string $joinitemid) : array {
     176          $sql = " LEFT JOIN {favourite} {$tablealias}
     177                          ON {$tablealias}.component = :favouritecomponent
     178                         AND {$tablealias}.itemtype = :favouriteitemtype
     179                         AND {$tablealias}.userid = {$this->userid}
     180                         AND {$tablealias}.itemid = {$joinitemid} ";
     181  
     182          $params = [
     183              'favouritecomponent' => $component,
     184              'favouriteitemtype' => $itemtype,
     185          ];
     186  
     187          return [$sql, $params];
     188      }
     189  
     190      /**
     191       * Delete a favourite item from an area and from within a context.
     192       *
     193       * E.g. delete a favourite course from the area 'core_course', 'course' with itemid 3 and from within the CONTEXT_USER context.
     194       *
     195       * @param string $component the frankenstyle component name.
     196       * @param string $itemtype the type of the favourited item.
     197       * @param int $itemid the id of the item which was favourited (not the favourite's id).
     198       * @param \context $context the context of the item which was favourited.
     199       * @throws \moodle_exception if the user does not control the favourite, or it doesn't exist.
     200       */
     201      public function delete_favourite(string $component, string $itemtype, int $itemid, \context $context) {
     202          if (!in_array($component, \core_component::get_component_names())) {
     203              throw new \moodle_exception("Invalid component name '$component'");
     204          }
     205  
     206          // Business logic: check the user owns the favourite.
     207          try {
     208              $favourite = $this->repo->find_favourite($this->userid, $component, $itemtype, $itemid, $context->id);
     209          } catch (\moodle_exception $e) {
     210              throw new \moodle_exception("Favourite does not exist for the user. Cannot delete.");
     211          }
     212  
     213          $this->repo->delete($favourite->id);
     214      }
     215  
     216      /**
     217       * Check whether an item has been marked as a favourite in the respective area.
     218       *
     219       * @param string $component the frankenstyle component name.
     220       * @param string $itemtype the type of the favourited item.
     221       * @param int $itemid the id of the item which was favourited (not the favourite's id).
     222       * @param \context $context the context of the item which was favourited.
     223       * @return bool true if the item is favourited, false otherwise.
     224       */
     225      public function favourite_exists(string $component, string $itemtype, int $itemid, \context $context) : bool {
     226          return $this->repo->exists_by(
     227              [
     228                  'userid' => $this->userid,
     229                  'component' => $component,
     230                  'itemtype' => $itemtype,
     231                  'itemid' => $itemid,
     232                  'contextid' => $context->id
     233              ]
     234          );
     235      }
     236  
     237      /**
     238       * Get the favourite.
     239       *
     240       * @param string $component the frankenstyle component name.
     241       * @param string $itemtype the type of the favourited item.
     242       * @param int $itemid the id of the item which was favourited (not the favourite's id).
     243       * @param \context $context the context of the item which was favourited.
     244       * @return favourite|null
     245       */
     246      public function get_favourite(string $component, string $itemtype, int $itemid, \context $context) {
     247          try {
     248              return $this->repo->find_favourite(
     249                  $this->userid,
     250                  $component,
     251                  $itemtype,
     252                  $itemid,
     253                  $context->id
     254              );
     255          } catch (\dml_missing_record_exception $e) {
     256              return null;
     257          }
     258      }
     259  
     260      /**
     261       * Count the favourite by item type.
     262       *
     263       * @param string $component the frankenstyle component name.
     264       * @param string $itemtype the type of the favourited item.
     265       * @param \context|null $context the context of the item which was favourited.
     266       * @return int
     267       */
     268      public function count_favourites_by_type(string $component, string $itemtype, \context $context = null) {
     269          $criteria = [
     270              'userid' => $this->userid,
     271              'component' => $component,
     272              'itemtype' => $itemtype
     273          ];
     274  
     275          if ($context) {
     276              $criteria['contextid'] = $context->id;
     277          }
     278  
     279          return $this->repo->count_by($criteria);
     280      }
     281  }