Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 16... Línea 16...
16
 
16
 
Línea 17... Línea 17...
17
declare(strict_types=1);
17
declare(strict_types=1);
Línea 18... Línea 18...
18
 
18
 
19
namespace core_reportbuilder\local\report;
19
namespace core_reportbuilder\local\report;
20
 
20
 
21
use coding_exception;
21
use core\exception\coding_exception;
22
use lang_string;
22
use core\lang_string;
23
use core_reportbuilder\local\helpers\aggregation;
23
use core\output\help_icon;
Línea 24... Línea 24...
24
use core_reportbuilder\local\helpers\database;
24
use core_reportbuilder\local\helpers\{aggregation, database, join_trait};
25
use core_reportbuilder\local\aggregation\base;
25
use core_reportbuilder\local\aggregation\base;
Línea 32... Línea 32...
32
 * @copyright   2020 Paul Holden <paulh@moodle.com>
32
 * @copyright   2020 Paul Holden <paulh@moodle.com>
33
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 */
34
 */
35
final class column {
35
final class column {
Línea -... Línea 36...
-
 
36
 
-
 
37
    use join_trait;
36
 
38
 
37
    /** @var int Column type is integer */
39
    /** @var int Column type is integer */
Línea 38... Línea 40...
38
    public const TYPE_INTEGER = 1;
40
    public const TYPE_INTEGER = 1;
39
 
41
 
Línea 53... Línea 55...
53
    public const TYPE_LONGTEXT = 6;
55
    public const TYPE_LONGTEXT = 6;
Línea 54... Línea 56...
54
 
56
 
55
    /** @var int $index Column index within a report */
57
    /** @var int $index Column index within a report */
Línea 56... Línea -...
56
    private $index;
-
 
57
 
-
 
58
    /** @var string $columnname Internal reference to name of column */
-
 
59
    private $columnname;
-
 
60
 
-
 
61
    /** @var lang_string $columntitle Used as a title for the column in reports */
-
 
62
    private $columntitle;
58
    private $index;
63
 
59
 
Línea 64... Línea -...
64
    /** @var bool $hascustomcolumntitle Used to store if the column has been given a custom title */
-
 
65
    private $hascustomcolumntitle = false;
-
 
66
 
-
 
67
    /** @var string $entityname Name of the entity this column belongs to */
60
    /** @var bool $hascustomcolumntitle Used to store if the column has been given a custom title */
68
    private $entityname;
61
    private $hascustomcolumntitle = false;
Línea 69... Línea -...
69
 
-
 
70
    /** @var int $type Column data type (one of the TYPE_* class constants) */
-
 
71
    private $type = self::TYPE_TEXT;
-
 
72
 
62
 
73
    /** @var string[] $joins List of SQL joins for this column */
63
    /** @var int $type Column data type (one of the TYPE_* class constants) */
Línea 74... Línea 64...
74
    private $joins = [];
64
    private $type = self::TYPE_TEXT;
75
 
65
 
Línea 84... Línea 74...
84
 
74
 
85
    /** @var array[] $callbacks Array of [callable, additionalarguments] */
75
    /** @var array[] $callbacks Array of [callable, additionalarguments] */
Línea 86... Línea 76...
86
    private $callbacks = [];
76
    private $callbacks = [];
87
 
77
 
-
 
78
    /** @var base|null $aggregation Aggregation type to apply to column */
-
 
79
    private base|null $aggregation = null;
-
 
80
 
Línea 88... Línea 81...
88
    /** @var base|null $aggregation Aggregation type to apply to column */
81
    /** @var array[] $aggregationoptions Aggregation type options */
89
    private $aggregation = null;
82
    private array $aggregationoptions = [];
Línea 90... Línea 83...
90
 
83
 
Línea 98... Línea 91...
98
    private $sortfields = [];
91
    private $sortfields = [];
Línea 99... Línea 92...
99
 
92
 
100
    /** @var array $attributes */
93
    /** @var array $attributes */
Línea -... Línea 94...
-
 
94
    private $attributes = [];
-
 
95
 
-
 
96
    /** @var help_icon|null $helpicon */
101
    private $attributes = [];
97
    private $helpicon = null;
102
 
98
 
Línea 103... Línea 99...
103
    /** @var bool $available Used to know if column is available to the current user or not */
99
    /** @var bool $available Used to know if column is available to the current user or not */
104
    protected $available = true;
100
    private $available = true;
Línea 105... Línea 101...
105
 
101
 
106
    /** @var bool $deprecated */
102
    /** @var bool $deprecated */
Línea 107... Línea 103...
107
    protected $deprecated = false;
103
    private $deprecated = false;
108
 
104
 
Línea 109... Línea 105...
109
    /** @var string $deprecatedmessage */
105
    /** @var string $deprecatedmessage */
110
    protected $deprecatedmessage;
106
    private $deprecatedmessage;
111
 
107
 
112
    /** @var column_model $persistent */
108
    /** @var column_model $persistent */
Línea 127... Línea 123...
127
     * @param lang_string|null $title Title of the column used in reports (null for blank)
123
     * @param lang_string|null $title Title of the column used in reports (null for blank)
128
     * @param string $entityname Name of the entity this column belongs to. Typically when creating columns within entities
124
     * @param string $entityname Name of the entity this column belongs to. Typically when creating columns within entities
129
     *      this value should be the result of calling {@see get_entity_name}, however if creating columns inside reports directly
125
     *      this value should be the result of calling {@see get_entity_name}, however if creating columns inside reports directly
130
     *      it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
126
     *      it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
131
     */
127
     */
-
 
128
    public function __construct(
132
    public function __construct(string $name, ?lang_string $title, string $entityname) {
129
        /** @var string Internal name of the column */
133
        $this->columnname = $name;
130
        private string $name,
-
 
131
        /** @var lang_string|null Title of the column used in reports */
134
        $this->columntitle = $title;
132
        private ?lang_string $title,
-
 
133
        /** @var string Name of the entity this column belongs to */
135
        $this->entityname = $entityname;
134
        private readonly string $entityname,
-
 
135
    ) {
-
 
136
 
136
    }
137
    }
Línea 137... Línea 138...
137
 
138
 
138
    /**
139
    /**
139
     * Set column name
140
     * Set column name
140
     *
141
     *
141
     * @param string $name
142
     * @param string $name
142
     * @return self
143
     * @return self
143
     */
144
     */
144
    public function set_name(string $name): self {
145
    public function set_name(string $name): self {
145
        $this->columnname = $name;
146
        $this->name = $name;
146
        return $this;
147
        return $this;
Línea 147... Línea 148...
147
    }
148
    }
148
 
149
 
149
    /**
150
    /**
150
     * Return column name
151
     * Return column name
151
     *
152
     *
152
     * @return mixed
153
     * @return mixed
153
     */
154
     */
154
    public function get_name(): string {
155
    public function get_name(): string {
Línea 155... Línea 156...
155
        return $this->columnname;
156
        return $this->name;
156
    }
157
    }
157
 
158
 
158
    /**
159
    /**
159
     * Set column title
160
     * Set column title
160
     *
161
     *
161
     * @param lang_string|null $title
162
     * @param lang_string|null $title
162
     * @return self
163
     * @return self
163
     */
164
     */
164
    public function set_title(?lang_string $title): self {
165
    public function set_title(?lang_string $title): self {
165
        $this->columntitle = $title;
166
        $this->title = $title;
Línea 166... Línea 167...
166
        $this->hascustomcolumntitle = true;
167
        $this->hascustomcolumntitle = true;
167
        return $this;
168
        return $this;
168
    }
169
    }
169
 
170
 
170
    /**
171
    /**
171
     * Return column title
172
     * Return column title
172
     *
173
     *
173
     * @return string
174
     * @return string
Línea 174... Línea 175...
174
     */
175
     */
175
    public function get_title(): string {
176
    public function get_title(): string {
176
        return $this->columntitle ? (string) $this->columntitle : '';
177
        return $this->title ? (string) $this->title : '';
Línea 217... Línea 218...
217
 
218
 
218
    /**
219
    /**
219
     * Set the column type, if not called then the type will be assumed to be {@see TYPE_TEXT}
220
     * Set the column type, if not called then the type will be assumed to be {@see TYPE_TEXT}
220
     *
221
     *
221
     * The type of a column is used to cast the first column field passed to any callbacks {@see add_callback} as well as the
222
     * The type of a column is used to cast the first column field passed to any callbacks {@see add_callback} as well as the
-
 
223
     * aggregation options available for the column. It should represent how the column content is returned from callbacks
222
     * aggregation options available for the column
224
     *
223
     *
225
     *
224
     * @param int $type
226
     * @param int $type
225
     * @return self
227
     * @return self
226
     * @throws coding_exception
228
     * @throws coding_exception
Línea 250... Línea 252...
250
    public function get_type(): int {
252
    public function get_type(): int {
251
        return $this->type;
253
        return $this->type;
252
    }
254
    }
Línea 253... Línea 255...
253
 
255
 
254
    /**
-
 
255
     * Add join clause required for this column to join to existing tables/entities
-
 
256
     *
-
 
257
     * This is necessary in the case where {@see add_field} is selecting data from a table that isn't otherwise queried
-
 
258
     *
-
 
259
     * @param string $join
-
 
260
     * @return self
-
 
261
     */
-
 
262
    public function add_join(string $join): self {
-
 
263
        $this->joins[trim($join)] = trim($join);
-
 
264
        return $this;
-
 
265
    }
-
 
266
 
-
 
267
    /**
-
 
268
     * Add multiple join clauses required for this column, passing each to {@see add_join}
-
 
269
     *
-
 
270
     * Typically when defining columns in entities, you should pass {@see \core_reportbuilder\local\report\base::get_joins} to
-
 
271
     * this method, so that all entity joins are included in the report when your column is added to it
-
 
272
     *
-
 
273
     * @param string[] $joins
-
 
274
     * @return self
-
 
275
     */
-
 
276
    public function add_joins(array $joins): self {
-
 
277
        foreach ($joins as $join) {
-
 
278
            $this->add_join($join);
-
 
279
        }
-
 
280
        return $this;
-
 
281
    }
-
 
282
 
-
 
283
    /**
-
 
284
     * Return column joins
-
 
285
     *
-
 
286
     * @return string[]
-
 
287
     */
-
 
288
    public function get_joins(): array {
-
 
289
        return array_values($this->joins);
-
 
290
    }
-
 
291
 
-
 
292
    /**
256
    /**
293
     * Adds a field to be queried from the database that is necessary for this column
257
     * Adds a field to be queried from the database that is necessary for this column
294
     *
258
     *
295
     * Multiple fields can be added per column, this method may be called several times. Field aliases must be unique inside
259
     * Multiple fields can be added per column, this method may be called several times. Field aliases must be unique inside
296
     * any given column, but there will be no conflicts if the same aliases are used in other columns in the same report
260
     * any given column, but there will be no conflicts if the same aliases are used in other columns in the same report
Línea 395... Línea 359...
395
     * @return array
359
     * @return array
396
     */
360
     */
397
    public function get_fields(): array {
361
    public function get_fields(): array {
398
        $fieldsalias = $this->get_fields_sql_alias();
362
        $fieldsalias = $this->get_fields_sql_alias();
Línea 399... Línea 363...
399
 
363
 
400
        if (!empty($this->aggregation)) {
364
        if ($this->aggregation !== null) {
401
            $fieldsaliassql = array_column($fieldsalias, 'sql');
365
            $fieldsaliassql = array_column($fieldsalias, 'sql');
Línea 402... Línea 366...
402
            $field = reset($fieldsalias);
366
            $field = reset($fieldsalias);
403
 
367
 
404
            // If aggregating the column, generate SQL from column fields and use it to generate aggregation SQL.
-
 
Línea 405... Línea 368...
405
            $columnfieldsql = $this->aggregation::get_column_field_sql($fieldsaliassql);
368
            // If aggregating the column, generate SQL from column fields and use it to generate aggregation SQL.
406
            $aggregationfieldsql = $this->aggregation::get_field_sql($columnfieldsql, $this->get_type());
369
            $aggregationfieldsql = $this->get_field_aggregation_sql($fieldsaliassql);
407
 
370
 
408
            $fields = ["{$aggregationfieldsql} AS {$field['alias']}"];
371
            $fields = ["{$aggregationfieldsql} AS {$field['alias']}"];
Línea 414... Línea 377...
414
 
377
 
415
        return array_values($fields);
378
        return array_values($fields);
Línea 416... Línea 379...
416
    }
379
    }
-
 
380
 
-
 
381
    /**
-
 
382
     * Return aggregated field SQL for the column
-
 
383
     *
-
 
384
     * @param string[] $sqlfields
-
 
385
     * @return string
-
 
386
     * @throws coding_exception
-
 
387
     */
-
 
388
    private function get_field_aggregation_sql(array $sqlfields): string {
-
 
389
        if ($this->aggregation === null) {
-
 
390
            throw new coding_exception('Column aggregation is undefined');
-
 
391
        }
-
 
392
 
-
 
393
        $columnfieldsql = $this->aggregation::get_column_field_sql($sqlfields);
-
 
394
        return $this->aggregation::get_field_sql($columnfieldsql, $this->get_type());
-
 
395
    }
417
 
396
 
418
    /**
397
    /**
419
     * Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report
398
     * Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report
420
     *
399
     *
421
     * @return array
400
     * @return array
Línea 462... Línea 441...
462
     * @return array
441
     * @return array
463
     */
442
     */
464
    public function get_groupby_sql(): array {
443
    public function get_groupby_sql(): array {
465
        global $DB;
444
        global $DB;
Línea -... Línea 445...
-
 
445
 
-
 
446
        // We can reference field aliases in GROUP BY only in MySQL/Postgres (MDL-78783).
-
 
447
        $usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
-
 
448
 
-
 
449
        $fieldsalias = $this->get_fields_sql_alias();
-
 
450
 
-
 
451
        // To ensure cross-platform support for column aggregation, where the aggregation should also be grouped, we need
-
 
452
        // to generate SQL from column fields and use it to generate aggregation SQL.
-
 
453
        if ($this->aggregation !== null && $this->aggregation::column_groupby()) {
-
 
454
            if ($usealias) {
-
 
455
                $this->set_groupby_sql($this->get_column_alias());
-
 
456
            } else {
-
 
457
                $fieldsaliassql = array_column($fieldsalias, 'sql');
-
 
458
                $this->set_groupby_sql($this->get_field_aggregation_sql($fieldsaliassql));
-
 
459
            }
-
 
460
        }
466
 
461
 
467
        // Return defined value if it's already been set during column definition.
462
        // Return defined value if it's been set.
468
        if (!empty($this->groupbysql)) {
463
        if (!empty($this->groupbysql)) {
469
            return [$this->groupbysql];
464
            return [$this->groupbysql];
Línea 470... Línea -...
470
        }
-
 
471
 
-
 
472
        $fieldsalias = $this->get_fields_sql_alias();
-
 
473
 
-
 
474
        // Note that we can reference field aliases in GROUP BY only in MySQL/Postgres.
465
        }
475
        $usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
-
 
476
        $columnname = $usealias ? 'alias' : 'sql';
466
 
477
 
467
        $columnname = $usealias ? 'alias' : 'sql';
Línea 478... Línea 468...
478
        return array_column($fieldsalias, $columnname);
468
        return array_column($fieldsalias, $columnname);
479
    }
469
    }
Línea 515... Línea 505...
515
 
505
 
516
    /**
506
    /**
517
     * Set column aggregation type
507
     * Set column aggregation type
518
     *
508
     *
-
 
509
     * @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
519
     * @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
510
     * @param array|null $options Aggregation type options
520
     * @return self
511
     * @return self
521
     * @throws coding_exception For invalid aggregation type, or one that is incompatible with column type
512
     * @throws coding_exception For invalid aggregation type, or one that is incompatible with column type
522
     */
513
     */
523
    public function set_aggregation(?string $aggregation): self {
514
    public function set_aggregation(?string $aggregation, ?array $options = null): self {
-
 
515
        if ((string) $aggregation !== '') {
-
 
516
 
524
        if (!empty($aggregation)) {
517
            // Convert aggregation to full class instance for internal storage.
525
            $aggregation = aggregation::get_full_classpath($aggregation);
518
            $aggregationclasspath = aggregation::get_full_classpath($aggregation);
526
            if (!aggregation::valid($aggregation) || !$aggregation::compatible($this->get_type())) {
519
            if (!aggregation::valid($aggregationclasspath) || !$aggregationclasspath::compatible($this->get_type())) {
527
                throw new coding_exception('Invalid column aggregation', $aggregation);
520
                throw new coding_exception('Invalid column aggregation', $aggregation);
-
 
521
            }
-
 
522
 
-
 
523
            $options ??= $this->get_aggregation_options($aggregation);
-
 
524
            $this->aggregation = new $aggregationclasspath($options);
-
 
525
        } else {
528
            }
526
            $this->aggregation = null;
Línea 529... Línea -...
529
        }
-
 
530
 
527
        }
531
        $this->aggregation = $aggregation;
528
 
Línea 532... Línea 529...
532
        return $this;
529
        return $this;
533
    }
530
    }
534
 
531
 
535
    /**
532
    /**
536
     * Get column aggregation type
533
     * Get column aggregation type
537
     *
534
     *
538
     * @return base|null
535
     * @return base|null
539
     */
536
     */
Línea 540... Línea 537...
540
    public function get_aggregation(): ?string {
537
    public function get_aggregation(): ?base {
-
 
538
        return $this->aggregation;
-
 
539
    }
-
 
540
 
-
 
541
    /**
-
 
542
     * Set options for the given aggregation type
-
 
543
     *
-
 
544
     * @param string $aggregation Type of aggregation, e.g. 'sum', 'count', etc
-
 
545
     * @param array $options Aggregation type options
-
 
546
     * @return self
-
 
547
     */
-
 
548
    public function set_aggregation_options(string $aggregation, array $options): self {
-
 
549
        $this->aggregationoptions[$aggregation] = $options;
-
 
550
        return $this;
-
 
551
    }
-
 
552
 
-
 
553
    /**
-
 
554
     * Get options for the given aggregation type
-
 
555
     *
-
 
556
     * @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
-
 
557
     * @return array
-
 
558
     */
-
 
559
    public function get_aggregation_options(?string $aggregation): array {
541
        return $this->aggregation;
560
        return $this->aggregationoptions[$aggregation] ?? [];
542
    }
561
    }
543
 
562
 
544
    /**
563
    /**
545
     * Set disabled aggregation methods for the column. Typically only those methods suitable for the current column type are
564
     * Set disabled aggregation methods for the column. Typically only those methods suitable for the current column type are
Línea 578... Línea 597...
578
 
597
 
579
    /**
598
    /**
580
     * Sets the column as sortable
599
     * Sets the column as sortable
581
     *
600
     *
582
     * @param bool $issortable
601
     * @param bool $issortable
583
     * @param array $sortfields Define the fields that should be used when the column is sorted, typically a subset of the fields
602
     * @param array $sortfields Define the fields that should be used when the column is sorted. Must be a subset of the fields
584
     *      selected for the column, via {@see add_field}. If omitted then the first selected field is used
603
     *      selected for the column, via {@see add_field}. If omitted then the first selected field is used
585
     * @return self
604
     * @return self
586
     */
605
     */
587
    public function set_is_sortable(bool $issortable, array $sortfields = []): self {
606
    public function set_is_sortable(bool $issortable, array $sortfields = []): self {
Línea 596... Línea 615...
596
     * @return bool
615
     * @return bool
597
     */
616
     */
598
    public function get_is_sortable(): bool {
617
    public function get_is_sortable(): bool {
Línea 599... Línea 618...
599
 
618
 
600
        // Defer sortable status to aggregation type if column is being aggregated.
619
        // Defer sortable status to aggregation type if column is being aggregated.
601
        if (!empty($this->aggregation)) {
620
        if ($this->aggregation !== null) {
602
            return $this->aggregation::sortable($this->issortable);
621
            return $this->aggregation::sortable($this->issortable);
Línea 603... Línea 622...
603
        }
622
        }
604
 
623
 
Línea 619... Línea 638...
619
            if (array_key_exists($sortfield, $fieldsalias)) {
638
            if (array_key_exists($sortfield, $fieldsalias)) {
620
                return $fieldsalias[$sortfield]['alias'];
639
                return $fieldsalias[$sortfield]['alias'];
621
            }
640
            }
Línea 622... Línea 641...
622
 
641
 
623
            // Check whether sortfield refers to field SQL.
642
            // Check whether sortfield refers to field SQL.
624
            foreach ($fieldsalias as $field) {
643
            return str_ireplace(
625
                if (strcasecmp($sortfield, $field['sql']) === 0) {
644
                array_column($fieldsalias, 'sql'),
626
                    $sortfield = $field['alias'];
645
                array_column($fieldsalias, 'alias'),
627
                    break;
-
 
628
                }
646
                $sortfield,
629
            }
-
 
630
 
-
 
631
            return $sortfield;
647
            );
632
        }, $this->sortfields);
648
        }, $this->sortfields);
Línea 633... Línea 649...
633
    }
649
    }
634
 
650
 
Línea 685... Línea 701...
685
     * @param array $row
701
     * @param array $row
686
     * @return mixed
702
     * @return mixed
687
     */
703
     */
688
    public function format_value(array $row) {
704
    public function format_value(array $row) {
689
        $values = $this->get_values($row);
705
        $values = $this->get_values($row);
690
        $value = self::get_default_value($values, $this->type);
-
 
Línea 691... Línea 706...
691
 
706
 
692
        // If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks.
707
        // If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks.
-
 
708
        if ($this->aggregation !== null) {
693
        if (!empty($this->aggregation)) {
709
            $value = self::get_default_value($values, $this->aggregation::get_column_type($this->get_type()));
694
            $value = $this->aggregation::format_value($value, $values, $this->callbacks, $this->type);
710
            $value = $this->aggregation->format_value($value, $values, $this->callbacks, $this->get_type());
-
 
711
        } else {
695
        } else {
712
            $value = self::get_default_value($values, $this->get_type());
696
            foreach ($this->callbacks as $callback) {
713
            foreach ($this->callbacks as $callback) {
697
                [$callable, $arguments] = $callback;
714
                [$callable, $arguments] = $callback;
698
                $value = ($callable)($value, (object) $values, $arguments, null);
715
                $value = ($callable)($value, (object) $values, $arguments, null);
699
            }
716
            }
Línea 721... Línea 738...
721
    public function get_attributes(): array {
738
    public function get_attributes(): array {
722
        return $this->attributes;
739
        return $this->attributes;
723
    }
740
    }
Línea 724... Línea 741...
724
 
741
 
-
 
742
    /**
-
 
743
     * Set column help icon
-
 
744
     *
-
 
745
     * @param help_icon $helpicon
-
 
746
     * @return self
-
 
747
     */
-
 
748
    public function set_help_icon(help_icon $helpicon): self {
-
 
749
        $this->helpicon = $helpicon;
-
 
750
        return $this;
-
 
751
    }
-
 
752
 
-
 
753
    /**
-
 
754
     * Return column help icon
-
 
755
     *
-
 
756
     * @return help_icon|null
-
 
757
     */
-
 
758
    public function get_help_icon(): ?help_icon {
-
 
759
        return $this->helpicon;
-
 
760
    }
-
 
761
 
725
    /**
762
    /**
726
     * Return available state of the column for the current user. For instance the column may be added to a report with the
763
     * Return available state of the column for the current user. For instance the column may be added to a report with the
727
     * expectation that only some users are able to see it
764
     * expectation that only some users are able to see it
728
     *
765
     *
729
     * @return bool
766
     * @return bool