Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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\report; 20 21 use coding_exception; 22 use context; 23 use lang_string; 24 use core_reportbuilder\local\entities\base as entity_base; 25 use core_reportbuilder\local\filters\base as filter_base; 26 use core_reportbuilder\local\helpers\database; 27 use core_reportbuilder\local\helpers\user_filter_manager; 28 use core_reportbuilder\local\models\report; 29 30 /** 31 * Base class for all reports 32 * 33 * @package core_reportbuilder 34 * @copyright 2020 Paul Holden <paulh@moodle.com> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 abstract class base { 38 39 /** @var int Custom report type value */ 40 public const TYPE_CUSTOM_REPORT = 0; 41 42 /** @var int System report type value */ 43 public const TYPE_SYSTEM_REPORT = 1; 44 45 /** @var int Default paging limit */ 46 public const DEFAULT_PAGESIZE = 30; 47 48 /** @var report $report Report persistent */ 49 private $report; 50 51 /** @var string $maintable */ 52 private $maintable = ''; 53 54 /** @var string $maintablealias */ 55 private $maintablealias = ''; 56 57 /** @var array $sqljoins */ 58 private $sqljoins = []; 59 60 /** @var array $sqlwheres */ 61 private $sqlwheres = []; 62 63 /** @var array $sqlparams */ 64 private $sqlparams = []; 65 66 /** @var entity_base[] $entities */ 67 private $entities = []; 68 69 /** @var lang_string[] */ 70 private $entitytitles = []; 71 72 /** @var column[] $columns */ 73 private $columns = []; 74 75 /** @var filter[] $conditions */ 76 private $conditions = []; 77 78 /** @var filter[] $filters */ 79 private $filters = []; 80 81 /** @var bool $downloadable Set if the report can be downloaded */ 82 private $downloadable = false; 83 84 /** @var string $downloadfilename Name of the downloaded file */ 85 private $downloadfilename = ''; 86 87 /** 88 * Base report constructor 89 * 90 * @param report $report 91 */ 92 public function __construct(report $report) { 93 $this->report = $report; 94 95 // Initialise and validate the report. 96 $this->initialise(); 97 $this->validate(); 98 } 99 100 /** 101 * Returns persistent class used when initialising this report 102 * 103 * @return report 104 */ 105 final public function get_report_persistent(): report { 106 return $this->report; 107 } 108 109 /** 110 * Initialise report. Specify which columns, filters, etc should be present 111 * 112 * To set the base query use: 113 * - {@see set_main_table} 114 * - {@see add_base_condition_simple} or {@see add_base_condition_sql} 115 * - {@see add_join} 116 * 117 * To add content to the report use: 118 * - {@see add_entity} 119 * - {@see add_column} 120 * - {@see add_filter} 121 * - etc 122 */ 123 abstract protected function initialise(): void; 124 125 /** 126 * Get the report availability. Sub-classes should override this method to declare themselves unavailable, for example if 127 * they require classes that aren't present due to missing plugin 128 * 129 * @return bool 130 */ 131 public static function is_available(): bool { 132 return true; 133 } 134 135 /** 136 * Perform some basic validation about expected class properties 137 * 138 * @throws coding_exception 139 */ 140 protected function validate(): void { 141 if (empty($this->maintable)) { 142 throw new coding_exception('Report must define main table by calling $this->set_main_table()'); 143 } 144 145 if (empty($this->columns)) { 146 throw new coding_exception('Report must define at least one column by calling $this->add_column()'); 147 } 148 } 149 150 /** 151 * Set the main table and alias for the SQL query 152 * 153 * @param string $tablename 154 * @param string $tablealias 155 */ 156 final public function set_main_table(string $tablename, string $tablealias = ''): void { 157 $this->maintable = $tablename; 158 $this->maintablealias = $tablealias; 159 } 160 161 /** 162 * Get the main table name 163 * 164 * @return string 165 */ 166 final public function get_main_table(): string { 167 return $this->maintable; 168 } 169 170 /** 171 * Get the alias for the main table 172 * 173 * @return string 174 */ 175 final public function get_main_table_alias(): string { 176 return $this->maintablealias; 177 } 178 179 /** 180 * Adds report JOIN clause that is always added 181 * 182 * @param string $join 183 * @param array $params 184 * @param bool $validateparams Some queries might add non-standard params and validation could fail 185 */ 186 protected function add_join(string $join, array $params = [], bool $validateparams = true): void { 187 if ($validateparams) { 188 database::validate_params($params); 189 } 190 191 $this->sqljoins[trim($join)] = trim($join); 192 $this->sqlparams += $params; 193 } 194 195 /** 196 * Return report JOIN clauses 197 * 198 * @return array 199 */ 200 public function get_joins(): array { 201 return array_values($this->sqljoins); 202 } 203 204 /** 205 * Define simple "field = value" clause to apply to the report query 206 * 207 * @param string $fieldname 208 * @param mixed $fieldvalue 209 */ 210 final public function add_base_condition_simple(string $fieldname, $fieldvalue): void { 211 if ($fieldvalue === null) { 212 $this->add_base_condition_sql("{$fieldname} IS NULL"); 213 } else { 214 $fieldvalueparam = database::generate_param_name(); 215 $this->add_base_condition_sql("{$fieldname} = :{$fieldvalueparam}", [ 216 $fieldvalueparam => $fieldvalue, 217 ]); 218 } 219 } 220 221 /** 222 * Define more complex clause that will always be applied to the report query 223 * 224 * @param string $where 225 * @param array $params Note that the param names should be generated by {@see database::generate_param_name} 226 */ 227 final public function add_base_condition_sql(string $where, array $params = []): void { 228 database::validate_params($params); 229 230 $this->sqlwheres[] = trim($where); 231 $this->sqlparams = $params + $this->sqlparams; 232 } 233 234 /** 235 * Return base select/params for the report query 236 * 237 * @return array [string $select, array $params] 238 */ 239 final public function get_base_condition(): array { 240 return [ 241 implode(' AND ', $this->sqlwheres), 242 $this->sqlparams, 243 ]; 244 } 245 246 /** 247 * Adds given entity, along with it's columns and filters, to the report 248 * 249 * @param entity_base $entity 250 */ 251 final protected function add_entity(entity_base $entity): void { 252 $entityname = $entity->get_entity_name(); 253 $this->annotate_entity($entityname, $entity->get_entity_title()); 254 $this->entities[$entityname] = $entity->initialise(); 255 } 256 257 /** 258 * Returns the entity added to the report from the given entity name 259 * 260 * @param string $name 261 * @return entity_base 262 * @throws coding_exception 263 */ 264 final protected function get_entity(string $name): entity_base { 265 if (!array_key_exists($name, $this->entities)) { 266 throw new coding_exception('Invalid entity name', $name); 267 } 268 269 return $this->entities[$name]; 270 } 271 272 /** 273 * Define a new entity for the report 274 * 275 * @param string $name 276 * @param lang_string $title 277 * @throws coding_exception 278 */ 279 final protected function annotate_entity(string $name, lang_string $title): void { 280 if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) { 281 throw new coding_exception('Entity name must be comprised of alphanumeric character, underscore or dash'); 282 } 283 284 $this->entitytitles[$name] = $title; 285 } 286 287 /** 288 * Returns title of given report entity 289 * 290 * @param string $name 291 * @return lang_string 292 * @throws coding_exception 293 */ 294 final public function get_entity_title(string $name): lang_string { 295 if (!array_key_exists($name, $this->entitytitles)) { 296 throw new coding_exception('Invalid entity name', $name); 297 } 298 299 return $this->entitytitles[$name]; 300 } 301 302 /** 303 * Adds a column to the report 304 * 305 * @param column $column 306 * @return column 307 * @throws coding_exception 308 */ 309 final protected function add_column(column $column): column { 310 if (!array_key_exists($column->get_entity_name(), $this->entitytitles)) { 311 throw new coding_exception('Invalid entity name', $column->get_entity_name()); 312 } 313 314 $name = $column->get_name(); 315 if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) { 316 throw new coding_exception('Column name must be comprised of alphanumeric character, underscore or dash'); 317 } 318 319 $uniqueidentifier = $column->get_unique_identifier(); 320 if (array_key_exists($uniqueidentifier, $this->columns)) { 321 throw new coding_exception('Duplicate column identifier', $uniqueidentifier); 322 } 323 324 $this->columns[$uniqueidentifier] = $column; 325 326 return $column; 327 } 328 329 /** 330 * Add given column to the report from an entity 331 * 332 * The entity must have already been added to the report before calling this method 333 * 334 * @param string $uniqueidentifier 335 * @return column 336 */ 337 final protected function add_column_from_entity(string $uniqueidentifier): column { 338 [$entityname, $columnname] = explode(':', $uniqueidentifier, 2); 339 340 return $this->add_column($this->get_entity($entityname)->get_column($columnname)); 341 } 342 343 /** 344 * Add given columns to the report from one or more entities 345 * 346 * Each entity must have already been added to the report before calling this method 347 * 348 * @param string[] $columns Unique identifier of each entity column 349 */ 350 final protected function add_columns_from_entities(array $columns): void { 351 foreach ($columns as $column) { 352 $this->add_column_from_entity($column); 353 } 354 } 355 356 /** 357 * Return report column by unique identifier 358 * 359 * @param string $uniqueidentifier 360 * @return column|null 361 */ 362 final public function get_column(string $uniqueidentifier): ?column { 363 return $this->columns[$uniqueidentifier] ?? null; 364 } 365 366 /** 367 * Return all available report columns 368 * 369 * @return column[] 370 */ 371 final public function get_columns(): array { 372 return array_filter($this->columns, static function(column $column): bool { 373 return $column->get_is_available(); 374 }); 375 } 376 377 /** 378 * Return all active report columns (by default, all available columns) 379 * 380 * @return column[] 381 */ 382 public function get_active_columns(): array { 383 return $this->get_columns(); 384 } 385 386 /** 387 * Return all active report columns, keyed by their alias (only active columns in a report would have a valid alias/index) 388 * 389 * @return column[] 390 */ 391 final public function get_active_columns_by_alias(): array { 392 $columns = []; 393 394 foreach ($this->get_active_columns() as $column) { 395 $columns[$column->get_column_alias()] = $column; 396 } 397 398 return $columns; 399 } 400 401 /** 402 * Adds a condition to the report 403 * 404 * @param filter $condition 405 * @return filter 406 * @throws coding_exception 407 */ 408 final protected function add_condition(filter $condition): filter { 409 if (!array_key_exists($condition->get_entity_name(), $this->entitytitles)) { 410 throw new coding_exception('Invalid entity name', $condition->get_entity_name()); 411 } 412 413 $name = $condition->get_name(); 414 if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) { 415 throw new coding_exception('Condition name must be comprised of alphanumeric character, underscore or dash'); 416 } 417 418 $uniqueidentifier = $condition->get_unique_identifier(); 419 if (array_key_exists($uniqueidentifier, $this->conditions)) { 420 throw new coding_exception('Duplicate condition identifier', $uniqueidentifier); 421 } 422 423 $this->conditions[$uniqueidentifier] = $condition; 424 425 return $condition; 426 } 427 428 /** 429 * Add given condition to the report from an entity 430 * 431 * The entity must have already been added to the report before calling this method 432 * 433 * @param string $uniqueidentifier 434 * @return filter 435 */ 436 final protected function add_condition_from_entity(string $uniqueidentifier): filter { 437 [$entityname, $conditionname] = explode(':', $uniqueidentifier, 2); 438 439 return $this->add_condition($this->get_entity($entityname)->get_condition($conditionname)); 440 } 441 442 /** 443 * Add given conditions to the report from one or more entities 444 * 445 * Each entity must have already been added to the report before calling this method 446 * 447 * @param string[] $conditions Unique identifier of each entity condition 448 */ 449 final protected function add_conditions_from_entities(array $conditions): void { 450 foreach ($conditions as $condition) { 451 $this->add_condition_from_entity($condition); 452 } 453 } 454 455 /** 456 * Return report condition by unique identifier 457 * 458 * @param string $uniqueidentifier 459 * @return filter|null 460 */ 461 final public function get_condition(string $uniqueidentifier): ?filter { 462 return $this->conditions[$uniqueidentifier] ?? null; 463 } 464 465 /** 466 * Return all available report conditions 467 * 468 * @return filter[] 469 */ 470 final public function get_conditions(): array { 471 return array_filter($this->conditions, static function(filter $condition): bool { 472 return $condition->get_is_available(); 473 }); 474 } 475 476 /** 477 * Return all active report conditions (by default, all available conditions) 478 * 479 * @return filter[] 480 */ 481 public function get_active_conditions(): array { 482 return $this->get_conditions(); 483 } 484 485 /** 486 * Return all active report condition instances 487 * 488 * @return filter_base[] 489 */ 490 final public function get_condition_instances(): array { 491 return array_map(static function(filter $condition): filter_base { 492 /** @var filter_base $conditionclass */ 493 $conditionclass = $condition->get_filter_class(); 494 495 return $conditionclass::create($condition); 496 }, $this->get_active_conditions()); 497 } 498 499 /** 500 * Set the condition values of the report 501 * 502 * @param array $values 503 * @return bool 504 */ 505 final public function set_condition_values(array $values): bool { 506 $this->report->set('conditiondata', json_encode($values)) 507 ->save(); 508 509 return true; 510 } 511 512 /** 513 * Get the condition values of the report 514 * 515 * @return array 516 */ 517 final public function get_condition_values(): array { 518 $conditions = (string) $this->report->get('conditiondata'); 519 520 return (array) json_decode($conditions); 521 } 522 523 /** 524 * Set the settings values of the report 525 * 526 * @param array $values 527 * @return bool 528 */ 529 final public function set_settings_values(array $values): bool { 530 $currentsettings = $this->get_settings_values(); 531 $settings = array_merge($currentsettings, $values); 532 $this->report->set('settingsdata', json_encode($settings)) 533 ->save(); 534 return true; 535 } 536 537 /** 538 * Get the settings values of the report 539 * 540 * @return array 541 */ 542 final public function get_settings_values(): array { 543 $settings = (string) $this->report->get('settingsdata'); 544 545 return (array) json_decode($settings); 546 } 547 548 /** 549 * Adds a filter to the report 550 * 551 * @param filter $filter 552 * @return filter 553 * @throws coding_exception 554 */ 555 final protected function add_filter(filter $filter): filter { 556 if (!array_key_exists($filter->get_entity_name(), $this->entitytitles)) { 557 throw new coding_exception('Invalid entity name', $filter->get_entity_name()); 558 } 559 560 $name = $filter->get_name(); 561 if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) { 562 throw new coding_exception('Filter name must be comprised of alphanumeric character, underscore or dash'); 563 } 564 565 $uniqueidentifier = $filter->get_unique_identifier(); 566 if (array_key_exists($uniqueidentifier, $this->filters)) { 567 throw new coding_exception('Duplicate filter identifier', $uniqueidentifier); 568 } 569 570 $this->filters[$uniqueidentifier] = $filter; 571 572 return $filter; 573 } 574 575 /** 576 * Add given filter to the report from an entity 577 * 578 * The entity must have already been added to the report before calling this method 579 * 580 * @param string $uniqueidentifier 581 * @return filter 582 */ 583 final protected function add_filter_from_entity(string $uniqueidentifier): filter { 584 [$entityname, $filtername] = explode(':', $uniqueidentifier, 2); 585 586 return $this->add_filter($this->get_entity($entityname)->get_filter($filtername)); 587 } 588 589 /** 590 * Add given filters to the report from one or more entities 591 * 592 * Each entity must have already been added to the report before calling this method 593 * 594 * @param string[] $filters Unique identifier of each entity filter 595 */ 596 final protected function add_filters_from_entities(array $filters): void { 597 foreach ($filters as $filter) { 598 $this->add_filter_from_entity($filter); 599 } 600 } 601 602 /** 603 * Return report filter by unique identifier 604 * 605 * @param string $uniqueidentifier 606 * @return filter|null 607 */ 608 final public function get_filter(string $uniqueidentifier): ?filter { 609 return $this->filters[$uniqueidentifier] ?? null; 610 } 611 612 /** 613 * Return all available report filters 614 * 615 * @return filter[] 616 */ 617 final public function get_filters(): array { 618 return array_filter($this->filters, static function(filter $filter): bool { 619 return $filter->get_is_available(); 620 }); 621 } 622 623 /** 624 * Return all active report filters (by default, all available filters) 625 * 626 * @return filter[] 627 */ 628 public function get_active_filters(): array { 629 return $this->get_filters(); 630 } 631 632 /** 633 * Return all active report filter instances 634 * 635 * @return filter_base[] 636 */ 637 final public function get_filter_instances(): array { 638 return array_map(static function(filter $filter): filter_base { 639 /** @var filter_base $filterclass */ 640 $filterclass = $filter->get_filter_class(); 641 642 return $filterclass::create($filter); 643 }, $this->get_active_filters()); 644 } 645 646 /** 647 * Set the filter values of the report 648 * 649 * @param array $values 650 * @return bool 651 */ 652 final public function set_filter_values(array $values): bool { 653 return user_filter_manager::set($this->report->get('id'), $values); 654 } 655 656 /** 657 * Get the filter values of the report 658 * 659 * @return array 660 */ 661 final public function get_filter_values(): array { 662 return user_filter_manager::get($this->report->get('id')); 663 } 664 665 /** 666 * Return the number of filter instances that are being applied based on the report's filter values (i.e. user has 667 * configured them from their initial "Any value" state) 668 * 669 * @return int 670 */ 671 final public function get_applied_filter_count(): int { 672 $values = $this->get_filter_values(); 673 $applied = array_filter($this->get_filter_instances(), static function(filter_base $filter) use ($values): bool { 674 return $filter->applies_to_values($values); 675 }); 676 677 return count($applied); 678 } 679 680 /** 681 * Set if the report can be downloaded. 682 * 683 * @param bool $downloadable 684 * @param string $downloadfilename If the report is downloadable, then a filename should be provided here 685 */ 686 final public function set_downloadable(bool $downloadable, string $downloadfilename = 'export'): void { 687 $this->downloadable = $downloadable; 688 $this->downloadfilename = $downloadfilename; 689 } 690 691 /** 692 * Get if the report can be downloaded. 693 * 694 * @return bool 695 */ 696 final public function is_downloadable(): bool { 697 return $this->downloadable; 698 } 699 700 /** 701 * Return the downloadable report filename 702 * 703 * @return string 704 */ 705 final public function get_downloadfilename(): string { 706 return $this->downloadfilename; 707 } 708 709 /** 710 * Returns the report context 711 * 712 * @return context 713 */ 714 public function get_context(): context { 715 return $this->report->get_context(); 716 } 717 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body