Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [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  declare(strict_types=1);
  18  
  19  namespace core_course\reportbuilder\local\entities;
  20  
  21  use core_reportbuilder\local\entities\base;
  22  use core_course\reportbuilder\local\formatters\completion as completion_formatter;
  23  use core_reportbuilder\local\filters\boolean_select;
  24  use core_reportbuilder\local\filters\date;
  25  use core_reportbuilder\local\helpers\format;
  26  use core_reportbuilder\local\report\column;
  27  use core_reportbuilder\local\report\filter;
  28  use lang_string;
  29  use stdClass;
  30  
  31  /**
  32   * Course completion entity implementation
  33   *
  34   * @package     core_course
  35   * @copyright   2022 David Matamoros <davidmc@moodle.com>
  36   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class completion extends base {
  39  
  40      /**
  41       * Database tables that this entity uses and their default aliases
  42       *
  43       * @return array
  44       */
  45      protected function get_default_table_aliases(): array {
  46          return [
  47              'course_completion' => 'ccomp',
  48              'course' => 'c',
  49              'grade_grades' => 'gg',
  50              'grade_items' => 'gi',
  51              'user' => 'u',
  52          ];
  53      }
  54  
  55      /**
  56       * The default title for this entity in the list of columns/conditions/filters in the report builder
  57       *
  58       * @return lang_string
  59       */
  60      protected function get_default_entity_title(): lang_string {
  61          return new lang_string('coursecompletion', 'completion');
  62      }
  63  
  64      /**
  65       * Initialise the entity
  66       *
  67       * @return base
  68       */
  69      public function initialise(): base {
  70          foreach ($this->get_all_columns() as $column) {
  71              $this->add_column($column);
  72          }
  73  
  74          // All the filters defined by the entity can also be used as conditions.
  75          foreach ($this->get_all_filters() as $filter) {
  76              $this
  77                  ->add_filter($filter)
  78                  ->add_condition($filter);
  79          }
  80  
  81          return $this;
  82      }
  83  
  84      /**
  85       * Returns list of all available columns
  86       *
  87       * @return column[]
  88       */
  89      protected function get_all_columns(): array {
  90          $coursecompletion = $this->get_table_alias('course_completion');
  91          $course = $this->get_table_alias('course');
  92          $grade = $this->get_table_alias('grade_grades');
  93          $gradeitem = $this->get_table_alias('grade_items');
  94          $user = $this->get_table_alias('user');
  95  
  96          // Completed column.
  97          $columns[] = (new column(
  98              'completed',
  99              new lang_string('completed', 'completion'),
 100              $this->get_entity_name()
 101          ))
 102              ->add_joins($this->get_joins())
 103              ->set_type(column::TYPE_BOOLEAN)
 104              ->add_field("CASE WHEN {$coursecompletion}.timecompleted > 0 THEN 1 ELSE 0 END", 'completed')
 105              ->add_field("{$user}.id", 'userid')
 106              ->set_is_sortable(true)
 107              ->add_callback(static function(bool $value, stdClass $row): string {
 108                  if (!$row->userid) {
 109                      return '';
 110                  }
 111                  return format::boolean_as_text($value);
 112              });
 113  
 114          // Progress percentage column.
 115          $columns[] = (new column(
 116              'progresspercent',
 117              new lang_string('progress', 'completion'),
 118              $this->get_entity_name()
 119          ))
 120              ->add_joins($this->get_joins())
 121              ->set_type(column::TYPE_TEXT)
 122              ->add_field("{$course}.id", 'courseid')
 123              ->add_field("{$user}.id", 'userid')
 124              ->set_is_sortable(false)
 125              ->add_callback([completion_formatter::class, 'completion_progress']);
 126  
 127          // Time enrolled.
 128          $columns[] = (new column(
 129              'timeenrolled',
 130              new lang_string('timeenrolled', 'enrol'),
 131              $this->get_entity_name()
 132          ))
 133              ->add_joins($this->get_joins())
 134              ->set_type(column::TYPE_TIMESTAMP)
 135              ->add_field("{$coursecompletion}.timeenrolled")
 136              ->set_is_sortable(true)
 137              ->add_callback([format::class, 'userdate']);
 138  
 139          // Time started.
 140          $columns[] = (new column(
 141              'timestarted',
 142              new lang_string('timestarted', 'enrol'),
 143              $this->get_entity_name()
 144          ))
 145              ->add_joins($this->get_joins())
 146              ->set_type(column::TYPE_TIMESTAMP)
 147              ->add_field("{$coursecompletion}.timestarted")
 148              ->set_is_sortable(true)
 149              ->add_callback([format::class, 'userdate']);
 150  
 151          // Time completed.
 152          $columns[] = (new column(
 153              'timecompleted',
 154              new lang_string('timecompleted', 'completion'),
 155              $this->get_entity_name()
 156          ))
 157              ->add_joins($this->get_joins())
 158              ->set_type(column::TYPE_TIMESTAMP)
 159              ->add_field("{$coursecompletion}.timecompleted")
 160              ->set_is_sortable(true)
 161              ->add_callback([format::class, 'userdate']);
 162  
 163          // Time reaggregated.
 164          $columns[] = (new column(
 165              'reaggregate',
 166              new lang_string('timereaggregated', 'enrol'),
 167              $this->get_entity_name()
 168          ))
 169              ->add_joins($this->get_joins())
 170              ->set_type(column::TYPE_TIMESTAMP)
 171              ->add_field("{$coursecompletion}.reaggregate")
 172              ->set_is_sortable(true)
 173              ->add_callback([format::class, 'userdate']);
 174  
 175          // Days taking course (days since course start date until completion or until current date if not completed).
 176          $currenttime = time();
 177          $columns[] = (new column(
 178              'dayscourse',
 179              new lang_string('daystakingcourse', 'course'),
 180              $this->get_entity_name()
 181          ))
 182              ->add_joins($this->get_joins())
 183              ->set_type(column::TYPE_INTEGER)
 184              ->add_field("(
 185                  CASE
 186                      WHEN {$coursecompletion}.timecompleted > 0 THEN
 187                          {$coursecompletion}.timecompleted
 188                      ELSE
 189                          {$currenttime}
 190                  END - {$course}.startdate) / " . DAYSECS, 'dayscourse')
 191              ->add_field("{$user}.id", 'userid')
 192              ->set_is_sortable(true)
 193              ->add_callback([completion_formatter::class, 'get_days']);
 194  
 195          // Days since last completion (days since last enrolment date until completion or until current date if not completed).
 196          $columns[] = (new column(
 197              'daysuntilcompletion',
 198              new lang_string('daysuntilcompletion', 'completion'),
 199              $this->get_entity_name()
 200          ))
 201              ->add_joins($this->get_joins())
 202              ->set_type(column::TYPE_INTEGER)
 203              ->add_field("(
 204                  CASE
 205                      WHEN {$coursecompletion}.timecompleted > 0 THEN
 206                          {$coursecompletion}.timecompleted
 207                      ELSE
 208                          {$currenttime}
 209                  END - {$coursecompletion}.timeenrolled) / " . DAYSECS, 'daysuntilcompletion')
 210              ->add_field("{$user}.id", 'userid')
 211              ->set_is_sortable(true)
 212              ->add_callback([completion_formatter::class, 'get_days']);
 213  
 214          // Student course grade.
 215          $columns[] = (new column(
 216              'grade',
 217              new lang_string('gradenoun'),
 218              $this->get_entity_name()
 219          ))
 220              ->add_joins($this->get_joins())
 221              ->add_join("
 222                  LEFT JOIN {grade_items} {$gradeitem}
 223                         ON ({$gradeitem}.itemtype = 'course' AND {$course}.id = {$gradeitem}.courseid)
 224              ")
 225              ->add_join("
 226                  LEFT JOIN {grade_grades} {$grade}
 227                         ON ({$user}.id = {$grade}.userid AND {$gradeitem}.id = {$grade}.itemid)
 228              ")
 229              ->set_type(column::TYPE_FLOAT)
 230              ->add_fields("{$grade}.finalgrade")
 231              ->set_is_sortable(true)
 232              ->add_callback(function(?float $value): string {
 233                  if ($value === null) {
 234                      return '';
 235                  }
 236                  return format_float($value, 2);
 237              });
 238  
 239          return $columns;
 240      }
 241  
 242      /**
 243       * Return list of all available filters
 244       *
 245       * @return filter[]
 246       */
 247      protected function get_all_filters(): array {
 248          $coursecompletion = $this->get_table_alias('course_completion');
 249  
 250          // Completed status filter.
 251          $filters[] = (new filter(
 252              boolean_select::class,
 253              'completed',
 254              new lang_string('completed', 'completion'),
 255              $this->get_entity_name(),
 256              "CASE WHEN {$coursecompletion}.timecompleted > 0 THEN 1 ELSE 0 END"
 257          ))
 258              ->add_joins($this->get_joins());
 259  
 260          // Time completed filter.
 261          $filters[] = (new filter(
 262              date::class,
 263              'timecompleted',
 264              new lang_string('timecompleted', 'completion'),
 265              $this->get_entity_name(),
 266              "{$coursecompletion}.timecompleted"
 267          ))
 268              ->add_joins($this->get_joins())
 269              ->set_limited_operators([
 270                  date::DATE_ANY,
 271                  date::DATE_NOT_EMPTY,
 272                  date::DATE_EMPTY,
 273                  date::DATE_RANGE,
 274                  date::DATE_LAST,
 275                  date::DATE_CURRENT,
 276              ]);
 277  
 278          return $filters;
 279      }
 280  }