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  declare(strict_types=1);
  18  
  19  namespace core_reportbuilder\local\aggregation;
  20  
  21  use core_badges_generator;
  22  use core_badges\reportbuilder\datasource\badges;
  23  use core_reportbuilder_testcase;
  24  use core_reportbuilder_generator;
  25  use core_reportbuilder\manager;
  26  use core_user\reportbuilder\datasource\users;
  27  use stdClass;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  global $CFG;
  32  require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
  33  
  34  /**
  35   * Unit tests for group concatenation aggregation
  36   *
  37   * @package     core_reportbuilder
  38   * @covers      \core_reportbuilder\local\aggregation\base
  39   * @covers      \core_reportbuilder\local\aggregation\groupconcat
  40   * @copyright   2021 Paul Holden <paulh@moodle.com>
  41   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class groupconcat_test extends core_reportbuilder_testcase {
  44  
  45      /**
  46       * Test aggregation when applied to column
  47       */
  48      public function test_column_aggregation(): void {
  49          $this->resetAfterTest();
  50  
  51          // Test subjects.
  52          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
  53          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
  54          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
  55  
  56          /** @var core_reportbuilder_generator $generator */
  57          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  58          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
  59  
  60          // First column, sorted.
  61          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
  62  
  63          // This is the column we'll aggregate.
  64          $generator->create_column([
  65              'reportid' => $report->get('id'),
  66              'uniqueidentifier' => 'user:lastname',
  67              'aggregation' => groupconcat::get_class_name(),
  68          ]);
  69  
  70          // Assert lastname column was aggregated, and sorted predictably.
  71          $content = $this->get_custom_report_content($report->get('id'));
  72          $this->assertEquals([
  73              [
  74                  'c0_firstname' => 'Admin',
  75                  'c1_lastname' => 'User',
  76              ],
  77              [
  78                  'c0_firstname' => 'Bob',
  79                  'c1_lastname' => 'Apple, Banana, Banana',
  80              ],
  81          ], $content);
  82      }
  83  
  84      /**
  85       * Test aggregation when applied to column with multiple fields
  86       */
  87      public function test_column_aggregation_multiple_fields(): void {
  88          $this->resetAfterTest();
  89  
  90          $user = $this->getDataGenerator()->create_user(['firstname' => 'Adam', 'lastname' => 'Apple']);
  91  
  92          /** @var core_reportbuilder_generator $generator */
  93          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  94          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
  95  
  96          // This is the column we'll aggregate.
  97          $generator->create_column([
  98              'reportid' => $report->get('id'),
  99              'uniqueidentifier' => 'user:fullnamewithlink',
 100              'aggregation' => groupconcat::get_class_name(),
 101          ]);
 102  
 103          $content = $this->get_custom_report_content($report->get('id'));
 104          $this->assertCount(1, $content);
 105  
 106          // Ensure users are sorted predictably (Adam -> Admin).
 107          [$userone, $usertwo] = explode(', ', reset($content[0]));
 108          $this->assertStringContainsString(fullname($user, true), $userone);
 109          $this->assertStringContainsString(fullname(get_admin(), true), $usertwo);
 110      }
 111  
 112      /**
 113       * Test aggregation when applied to column with callback
 114       */
 115      public function test_column_aggregation_with_callback(): void {
 116          $this->resetAfterTest();
 117  
 118          // Test subjects.
 119          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
 120          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 0]);
 121          $this->getDataGenerator()->create_user(['firstname' => 'Bob', 'confirmed' => 1]);
 122  
 123          /** @var core_reportbuilder_generator $generator */
 124          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 125          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
 126  
 127          // First column, sorted.
 128          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname', 'sortenabled' => 1]);
 129  
 130          // This is the column we'll aggregate.
 131          $generator->create_column([
 132              'reportid' => $report->get('id'),
 133              'uniqueidentifier' => 'user:confirmed',
 134              'aggregation' => groupconcat::get_class_name(),
 135          ]);
 136  
 137          // Add callback to format the column.
 138          $instance = manager::get_report_from_persistent($report);
 139          $instance->get_column('user:confirmed')
 140              ->add_callback(static function(string $value, stdClass $row, $arguments, ?string $aggregation): string {
 141                  // Simple callback to return the given value, and append aggregation type.
 142                  return "{$value} ({$aggregation})";
 143              });
 144  
 145          // Assert confirmed column was aggregated, and sorted predictably with callback applied.
 146          $content = $this->get_custom_report_content($report->get('id'));
 147          $this->assertEquals([
 148              [
 149                  'c0_firstname' => 'Admin',
 150                  'c1_confirmed' => 'Yes (groupconcat)',
 151              ],
 152              [
 153                  'c0_firstname' => 'Bob',
 154                  'c1_confirmed' => 'No (groupconcat), Yes (groupconcat), Yes (groupconcat)',
 155              ],
 156          ], $content);
 157      }
 158  
 159      /**
 160       * Test aggregation when applied to column with callback that expects/handles null values
 161       */
 162      public function test_datasource_aggregate_column_callback_with_null(): void {
 163          $this->resetAfterTest();
 164          $this->setAdminUser();
 165  
 166          $userone = $this->getDataGenerator()->create_user(['description' => 'First user']);
 167          $usertwo = $this->getDataGenerator()->create_user(['description' => 'Second user']);
 168  
 169          /** @var core_badges_generator $generator */
 170          $generator = $this->getDataGenerator()->get_plugin_generator('core_badges');
 171  
 172          // Create course badge, issue to both users.
 173          $badgeone = $generator->create_badge(['name' => 'First badge']);
 174          $badgeone->issue($userone->id, true);
 175          $badgeone->issue($usertwo->id, true);
 176  
 177          // Create second badge, without issuing to anyone.
 178          $badgetwo = $generator->create_badge(['name' => 'Second badge']);
 179  
 180          /** @var core_reportbuilder_generator $generator */
 181          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 182          $report = $generator->create_report(['name' => 'Badges', 'source' => badges::class, 'default' => 0]);
 183  
 184          // First column, sorted.
 185          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:name', 'sortenabled' => 1]);
 186  
 187          // This is the column we'll aggregate.
 188          $generator->create_column([
 189              'reportid' => $report->get('id'),
 190              'uniqueidentifier' => 'user:description',
 191              'aggregation' => groupconcat::get_class_name(),
 192          ]);
 193  
 194          // Assert description column was aggregated, with callbacks accounting for null values.
 195          $content = $this->get_custom_report_content($report->get('id'));
 196          $this->assertEquals([
 197              [
 198                  'c0_name' => $badgeone->name,
 199                  'c1_description' => "{$userone->description}, {$usertwo->description}",
 200              ],
 201              [
 202                  'c0_name' => $badgetwo->name,
 203                  'c1_description' => '',
 204              ],
 205          ], $content);
 206      }
 207  }