Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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   * Event vault class
  19   *
  20   * @package    core_calendar
  21   * @copyright  2017 Ryan Wyllie <ryan@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_calendar\local\event\data_access;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use core_calendar\local\event\entities\action_event_interface;
  30  use core_calendar\local\event\entities\event_interface;
  31  use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
  32  use core_calendar\local\event\factories\action_factory_interface;
  33  use core_calendar\local\event\factories\event_factory_interface;
  34  use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
  35  
  36  /**
  37   * Event vault class.
  38   *
  39   * This class will handle interacting with the database layer to retrieve
  40   * the records. This is required to house the complex logic required for
  41   * pagination because it's not a one-to-one mapping between database records
  42   * and users.
  43   *
  44   * This is a repository. It's called a vault to reduce confusion because
  45   * Moodle has already taken the name repository. Vault is cooler anyway.
  46   *
  47   * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
  48   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class event_vault implements event_vault_interface {
  51  
  52      /**
  53       * @var event_factory_interface $factory Factory for creating events.
  54       */
  55      protected $factory;
  56  
  57      /**
  58       * @var raw_event_retrieval_strategy_interface $retrievalstrategy Strategy for getting events from the DB.
  59       */
  60      protected $retrievalstrategy;
  61  
  62      /**
  63       * Create an event vault.
  64       *
  65       * @param event_factory_interface $factory An event factory
  66       * @param raw_event_retrieval_strategy_interface $retrievalstrategy
  67       */
  68      public function __construct(
  69          event_factory_interface $factory,
  70          raw_event_retrieval_strategy_interface $retrievalstrategy
  71      ) {
  72          $this->factory = $factory;
  73          $this->retrievalstrategy = $retrievalstrategy;
  74      }
  75  
  76      public function get_event_by_id($id) {
  77          global $DB;
  78  
  79          if ($record = $DB->get_record('event', ['id' => $id])) {
  80              return $this->transform_from_database_record($record);
  81          } else {
  82              return false;
  83          }
  84      }
  85  
  86      public function get_events(
  87          $timestartfrom = null,
  88          $timestartto = null,
  89          $timesortfrom = null,
  90          $timesortto = null,
  91          event_interface $timestartafterevent = null,
  92          event_interface $timesortafterevent = null,
  93          $limitnum = 20,
  94          $type = null,
  95          array $usersfilter = null,
  96          array $groupsfilter = null,
  97          array $coursesfilter = null,
  98          array $categoriesfilter = null,
  99          $withduration = true,
 100          $ignorehidden = true,
 101          callable $filter = null
 102      ) {
 103  
 104          $fromquery = function($field, $timefrom, $lastseenmethod, $afterevent, $withduration) {
 105              if (!$timefrom) {
 106                  return false;
 107              }
 108  
 109              return $this->timefield_pagination_from(
 110                  $field,
 111                  $timefrom,
 112                  $afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
 113                  $afterevent ? $afterevent->get_id() : null,
 114                  $withduration
 115              );
 116          };
 117  
 118          $toquery = function($field, $timeto, $lastseenmethod, $afterevent) {
 119              if (!$timeto) {
 120                  return false;
 121              }
 122  
 123              return $this->timefield_pagination_to(
 124                  $field,
 125                  $timeto,
 126                  $afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
 127                  $afterevent ? $afterevent->get_id() : null
 128              );
 129          };
 130  
 131          $timesortfromquery = $fromquery('timesort', $timesortfrom, 'get_sort_time', $timesortafterevent, $withduration);
 132          $timesorttoquery = $toquery('timesort', $timesortto, 'get_sort_time', $timesortafterevent);
 133          $timestartfromquery = $fromquery('timestart', $timestartfrom, 'get_start_time', $timestartafterevent, $withduration);
 134          $timestarttoquery = $toquery('timestart', $timestartto, 'get_start_time', $timestartafterevent);
 135  
 136          if (($timesortto && !$timesorttoquery) || ($timestartto && !$timestarttoquery)) {
 137              return [];
 138          }
 139  
 140          $params = array_merge(
 141              $type ? ['type' => $type] : [],
 142              $timesortfromquery ? $timesortfromquery['params'] : [],
 143              $timesorttoquery ? $timesorttoquery['params'] : [],
 144              $timestartfromquery ? $timestartfromquery['params'] : [],
 145              $timestarttoquery ? $timestarttoquery['params'] : []
 146          );
 147  
 148          $where = array_merge(
 149              $type ? ['type = :type'] : [],
 150              $timesortfromquery ? $timesortfromquery['where'] : [],
 151              $timesorttoquery ? $timesorttoquery['where'] : [],
 152              $timestartfromquery ? $timestartfromquery['where'] : [],
 153              $timestarttoquery ? $timestarttoquery['where'] : []
 154          );
 155  
 156          $offset = 0;
 157          $events = [];
 158  
 159          while ($records = array_values($this->retrievalstrategy->get_raw_events(
 160              $usersfilter,
 161              $groupsfilter,
 162              $coursesfilter,
 163              $categoriesfilter,
 164              $where,
 165              $params,
 166              "COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
 167              $offset,
 168              $limitnum,
 169              $ignorehidden
 170          ))) {
 171              foreach ($records as $record) {
 172                  if ($event = $this->transform_from_database_record($record)) {
 173                      $filtertest = $filter ? $filter($event) : true;
 174  
 175                      if ($event && $filtertest) {
 176                          $events[] = $event;
 177                      }
 178  
 179                      if (count($events) == $limitnum) {
 180                          // We've got all of the events so break both loops.
 181                          break 2;
 182                      }
 183                  }
 184              }
 185  
 186              if (!$limitnum) {
 187                  break;
 188              } else {
 189                  $offset += $limitnum;
 190              }
 191          }
 192  
 193          return $events;
 194      }
 195  
 196      public function get_action_events_by_timesort(
 197          \stdClass $user,
 198          $timesortfrom = null,
 199          $timesortto = null,
 200          event_interface $afterevent = null,
 201          $limitnum = 20,
 202          $limittononsuspendedevents = false
 203      ) {
 204          $courseids = array_map(function($course) {
 205              return $course->id;
 206          }, enrol_get_all_users_courses($user->id, $limittononsuspendedevents));
 207  
 208          $groupids = array_reduce($courseids, function($carry, $courseid) use ($user) {
 209              $groupings = groups_get_user_groups($courseid, $user->id);
 210              // Grouping 0 is all groups.
 211              return array_merge($carry, $groupings[0]);
 212          }, []);
 213  
 214          // Always include the site events.
 215          $courseids = $courseids ? array_merge($courseids, [SITEID]) : $courseids;
 216  
 217          return $this->get_events(
 218              null,
 219              null,
 220              $timesortfrom,
 221              $timesortto,
 222              null,
 223              $afterevent,
 224              $limitnum,
 225              CALENDAR_EVENT_TYPE_ACTION,
 226              [$user->id],
 227              $groupids ? $groupids : null,
 228              $courseids ? $courseids : null,
 229              null, // All categories.
 230              true,
 231              true,
 232              function ($event) {
 233                  return $event instanceof action_event_interface;
 234              }
 235          );
 236      }
 237  
 238      public function get_action_events_by_course(
 239          \stdClass $user,
 240          \stdClass $course,
 241          $timesortfrom = null,
 242          $timesortto = null,
 243          event_interface $afterevent = null,
 244          $limitnum = 20
 245      ) {
 246          $groupings = groups_get_user_groups($course->id, $user->id);
 247          return array_values(
 248              $this->get_events(
 249                  null,
 250                  null,
 251                  $timesortfrom,
 252                  $timesortto,
 253                  null,
 254                  $afterevent,
 255                  $limitnum,
 256                  CALENDAR_EVENT_TYPE_ACTION,
 257                  [$user->id],
 258                  $groupings[0] ? $groupings[0] : null,
 259                  [$course->id],
 260                  [],
 261                  true,
 262                  true,
 263                  function ($event) use ($course) {
 264                      return $event instanceof action_event_interface && $event->get_course()->get('id') == $course->id;
 265                  }
 266              )
 267          );
 268      }
 269  
 270      /**
 271       * Generates SQL subquery and parameters for 'from' pagination.
 272       *
 273       * @param string    $field
 274       * @param int       $timefrom
 275       * @param int|null  $lastseentime
 276       * @param int|null  $lastseenid
 277       * @param bool      $withduration
 278       * @return array
 279       */
 280      protected function timefield_pagination_from(
 281          $field,
 282          $timefrom,
 283          $lastseentime = null,
 284          $lastseenid = null,
 285          $withduration = true
 286      ) {
 287          $where = '';
 288          $params = [];
 289  
 290          if ($lastseentime && $lastseentime >= $timefrom) {
 291              $where = '((timesort = :timefrom1 AND e.id > :timefromid) OR timesort > :timefrom2)';
 292              if ($field === 'timestart') {
 293                  $where = '((timestart = :timefrom1 AND e.id > :timefromid) OR timestart > :timefrom2' .
 294                         ($withduration ? ' OR timestart + timeduration > :timefrom3' : '') . ')';
 295              }
 296              $params['timefromid'] = $lastseenid;
 297              $params['timefrom1'] = $lastseentime;
 298              $params['timefrom2'] = $lastseentime;
 299              $params['timefrom3'] = $lastseentime;
 300          } else {
 301              $where = 'timesort >= :timefrom';
 302              if ($field === 'timestart') {
 303                  $where = '(timestart >= :timefrom' .
 304                         ($withduration ? ' OR timestart + timeduration > :timefrom2' : '') . ')';
 305              }
 306  
 307              $params['timefrom'] = $timefrom;
 308              $params['timefrom2'] = $timefrom;
 309          }
 310  
 311          return ['where' => [$where], 'params' => $params];
 312      }
 313  
 314      /**
 315       * Generates SQL subquery and parameters for 'to' pagination.
 316       *
 317       * @param string   $field
 318       * @param int      $timeto
 319       * @param int|null $lastseentime
 320       * @param int|null $lastseenid
 321       * @return array|bool
 322       */
 323      protected function timefield_pagination_to(
 324          $field,
 325          $timeto,
 326          $lastseentime = null,
 327          $lastseenid = null
 328      ) {
 329          $where = [];
 330          $params = [];
 331  
 332          if ($lastseentime && $lastseentime > $timeto) {
 333              // The last seen event from this set is after the time sort range which
 334              // means all events in this range have been seen, so we can just return
 335              // early here.
 336              return false;
 337          } else if ($lastseentime && $lastseentime == $timeto) {
 338              $where[] = '((timesort = :timeto1 AND e.id > :timetoid) OR timesort < :timeto2)';
 339              if ($field === 'timestart') {
 340                  $where[] = '((timestart = :timeto1 AND e.id > :timetoid) OR timestart < :timeto2)';
 341              }
 342              $params['timetoid'] = $lastseenid;
 343              $params['timeto1'] = $timeto;
 344              $params['timeto2'] = $timeto;
 345          } else {
 346              $where[] = ($field === 'timestart' ? 'timestart' : 'timesort') . ' <= :timeto';
 347              $params['timeto'] = $timeto;
 348          }
 349  
 350          return ['where' => $where, 'params' => $params];
 351      }
 352  
 353      /**
 354       * Create an event from a database record.
 355       *
 356       * @param \stdClass $record The database record
 357       * @return event_interface|null
 358       */
 359      protected function transform_from_database_record(\stdClass $record) {
 360          return $this->factory->create_instance($record);
 361      }
 362  
 363      /**
 364       * Fetches records from DB.
 365       *
 366       * @param int    $userid
 367       * @param string $whereconditions
 368       * @param array  $whereparams
 369       * @param string $ordersql
 370       * @param int    $offset
 371       * @param int    $limitnum
 372       * @return array
 373       */
 374      protected function get_from_db(
 375          $userid,
 376          $whereconditions,
 377          $whereparams,
 378          $ordersql,
 379          $offset,
 380          $limitnum
 381      ) {
 382          return array_values(
 383              $this->retrievalstrategy->get_raw_events(
 384                  [$userid],
 385                  null,
 386                  null,
 387                  null,
 388                  $whereconditions,
 389                  $whereparams,
 390                  $ordersql,
 391                  $offset,
 392                  $limitnum
 393              )
 394          );
 395      }
 396  }