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  declare(strict_types=1);
  18  
  19  namespace core_badges\reportbuilder\local\systemreports;
  20  
  21  use core\context\{course, system};
  22  use core_badges\reportbuilder\local\entities\badge;
  23  use core_reportbuilder\local\helpers\database;
  24  use core_reportbuilder\local\report\{action, column};
  25  use core_reportbuilder\system_report;
  26  use lang_string;
  27  use moodle_url;
  28  use pix_icon;
  29  use stdClass;
  30  
  31  defined('MOODLE_INTERNAL') || die;
  32  
  33  global $CFG;
  34  require_once("{$CFG->libdir}/badgeslib.php");
  35  
  36  /**
  37   * Badges system report class implementation
  38   *
  39   * @package    core_badges
  40   * @copyright  2023 David Carrillo <davidmc@moodle.com>
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class badges extends system_report {
  44  
  45      /**
  46       * Initialise report, we need to set the main table, load our entities and set columns/filters
  47       */
  48      protected function initialise(): void {
  49          // Our main entity, it contains all of the column definitions that we need.
  50          $badgeentity = new badge();
  51          $entityalias = $badgeentity->get_table_alias('badge');
  52  
  53          $this->set_main_table('badge', $entityalias);
  54          $this->add_entity($badgeentity);
  55  
  56          $paramtype = database::generate_param_name();
  57          $context = $this->get_context();
  58          if ($context instanceof system) {
  59              $type = BADGE_TYPE_SITE;
  60              $this->add_base_condition_sql("{$entityalias}.type = :$paramtype", [$paramtype => $type]);
  61          } else {
  62              $type = BADGE_TYPE_COURSE;
  63              $paramcourseid = database::generate_param_name();
  64              $this->add_base_condition_sql("{$entityalias}.type = :$paramtype AND {$entityalias}.courseid = :$paramcourseid",
  65                  [$paramtype => $type, $paramcourseid => $context->instanceid]);
  66          }
  67  
  68          // Any columns required by actions should be defined here to ensure they're always available.
  69          $this->add_base_fields("{$entityalias}.id, {$entityalias}.type, {$entityalias}.courseid, {$entityalias}.status");
  70  
  71          // Now we can call our helper methods to add the content we want to include in the report.
  72          $this->add_columns($badgeentity);
  73          $this->add_filters();
  74          $this->add_actions();
  75  
  76          // Set initial sorting by name.
  77          $this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
  78  
  79          // Set if report can be downloaded.
  80          $this->set_downloadable(false);
  81      }
  82  
  83      /**
  84       * Validates access to view this report
  85       *
  86       * @return bool
  87       */
  88      protected function can_view(): bool {
  89          return has_any_capability([
  90              'moodle/badges:viewawarded',
  91              'moodle/badges:createbadge',
  92              'moodle/badges:awardbadge',
  93              'moodle/badges:configurecriteria',
  94              'moodle/badges:configuremessages',
  95              'moodle/badges:configuredetails',
  96              'moodle/badges:deletebadge'], $this->get_context());
  97      }
  98  
  99      /**
 100       * Adds the columns we want to display in the report
 101       *
 102       * They are provided by the entities we previously added in the {@see initialise} method, referencing each by their
 103       * unique identifier. If custom columns are needed just for this report, they can be defined here.
 104       *
 105       * @param badge $badgeentity
 106       */
 107      public function add_columns(badge $badgeentity): void {
 108          $columns = [
 109              'badge:image',
 110              'badge:namewithlink',
 111              'badge:status',
 112              'badge:criteria',
 113          ];
 114  
 115          $this->add_columns_from_entities($columns);
 116  
 117          // Issued badges column.
 118          // TODO: Move this column to the entity when MDL-76392 is integrated.
 119          $tempbadgealias = database::generate_alias();
 120          $badgeentityalias = $badgeentity->get_table_alias('badge');
 121          $this->add_column((new column(
 122              'issued',
 123              new lang_string('awards', 'core_badges'),
 124              $badgeentity->get_entity_name()
 125          ))
 126              ->add_joins($this->get_joins())
 127              ->set_type(column::TYPE_INTEGER)
 128              ->add_field("(SELECT COUNT({$tempbadgealias}.userid)
 129                              FROM {badge_issued} {$tempbadgealias}
 130                        INNER JOIN {user} u
 131                                ON {$tempbadgealias}.userid = u.id
 132                             WHERE {$tempbadgealias}.badgeid = {$badgeentityalias}.id AND u.deleted = 0)", 'issued')
 133              ->set_is_sortable(true));
 134  
 135          // Remove title from image column.
 136          $this->get_column('badge:image')->set_title(null);
 137  
 138          // Change title from namewithlink column.
 139          $this->get_column('badge:namewithlink')->set_title(new lang_string('name'));
 140      }
 141  
 142      /**
 143       * Adds the filters we want to display in the report
 144       *
 145       * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
 146       * unique identifier
 147       */
 148      protected function add_filters(): void {
 149          $filters = [
 150              'badge:name',
 151              'badge:status',
 152          ];
 153          $this->add_filters_from_entities($filters);
 154      }
 155  
 156      /**
 157       * Add the system report actions. An extra column will be appended to each row, containing all actions added here
 158       *
 159       * Note the use of ":id" placeholder which will be substituted according to actual values in the row
 160       */
 161      protected function add_actions(): void {
 162          // Activate badge.
 163          $this->add_action((new action(
 164              new moodle_url('/badges/action.php', [
 165                  'id' => ':id',
 166                  'activate' => true,
 167                  'return' => ':return',
 168              ]),
 169              new pix_icon('t/show', '', 'core'),
 170              [],
 171              false,
 172              new lang_string('activate', 'badges')
 173          ))->add_callback(static function(stdclass $row): bool {
 174              $badge = new \core_badges\badge($row->id);
 175  
 176              // Populate the return URL.
 177              $row->return = (new moodle_url('/badges/index.php',
 178                  ['type' => $badge->type, 'id' => (int) $badge->courseid]))->out_as_local_url(false);
 179  
 180              return has_capability('moodle/badges:configuredetails', $badge->get_context()) &&
 181                  $badge->has_criteria() &&
 182                  ($row->status == BADGE_STATUS_INACTIVE || $row->status == BADGE_STATUS_INACTIVE_LOCKED);
 183  
 184          }));
 185  
 186          // Deactivate badge.
 187          $this->add_action((new action(
 188              new moodle_url('/badges/index.php', [
 189                  'lock' => ':id',
 190                  'sesskey' => sesskey(),
 191                  'type' => ':type',
 192                  'id' => ':courseid',
 193              ]),
 194              new pix_icon('t/hide', '', 'core'),
 195              [],
 196              false,
 197              new lang_string('deactivate', 'badges')
 198          ))->add_callback(static function(stdclass $row): bool {
 199              $badge = new \core_badges\badge($row->id);
 200              return has_capability('moodle/badges:configuredetails', $badge->get_context()) &&
 201                  $badge->has_criteria() &&
 202                  $row->status != BADGE_STATUS_INACTIVE && $row->status != BADGE_STATUS_INACTIVE_LOCKED;
 203          }));
 204  
 205          // Award badge manually.
 206          $this->add_action((new action(
 207              new moodle_url('/badges/award.php', [
 208                  'id' => ':id',
 209              ]),
 210              new pix_icon('t/award', '', 'core'),
 211              [],
 212              false,
 213              new lang_string('award', 'badges')
 214          ))->add_callback(static function(stdclass $row): bool {
 215              $badge = new \core_badges\badge($row->id);
 216              return has_capability('moodle/badges:awardbadge', $badge->get_context()) &&
 217                  $badge->has_manual_award_criteria() &&
 218                  $badge->is_active();
 219          }));
 220  
 221          // Edit action.
 222          $this->add_action((new action(
 223              new moodle_url('/badges/edit.php', [
 224                  'id' => ':id',
 225                  'action' => 'badge',
 226              ]),
 227              new pix_icon('t/edit', '', 'core'),
 228              [],
 229              false,
 230              new lang_string('edit', 'core')
 231          ))->add_callback(static function(stdclass $row): bool {
 232              $context = self::get_badge_context((int)$row->type, (int)$row->courseid);
 233              return has_capability('moodle/badges:configuredetails', $context);
 234  
 235          }));
 236  
 237          // Duplicate action.
 238          $this->add_action((new action(
 239              new moodle_url('/badges/action.php', [
 240                  'id' => ':id',
 241                  'copy' => 1,
 242                  'sesskey' => sesskey(),
 243              ]),
 244              new pix_icon('t/copy', '', 'core'),
 245              [],
 246              false,
 247              new lang_string('copy', 'badges')
 248          ))->add_callback(static function(stdclass $row): bool {
 249              $context = self::get_badge_context((int)$row->type, (int)$row->courseid);
 250              return has_capability('moodle/badges:createbadge', $context);
 251          }));
 252  
 253          // Delete action.
 254          $this->add_action((new action(
 255              new moodle_url('/badges/index.php', [
 256                  'delete' => ':id',
 257                  'type' => ':type',
 258                  'id' => ':courseid',
 259              ]),
 260              new pix_icon('t/delete', '', 'core'),
 261              [],
 262              false,
 263              new lang_string('delete', 'core')
 264          ))->add_callback(static function(stdclass $row): bool {
 265              $context = self::get_badge_context((int)$row->type, (int)$row->courseid);
 266              return has_capability('moodle/badges:deletebadge', $context);
 267          }));
 268      }
 269  
 270      /**
 271       * Return badge context based on type and courseid
 272       *
 273       * @param int $type
 274       * @param int $courseid
 275       * @return \core\context
 276       * @throws \coding_exception
 277       */
 278      private static function get_badge_context(int $type, int $courseid): \core\context {
 279          switch ($type) {
 280              case BADGE_TYPE_SITE:
 281                  return system::instance();
 282              case BADGE_TYPE_COURSE:
 283                  return course::instance($courseid);
 284              default:
 285                  throw new \coding_exception('Wrong context');
 286          }
 287      }
 288  
 289      /**
 290       * CSS classes to add to the row
 291       *
 292       * @param stdClass $row
 293       * @return string
 294       */
 295      public function get_row_class(stdClass $row): string {
 296          return ($row->status == BADGE_STATUS_INACTIVE_LOCKED || $row->status == BADGE_STATUS_INACTIVE) ? 'text-muted' : '';
 297      }
 298  }