See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401] [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_reportbuilder\local\aggregation; 20 21 use lang_string; 22 use core_reportbuilder\local\helpers\database; 23 use core_reportbuilder\local\report\column; 24 25 /** 26 * Column group concatenation aggregation type 27 * 28 * @package core_reportbuilder 29 * @copyright 2021 Paul Holden <paulh@moodle.com> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class groupconcat extends base { 33 34 /** @var string Character to use as a delimeter between column fields */ 35 protected const COLUMN_FIELD_DELIMETER = '<|>'; 36 37 /** @var string Character to use a null coalesce value */ 38 protected const COLUMN_NULL_COALESCE = '<^>'; 39 40 /** @var string Character to use as a delimeter between field values */ 41 protected const FIELD_VALUE_DELIMETER = '<,>'; 42 43 /** 44 * Return aggregation name 45 * 46 * @return lang_string 47 */ 48 public static function get_name(): lang_string { 49 return new lang_string('aggregationgroupconcat', 'core_reportbuilder'); 50 } 51 52 /** 53 * This aggregation can be performed on all non-timestamp columns 54 * 55 * @param int $columntype 56 * @return bool 57 */ 58 public static function compatible(int $columntype): bool { 59 return !in_array($columntype, [ 60 column::TYPE_TIMESTAMP, 61 ]); 62 } 63 64 /** 65 * We cannot sort this aggregation type 66 * 67 * @param bool $columnsortable 68 * @return bool 69 */ 70 public static function sortable(bool $columnsortable): bool { 71 return false; 72 } 73 74 /** 75 * Override base method to ensure all SQL fields are concatenated together if there are multiple 76 * 77 * @param array $sqlfields 78 * @return string 79 */ 80 public static function get_column_field_sql(array $sqlfields): string { 81 if (count($sqlfields) === 1) { 82 return parent::get_column_field_sql($sqlfields); 83 } 84 85 return self::get_column_fields_concat($sqlfields, self::COLUMN_FIELD_DELIMETER, self::COLUMN_NULL_COALESCE); 86 } 87 88 /** 89 * Return the aggregated field SQL 90 * 91 * @param string $field 92 * @param int $columntype 93 * @return string 94 */ 95 public static function get_field_sql(string $field, int $columntype): string { 96 global $DB; 97 98 $fieldsort = database::sql_group_concat_sort($field); 99 100 return $DB->sql_group_concat($field, self::FIELD_VALUE_DELIMETER, $fieldsort); 101 } 102 103 /** 104 * Return formatted value for column when applying aggregation, note we need to split apart the concatenated string 105 * and apply callbacks to each concatenated value separately 106 * 107 * @param mixed $value 108 * @param array $values 109 * @param array $callbacks 110 * @param int $columntype 111 * @return mixed 112 */ 113 public static function format_value($value, array $values, array $callbacks, int $columntype) { 114 $firstvalue = reset($values); 115 if ($firstvalue === null) { 116 return ''; 117 } 118 119 $formattedvalues = []; 120 121 // Store original names of all values that would be present without aggregation. 122 $valuenames = array_keys($values); 123 $valuenamescount = count($valuenames); 124 125 // Loop over each extracted value from the concatenated string. 126 $values = explode(self::FIELD_VALUE_DELIMETER, (string)$firstvalue); 127 foreach ($values as $value) { 128 129 // Ensure we have equal number of value names/data, account for truncation by DB. 130 $valuedata = explode(self::COLUMN_FIELD_DELIMETER, $value); 131 if ($valuenamescount !== count($valuedata)) { 132 continue; 133 } 134 135 // Re-construct original values, also ensuring any nulls contained within are restored. 136 $originalvalues = array_map(static function(string $value): ?string { 137 return $value === self::COLUMN_NULL_COALESCE ? null : $value; 138 }, array_combine($valuenames, $valuedata)); 139 140 $originalvalue = column::get_default_value($originalvalues, $columntype); 141 142 // Once we've re-constructed each value, we can apply callbacks to it. 143 $formattedvalues[] = parent::format_value($originalvalue, $originalvalues, $callbacks, $columntype); 144 } 145 146 $listseparator = get_string('listsep', 'langconfig') . ' '; 147 return implode($listseparator, $formattedvalues); 148 } 149 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body