This commit is contained in:
Sara Arjona
2022-05-31 17:04:23 +02:00
9 changed files with 736 additions and 18 deletions
@@ -0,0 +1,125 @@
<?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_badges\reportbuilder\datasource;
use lang_string;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\{course, user};
use core_badges\reportbuilder\local\entities\{badge, badge_issued};
/**
* Badges datasource
*
* @package core_badges
* @copyright 2022 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badges extends datasource {
/**
* Return user friendly name of the report source
*
* @return string
*/
public static function get_name(): string {
return get_string('badges', 'core_badges');
}
/**
* Initialise report
*/
protected function initialise(): void {
$badgeentity = new badge();
$badgealias = $badgeentity->get_table_alias('badge');
$this->set_main_table('badge', $badgealias);
$this->add_entity($badgeentity);
// Join the badge issued entity to the badge entity.
$badgeissuedentity = new badge_issued();
$badgeissuedalias = $badgeissuedentity->get_table_alias('badge_issued');
$this->add_entity($badgeissuedentity
->add_join("LEFT JOIN {badge_issued} {$badgeissuedalias}
ON {$badgeissuedalias}.badgeid = {$badgealias}.id")
);
// Join the user entity to the badge issued entity.
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($badgeissuedentity->get_joins())
->add_join("LEFT JOIN {user} {$useralias}
ON {$useralias}.id = {$badgeissuedalias}.userid")
->set_entity_title(new lang_string('recipient', 'core_badges'))
);
// Join the course entity to the badge entity, coalescing courseid with the siteid for site badges.
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
$this->add_entity($courseentity
->add_join("LEFT JOIN {course} {$coursealias}
ON {$coursealias}.id = COALESCE({$badgealias}.courseid, 1)")
);
// Add report elements from each of the entities we added to the report.
$this->add_all_from_entities();
}
/**
* Return the columns that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'badge:name',
'badge:description',
'user:fullname',
'badge_issued:issued',
];
}
/**
* Return the filters that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'badge:name',
'user:fullname',
'badge_issued:issued',
];
}
/**
* Return the conditions that will be added to the report upon creation
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'badge:type',
'badge:name',
];
}
}
@@ -0,0 +1,280 @@
<?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_badges\reportbuilder\local\entities;
use context_course;
use context_system;
use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{select, text};
use core_reportbuilder\local\report\{column, filter};
defined('MOODLE_INTERNAL') or die;
global $CFG;
require_once("{$CFG->libdir}/badgeslib.php");
/**
* Badge entity
*
* @package core_badges
* @copyright 2022 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badge extends base {
/**
* Database tables that this entity uses and their default aliases
*
* @return array
*/
protected function get_default_table_aliases(): array {
return ['badge' => 'b'];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('badgedetails', 'core_badges');
}
/**
* 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 {
$badgealias = $this->get_table_alias('badge');
// Name.
$columns[] = (new column(
'name',
new lang_string('name'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.name")
->set_is_sortable(true);
// Description.
$columns[] = (new column(
'description',
new lang_string('description', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_LONGTEXT)
->add_field("{$badgealias}.description");
// Criteria.
$columns[] = (new column(
'criteria',
new lang_string('bcriteria', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.id")
->set_disabled_aggregation_all()
->add_callback(static function($badgeid): string {
global $PAGE;
$badge = new \core_badges\badge($badgeid);
$renderer = $PAGE->get_renderer('core_badges');
return $renderer->print_badge_criteria($badge, 'short');
});
// Image.
$columns[] = (new column(
'image',
new lang_string('badgeimage', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$badgealias}.id, {$badgealias}.type, {$badgealias}.courseid, {$badgealias}.imagecaption")
->set_disabled_aggregation_all()
->add_callback(static function(int $badgeid, stdClass $badge): string {
$context = $badge->type == BADGE_TYPE_SITE
? context_system::instance()
: context_course::instance($badge->courseid);
$badgeimage = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badgeid, '/', 'f2');
return html_writer::img($badgeimage, $badge->imagecaption);
});
// Language.
$columns[] = (new column(
'language',
new lang_string('language'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.language")
->set_is_sortable(true)
->add_callback(static function($language): string {
$languages = get_string_manager()->get_list_of_languages();
return $languages[$language] ?? $language;
});
// Version.
$columns[] = (new column(
'version',
new lang_string('version', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.version")
->set_is_sortable(true);
// Status.
$columns[] = (new column(
'status',
new lang_string('status', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.status")
->set_is_sortable(true)
->add_callback(static function($status): string {
return get_string("badgestatus_{$status}", 'core_badges');
});
// Expiry date/period.
$columns[] = (new column(
'expiry',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$badgealias}.expiredate, {$badgealias}.expireperiod")
->set_is_sortable(true, ["{$badgealias}.expiredate", "{$badgealias}.expireperiod"])
->set_disabled_aggregation_all()
->add_callback(static function(int $expiredate, stdClass $badge): string {
if ($expiredate) {
return userdate($expiredate);
} else if ($badge->expireperiod) {
return format_time($badge->expireperiod);
} else {
return get_string('never', 'core_badges');
}
});
// Image author details.
foreach (['imageauthorname', 'imageauthoremail', 'imageauthorurl'] as $imageauthorfield) {
$columns[] = (new column(
$imageauthorfield,
new lang_string($imageauthorfield, 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$badgealias}.{$imageauthorfield}")
->set_is_sortable(true);
}
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$badgealias = $this->get_table_alias('badge');
// Name.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name'),
$this->get_entity_name(),
"{$badgealias}.name"
))
->add_joins($this->get_joins());
// Status.
$filters[] = (new filter(
select::class,
'status',
new lang_string('status', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.status"
))
->add_joins($this->get_joins())
->set_options([
BADGE_STATUS_INACTIVE => new lang_string('badgestatus_0', 'core_badges'),
BADGE_STATUS_ACTIVE => new lang_string('badgestatus_1', 'core_badges'),
BADGE_STATUS_INACTIVE_LOCKED => new lang_string('badgestatus_2', 'core_badges'),
BADGE_STATUS_ACTIVE_LOCKED => new lang_string('badgestatus_3', 'core_badges'),
BADGE_STATUS_ARCHIVED => new lang_string('badgestatus_4', 'core_badges'),
]);
// Type.
$filters[] = (new filter(
select::class,
'type',
new lang_string('type', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.type"
))
->add_joins($this->get_joins())
->set_options([
BADGE_TYPE_SITE => new lang_string('site'),
BADGE_TYPE_COURSE => new lang_string('course'),
]);
return $filters;
}
}
@@ -0,0 +1,162 @@
<?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_badges\reportbuilder\local\entities;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{boolean_select, date};
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\{column, filter};
/**
* Badge issued entity
*
* @package core_badges
* @copyright 2022 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badge_issued extends base {
/**
* Database tables that this entity uses and their default aliases
*
* @return array
*/
protected function get_default_table_aliases(): array {
return ['badge_issued' => 'bi'];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('badgeissued', 'core_badges');
}
/**
* 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 {
$badgeissuedalias = $this->get_table_alias('badge_issued');
// Date issued.
$columns[] = (new column(
'issued',
new lang_string('dateawarded', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$badgeissuedalias}.dateissued")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Date expires.
$columns[] = (new column(
'expire',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_field("{$badgeissuedalias}.dateexpire")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Visible.
$columns[] = (new column(
'visible',
new lang_string('visible', 'core_badges'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_fields("{$badgeissuedalias}.visible")
->add_callback([format::class, 'boolean_as_text']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$badgealias = $this->get_table_alias('badge_issued');
// Date issued.
$filters[] = (new filter(
date::class,
'issued',
new lang_string('dateawarded', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.dateissued"
))
->add_joins($this->get_joins());
// Date expires.
$filters[] = (new filter(
date::class,
'expires',
new lang_string('expirydate', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.dateexpire"
))
->add_joins($this->get_joins());
// Visible.
$filters[] = (new filter(
boolean_select::class,
'visible',
new lang_string('visible', 'core_badges'),
$this->get_entity_name(),
"{$badgealias}.visible"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,135 @@
<?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_badges\reportbuilder\datasource;
use core_badges\badge;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for badges datasource
*
* @package core_badges
* @covers \core_badges\reportbuilder\datasource\badges
* @copyright 2022 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class badges_test extends core_reportbuilder_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->libdir}/badgeslib.php");
}
/**
* Test datasource
*/
public function test_datasource(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Test users with a badge we can issue them with.
$user1 = $this->getDataGenerator()->create_user(['firstname' => 'Alan', 'lastname' => 'Apple']);
$user2 = $this->getDataGenerator()->create_user(['firstname' => 'Barry', 'lastname' => 'Banana']);
$sitebadge = $this->create_badge(['name' => 'Badge 1']);
$sitebadge->issue($user1->id, true);
$sitebadge->issue($user2->id, true);
// Another badge, in a course, no issues.
$course = $this->getDataGenerator()->create_course();
$coursebadge = $this->create_badge(['name' => 'Badge 2', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Badges', 'source' => badges::class, 'default' => 0]);
// Badge course.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname'])
->set_many(['sortenabled' => true, 'sortdirection' => SORT_ASC])->update();
// Badge name.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:name'])
->set_many(['sortenabled' => true, 'sortdirection' => SORT_ASC])->update();
// User fullname.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname'])
->set_many(['sortenabled' => true, 'sortdirection' => SORT_ASC])->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(3, $content);
$this->assertEquals([
['PHPUnit test site', $sitebadge->name, fullname($user1, true)],
['PHPUnit test site', $sitebadge->name, fullname($user2, true)],
[$course->fullname, $coursebadge->name, ''],
], array_map(static function(array $row): array {
return array_values($row);
}, $content));
}
/**
* Helper method to create a badge
*
* @param array $params
* @return badge
*/
protected function create_badge(array $params = []): badge {
global $DB, $USER;
$record = (object) array_merge([
'name' => 'Test badge',
'description' => 'Testing badges',
'timecreated' => time(),
'timemodified' => time(),
'usercreated' => $USER->id,
'usermodified' => $USER->id,
'issuername' => 'Test issuer',
'issuerurl' => 'http://issuer-url.domain.co.nz',
'issuercontact' => '[email protected]',
'expiredate' => null,
'expireperiod' => null,
'type' => BADGE_TYPE_SITE,
'courseid' => null,
'messagesubject' => 'Test message subject',
'message' => 'Test message body',
'attachment' => 1,
'notification' => 0,
'status' => BADGE_STATUS_ACTIVE,
'version' => OPEN_BADGES_V2,
'language' => 'en',
'imageauthorname' => 'Image author',
'imageauthoremail' => '[email protected]',
'imageauthorurl' => 'http://image.example.com/',
'imagecaption' => 'Image caption'
], $params);
$record->id = $DB->insert_record('badge', $record);
return new badge($record->id);
}
}
@@ -56,19 +56,20 @@ class cohorts extends datasource {
$cohortmemberentity = new cohort_member();
$cohortmembertablealias = $cohortmemberentity->get_table_alias('cohort_members');
$cohortmemberjoin = "LEFT JOIN {cohort_members} {$cohortmembertablealias}
ON {$cohortmembertablealias}.cohortid = {$cohorttablealias}.id";
$this->add_entity($cohortmemberentity->add_join($cohortmemberjoin));
$this->add_entity($cohortmemberentity
->add_join("LEFT JOIN {cohort_members} {$cohortmembertablealias}
ON {$cohortmembertablealias}.cohortid = {$cohorttablealias}.id")
);
// Join the user entity to the cohort member entity.
$userentity = new user();
$usertablealias = $userentity->get_table_alias('user');
$userjoin = "LEFT JOIN {user} {$usertablealias}
ON {$usertablealias}.id = {$cohortmembertablealias}.userid";
$this->add_entity($userentity->add_joins([$cohortmemberjoin, $userjoin]));
$this->add_entity($userentity
->add_joins($cohortmemberentity->get_joins())
->add_join("LEFT JOIN {user} {$usertablealias}
ON {$usertablealias}.id = {$cohortmembertablealias}.userid")
);
// Add all columns/filters/conditions from entities to be available in custom reports.
$this->add_all_from_entities();
+2 -1
View File
@@ -141,7 +141,7 @@ $string['badges'] = 'Badges';
$string['badgedetails'] = 'Badge details';
$string['badgeimage'] = 'Image';
$string['badgeimage_help'] = 'The image should be at least 300 x 300 pixels in size. It will be displayed as 300 x 300 pixels on the badge page and 100 x 100 pixels on the user\'s profile page.';
$string['badgeissued'] = 'Badge issued';
$string['badgeprivacysetting'] = 'Badge privacy settings';
$string['badgeprivacysetting_help'] = 'Badges you earn can be displayed on your account profile page. This setting allows you to automatically set the visibility of the newly earned badges.
@@ -493,6 +493,7 @@ $string['privacy:metadata:manualaward:datemet'] = 'The date when the user was aw
$string['privacy:metadata:manualaward:issuerid'] = 'The ID of the user awarding the badge';
$string['privacy:metadata:manualaward:issuerrole'] = 'The role of the user awarding the badge';
$string['privacy:metadata:manualaward:recipientid'] = 'The ID of the user who is manually awarded a badge';
$string['recipient'] = 'Badge recipient';
$string['recipients'] = 'Badge recipients';
$string['recipientvalidationproblem'] = 'This user cannot be verified as a recipient of this badge.';
$string['relative'] = 'Relative date';
@@ -200,7 +200,7 @@ abstract class base {
*
* @return string[]
*/
final protected function get_joins(): array {
final public function get_joins(): array {
return array_values($this->joins);
}
@@ -22,7 +22,6 @@ use advanced_testcase;
use coding_exception;
use context_system;
use lang_string;
use ReflectionClass;
use core_reportbuilder\course_entity_report;
use core_reportbuilder\manager;
use core_reportbuilder\testable_system_report_table;
@@ -415,13 +414,11 @@ class course_test extends advanced_testcase {
$tablejoin = "JOIN {course} c2 ON c2.id = c1.id";
$courseentity->add_join($tablejoin);
$method = (new ReflectionClass(course::class))->getMethod('get_joins');
$method->setAccessible(true);
$this->assertEquals([$tablejoin], $method->invoke($courseentity));
$this->assertEquals([$tablejoin], $courseentity->get_joins());
}
/**
* Test adding multiple join
* Test adding multiple joins
*/
public function test_add_joins(): void {
$courseentity = (new course())
@@ -433,9 +430,25 @@ class course_test extends advanced_testcase {
];
$courseentity->add_joins($tablejoins);
$method = (new ReflectionClass(course::class))->getMethod('get_joins');
$method->setAccessible(true);
$this->assertEquals($tablejoins, $method->invoke($courseentity));
$this->assertEquals($tablejoins, $courseentity->get_joins());
}
/**
* Test adding duplicate joins
*/
public function test_add_duplicate_joins(): void {
$courseentity = (new course())
->set_table_alias('course', 'c1');
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$courseentity
->add_joins($tablejoins)
->add_joins($tablejoins);
$this->assertEquals($tablejoins, $courseentity->get_joins());
}
/**
+1
View File
@@ -10,3 +10,4 @@ Information provided here is intended especially for developers.
* New database helper methods for generating multiple unique values: `generate_aliases` and `generate_param_names`
* The base aggregation `format_value` method has a `$columntype` argument in order to preserve type during aggregation. When
defining column callbacks, strict typing will now be preserved in your callback methods when the column is being aggregated
* The method `get_joins()` in the base entity class is now public, allowing for easier joins within reports