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.
   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 mod_lti\reportbuilder\local\systemreports;
  18  
  19  use core_reportbuilder\local\helpers\database;
  20  use core_reportbuilder\local\report\column;
  21  use mod_lti\reportbuilder\local\entities\tool_types;
  22  use core_reportbuilder\system_report;
  23  
  24  /**
  25   * Course external tools list system report class implementation.
  26   *
  27   * @package    mod_lti
  28   * @copyright  2023 Jake Dallimore <jrhdallimore@gmail.com>
  29   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class course_external_tools_list extends system_report {
  32  
  33      /** @var \stdClass the course to constrain the report to. */
  34      protected \stdClass $course;
  35  
  36      /** @var int the usage count for the tool represented in a row, and set by row_callback(). */
  37      protected int $perrowtoolusage = 0;
  38  
  39      /**
  40       * Initialise report, we need to set the main table, load our entities and set columns/filters
  41       */
  42      protected function initialise(): void {
  43          global $DB, $CFG;
  44          require_once($CFG->dirroot . '/mod/lti/locallib.php');
  45  
  46          $this->course = get_course($this->get_context()->instanceid);
  47  
  48          // Our main entity, it contains all the column definitions that we need.
  49          $entitymain = new tool_types();
  50          $entitymainalias = $entitymain->get_table_alias('lti_types');
  51  
  52          $this->set_main_table('lti_types', $entitymainalias);
  53          $this->add_entity($entitymain);
  54  
  55          // Now we can call our helper methods to add the content we want to include in the report.
  56          $this->add_columns($entitymain);
  57          $this->add_filters();
  58          $this->add_actions();
  59  
  60          // We need id and course in the actions column, without entity prefixes, so add these here.
  61          // We also need access to the tool usage count in a few places (the usage column as well as the actions column).
  62          $ti = database::generate_param_name(); // Tool instance param.
  63          $this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.course, ".
  64              "(SELECT COUNT($ti.id)
  65                  FROM {lti} $ti
  66                  WHERE $ti.typeid = {$entitymainalias}.id) AS toolusage");
  67  
  68          // Join the types_categories table, to include only tools available to the current course's category.
  69          $cattablealias = database::generate_alias();
  70          $joinsql = "LEFT JOIN {lti_types_categories} {$cattablealias}
  71                             ON ({$cattablealias}.typeid = {$entitymainalias}.id)";
  72          $this->add_join($joinsql);
  73  
  74          // Scope the report to the course context and include only those tools available to the category.
  75          $paramprefix = database::generate_param_name();
  76          $coursevisibleparam = database::generate_param_name();
  77          $categoryparam = database::generate_param_name();
  78          $toolstateparam = database::generate_param_name();
  79          [$insql, $params] = $DB->get_in_or_equal([get_site()->id, $this->course->id], SQL_PARAMS_NAMED, "{$paramprefix}_");
  80          $wheresql = "{$entitymainalias}.course {$insql} ".
  81              "AND {$entitymainalias}.coursevisible NOT IN (:{$coursevisibleparam}) ".
  82              "AND ({$cattablealias}.id IS NULL OR {$cattablealias}.categoryid = :{$categoryparam}) ".
  83              "AND {$entitymainalias}.state = :{$toolstateparam}";
  84          $params = array_merge(
  85              $params,
  86              [
  87                  $coursevisibleparam => LTI_COURSEVISIBLE_NO,
  88                  $categoryparam => $this->course->category,
  89                  $toolstateparam => LTI_TOOL_STATE_CONFIGURED
  90              ]
  91          );
  92          $this->add_base_condition_sql($wheresql, $params);
  93  
  94          $this->set_downloadable(false, get_string('pluginname', 'mod_lti'));
  95          $this->set_default_per_page(10);
  96          $this->set_default_no_results_notice(null);
  97      }
  98  
  99      /**
 100       * Validates access to view this report
 101       *
 102       * @return bool
 103       */
 104      protected function can_view(): bool {
 105          return has_capability('mod/lti:addpreconfiguredinstance', $this->get_context());
 106      }
 107  
 108      public function row_callback(\stdClass $row): void {
 109          $this->perrowtoolusage = $row->toolusage;
 110      }
 111  
 112      /**
 113       * Adds the columns we want to display in the report.
 114       *
 115       * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
 116       * unique identifier
 117       * @param tool_types $tooltypesentity
 118       * @return void
 119       */
 120      protected function add_columns(tool_types $tooltypesentity): void {
 121          $entitymainalias = $tooltypesentity->get_table_alias('lti_types');
 122  
 123          $columns = [
 124              'tool_types:name',
 125              'tool_types:description',
 126          ];
 127  
 128          $this->add_columns_from_entities($columns);
 129  
 130          // Tool usage column using a custom SQL subquery (defined in initialise method) to count tool instances within the course.
 131          // TODO: This should be replaced with proper column aggregation once that's added to system_report instances in MDL-76392.
 132          $this->add_column(new column(
 133              'usage',
 134              new \lang_string('usage', 'mod_lti'),
 135              $tooltypesentity->get_entity_name()
 136          ))
 137              ->set_type(column::TYPE_INTEGER)
 138              ->set_is_sortable(true)
 139              ->add_field("{$entitymainalias}.id")
 140              ->add_callback(fn() => $this->perrowtoolusage);
 141  
 142          // Enable toggle column.
 143          $this->add_column((new column(
 144              'showinactivitychooser',
 145              new \lang_string('showinactivitychooser', 'mod_lti'),
 146              $tooltypesentity->get_entity_name()
 147          ))
 148              // Site tools can be overridden on course level.
 149              ->add_join("LEFT JOIN {lti_coursevisible} lc ON lc.typeid = {$entitymainalias}.id AND lc.courseid = " . $this->course->id)
 150              ->set_type(column::TYPE_INTEGER)
 151              ->add_fields("{$entitymainalias}.id, {$entitymainalias}.coursevisible, lc.coursevisible as coursevisibleoverridden")
 152              ->set_is_sortable(false)
 153              ->set_callback(function(int $id, \stdClass $row): string {
 154                  global $PAGE;
 155                  $coursevisible = $row->coursevisible;
 156                  $courseid = $this->course->id;
 157                  if (!empty($row->coursevisibleoverridden)) {
 158                      $coursevisible = $row->coursevisibleoverridden;
 159                  }
 160  
 161                  if ($coursevisible == LTI_COURSEVISIBLE_ACTIVITYCHOOSER) {
 162                      $coursevisible = true;
 163                  } else {
 164                      $coursevisible = false;
 165                  }
 166  
 167                  $renderer = $PAGE->get_renderer('core_reportbuilder');
 168                  $attributes = [
 169                      ['name' => 'id', 'value' => $row->id],
 170                      ['name' => 'courseid', 'value' => $courseid],
 171                      ['name' => 'action', 'value' => 'showinactivitychooser-toggle'],
 172                      ['name' => 'state', 'value' => $coursevisible],
 173                  ];
 174                  $label = $coursevisible ? get_string('dontshowinactivitychooser', 'mod_lti')
 175                      : get_string('showinactivitychooser', 'mod_lti');
 176  
 177                  $disabled = !has_capability('mod/lti:addcoursetool', \context_course::instance($courseid));
 178  
 179                  return $renderer->render_from_template('core/toggle', [
 180                      'id' => 'showinactivitychooser-toggle-' . $row->id,
 181                      'checked' => $coursevisible,
 182                      'disabled' => $disabled,
 183                      'dataattributes' => $attributes,
 184                      'label' => $label,
 185                      'labelclasses' => 'sr-only'
 186                  ]);
 187              })
 188          );
 189  
 190          // Attempt to create a dummy actions column, working around the limitations of the official actions feature.
 191          $this->add_column(new column(
 192              'actions', new \lang_string('actions'),
 193              $tooltypesentity->get_entity_name()
 194          ))
 195              ->set_type(column::TYPE_TEXT)
 196              ->set_is_sortable(false)
 197              ->add_fields("{$entitymainalias}.id, {$entitymainalias}.course, {$entitymainalias}.name")
 198              ->add_callback(function($field, $row) {
 199                  global $OUTPUT;
 200  
 201                  // Lock actions for site-level preconfigured tools.
 202                  if (get_site()->id == $row->course) {
 203                      return \html_writer::div(
 204                          \html_writer::div(
 205                              $OUTPUT->pix_icon('t/locked', get_string('courseexternaltoolsnoeditpermissions', 'mod_lti')
 206                          ), 'tool-action-icon-container'), 'd-flex justify-content-end'
 207                      );
 208                  }
 209  
 210                  // Lock actions when the user can't add course tools.
 211                  if (!has_capability('mod/lti:addcoursetool', \context_course::instance($row->course))) {
 212                      return \html_writer::div(
 213                          \html_writer::div(
 214                              $OUTPUT->pix_icon('t/locked', get_string('courseexternaltoolsnoeditpermissions', 'mod_lti')
 215                          ), 'tool-action-icon-container'), 'd-flex justify-content-end'
 216                      );
 217                  }
 218  
 219                  // Build and display an action menu.
 220                  $menu = new \action_menu();
 221                  $menu->set_menu_trigger($OUTPUT->pix_icon('i/moremenu', get_string('actions', 'core')),
 222                      'btn btn-icon d-flex align-items-center justify-content-center'); // TODO check 'actions' lang string with UX.
 223  
 224                  $menu->add(new \action_menu_link(
 225                      new \moodle_url('/mod/lti/coursetooledit.php', ['course' => $row->course, 'typeid' => $row->id]),
 226                      null,
 227                      get_string('edit', 'core'),
 228                      null
 229                  ));
 230  
 231                  $menu->add(new \action_menu_link(
 232                      new \moodle_url('#'),
 233                      null,
 234                      get_string('delete', 'core'),
 235                      null,
 236                      [
 237                          'data-action' => 'course-tool-delete',
 238                          'data-course-tool-id' => $row->id,
 239                          'data-course-tool-name' => $row->name,
 240                          'data-course-tool-usage' => $this->perrowtoolusage
 241                      ],
 242                  ));
 243  
 244                  return $OUTPUT->render($menu);
 245              });
 246  
 247          // Default sorting.
 248          $this->set_initial_sort_column('tool_types:name', SORT_ASC);
 249      }
 250  
 251      /**
 252       * Add any actions for this report.
 253       *
 254       * @return void
 255       */
 256      protected function add_actions(): void {
 257      }
 258  
 259      /**
 260       * Adds the filters we want to display in the report
 261       *
 262       * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
 263       * unique identifier
 264       */
 265      protected function add_filters(): void {
 266  
 267          $this->add_filters_from_entities([]);
 268      }
 269  }