Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 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  namespace core_course\external;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  use context_user;
  22  use core_calendar_external;
  23  use core_course_external;
  24  use core_external\external_api;
  25  use core_external\external_function_parameters;
  26  use core_external\external_multiple_structure;
  27  use core_external\external_single_structure;
  28  use core_external\external_value;
  29  
  30  require_once("{$CFG->dirroot}/calendar/externallib.php");
  31  require_once("{$CFG->dirroot}/course/externallib.php");
  32  
  33  /**
  34   * Class for fetching courses which have action event(s) and match given filter parameters.
  35   *
  36   * @package    core_course
  37   * @copyright  2022 Michael Hawkins <michaelh@moodle.com>
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class get_enrolled_courses_with_action_events_by_timeline_classification extends external_api {
  41  
  42      /**
  43       * Returns the description of method parameters.
  44       *
  45       * @return external_function_parameters
  46       */
  47      public static function execute_parameters(): external_function_parameters {
  48          return new external_function_parameters(
  49              [
  50                  'classification' => new external_value(PARAM_ALPHA, 'future, inprogress, or past'),
  51                  'limit' => new external_value(PARAM_INT, 'Result set limit', VALUE_DEFAULT, 0),
  52                  'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0),
  53                  'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null),
  54                  'customfieldname' => new external_value(PARAM_ALPHANUMEXT, 'Used when classification = customfield',
  55                      VALUE_DEFAULT, null),
  56                  'customfieldvalue' => new external_value(PARAM_RAW, 'Used when classification = customfield',
  57                      VALUE_DEFAULT, null),
  58                  'searchvalue' => new external_value(PARAM_RAW, 'The value a user wishes to search against',
  59                      VALUE_DEFAULT, null),
  60                  'eventsfrom' => new external_value(PARAM_INT, 'Optional starting timestamp for action events',
  61                  VALUE_DEFAULT, null),
  62                  'eventsto' => new external_value(PARAM_INT, 'Optional ending timestamp for action events',
  63                  VALUE_DEFAULT, null),
  64              ]
  65          );
  66      }
  67  
  68      /**
  69       * Get courses matching the given timeline classification which have action event(s).
  70       *
  71       * Fetches courses by timeline classification which have at least one action event within the specified filtering.
  72       *
  73       * @param  string $classification past, inprogress, or future
  74       * @param  int $limit Number of courses with events to attempt to fetch
  75       * @param  int $offset Offset the full course set before timeline classification is applied
  76       * @param  string $sort SQL sort string for results
  77       * @param  string $customfieldname Custom field name used when when classification is customfield
  78       * @param  string $customfieldvalue Custom field value used when when classification is customfield
  79       * @param  string $searchvalue Text search being applied
  80       * @param  int $eventsfrom The start timestamp (inclusive) to search from for action events in the course
  81       * @param  int $eventsto The end timestamp (inclusive) to search to for action events in the course
  82       * @return array list of courses and any warnings
  83       */
  84      public static function execute(
  85          string $classification,
  86          int $limit = 0,
  87          int $offset = 0,
  88          string $sort = null,
  89          string $customfieldname = null,
  90          string $customfieldvalue = null,
  91          string $searchvalue = null,
  92          int $eventsfrom = null,
  93          int $eventsto = null
  94      ): array {
  95          global $USER;
  96  
  97          self::validate_context(context_user::instance($USER->id));
  98  
  99          $params = self::validate_parameters(
 100              self::execute_parameters(),
 101              [
 102                  'classification' => $classification,
 103                  'limit' => $limit,
 104                  'offset' => $offset,
 105                  'sort' => $sort,
 106                  'customfieldname' => $customfieldname,
 107                  'customfieldvalue' => $customfieldvalue,
 108                  'searchvalue' => $searchvalue,
 109                  'eventsfrom' => $eventsfrom,
 110                  'eventsto' => $eventsto,
 111              ]
 112          );
 113  
 114          $classification = $params['classification'];
 115          $limit = $params['limit'];
 116          $offset = $params['offset'];
 117          $sort = $params['sort'];
 118          $customfieldname = $params['customfieldname'];
 119          $customfieldvalue = $params['customfieldvalue'];
 120          $searchvalue = clean_param($params['searchvalue'], PARAM_TEXT);
 121          $eventsfrom = $params['eventsfrom'];
 122          $eventsto = $params['eventsto'];
 123          $morecoursestofetch = true;
 124          $morecoursespossible = true;
 125          $coursesfinal = [];
 126  
 127          do {
 128              // Fetch courses.
 129              [
 130                  'courses' => $coursesfetched,
 131                  'nextoffset' => $offset,
 132              ] = core_course_external::get_enrolled_courses_by_timeline_classification($classification, $limit,
 133                      $offset, $sort, $customfieldname, $customfieldvalue, $searchvalue);
 134  
 135              $courseids = array_column($coursesfetched, 'id');
 136              $coursesfetched = array_combine($courseids, $coursesfetched);
 137  
 138              if (!empty($courseids)) {
 139                  // Need to check this to know how many are expected (since it is possible for this to be less than the limit).
 140                  $numcoursesfetched = count($courseids);
 141                  $numfetchedwithevents = 0;
 142  
 143                  // If less courses are fetched than we requested, we know it is not possible for more courses to be available.
 144                  if ($numcoursesfetched < $limit) {
 145                      $morecoursestofetch = false;
 146                      $morecoursespossible = false;
 147                  }
 148  
 149                  // Try to fetch one action event within the time/search parameters for each course.
 150                  $events = core_calendar_external::get_calendar_action_events_by_courses($courseids, $eventsfrom, $eventsto, 1,
 151                      $searchvalue);
 152  
 153                  foreach ($events->groupedbycourse as $courseevents) {
 154                      $courseid = $courseevents->courseid;
 155  
 156                      // Only include courses which contain at least one event.
 157                      if (empty($courseevents->events)) {
 158                          unset($coursesfetched[$courseid]);
 159                      } else {
 160                          $numfetchedwithevents++;
 161                      }
 162                  }
 163  
 164                  // Add courses with events to the final course list in order.
 165                  $coursesfinal = array_merge($coursesfinal, $coursesfetched);
 166  
 167                  // If any courses did not have events, adjust the limit so we can attempt to fetch as many as are still required.
 168                  if ($numfetchedwithevents < $numcoursesfetched) {
 169                      $limit -= $numfetchedwithevents;
 170                  } else {
 171                      // If we have found as many courses as required or are available, no need to attempt fetching more.
 172                      $morecoursestofetch = false;
 173                  }
 174              } else {
 175                  $morecoursestofetch = false;
 176                  $morecoursespossible = false;
 177              }
 178  
 179          } while ($morecoursestofetch);
 180  
 181          static $isrecursivecall = false;
 182          $morecoursesavailable = false;
 183  
 184          // Recursively call this method to check if at least one more course is available if we know that is a possibility.
 185          if (!$isrecursivecall && $morecoursespossible) {
 186              // Prevent infinite recursion.
 187              $isrecursivecall = true;
 188  
 189              $additionalcourses = self::execute(
 190                  $classification, 1, $offset, $sort, $customfieldname, $customfieldvalue, $searchvalue, $eventsfrom, $eventsto
 191              );
 192  
 193              if (!empty($additionalcourses['courses'])) {
 194                  $morecoursesavailable = true;
 195              }
 196          }
 197  
 198          return [
 199              'courses' => $coursesfinal,
 200              'nextoffset' => $offset,
 201              'morecoursesavailable' => $morecoursesavailable,
 202          ];
 203      }
 204  
 205      /**
 206       * Returns description of method result value.
 207       *
 208       * @return \core_external\external_description
 209       */
 210      public static function execute_returns(): external_single_structure {
 211          return new external_single_structure(
 212              [
 213                  'courses' => new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Course'),
 214                  'nextoffset' => new external_value(PARAM_INT, 'Offset for the next request'),
 215                  'morecoursesavailable' => new external_value(PARAM_BOOL,
 216                      'Whether more courses with events exist within the provided parameters'),
 217              ]
 218          );
 219      }
 220  }