Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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\helpers; 20 21 use core_reportbuilder\local\filters\boolean_select; 22 use core_reportbuilder\local\filters\date; 23 use core_reportbuilder\local\filters\number; 24 use core_reportbuilder\local\filters\select; 25 use core_reportbuilder\local\filters\text; 26 use core_reportbuilder\local\report\column; 27 use core_reportbuilder\local\report\filter; 28 use lang_string; 29 use stdClass; 30 use core_customfield\data_controller; 31 use core_customfield\field_controller; 32 use core_customfield\handler; 33 34 /** 35 * Helper class for course custom fields. 36 * 37 * @package core_reportbuilder 38 * @copyright 2021 Sara Arjona <sara@moodle.com> based on David Matamoros <davidmc@moodle.com> code. 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class custom_fields { 42 43 /** @var string $entityname Name of the entity */ 44 private $entityname; 45 46 /** @var handler $handler The handler for the customfields */ 47 private $handler; 48 49 /** @var int $tablefieldalias The table alias and the field name (table.field) that matches the customfield instanceid. */ 50 private $tablefieldalias; 51 52 /** @var array additional joins */ 53 private $joins = []; 54 55 /** 56 * Class customfields constructor. 57 * 58 * @param string $tablefieldalias table alias and the field name (table.field) that matches the customfield instanceid. 59 * @param string $entityname name of the entity in the report where we add custom fields. 60 * @param string $component component name of full frankenstyle plugin name. 61 * @param string $area name of the area (each component/plugin may define handlers for multiple areas). 62 * @param int $itemid item id if the area uses them (usually not used). 63 */ 64 public function __construct(string $tablefieldalias, string $entityname, string $component, string $area, int $itemid = 0) { 65 $this->tablefieldalias = $tablefieldalias; 66 $this->entityname = $entityname; 67 $this->handler = handler::get_handler($component, $area, $itemid); 68 } 69 70 /** 71 * Additional join that is needed. 72 * 73 * @param string $join 74 * @return self 75 */ 76 public function add_join(string $join): self { 77 $this->joins[trim($join)] = trim($join); 78 return $this; 79 } 80 81 /** 82 * Additional joins that are needed. 83 * 84 * @param array $joins 85 * @return self 86 */ 87 public function add_joins(array $joins): self { 88 foreach ($joins as $join) { 89 $this->add_join($join); 90 } 91 return $this; 92 } 93 94 /** 95 * Return joins 96 * 97 * @return string[] 98 */ 99 private function get_joins(): array { 100 return array_values($this->joins); 101 } 102 103 /** 104 * Get table alias for given custom field 105 * 106 * The entity name is used to ensure the alias differs when the entity is used multiple times within the same report, each 107 * having their own table alias/join 108 * 109 * @param field_controller $field 110 * @return string 111 */ 112 private function get_table_alias(field_controller $field): string { 113 static $aliases = []; 114 115 $aliaskey = "{$this->entityname}_{$field->get('id')}"; 116 if (!array_key_exists($aliaskey, $aliases)) { 117 $aliases[$aliaskey] = database::generate_alias(); 118 } 119 120 return $aliases[$aliaskey]; 121 } 122 123 /** 124 * Get table join for given custom field 125 * 126 * @param field_controller $field 127 * @return string 128 */ 129 private function get_table_join(field_controller $field): string { 130 $customdatatablealias = $this->get_table_alias($field); 131 132 return "LEFT JOIN {customfield_data} {$customdatatablealias} 133 ON {$customdatatablealias}.fieldid = {$field->get('id')} 134 AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}"; 135 } 136 137 /** 138 * Gets the custom fields columns for the report. 139 * 140 * Column will be named as 'customfield_' + customfield shortname. 141 * 142 * @return column[] 143 */ 144 public function get_columns(): array { 145 global $DB; 146 147 $columns = []; 148 149 $categorieswithfields = $this->handler->get_categories_with_fields(); 150 foreach ($categorieswithfields as $fieldcategory) { 151 $categoryfields = $fieldcategory->get_fields(); 152 foreach ($categoryfields as $field) { 153 $customdatatablealias = $this->get_table_alias($field); 154 155 $datacontroller = data_controller::create(0, null, $field); 156 157 $datafield = $datacontroller->datafield(); 158 $datafieldsql = "{$customdatatablealias}.{$datafield}"; 159 160 // Long text fields should be cast for Oracle, for aggregation support. 161 $columntype = $this->get_column_type($field, $datafield); 162 if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { 163 $datafieldsql = $DB->sql_order_by_text($datafieldsql, 1024); 164 } 165 166 // Select enough fields to re-create and format each custom field instance value. 167 $selectfields = "{$customdatatablealias}.id, {$customdatatablealias}.contextid"; 168 if ($datafield === 'value') { 169 // We will take the format into account when displaying the individual values. 170 $selectfields .= ", {$customdatatablealias}.valueformat"; 171 } 172 173 $columns[] = (new column( 174 'customfield_' . $field->get('shortname'), 175 new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()), 176 $this->entityname 177 )) 178 ->add_joins($this->get_joins()) 179 ->add_join($this->get_table_join($field)) 180 ->add_field($datafieldsql, $datafield) 181 ->add_fields($selectfields) 182 ->set_type($columntype) 183 ->set_is_sortable($columntype !== column::TYPE_LONGTEXT) 184 ->add_callback(static function($value, stdClass $row, field_controller $field): string { 185 return (string) data_controller::create(0, $row, $field)->export_value(); 186 }, $field) 187 // Important. If the handler implements can_view() function, it will be called with parameter $instanceid=0. 188 // This means that per-instance access validation will be ignored. 189 ->set_is_available($this->handler->can_view($field, 0)); 190 } 191 } 192 return $columns; 193 } 194 195 /** 196 * Returns the column type 197 * 198 * @param field_controller $field 199 * @param string $datafield 200 * @return int 201 */ 202 private function get_column_type(field_controller $field, string $datafield): int { 203 if ($field->get('type') === 'checkbox') { 204 return column::TYPE_BOOLEAN; 205 } 206 207 if ($field->get('type') === 'date') { 208 return column::TYPE_TIMESTAMP; 209 } 210 211 if ($datafield === 'intvalue') { 212 return column::TYPE_INTEGER; 213 } 214 215 if ($datafield === 'decvalue') { 216 return column::TYPE_FLOAT; 217 } 218 219 if ($datafield === 'value') { 220 return column::TYPE_LONGTEXT; 221 } 222 223 return column::TYPE_TEXT; 224 } 225 226 /** 227 * Returns all available filters on custom fields. 228 * 229 * Filter will be named as 'customfield_' + customfield shortname. 230 * 231 * @return filter[] 232 */ 233 public function get_filters(): array { 234 global $DB; 235 236 $filters = []; 237 238 $categorieswithfields = $this->handler->get_categories_with_fields(); 239 foreach ($categorieswithfields as $fieldcategory) { 240 $categoryfields = $fieldcategory->get_fields(); 241 foreach ($categoryfields as $field) { 242 $customdatatablealias = $this->get_table_alias($field); 243 244 $datacontroller = data_controller::create(0, null, $field); 245 246 $datafield = $datacontroller->datafield(); 247 $datafieldsql = "{$customdatatablealias}.{$datafield}"; 248 if ($datafield === 'value') { 249 $datafieldsql = $DB->sql_cast_to_char($datafieldsql); 250 } 251 252 $typeclass = $this->get_filter_class_type($datacontroller); 253 $filter = (new filter( 254 $typeclass, 255 'customfield_' . $field->get('shortname'), 256 new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()), 257 $this->entityname, 258 $datafieldsql 259 )) 260 ->add_joins($this->get_joins()) 261 ->add_join($this->get_table_join($field)); 262 263 // Options are stored inside configdata json string and we need to convert it to array. 264 if ($field->get('type') === 'select') { 265 $filter->set_options_callback(static function() use ($field): array { 266 $options = explode("\r\n", $field->get_configdata_property('options')); 267 // Method set_options starts using array at index 1. we shift one position on this array. 268 // In course settings this menu has an empty option and we need to respect that. 269 array_unshift($options, " "); 270 unset($options[0]); 271 return $options; 272 }); 273 } 274 275 $filters[] = $filter; 276 } 277 } 278 return $filters; 279 } 280 281 /** 282 * Returns class for the filter element that should be used for the field 283 * 284 * In some situation we can assume what kind of data is stored in the customfield plugin and we can 285 * display appropriate filter form element. For all others assume text filter. 286 * 287 * @param data_controller $datacontroller 288 * @return string 289 */ 290 private function get_filter_class_type(data_controller $datacontroller): string { 291 $type = $datacontroller->get_field()->get('type'); 292 293 switch ($type) { 294 case 'checkbox': 295 $classtype = boolean_select::class; 296 break; 297 case 'date': 298 $classtype = date::class; 299 break; 300 case 'select': 301 $classtype = select::class; 302 break; 303 default: 304 // To support third party field type we need to account for stored numbers. 305 $datafield = $datacontroller->datafield(); 306 if ($datafield === 'intvalue' || $datafield === 'decvalue') { 307 $classtype = number::class; 308 } else { 309 $classtype = text::class; 310 } 311 break; 312 } 313 314 return $classtype; 315 } 316 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body