first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,134 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\datasource;
use core_cohort\reportbuilder\local\entities\cohort;
use core_course\reportbuilder\local\entities\course_category;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\{course, user};
use core_role\reportbuilder\local\entities\role;
/**
* Course categories datasource
*
* @package core_course
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class categories extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('coursecategories');
}
/**
* Initialise report
*/
protected function initialise(): void {
$categoryentity = new course_category();
$categoryalias = $categoryentity->get_table_alias('course_categories');
$contextalias = $categoryentity->get_table_alias('context');
$this->set_main_table('course_categories', $categoryalias);
$this->add_entity($categoryentity);
// Join course entity.
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
$this->add_entity($courseentity
->add_join("LEFT JOIN {course} {$coursealias} ON {$coursealias}.category = {$categoryalias}.id"));
// Join cohort entity (indicate context table join alias).
$cohortentity = (new cohort())
->set_table_join_alias('context', $contextalias);
$cohort = $cohortentity->get_table_alias('cohort');
$this->add_entity($cohortentity
->add_join($categoryentity->get_context_join())
->add_join("LEFT JOIN {cohort} {$cohort} ON {$cohort}.contextid = {$contextalias}.id"));
// Join role entity.
$roleentity = (new role())
->set_table_alias('context', $contextalias);
$role = $roleentity->get_table_alias('role');
$this->add_entity($roleentity
->add_join($categoryentity->get_context_join())
->add_join("LEFT JOIN {role_assignments} ras ON ras.contextid = {$contextalias}.id")
->add_join("LEFT JOIN {role} {$role} ON {$role}.id = ras.roleid"));
// Join user entity.
$userentity = new user();
$user = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($roleentity->get_joins())
->add_join("LEFT JOIN {user} {$user} ON {$user}.id = ras.userid"));
// Add all elements from entities to be available in custom reports.
$this->add_all_from_entities();
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'course_category:name',
'course_category:idnumber',
'course_category:coursecount',
];
}
/**
* Return the default sorting that will be added to the report as part of default setup
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'course_category:name' => SORT_ASC,
];
}
/**
* Return the filters that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'course_category:name',
];
}
/**
* Return the conditions that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_conditions(): array {
return [];
}
}
@@ -0,0 +1,149 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\datasource;
use core_course\reportbuilder\local\entities\course_category;
use core_files\reportbuilder\local\entities\file;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\course;
use core_reportbuilder\local\helpers\database;
use core_tag\reportbuilder\local\entities\tag;
use lang_string;
/**
* Courses datasource
*
* @package core_course
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class courses extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('courses');
}
/**
* Initialise report
*/
protected function initialise(): void {
$courseentity = new course();
$coursetablealias = $courseentity->get_table_alias('course');
// Exclude site course.
$paramsiteid = database::generate_param_name();
$this->set_main_table('course', $coursetablealias);
$this->add_base_condition_sql("{$coursetablealias}.id != :{$paramsiteid}", [$paramsiteid => SITEID]);
$this->add_entity($courseentity);
// Join the course category entity.
$coursecatentity = new course_category();
$coursecattablealias = $coursecatentity->get_table_alias('course_categories');
$this->add_entity($coursecatentity
->add_join("JOIN {course_categories} {$coursecattablealias}
ON {$coursecattablealias}.id = {$coursetablealias}.category"));
// Join the tag entity.
$tagentity = (new tag())
->set_table_alias('tag', $courseentity->get_table_alias('tag'));
$this->add_entity($tagentity
->add_joins($courseentity->get_tag_joins()));
// Join the files entity.
$contextalias = $courseentity->get_table_alias('context');
$fileentity = (new file())
->set_entity_title(new lang_string('courseoverviewfiles'));
$filesalias = $fileentity->get_table_alias('files');
$this->add_entity($fileentity
->add_join($courseentity->get_context_join())
->add_join("LEFT JOIN {files} {$filesalias}
ON {$filesalias}.contextid = {$contextalias}.id
AND {$filesalias}.component = 'course'
AND {$filesalias}.filearea = 'overviewfiles'
AND {$filesalias}.itemid = 0
AND {$filesalias}.filename != '.'"));
// Add all columns/filters/conditions from entities to be available in custom reports.
$this->add_all_from_entity($coursecatentity->get_entity_name());
$this->add_all_from_entity($courseentity->get_entity_name());
// Add specific tag entity elements.
$this->add_columns_from_entity($tagentity->get_entity_name(), ['name', 'namewithlink']);
$this->add_filter($tagentity->get_filter('name'));
$this->add_condition($tagentity->get_condition('name'));
// Add specific file entity elements.
$this->add_columns_from_entity($fileentity->get_entity_name(), ['name', 'size', 'type', 'timecreated']);
$this->add_filters_from_entity($fileentity->get_entity_name(), ['name', 'size', 'timecreated']);
$this->add_conditions_from_entity($fileentity->get_entity_name(), ['name', 'size', 'timecreated']);
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'course_category:name',
'course:shortname',
'course:fullname',
'course:idnumber',
];
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
public function get_default_filters(): array {
return ['course_category:name', 'course:fullname', 'course:idnumber'];
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
public function get_default_conditions(): array {
return ['course_category:name'];
}
/**
* Return the default sorting that will be added to the report once it is created
*
* @return array|int[]
*/
public function get_default_column_sorting(): array {
return [
'course_category:name' => SORT_ASC,
'course:shortname' => SORT_ASC,
'course:fullname' => SORT_ASC,
];
}
}
@@ -0,0 +1,215 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\datasource;
use core_course\reportbuilder\local\entities\course_category;
use core_course\reportbuilder\local\entities\access;
use core_course\reportbuilder\local\entities\completion;
use core_course\reportbuilder\local\entities\enrolment;
use core_enrol\reportbuilder\local\entities\enrol;
use core_group\reportbuilder\local\entities\group;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\course;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\helpers\database;
use core_role\reportbuilder\local\entities\role;
use core_user\output\status_field;
/**
* Course participants datasource
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class participants extends datasource {
/**
* Initialise report
*/
protected function initialise(): void {
$courseentity = new course();
$this->add_entity($courseentity);
$context = $courseentity->get_table_alias('context');
$course = $courseentity->get_table_alias('course');
$this->set_main_table('course', $course);
// Exclude site course.
$paramsiteid = database::generate_param_name();
$this->add_base_condition_sql("{$course}.id != :{$paramsiteid}", [$paramsiteid => SITEID]);
// Join the course category entity.
$coursecatentity = new course_category();
$categories = $coursecatentity->get_table_alias('course_categories');
$this->add_entity($coursecatentity
->add_join("JOIN {course_categories} {$categories} ON {$categories}.id = {$course}.category"));
// Join the enrolment method entity.
$enrolentity = new enrol();
$enrol = $enrolentity->get_table_alias('enrol');
$this->add_entity($enrolentity
->add_join("LEFT JOIN {enrol} {$enrol} ON {$enrol}.courseid = {$course}.id"));
// Join the enrolments entity.
$enrolmententity = (new enrolment())
->set_table_alias('enrol', $enrol);
$userenrolment = $enrolmententity->get_table_alias('user_enrolments');
$this->add_entity($enrolmententity
->add_joins($enrolentity->get_joins())
->add_join("LEFT JOIN {user_enrolments} {$userenrolment} ON {$userenrolment}.enrolid = {$enrol}.id"));
// Join user entity.
$userentity = new user();
$user = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($enrolmententity->get_joins())
->add_join("LEFT JOIN {user} {$user} ON {$user}.id = {$userenrolment}.userid AND {$user}.deleted = 0"));
// Join the role entity.
$roleentity = (new role())
->set_table_alias('context', $context);
$role = $roleentity->get_table_alias('role');
$this->add_entity($roleentity
->add_joins($userentity->get_joins())
->add_join($courseentity->get_context_join())
->add_join("LEFT JOIN {role_assignments} ras ON ras.contextid = {$context}.id AND ras.userid = {$user}.id")
->add_join("LEFT JOIN {role} {$role} ON {$role}.id = ras.roleid")
);
// Join group entity.
$groupentity = (new group())
->set_table_alias('context', $context);
$groups = $groupentity->get_table_alias('groups');
// Sub-select for all course group members.
$groupsinnerselect = "
SELECT grs.*, grms.userid
FROM {groups} grs
JOIN {groups_members} grms ON grms.groupid = grs.id";
$this->add_entity($groupentity
->add_join($courseentity->get_context_join())
->add_joins($userentity->get_joins())
->add_join("
LEFT JOIN ({$groupsinnerselect}) {$groups}
ON {$groups}.courseid = {$course}.id AND {$groups}.userid = {$user}.id")
);
// Join completion entity.
$completionentity = (new completion())
->set_table_aliases([
'course' => $course,
'user' => $user,
]);
$completion = $completionentity->get_table_alias('course_completion');
$this->add_entity($completionentity
->add_joins($userentity->get_joins())
->add_join("
LEFT JOIN {course_completions} {$completion}
ON {$completion}.course = {$course}.id AND {$completion}.userid = {$user}.id")
);
// Join course access entity.
$accessentity = (new access())
->set_table_alias('user', $user);
$lastaccess = $accessentity->get_table_alias('user_lastaccess');
$this->add_entity($accessentity
->add_joins($userentity->get_joins())
->add_join("
LEFT JOIN {user_lastaccess} {$lastaccess}
ON {$lastaccess}.userid = {$user}.id AND {$lastaccess}.courseid = {$course}.id"));
// Add all entities columns/filters/conditions.
$this->add_all_from_entities();
}
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('courseparticipants', 'course');
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'course:coursefullnamewithlink',
'user:fullnamewithlink',
'enrol:name',
];
}
/**
* Return the column sorting that will be added to the report upon creation
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'course:coursefullnamewithlink' => SORT_ASC,
'user:fullnamewithlink' => SORT_ASC,
'enrol:name' => SORT_ASC,
];
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'user:suspended',
'user:confirmed',
];
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'enrolment:status',
'user:suspended',
'user:confirmed',
];
}
/**
* Return the condition values that will be set for the report upon creation
*
* @return array
*/
public function get_default_condition_values(): array {
return [
'enrolment:status_operator' => select::EQUAL_TO,
'enrolment:status_value' => status_field::STATUS_ACTIVE,
];
}
}
@@ -0,0 +1,139 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\entities;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use lang_string;
use stdClass;
/**
* Course access entity implementation
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class access extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'user_lastaccess',
'user',
];
}
/**
* The default title for this entity in the list of columns/conditions/filters in the report builder
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('courseaccess', 'course');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
foreach ($this->get_all_columns() as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
foreach ($this->get_all_filters() as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('user_lastaccess');
$user = $this->get_table_alias('user');
// Last course access column.
$columns[] = (new column(
'timeaccess',
new lang_string('lastcourseaccess', 'moodle'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$tablealias}.timeaccess")
->add_field("{$user}.id", 'userid')
->set_is_sortable(true)
->add_callback(static function(?int $value, stdClass $row, $arguments, ?string $aggregation): string {
if ($row->userid === null && $aggregation === null) {
return '';
} else if ($value === null) {
return get_string('never');
}
return format::userdate($value, $row);
});
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('user_lastaccess');
// Last course access filter.
$filters[] = (new filter(
date::class,
'timeaccess',
new lang_string('lastcourseaccess', 'moodle'),
$this->get_entity_name(),
"{$tablealias}.timeaccess"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
return $filters;
}
}
@@ -0,0 +1,367 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\entities;
use core_reportbuilder\local\entities\base;
use core_course\reportbuilder\local\formatters\completion as completion_formatter;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use completion_criteria_completion;
use completion_info;
use html_writer;
use lang_string;
use stdClass;
/**
* Course completion entity implementation
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'course_completion',
'course',
'grade_grades' ,
'grade_items',
'user',
];
}
/**
* The default title for this entity in the list of columns/conditions/filters in the report builder
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('coursecompletion', 'completion');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
foreach ($this->get_all_columns() as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
foreach ($this->get_all_filters() as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
[
'course_completion' => $coursecompletion,
'course' => $course,
'grade_grades' => $grade,
'grade_items' => $gradeitem,
'user' => $user,
] = $this->get_table_aliases();
// Completed column.
$columns[] = (new column(
'completed',
new lang_string('completed', 'completion'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_field("
CASE
WHEN {$coursecompletion}.id IS NULL THEN NULL
WHEN {$coursecompletion}.timecompleted > 0 THEN 1
ELSE 0
END", 'completed')
->set_is_sortable(true)
->add_callback([format::class, 'boolean_as_text']);
// Completion criteria column.
$criterias = database::generate_alias();
$columns[] = (new column(
'criteria',
new lang_string('criteria', 'core_completion'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
// Determine whether any criteria exist for the course. We also group per course, rather than report each separately.
->add_join("LEFT JOIN (
SELECT DISTINCT course FROM {course_completion_criteria}
) {$criterias} ON {$criterias}.course = {$course}.id")
->set_type(column::TYPE_TEXT)
// Select enough fields to determine user criteria for the course.
->add_field("{$criterias}.course", 'courseid')
->add_field("{$course}.enablecompletion")
->add_field("{$user}.id", 'userid')
->set_disabled_aggregation_all()
->add_callback(static function($id, stdClass $record): string {
if (!$record->courseid) {
return '';
}
$info = new completion_info((object) ['id' => $record->courseid, 'enablecompletion' => $record->enablecompletion]);
if ($info->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
$title = get_string('criteriarequiredall', 'core_completion');
} else {
$title = get_string('criteriarequiredany', 'core_completion');
}
// Map all completion data to their criteria summaries.
$items = array_map(static function(completion_criteria_completion $completion): string {
$criteria = $completion->get_criteria();
return get_string('criteriasummary', 'core_completion', [
'type' => $criteria->get_details($completion)['type'],
'summary' => $criteria->get_title_detailed(),
]);
}, $info->get_completions((int) $record->userid));
return $title . html_writer::alist($items);
});
// Progress percentage column.
$columns[] = (new column(
'progresspercent',
new lang_string('progress', 'completion'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$course}.id", 'courseid')
->add_field("{$user}.id", 'userid')
->set_is_sortable(false)
->add_callback([completion_formatter::class, 'completion_progress']);
// Time enrolled.
$columns[] = (new column(
'timeenrolled',
new lang_string('timeenrolled', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$coursecompletion}.timeenrolled")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Time started.
$columns[] = (new column(
'timestarted',
new lang_string('timestarted', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$coursecompletion}.timestarted")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Time completed.
$columns[] = (new column(
'timecompleted',
new lang_string('timecompleted', 'completion'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$coursecompletion}.timecompleted")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Time reaggregated.
$columns[] = (new column(
'reaggregate',
new lang_string('timereaggregated', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$coursecompletion}.reaggregate")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Days taking course (days since course start date until completion or until current date if not completed).
$currenttime = time();
$columns[] = (new column(
'dayscourse',
new lang_string('daystakingcourse', 'course'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_field("(
CASE
WHEN {$coursecompletion}.id IS NULL THEN NULL
ELSE (CASE WHEN {$coursecompletion}.timecompleted > 0 THEN
{$coursecompletion}.timecompleted
ELSE
{$currenttime}
END - {$course}.startdate) / " . DAYSECS . "
END)", 'dayscourse')
->set_is_sortable(true);
// Days since last completion (days since last enrolment date until completion or until current date if not completed).
$columns[] = (new column(
'daysuntilcompletion',
new lang_string('daysuntilcompletion', 'completion'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_field("(
CASE
WHEN {$coursecompletion}.id IS NULL THEN NULL
ELSE (CASE WHEN {$coursecompletion}.timecompleted > 0 THEN
{$coursecompletion}.timecompleted
ELSE
{$currenttime}
END - {$coursecompletion}.timeenrolled) / " . DAYSECS . "
END)", 'daysuntilcompletion')
->set_is_sortable(true);
// Student course grade.
$columns[] = (new column(
'grade',
new lang_string('gradenoun'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join("
LEFT JOIN {grade_items} {$gradeitem}
ON ({$gradeitem}.itemtype = 'course' AND {$course}.id = {$gradeitem}.courseid)
")
->add_join("
LEFT JOIN {grade_grades} {$grade}
ON ({$user}.id = {$grade}.userid AND {$gradeitem}.id = {$grade}.itemid)
")
->set_type(column::TYPE_FLOAT)
->add_fields("{$grade}.finalgrade")
->set_is_sortable(true)
->add_callback(function(?float $value): string {
if ($value === null) {
return '';
}
return format_float($value, 2);
});
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$coursecompletion = $this->get_table_alias('course_completion');
// Completed status filter.
$filters[] = (new filter(
boolean_select::class,
'completed',
new lang_string('completed', 'completion'),
$this->get_entity_name(),
"CASE WHEN {$coursecompletion}.timecompleted > 0 THEN 1 ELSE 0 END"
))
->add_joins($this->get_joins());
// Time completed filter.
$filters[] = (new filter(
date::class,
'timecompleted',
new lang_string('timecompleted', 'completion'),
$this->get_entity_name(),
"{$coursecompletion}.timecompleted"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
// Time enrolled/started filter and condition.
$fields = ['timeenrolled', 'timestarted'];
foreach ($fields as $field) {
$filters[] = (new filter(
date::class,
$field,
new lang_string($field, 'enrol'),
$this->get_entity_name(),
"{$coursecompletion}.{$field}"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
}
// Time reaggregated filter and condition.
$filters[] = (new filter(
date::class,
'reaggregate',
new lang_string('timereaggregated', 'enrol'),
$this->get_entity_name(),
"{$coursecompletion}.reaggregate"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
return $filters;
}
}
@@ -0,0 +1,306 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\entities;
use context_coursecat;
use context_helper;
use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use theme_config;
use core_course_category;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{category, select, text};
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Course category entity
*
* @package core_course
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_category extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'context',
'course_categories',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('coursecategory');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$tablealias = $this->get_table_alias('course_categories');
$tablealiascontext = $this->get_table_alias('context');
// Name column.
$columns[] = (new column(
'name',
new lang_string('categoryname'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join($this->get_context_join())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name, {$tablealias}.id")
->add_fields(context_helper::get_preload_record_columns_sql($tablealiascontext))
->add_callback(static function(?string $name, stdClass $category): string {
if (empty($category->id)) {
return '';
}
context_helper::preload_from_record($category);
$context = context_coursecat::instance($category->id);
return format_string($category->name, true, ['context' => $context]);
})
->set_is_sortable(true);
// Category name with link column.
$columns[] = (new column(
'namewithlink',
new lang_string('namewithlink', 'core_course'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join($this->get_context_join())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name, {$tablealias}.id")
->add_fields(context_helper::get_preload_record_columns_sql($tablealiascontext))
->add_callback(static function(?string $name, stdClass $category): string {
if (empty($category->id)) {
return '';
}
context_helper::preload_from_record($category);
$context = context_coursecat::instance($category->id);
$url = new moodle_url('/course/management.php', ['categoryid' => $category->id]);
return html_writer::link($url,
format_string($category->name, true, ['context' => $context]));
})
->set_is_sortable(true);
// Path column.
$columns[] = (new column(
'path',
new lang_string('categorypath'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name, {$tablealias}.id")
->add_callback(static function(?string $name, stdClass $category): string {
return empty($category->id) ? '' :
core_course_category::get($category->id, MUST_EXIST, true)->get_nested_name(false);
})
->set_disabled_aggregation(['groupconcat', 'groupconcatdistinct'])
->set_is_sortable(true);
// ID number column.
$columns[] = (new column(
'idnumber',
new lang_string('idnumbercoursecategory'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.idnumber")
->set_is_sortable(true);
// Description column (note we need to join/select from the context table in order to format the column).
$descriptionfieldsql = "{$tablealias}.description";
if ($DB->get_dbfamily() === 'oracle') {
$descriptionfieldsql = $DB->sql_order_by_text($descriptionfieldsql, 1024);
}
$columns[] = (new column(
'description',
new lang_string('description'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join($this->get_context_join())
->set_type(column::TYPE_LONGTEXT)
->add_field($descriptionfieldsql, 'description')
->add_fields("{$tablealias}.descriptionformat, {$tablealias}.id")
->add_fields(context_helper::get_preload_record_columns_sql($tablealiascontext))
->add_callback(static function(?string $description, stdClass $category): string {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
if ($description === null) {
return '';
}
context_helper::preload_from_record($category);
$context = context_coursecat::instance($category->id);
$description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'coursecat',
'description', null);
return format_text($description, $category->descriptionformat, ['context' => $context->id]);
});
// Theme column.
$columns[] = (new column(
'theme',
new lang_string('theme'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.theme")
->set_is_sortable(true)
->add_callback(static function (?string $theme): string {
if ((string) $theme === '') {
return '';
}
return get_string('pluginname', "theme_{$theme}");
});
// Course count column.
$columns[] = (new column(
'coursecount',
new lang_string('coursecount', 'core_course'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$tablealias}.coursecount")
->set_is_sortable(true);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('course_categories');
// Select category filter.
$filters[] = (new filter(
category::class,
'name',
new lang_string('categoryselect', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.id"
))
->add_joins($this->get_joins())
->set_options([
'requiredcapabilities' => 'moodle/category:viewcourselist',
]);
// Name filter.
$filters[] = (new filter(
text::class,
'text',
new lang_string('categoryname'),
$this->get_entity_name(),
"{$tablealias}.name"
))
->add_joins($this->get_joins());
// ID number filter.
$filters[] = (new filter(
text::class,
'idnumber',
new lang_string('idnumbercoursecategory'),
$this->get_entity_name(),
"{$tablealias}.idnumber"
))
->add_joins($this->get_joins());
// Theme filter.
$filters[] = (new filter(
select::class,
'theme',
new lang_string('theme'),
$this->get_entity_name(),
"{$tablealias}.theme",
))
->set_options_callback(static function(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
})
->add_joins($this->get_joins());
return $filters;
}
/**
* Return context join used by columns
*
* @return string
*/
public function get_context_join(): string {
$coursecategories = $this->get_table_alias('course_categories');
$context = $this->get_table_alias('context');
return "LEFT JOIN {context} {$context} ON {$context}.instanceid = {$coursecategories}.id
AND {$context}.contextlevel = " . CONTEXT_COURSECAT;
}
}
@@ -0,0 +1,307 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\entities;
use context_course;
use core_course\reportbuilder\local\formatters\enrolment as enrolment_formatter;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use core_user\output\status_field;
use enrol_plugin;
use lang_string;
use stdClass;
/**
* Course enrolment entity implementation
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class enrolment extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'user_enrolments',
'enrol',
];
}
/**
* The default title for this entity in the list of columns/conditions/filters in the report builder
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('enrolment', 'enrol');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
foreach ($this->get_all_columns() as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
foreach ($this->get_all_filters() as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$userenrolments = $this->get_table_alias('user_enrolments');
$enrol = $this->get_table_alias('enrol');
// Enrolment method column (Deprecated since Moodle 4.3, to remove in MDL-78118).
$columns[] = (new column(
'method',
new lang_string('method', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$enrol}.enrol, {$enrol}.id")
->set_is_sortable(true)
->set_is_deprecated('See \'enrol:name\' for replacement')
->add_callback([enrolment_formatter::class, 'enrolment_name']);
// Enrolment time created.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'moodle'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$userenrolments}.timecreated")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Enrolment time started.
$columns[] = (new column(
'timestarted',
new lang_string('timestarted', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("
CASE WHEN {$userenrolments}.timestart = 0
THEN {$userenrolments}.timecreated
ELSE {$userenrolments}.timestart
END", 'timestarted')
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Enrolment time ended.
$columns[] = (new column(
'timeended',
new lang_string('timeended', 'enrol'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$userenrolments}.timeend")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Enrolment status.
$columns[] = (new column(
'status',
new lang_string('status', 'moodle'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field($this->get_status_field_sql(), 'status')
->set_is_sortable(true)
->add_callback([enrolment_formatter::class, 'enrolment_status']);
// Role column (Deprecated since Moodle 4.3, to remove in MDL-78118).
$ctx = database::generate_alias();
$ra = database::generate_alias();
$r = database::generate_alias();
$columns[] = (new column(
'role',
new lang_string('role', 'moodle'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join("LEFT JOIN {context} {$ctx}
ON {$ctx}.instanceid = {$enrol}.courseid AND {$ctx}.contextlevel = " . CONTEXT_COURSE)
->add_join("LEFT JOIN {role_assignments} {$ra}
ON {$ra}.contextid = {$ctx}.id AND {$ra}.userid = {$userenrolments}.userid")
->add_join("LEFT JOIN {role} {$r} ON {$r}.id = {$ra}.roleid")
->set_type(column::TYPE_TEXT)
->add_fields("{$r}.id, {$r}.name, {$r}.shortname, {$ctx}.instanceid")
->set_is_sortable(true, ["{$r}.shortname"])
->set_is_deprecated('See \'role:name\' for replacement')
->add_callback(static function(?string $value, stdClass $row): string {
if (!$row->id) {
return '';
}
$context = context_course::instance($row->instanceid);
return role_get_name($row, $context, ROLENAME_ALIAS);
});
return $columns;
}
/**
* Generate SQL snippet suitable for returning enrolment status field
*
* @return string
*/
private function get_status_field_sql(): string {
$time = time();
$userenrolments = $this->get_table_alias('user_enrolments');
$enrol = $this->get_table_alias('enrol');
return "
CASE WHEN {$userenrolments}.status = " . ENROL_USER_ACTIVE . "
THEN CASE WHEN ({$userenrolments}.timestart > {$time})
OR ({$userenrolments}.timeend > 0 AND {$userenrolments}.timeend < {$time})
OR ({$enrol}.status = " . ENROL_INSTANCE_DISABLED . ")
THEN " . status_field::STATUS_NOT_CURRENT . "
ELSE " . status_field::STATUS_ACTIVE . "
END
ELSE {$userenrolments}.status
END";
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$userenrolments = $this->get_table_alias('user_enrolments');
$enrol = $this->get_table_alias('enrol');
// Enrolment method (Deprecated since Moodle 4.3, to remove in MDL-78118).
$enrolmentmethods = static function(): array {
return array_map(static function(enrol_plugin $plugin): string {
return get_string('pluginname', 'enrol_' . $plugin->get_name());
}, enrol_get_plugins(true));
};
$filters[] = (new filter(
select::class,
'method',
new lang_string('method', 'enrol'),
$this->get_entity_name(),
"{$enrol}.enrol"
))
->add_joins($this->get_joins())
->set_is_deprecated('See \'enrol:plugin\' for replacement')
->set_options_callback($enrolmentmethods);
// Enrolment time created.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'moodle'),
$this->get_entity_name(),
"{$userenrolments}.timecreated"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
// Enrolment time started.
$filters[] = (new filter(
date::class,
'timestarted',
new lang_string('timestarted', 'enrol'),
$this->get_entity_name(),
"CASE WHEN {$userenrolments}.timestart = 0
THEN {$userenrolments}.timecreated
ELSE {$userenrolments}.timestart
END"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
// Enrolment time ended.
$filters[] = (new filter(
date::class,
'timeended',
new lang_string('timeended', 'enrol'),
$this->get_entity_name(),
"{$userenrolments}.timeend"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_NOT_EMPTY,
date::DATE_EMPTY,
date::DATE_RANGE,
date::DATE_LAST,
date::DATE_CURRENT,
]);
// Enrolment status.
$filters[] = (new filter(
select::class,
'status',
new lang_string('status', 'moodle'),
$this->get_entity_name(),
$this->get_status_field_sql()
))
->add_joins($this->get_joins())
->set_options(enrolment_formatter::enrolment_values());
return $filters;
}
}
@@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\formatters;
use core_completion\progress;
use core_reportbuilder\local\helpers\format;
use stdClass;
/**
* Formatters for the course completion entity
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion {
/**
* Return completion progress as a percentage
*
* @param string|null $value
* @param stdClass $row
* @return string
*/
public static function completion_progress(?string $value, stdClass $row): string {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
// Do not show progress if there is no userid.
if (!$row->userid) {
return '';
}
// Make sure courseid and userid have a value, specially userid because get_course_progress_percentage() defaults
// to the current user if this is null and the result would be wrong.
$courseid = (int) $row->courseid;
$userid = (int) $row->userid;
if ($courseid === 0 || $userid === 0) {
return format::percent(0);
}
$course = get_course($courseid);
$progress = (float) progress::get_course_progress_percentage($course, $userid);
return format::percent($progress);
}
/**
* Return number of days for methods daystakingcourse and daysuntilcompletion
*
* @param int|null $value
* @param stdClass $row
* @return int|null
*/
public static function get_days(?int $value, stdClass $row): ?int {
// Do not show anything if there is no userid.
if (!$row->userid) {
return null;
}
return $value;
}
}
@@ -0,0 +1,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_course\reportbuilder\local\formatters;
use core_user\output\status_field;
use lang_string;
use stdClass;
/**
* Formatters for the course enrolment entity
*
* @package core_course
* @copyright 2022 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class enrolment {
/**
* Return enrolment plugin instance name
*
* @param string|null $value
* @param stdClass $row
* @return string
*
* @deprecated since Moodle 4.3 - please do not use this function any more (to remove in MDL-78118)
*/
public static function enrolment_name(?string $value, stdClass $row): string {
global $DB;
if (empty($value)) {
return '';
}
$instance = $DB->get_record('enrol', ['id' => $row->id, 'enrol' => $row->enrol], '*', MUST_EXIST);
$plugin = enrol_get_plugin($row->enrol);
return $plugin ? $plugin->get_instance_name($instance) : '-';
}
/**
* Returns list of enrolment statuses
*
* @return lang_string[]
*/
public static function enrolment_values(): array {
return [
status_field::STATUS_ACTIVE => new lang_string('participationactive', 'enrol'),
status_field::STATUS_SUSPENDED => new lang_string('participationsuspended', 'enrol'),
status_field::STATUS_NOT_CURRENT => new lang_string('participationnotcurrent', 'enrol'),
];
}
/**
* Return enrolment status for user
*
* @param string|null $value
* @return string|null
*/
public static function enrolment_status(?string $value): ?string {
if ($value === null) {
return null;
}
$statusvalues = self::enrolment_values();
$value = (int) $value;
if (!array_key_exists($value, $statusvalues)) {
return null;
}
return (string) $statusvalues[$value];
}
}