Merge branch 'MDL-67791-master' of git://github.com/ferranrecio/moodle

This commit is contained in:
Adrian Greeve 2020-05-18 11:55:36 +08:00
commit 410cec0d40
44 changed files with 4067 additions and 13 deletions

View File

@ -41,14 +41,14 @@ class backup_h5pactivity_activity_structure_step extends backup_activity_structu
// Replace with the attributes and final elements that the element will handle.
$attributes = ['id'];
$finalelements = ['name', 'timecreated', 'timemodified', 'intro',
'introformat', 'grade', 'displayoptions', 'enabletracking', 'grademethod'];
'introformat', 'grade', 'displayoptions', 'enabletracking', 'grademethod', 'reviewmode'];
$root = new backup_nested_element('h5pactivity', $attributes, $finalelements);
$attempts = new backup_nested_element('attempts');
$attempt = new backup_nested_element('attempt', ['id'],
['h5pactivityid', 'userid', 'timecreated', 'timemodified', 'attempt', 'rawscore', 'maxscore',
'duration', 'completion', 'success']
'duration', 'completion', 'success', 'scaled']
);
$results = new backup_nested_element('results');

View File

@ -0,0 +1,131 @@
<?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/>.
/**
* H5P activity report viewed.
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\event;
defined('MOODLE_INTERNAL') || die();
/**
* The report_viewed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int instanceid: The instance ID
* - int userid: The optional user ID
* - int attemptid: The optional attempt ID
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init(): void {
$this->data['objecttable'] = 'h5pactivity';
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('report_viewed', 'mod_h5pactivity');
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->other['instanceid'])) {
throw new \coding_exception('The \'instanceid\' value must be set in other.');
}
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the H5P report for the H5P with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Get URL related to the action
*
* @return \moodle_url
*/
public function get_url() {
$params = ['a' => $this->other['instanceid']];
if (!empty($this->other['userid'])) {
$params['userid'] = $this->other['userid'];
}
if (!empty($this->other['attemptid'])) {
$params['attemptid'] = $this->other['attemptid'];
}
return new \moodle_url('/mod/h5pactivity/report.php', $params);
}
/**
* This is used when restoring course logs where it is required that we
* map the objectid to it's new value in the new course.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'h5pactivity', 'restore' => 'h5pactivity'];
}
/**
* Return the other field mapping.
*
* @return array
*/
public static function get_other_mapping() {
$othermapped = array();
$othermapped['attemptid'] = array('db' => 'h5pactivity_attempts', 'restore' => 'h5pactivity_attempts');
$othermapped['userid'] = array('db' => 'user', 'restore' => 'user');
return $othermapped;
}
}

View File

@ -293,6 +293,17 @@ class attempt {
return $DB->count_records('h5pactivity_attempts_results', $conditions);
}
/**
* Return all results stored in this attempt.
*
* @return stdClass[] results records.
*/
public function get_results(): array {
global $DB;
$conditions = ['attemptid' => $this->record->id];
return $DB->get_records('h5pactivity_attempts_results', $conditions, 'id ASC');
}
/**
* Get additional data for some interaction types.
*
@ -404,6 +415,24 @@ class attempt {
return $this->record->userid;
}
/**
* Return the attempt H5P timecreated.
*
* @return int the attempt timecreated
*/
public function get_timecreated(): int {
return $this->record->timecreated;
}
/**
* Return the attempt H5P timemodified.
*
* @return int the attempt timemodified
*/
public function get_timemodified(): int {
return $this->record->timemodified;
}
/**
* Return the attempt H5P activity ID.
*
@ -458,6 +487,15 @@ class attempt {
return $this->record->success;
}
/**
* Return the attempt scaled.
*
* @return int|null the scaled value
*/
public function get_scaled(): ?int {
return $this->record->scaled;
}
/**
* Return if the attempt has been modified.
*

View File

@ -25,9 +25,13 @@
namespace mod_h5pactivity\local;
use mod_h5pactivity\local\report\participants;
use mod_h5pactivity\local\report\attempts;
use mod_h5pactivity\local\report\results;
use context_module;
use cm_info;
use moodle_recordset;
use core_user;
use stdClass;
/**
@ -55,6 +59,12 @@ class manager {
/** Use first attempt results for grading. */
const GRADEFIRSTATTEMPT = 4;
/** Participants cannot review their own attempts. */
const REVIEWNONE = 0;
/** Participants can review their own attempts when have one attempt completed. */
const REVIEWCOMPLETION = 1;
/** @var stdClass course_module record. */
private $instance;
@ -118,6 +128,38 @@ class manager {
];
}
/**
* Return the selected attempt criteria.
* @return string[] an array "grademethod value", "attempt description"
*/
public function get_selected_attempt(): array {
$types = [
self::GRADEHIGHESTATTEMPT => get_string('attempt_highest', 'mod_h5pactivity'),
self::GRADEAVERAGEATTEMPT => get_string('attempt_average', 'mod_h5pactivity'),
self::GRADELASTATTEMPT => get_string('attempt_last', 'mod_h5pactivity'),
self::GRADEFIRSTATTEMPT => get_string('attempt_first', 'mod_h5pactivity'),
self::GRADEMANUAL => get_string('attempt_none', 'mod_h5pactivity'),
];
if ($this->instance->enabletracking) {
$key = $this->instance->grademethod;
} else {
$key = self::GRADEMANUAL;
}
return [$key, $types[$key]];
}
/**
* Return the available review modes.
*
* @return string[] an array "option value" => "option description"
*/
public static function get_review_modes(): array {
return [
self::REVIEWCOMPLETION => get_string('review_on_completion', 'mod_h5pactivity'),
self::REVIEWNONE => get_string('review_none', 'mod_h5pactivity'),
];
}
/**
* Check if tracking is enabled in a particular h5pactivity for a specific user.
*
@ -135,6 +177,50 @@ class manager {
return has_capability('mod/h5pactivity:submit', $this->context, $user, false);
}
/**
* Check if a user can see the activity attempts list.
*
* @param stdClass|null $user user record (default $USER)
* @return bool if the user can see the attempts link
*/
public function can_view_all_attempts (stdClass $user = null): bool {
global $USER;
if (!$this->instance->enabletracking) {
return false;
}
if (empty($user)) {
$user = $USER;
}
return has_capability('mod/h5pactivity:reviewattempts', $this->context, $user);
}
/**
* Check if a user can see own attempts.
*
* @param stdClass|null $user user record (default $USER)
* @return bool if the user can see the own attempts link
*/
public function can_view_own_attempts (stdClass $user = null): bool {
global $USER;
if (!$this->instance->enabletracking) {
return false;
}
if (empty($user)) {
$user = $USER;
}
if (has_capability('mod/h5pactivity:reviewattempts', $this->context, $user, false)) {
return true;
}
if ($this->instance->reviewmode == self::REVIEWNONE) {
return false;
}
if ($this->instance->reviewmode == self::REVIEWCOMPLETION) {
return true;
}
return false;
}
/**
* Return a relation of userid and the valid attempt's scaled score.
*
@ -198,6 +284,46 @@ class manager {
return $DB->get_records_sql($sql, $params);
}
/**
* Count the activity completed attempts.
*
* If no user is provided will count all activity attempts.
*
* @param int|null $userid optional user id (default null)
* @return int the total amount of attempts
*/
public function count_attempts(int $userid = null): int {
global $DB;
$params = [
'h5pactivityid' => $this->instance->id,
'completion' => 1
];
if ($userid) {
$params['userid'] = $userid;
}
return $DB->count_records('h5pactivity_attempts', $params);
}
/**
* Return an array of all users and it's total attempts.
*
* Note: this funciton only returns the list of users with attempts,
* it does not check all participants.
*
* @return array indexed count userid => total number of attempts
*/
public function count_users_attempts(): array {
global $DB;
$params = [
'h5pactivityid' => $this->instance->id,
];
$sql = "SELECT userid, count(*)
FROM {h5pactivity_attempts}
WHERE h5pactivityid = :h5pactivityid
GROUP BY userid";
return $DB->get_records_sql_menu($sql, $params);
}
/**
* Return the current context.
*
@ -208,7 +334,7 @@ class manager {
}
/**
* Return the current context.
* Return the current instance.
*
* @return stdClass the instance record
*/
@ -234,4 +360,89 @@ class manager {
$idnumber = $this->coursemodule->idnumber ?? '';
return new grader($this->instance, $idnumber);
}
/**
* Return the suitable report to show the attempts.
*
* This method controls the access to the different reports
* the activity have.
*
* @param int $userid an opional userid to show
* @param int $attemptid an optional $attemptid to show
* @return report|null available report (or null if no report available)
*/
public function get_report(int $userid = null, int $attemptid = null): ?report {
global $USER;
$attempt = null;
if ($attemptid) {
$attempt = $this->get_attempt($attemptid);
if (!$attempt) {
return null;
}
// If we have and attempt we can ignore the provided $userid.
$userid = $attempt->get_userid();
}
if ($this->can_view_all_attempts()) {
$user = core_user::get_user($userid);
} else if ($this->can_view_own_attempts()) {
$user = $USER;
if ($userid && $user->id != $userid) {
return null;
}
} else {
return null;
}
// Check if that user can be tracked.
if ($user && !$this->is_tracking_enabled($user)) {
return null;
}
// Create the proper report.
if ($user && $attempt) {
return new results($this, $user, $attempt);
} else if ($user) {
return new attempts($this, $user);
}
return new participants($this);
}
/**
* Return a single attempt.
*
* @param int $attemptid the attempt id
* @return attempt
*/
public function get_attempt(int $attemptid): ?attempt {
global $DB;
$record = $DB->get_record('h5pactivity_attempts', ['id' => $attemptid]);
if (!$record) {
return null;
}
return new attempt($record);
}
/**
* Return an array of all user attempts (including incompleted)
*
* @param int $userid the user id
* @return attempt[]
*/
public function get_user_attempts(int $userid): array {
global $DB;
$records = $DB->get_records(
'h5pactivity_attempts',
['userid' => $userid, 'h5pactivityid' => $this->instance->id],
'id ASC'
);
if (!$records) {
return [];
}
$result = [];
foreach ($records as $record) {
$result[] = new attempt($record);
}
return $result;
}
}

View File

@ -0,0 +1,58 @@
<?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/>.
/**
* H5P activity report interface
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\local;
use templatable;
use stdClass;
/**
* Interface for any mod_h5pactivity report.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
*/
interface report {
/**
* Return the report user record.
*
* @return stdClass|null a user or null
*/
public function get_user(): ?stdClass;
/**
* Return the report attempt object.
*
* @return attempt|null the attempt object or null
*/
public function get_attempt(): ?attempt;
/**
* Print the report visualization.
*/
public function print(): void;
}

View File

@ -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/>.
/**
* H5P activity attempts report
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\local\report;
use mod_h5pactivity\local\report;
use mod_h5pactivity\local\manager;
use mod_h5pactivity\local\attempt;
use mod_h5pactivity\output\reportattempts;
use stdClass;
/**
* Class H5P activity attempts report.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
*/
class attempts implements report {
/** @var manager the H5P activity manager instance. */
private $manager;
/** @var stdClass the user record. */
private $user;
/**
* Create a new participants report.
*
* @param manager $manager h5pactivity manager object
* @param stdClass $user user record
*/
public function __construct(manager $manager, stdClass $user) {
$this->manager = $manager;
$this->user = $user;
}
/**
* Return the report user record.
*
* @return stdClass|null a user or null
*/
public function get_user(): ?stdClass {
return $this->user;
}
/**
* Return the report attempt object.
*
* Attempts report has no specific attempt.
*
* @return attempt|null the attempt object or null
*/
public function get_attempt(): ?attempt {
return null;
}
/**
* Print the report.
*/
public function print(): void {
global $OUTPUT;
$manager = $this->manager;
$cm = $manager->get_coursemodule();
$scored = $this->get_scored();
$title = $scored->title ?? null;
$scoredattempt = $scored->attempt ?? null;
$attempts = $this->get_attempts();
$widget = new reportattempts($attempts, $this->user, $cm->course, $title, $scoredattempt);
echo $OUTPUT->render($widget);
}
/**
* Return the current report attempts.
*
* This method is used to render the report in both browser and mobile.
*
* @return attempts[]
*/
public function get_attempts(): array {
return $this->manager->get_user_attempts($this->user->id);
}
/**
* Return the current report attempts.
*
* This method is used to render the report in both browser and mobile.
*
* @return stdClass|null a structure with
* - title => name of the selected attempt (or null)
* - attempt => the selected attempt object (or null)
* - gradingmethos => the activity grading method (or null)
*/
public function get_scored(): ?stdClass {
$manager = $this->manager;
$scores = $manager->get_users_scaled_score($this->user->id);
$score = $scores[$this->user->id] ?? null;
if (empty($score->attemptid)) {
return null;
}
list($grademethod, $title) = $manager->get_selected_attempt();
$scoredattempt = $manager->get_attempt($score->attemptid);
$result = (object)[
'title' => $title,
'attempt' => $scoredattempt,
'grademethod' => $grademethod,
];
return $result;
}
}

View File

@ -0,0 +1,204 @@
<?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/>.
/**
* H5P activity participants report
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\local\report;
use mod_h5pactivity\local\report;
use mod_h5pactivity\local\manager;
use mod_h5pactivity\local\attempt;
use table_sql;
use moodle_url;
use html_writer;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir.'/tablelib.php');
/**
* Class H5P activity participants report.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
*/
class participants extends table_sql implements report {
/** @var manager the H5P activity manager instance. */
private $manager;
/** @var array the users scored attempts. */
private $scores;
/** @var array the user attempts count. */
private $count;
/**
* Create a new participants report.
*
* @param manager $manager h5pactivitymanager object
*/
public function __construct(manager $manager) {
parent::__construct('mod_h5pactivity-participants');
$this->manager = $manager;
$this->scores = $manager->get_users_scaled_score();
$this->count = $manager->count_users_attempts();
// Setup table_sql.
$columns = ['fullname', 'timemodified', 'score', 'attempts'];
$headers = [
get_string('fullname'), get_string('date'),
get_string('score', 'mod_h5pactivity'), get_string('attempts', 'mod_h5pactivity'),
];
$this->define_columns($columns);
$this->define_headers($headers);
$this->set_attribute('class', 'generaltable generalbox boxaligncenter boxwidthwide');
$this->sortable(true);
$this->no_sorting('score');
$this->no_sorting('timemodified');
$this->no_sorting('attempts');
$this->pageable(true);
// Set query SQL.
$capjoin = get_enrolled_with_capabilities_join($this->manager->get_context(), '', 'mod/h5pactivity:submit');
$this->set_sql(
'u.*',
"{user} u $capjoin->joins",
$capjoin->wheres,
$capjoin->params);
}
/**
* Return the report user record.
*
* Participants report has no specific user.
*
* @return stdClass|null a user or null
*/
public function get_user(): ?stdClass {
return null;
}
/**
* Return the report attempt object.
*
* Participants report has no specific attempt.
*
* @return attempt|null the attempt object or null
*/
public function get_attempt(): ?attempt {
return null;
}
/**
* Print the report.
*/
public function print(): void {
global $PAGE, $OUTPUT;
$this->define_baseurl($PAGE->url);
echo $OUTPUT->heading(get_string('attempts_report', 'mod_h5pactivity'));
$this->out($this->get_page_size(), true);
}
/**
* Warning in case no user has the selected initials letters.
*
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $this->render_reset_button();
$this->print_initials_bar();
echo $OUTPUT->notification(get_string('noparticipants', 'mod_h5pactivity'), 'warning');
}
/**
* Generate the fullname column.
*
* @param stdClass $user
* @return string
*/
public function col_fullname($user): string {
global $OUTPUT;
$cm = $this->manager->get_coursemodule();
return $OUTPUT->user_picture($user, ['size' => 35, 'courseid' => $cm->course, 'includefullname' => true]);
}
/**
* Generate score column.
*
* @param stdClass $user the user record
* @return string
*/
public function col_score(stdClass $user): string {
$cm = $this->manager->get_coursemodule();
if (isset($this->scores[$user->id])) {
$score = $this->scores[$user->id];
$maxgrade = floatval(100);
$scaled = round($maxgrade * $score->scaled).'%';
if (empty($score->attemptid)) {
return $scaled;
} else {
$url = new moodle_url('/mod/h5pactivity/report.php', ['a' => $cm->instance, 'attemptid' => $score->attemptid]);
return html_writer::link($url, $scaled);
}
}
return '';
}
/**
* Generate attempts count column, if any.
*
* @param stdClass $user the user record
* @return string
*/
public function col_attempts(stdClass $user): string {
$cm = $this->manager->get_coursemodule();
if (isset($this->count[$user->id])) {
$msg = get_string('review_user_attempts', 'mod_h5pactivity', $this->count[$user->id]);
$url = new moodle_url('/mod/h5pactivity/report.php', ['a' => $cm->instance, 'userid' => $user->id]);
return html_writer::link($url, $msg);
}
return '';
}
/**
* Generate attempt timemodified column, if any.
*
* @param stdClass $user the user record
* @return string
*/
public function col_timemodified(stdClass $user): string {
if (isset($this->scores[$user->id])) {
$score = $this->scores[$user->id];
return userdate($score->timemodified);
}
return '';
}
}

View File

@ -0,0 +1,98 @@
<?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/>.
/**
* H5P activity results report.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\local\report;
use mod_h5pactivity\local\report;
use mod_h5pactivity\local\manager;
use mod_h5pactivity\local\attempt;
use mod_h5pactivity\output\reportresults;
use stdClass;
/**
* Class H5P activity results report.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
*/
class results implements report {
/** @var manager the H5P activity manager instance. */
private $manager;
/** @var stdClass the user record. */
private $user;
/** @var attempt the h5pactivity attempt to show. */
private $attempt;
/**
* Create a new participants report.
*
* @param manager $manager h5pactivity manager object
* @param stdClass $user user record
* @param attempt $attempt attempt object
*/
public function __construct(manager $manager, stdClass $user, attempt $attempt) {
$this->manager = $manager;
$this->user = $user;
$this->attempt = $attempt;
}
/**
* Return the report user record.
*
* @return stdClass|null a user or null
*/
public function get_user(): ?stdClass {
return $this->user;
}
/**
* Return the report attempt object.
*
* Attempts report has no specific attempt.
*
* @return attempt|null the attempt object or null
*/
public function get_attempt(): ?attempt {
return $this->attempt;
}
/**
* Print the report.
*/
public function print(): void {
global $OUTPUT;
$manager = $this->manager;
$attempt = $this->attempt;
$cm = $manager->get_coursemodule();
$widget = new reportresults($attempt, $this->user, $cm->course);
echo $OUTPUT->render($widget);
}
}

View File

@ -0,0 +1,227 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\reportlink
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\local\attempt as activity_attempt;
use renderable;
use templatable;
use renderer_base;
use moodle_url;
use user_picture;
use stdClass;
/**
* Class to help display report link in mod_h5pactivity.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt implements renderable, templatable {
/** @var activity_attempt attempt */
public $attempt;
/** @var stdClass user record */
public $user;
/** @var int courseid necesary to present user picture */
public $courseid;
/**
* Constructor.
*
* @param activity_attempt $attempt the attempt object
* @param stdClass $user a user record (default null).
* @param int $courseid optional course id (default null).
*/
public function __construct(activity_attempt $attempt, stdClass $user = null, int $courseid = null) {
$this->attempt = $attempt;
$this->user = $user;
$this->courseid = $courseid;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
$attempt = $this->attempt;
$data = (object)[
'id' => $attempt->get_id(),
'h5pactivityid' => $attempt->get_h5pactivityid(),
'userid' => $attempt->get_userid(),
'timecreated' => $attempt->get_timecreated(),
'timemodified' => $attempt->get_timemodified(),
'attempt' => $attempt->get_attempt(),
'rawscore' => $attempt->get_rawscore(),
'maxscore' => $attempt->get_maxscore(),
'duration' => '-',
'durationcompact' => '-',
'completion' => $attempt->get_completion(),
'completionicon' => $this->completion_icon($output, $attempt->get_completion()),
'completiontext' => $this->completion_icon($output, $attempt->get_completion(), true),
'success' => $attempt->get_success(),
'successicon' => $this->success_icon($output, $attempt->get_success()),
'successtext' => $this->success_icon($output, $attempt->get_success(), true),
'scaled' => $attempt->get_scaled(),
'reporturl' => new moodle_url('/mod/h5pactivity/report.php', [
'a' => $attempt->get_h5pactivityid(), 'attemptid' => $attempt->get_id()
]),
];
if ($attempt->get_duration() !== null) {
$duration = $this->extract_duration($attempt->get_duration());
$data->duration = $this->format_duration($duration);
$data->durationcompact = $this->format_duration_short($duration);
}
if (!empty($data->maxscore)) {
$data->score = get_string('score_out_of', 'mod_h5pactivity', $data);
}
if ($this->user) {
$data->user = $this->user;
$userpicture = new user_picture($this->user);
$userpicture->courseid = $this->courseid;
$data->user->picture = $output->render($userpicture);
$data->user->fullname = fullname($this->user);
}
return $data;
}
/**
* Return a completion icon HTML.
*
* @param renderer_base $output the renderer base object
* @param int|null $completion the current completion value
* @param bool $showtext if the icon must have a text or only icon
* @return string icon HTML
*/
private function completion_icon(renderer_base $output, int $completion = null, bool $showtext = false): string {
if ($completion === null) {
return '';
}
if ($completion) {
$alt = get_string('attempt_completion_yes', 'mod_h5pactivity');
$icon = 'i/completion-auto-y';
} else {
$alt = get_string('attempt_completion_no', 'mod_h5pactivity');
$icon = 'i/completion-auto-n';
}
$text = '';
if ($showtext) {
$text = $alt;
$alt = '';
}
return $output->pix_icon($icon, $alt).$text;
}
/**
* Return a success icon
* @param renderer_base $output the renderer base object
* @param int|null $success the current success value
* @param bool $showtext if the icon must have a text or only icon
* @return string icon HTML
*/
private function success_icon(renderer_base $output, int $success = null, bool $showtext = false): string {
if ($success === null) {
$alt = get_string('attempt_success_unknown', 'mod_h5pactivity');
if ($showtext) {
return $alt;
}
$icon = 'i/empty';
} else if ($success) {
$alt = get_string('attempt_success_pass', 'mod_h5pactivity');
$icon = 'i/checkedcircle';
} else {
$alt = get_string('attempt_success_fail', 'mod_h5pactivity');
$icon = 'i/uncheckedcircle';
}
$text = '';
if ($showtext) {
$text = $alt;
$alt = '';
}
return $output->pix_icon($icon, $alt).$text;
}
/**
* Return the duration in long format (localized)
*
* @param stdClass $duration object with (h)hours, (m)minutes and (s)seconds
* @return string the long format duration
*/
private function format_duration (stdClass $duration): string {
$result = [];
if ($duration->h) {
$result[] = get_string('numhours', 'moodle', $duration->h);
}
if ($duration->m) {
$result[] = get_string('numminutes', 'moodle', $duration->m);
}
if ($duration->s) {
$result[] = get_string('numseconds', 'moodle', $duration->s);
}
return implode(' ', $result);
}
/**
* Return the duration en short format (for example: 145' 43'')
*
* Note: this method is used to make duration responsive.
*
* @param stdClass $duration object with (h)hours, (m)minutes and (s)seconds
* @return string the short format duration
*/
private function format_duration_short (stdClass $duration): string {
$result = [];
if ($duration->h || $duration->m) {
$result[] = ($duration->h * 60 + $duration->m)."'";
}
if ($duration->s) {
$result[] = $duration->s."''";
}
return implode(' ', $result);
}
/**
* Extract hours and minutes from second duration.
*
* Note: this function is used to generate the param for format_duration
* and format_duration_short
*
* @param int $seconds number of second
* @return stdClass with (h)hours, (m)minutes and (s)seconds
*/
private function extract_duration (int $seconds): stdClass {
$h = floor($seconds / 3600);
$m = floor(($seconds - $h * 3600) / 60);
$s = $seconds - ($h * 3600 + $m * 60);
return (object)['h' => $h, 'm' => $m, 's' => $s];
}
}

View File

@ -0,0 +1,118 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\report\attempts
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\local\attempt;
use mod_h5pactivity\output\attempt as output_attempt;
use renderable;
use templatable;
use renderer_base;
use user_picture;
use stdClass;
/**
* Class to output an attempts report on mod_h5pactivity.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reportattempts implements renderable, templatable {
/** @var attempt[] attempts */
public $attempts;
/** @var stdClass user record */
public $user;
/** @var int courseid necesary to present user picture */
public $courseid;
/** @var attempt scored attempt */
public $scored;
/** @var string scored attempt title */
public $title;
/**
* Constructor.
*
* The "scored attempt" is the attempt used for grading. By default it is the max score attempt
* but this could be defined in the activity settings. In some cases this scored attempts does not
* exists at all, this is the reason why it's an optional param.
*
* @param array $attempts an array of attempts
* @param stdClass $user a user record
* @param int $courseid course id
* @param string|null $title title to display on the scored attempt (null if none attempt is the scored one)
* @param attempt|null $scored the scored attempt (null if none)
*/
public function __construct(array $attempts, stdClass $user, int $courseid, string $title = null, attempt $scored = null) {
$this->attempts = $attempts;
$this->user = $user;
$this->courseid = $courseid;
$this->title = $title;
$this->scored = $scored;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $USER;
$data = (object)['attempts' => [], 'user' => $this->user];
foreach ($this->attempts as $attempt) {
$outputattempt = new output_attempt($attempt);
$data->attempts[] = $outputattempt->export_for_template($output);
}
$data->attemptscount = count($data->attempts);
$userpicture = new user_picture($this->user);
$userpicture->courseid = $this->courseid;
$data->user->fullname = fullname($this->user);
$data->user->picture = $output->render($userpicture);
if ($USER->id == $this->user->id) {
$data->title = get_string('myattempts', 'mod_h5pactivity');
}
if (!empty($this->title)) {
$scored = (object)[
'title' => $this->title,
'attempts' => [],
];
$outputattempt = new output_attempt($this->scored);
$scored->attempts[] = $outputattempt->export_for_template($output);
$data->scored = $scored;
}
return $data;
}
}

View File

@ -0,0 +1,68 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\reportlink
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use templatable;
use renderer_base;
use moodle_url;
/**
* Class to help display report link in mod_h5pactivity.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reportlink implements renderable, templatable {
/** @var H5P factory */
public $url;
/** @var H5P library list */
public $message;
/**
* Constructor.
*
* @param moodle_url $url the destination url
* @param string $message the link message
*/
public function __construct(moodle_url $url, string $message) {
$this->url = $url;
$this->message = $message;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
return $this;
}
}

View File

@ -0,0 +1,92 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\reportresults
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\local\attempt;
use mod_h5pactivity\output\attempt as output_attempt;
use mod_h5pactivity\output\result as output_result;
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Class to display the result report in mod_h5pactivity.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reportresults implements renderable, templatable {
/** @var attempt the header attempt */
public $attempt;
/** @var stdClass user record */
public $user;
/** @var int courseid necesary to present user picture */
public $courseid;
/**
* Constructor.
*
* @param attempt $attempt the current attempt
* @param stdClass $user a user record
* @param int $courseid course id
*/
public function __construct(attempt $attempt, stdClass $user, int $courseid) {
$this->attempt = $attempt;
$this->user = $user;
$this->courseid = $courseid;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
$outputattempt = new output_attempt($this->attempt, $this->user, $this->courseid);
$data = (object)[
'attempt' => $outputattempt->export_for_template($output),
];
$results = $this->attempt->get_results();
$data->results = [];
foreach ($results as $key => $result) {
$outputresult = output_result::create_from_record($result);
if ($outputresult) {
$data->results[] = $outputresult->export_for_template($output);
}
}
return $data;
}
}

View File

@ -0,0 +1,300 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use templatable;
use renderer_base;
use stdClass;
/**
* Class to display an attempt tesult in mod_h5pactivity.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class result implements renderable, templatable {
/** Correct answer state. */
const CORRECT = 1;
/** Incorrect answer state. */
const INCORRECT = 2;
/** Checked answer state. */
const CHECKED = 3;
/** Unchecked answer state. */
const UNCHECKED = 4;
/** Pass answer state. */
const PASS = 5;
/** Pass answer state. */
const FAIL = 6;
/** Unkown answer state. */
const UNKNOWN = 7;
/** Text answer state. */
const TEXT = 8;
/** @var stdClass result record */
protected $result;
/** @var mixed additional decoded data */
protected $additionals;
/** @var mixed response decoded data */
protected $response;
/** @var mixed correctpattern decoded data */
protected $correctpattern = [];
/**
* Constructor.
*
* @param stdClass $result a h5pactivity_attempts_results record
*/
protected function __construct(stdClass $result) {
$this->result = $result;
if (empty($result->additionals)) {
$this->additionals = new stdClass();
} else {
$this->additionals = json_decode($result->additionals);
}
$this->response = $this->decode_response($result->response);
if (!empty($result->correctpattern)) {
$correctpattern = json_decode($result->correctpattern);
foreach ($correctpattern as $pattern) {
$this->correctpattern[] = $this->decode_response($pattern);
}
}
}
/**
* return the correct result output depending on the interactiontype
*
* @param stdClass $result h5pactivity_attempts_results record
* @return result|null the result output class if any
*/
public static function create_from_record(stdClass $result): ?self {
// Compound result track is omitted from the report.
if ($result->interactiontype == 'compound') {
return null;
}
$classname = "mod_h5pactivity\\output\\result\\{$result->interactiontype}";
$classname = str_replace('-', '', $classname);
if (class_exists($classname)) {
return new $classname($result);
}
return new self($result);
}
/**
* Return a decoded response structure.
*
* @param string $value the current response structure
* @return array an array of reponses
*/
private function decode_response(string $value): array {
// If [,] means a list of elements.
$list = explode('[,]', $value);
// Inside a list element [.] means sublist (pair) and [:] a range.
foreach ($list as $key => $item) {
if (strpos($item, '[.]') !== false) {
$list[$key] = explode('[.]', $item);
} else if (strpos($item, '[:]') !== false) {
$list[$key] = explode('[:]', $item);
}
}
return $list;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$result = $this->result;
$data = (object)[
'id' => $result->id,
'attemptid' => $result->attemptid,
'subcontent' => $result->subcontent,
'timecreated' => $result->timecreated,
'interactiontype' => $result->interactiontype,
'description' => format_string($result->description),
'rawscore' => $result->rawscore,
'maxscore' => $result->maxscore,
'duration' => $result->duration,
'completion' => $result->completion,
'success' => $result->success,
];
$result;
$options = $this->export_options();
if (!empty($options)) {
$data->hasoptions = true;
$data->optionslabel = $this->get_optionslabel();
$data->correctlabel = $this->get_correctlabel();
$data->answerlabel = $this->get_answerlabel();
$data->options = array_values($options);
$data->track = true;
}
if (!empty($result->maxscore)) {
$data->score = get_string('score_out_of', 'mod_h5pactivity', $result);
}
return $data;
}
/**
* Return the options data structure.
*
* Result types have to override this method generate a specific options report.
*
* An option is an object with:
* - id: the option ID
* - description: option description text
* - useranswer (optional): what the user answer (see get_answer method)
* - correctanswer (optional): the correct answer (see get_answer method)
*
* @return array of options
*/
protected function export_options(): ?array {
return [];
}
/**
* Return a label for result user options/choices.
*
* Specific result types can override this method to customize
* the result options table header.
*
* @return string to use in options table
*/
protected function get_optionslabel(): string {
return get_string('choice', 'mod_h5pactivity');
}
/**
* Return a label for result user correct answer.
*
* Specific result types can override this method to customize
* the result options table header.
*
* @return string to use in options table
*/
protected function get_correctlabel(): string {
return get_string('correct_answer', 'mod_h5pactivity');
}
/**
* Return a label for result user attempt answer.
*
* Specific result types can override this method to customize
* the result options table header.
*
* @return string to use in options table
*/
protected function get_answerlabel(): string {
return get_string('attempt_answer', 'mod_h5pactivity');
}
/**
* Extract descriptions from array.
*
* @param array $data additional attribute to parse
* @return string[] the resulting strings
*/
protected function get_descriptions(array $data): array {
$result = [];
foreach ($data as $key => $value) {
$description = $this->get_description($value);
$index = $value->id ?? $key;
$index = trim($index);
if (is_numeric($index)) {
$index = intval($index);
}
$result[$index] = (object)['description' => $description, 'id' => $index];
}
ksort($result);
return $result;
}
/**
* Extract description from data element.
*
* @param stdClass $data additional attribute to parse
* @return string the resulting string
*/
protected function get_description(stdClass $data): string {
if (!isset($data->description)) {
return '';
}
$translations = (array) $data->description;
if (empty($translations)) {
return '';
}
// By default, H5P packages only send "en-US" descriptions.
$result = $translations['en-US'] ?? array_shift($translations);
return trim($result);
}
/**
* Return an answer data to show results.
*
* @param int $state the answer state
* @param string $answer the extra text to display (default null)
* @return stdClass with "answer" text and the state attribute to be displayed
*/
protected function get_answer(int $state, string $answer = null): stdClass {
$states = [
self::CORRECT => 'correct',
self::INCORRECT => 'incorrect',
self::CHECKED => 'checked',
self::UNCHECKED => 'unchecked',
self::PASS => 'pass',
self::FAIL => 'fail',
self::UNKNOWN => 'unkown',
self::TEXT => 'text',
];
$state = $states[$state] ?? self::UNKNOWN;
if ($answer === null) {
$answer = get_string('answer_'.$state, 'mod_h5pactivity');
}
$result = (object)[
'answer' => $answer,
$state => true,
];
return $result;
}
}

View File

@ -0,0 +1,101 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\choice
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
/**
* Class to display H5P choice result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class choice extends result {
/**
* Return the options data structure.
*
* @return array of options
*/
protected function export_options(): ?array {
// Suppose H5P choices have only a single list of valid answers.
$correctpattern = reset($this->correctpattern);
if (empty($correctpattern)) {
$correctpattern = [];
}
$additionals = $this->additionals;
// H5P has a special extension for long choices.
$extensions = (array) $additionals->extensions ?? [];
$filter = isset($extensions['https://h5p.org/x-api/line-breaks']) ? true : false;
if (isset($additionals->choices)) {
$options = $this->get_descriptions($additionals->choices);
} else {
$options = [];
}
// Some H5P activities like Find the Words don't user the standard CMI format delimiter
// and don't use propper choice additionals. In those cases the report needs to fix this
// using the correct pattern as choices and using a non standard delimiter.
if (empty($options)) {
if (count($correctpattern) == 1) {
$correctpattern = explode(',', reset($correctpattern));
}
foreach ($correctpattern as $value) {
$option = (object)[
'id' => $value,
'description' => $value,
];
$options[$value] = $option;
}
}
foreach ($options as $key => $value) {
$correctstate = (in_array($key, $correctpattern)) ? parent::CHECKED : parent::UNCHECKED;
if (in_array($key, $this->response)) {
$answerstate = ($correctstate == parent::CHECKED) ? parent::PASS : parent::FAIL;
// In some cases, like Branching scenario H5P activity, no correct Pattern is provided
// so any answer is just a check.
if (empty($correctpattern)) {
$answerstate = parent::CHECKED;
}
$value->useranswer = $this->get_answer($answerstate);
}
$value->correctanswer = $this->get_answer($correctstate);
if ($filter && $correctstate == parent::UNCHECKED && !isset($value->useranswer)) {
unset($options[$key]);
}
}
return $options;
}
}

View File

@ -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/>.
/**
* Contains class mod_h5pactivity\output\result\fillin
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
use stdClass;
/**
* Class to display H5P fill-in result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fillin extends result {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = parent::export_for_template($output);
$data->content = $this->result->description;
$data->description = get_string('result_fill-in', 'mod_h5pactivity');
return $data;
}
/**
* Return the options data structure.
*
* @return array of options
*/
protected function export_options(): ?array {
$correctpatterns = $this->correctpattern;
$additionals = $this->additionals;
$extensions = (array) $additionals->extensions ?? [];
// There are two way in which H5P could force case sensitivity, with extensions
// or using options in the correctpatterns. By default it is case sensible.
$casesensitive = $extensions['https://h5p.org/x-api/case-sensitivity'] ?? true;
if (!empty($this->result->correctpattern) && strpos($this->result->correctpattern, '{case_matters=false}') !== null) {
$casesensitive = false;
}
$values = [];
// Add all possibilities from $additionals.
if (isset($extensions['https://h5p.org/x-api/alternatives'])) {
foreach ($extensions['https://h5p.org/x-api/alternatives'] as $key => $value) {
if (!is_array($value)) {
$value = [$value];
}
$values[$key] = ($casesensitive) ? $value : array_change_key_case($value);
}
}
// Add possibilities from correctpattern.
foreach ($correctpatterns as $correctpattern) {
foreach ($correctpattern as $key => $pattern) {
// The xAPI admits more params a part form values.
// For now this extra information is not used in reporting
// but it is posible future H5P types need them.
$value = preg_replace('/\{.+=.*\}/', '', $pattern);
$value = ($casesensitive) ? $value : strtolower($value);
if (!isset($values[$key])) {
$values[$key] = [];
}
if (!in_array($value, $values[$key])) {
array_unshift($values[$key], $value);
}
}
}
// Generate options.
$options = [];
$num = 1;
foreach ($values as $key => $value) {
$option = (object)[
'id' => $key,
'description' => get_string('result_fill-in_gap', 'mod_h5pactivity', $num),
];
$gapresponse = $this->response[$key] ?? null;
$gapresponse = ($casesensitive) ? $gapresponse : strtolower($gapresponse);
if ($gapresponse !== null && in_array($gapresponse, $value)) {
$state = parent::CORRECT;
} else {
$state = parent::INCORRECT;
}
$option->useranswer = $this->get_answer($state, $gapresponse);
$option->correctanswer = $this->get_answer(parent::TEXT, implode(' / ', $value));
$options[] = $option;
$num++;
}
return $options;
}
/**
* Return a label for result user options/choices
*
* Specific result types can override this method to customize
* the result options table header.
*
* @return string to use in options table
*/
protected function get_optionslabel(): string {
return get_string('result_matching', 'mod_h5pactivity');
}
}

View File

@ -0,0 +1,53 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\longfillin
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
use stdClass;
/**
* Class to display H5P long fill in result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class longfillin extends result {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = parent::export_for_template($output);
$data->content = reset($this->response);
$data->track = true;
return $data;
}
}

View File

@ -0,0 +1,126 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\matching
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
/**
* Class to display H5P matching result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class matching extends result {
/**
* Return the options data structure.
*
* @return array of options
*/
protected function export_options(): ?array {
// Suppose H5P choices have only list of valid answers.
$correctpattern = reset($this->correctpattern);
$additionals = $this->additionals;
// Get sources (options).
if (isset($additionals->source)) {
$options = $this->get_descriptions($additionals->source);
} else {
$options = [];
}
// Get targets.
if (isset($additionals->target)) {
$targets = $this->get_descriptions($additionals->target);
} else {
$targets = [];
}
// Correct answers.
foreach ($correctpattern as $pattern) {
if (!is_array($pattern) || count($pattern) != 2) {
continue;
}
// One pattern must be from options and the other from targets.
if (isset($options[$pattern[0]]) && isset($targets[$pattern[1]])) {
$option = $options[$pattern[0]];
$target = $targets[$pattern[1]];
} else if (isset($targets[$pattern[0]]) && isset($options[$pattern[1]])) {
$option = $options[$pattern[1]];
$target = $targets[$pattern[0]];
} else {
$option = null;
}
if ($option) {
$option->correctanswer = $this->get_answer(parent::TEXT, $target->description);
$option->correctanswerid = $target->id;
}
}
// User responses.
foreach ($this->response as $response) {
if (!is_array($response) || count($response) != 2) {
continue;
}
// One repsonse must be from options and the other from targets.
if (isset($options[$response[0]]) && isset($targets[$response[1]])) {
$option = $options[$response[0]];
$target = $targets[$response[1]];
$answer = $response[1];
} else if (isset($targets[$response[0]]) && isset($options[$response[1]])) {
$option = $options[$response[1]];
$target = $targets[$response[0]];
$answer = $response[0];
} else {
$option = null;
}
if ($option) {
if (isset($option->correctanswerid) && $option->correctanswerid == $answer) {
$state = parent::CORRECT;
} else {
$state = parent::INCORRECT;
}
$option->useranswer = $this->get_answer($state, $target->description);
}
}
return $options;
}
/**
* Return a label for result user options/choices
*
* Specific result types can override this method to customize
* the result options table header.
*
* @return string to use in options table
*/
protected function get_optionslabel(): string {
return get_string('result_matching', 'mod_h5pactivity');
}
}

View File

@ -0,0 +1,54 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\other
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
use stdClass;
/**
* Class to display H5P other result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class other extends result {
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$data = parent::export_for_template($output);
if (empty($data->description)) {
$data->description = get_string('result_other', 'mod_h5pactivity');
}
return $data;
}
}

View File

@ -0,0 +1,101 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\sequencing
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
/**
* Class to display H5P sequencing result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sequencing extends result {
/**
* Return the options data structure.
*
* @return array of options
*/
protected function export_options(): ?array {
$correctpattern = reset($this->correctpattern);
$additionals = $this->additionals;
$response = $this->response;
if (isset($additionals->choices)) {
$choices = $this->get_descriptions($additionals->choices);
} else {
$choices = [];
}
$options = [];
$num = 1;
foreach ($correctpattern as $key => $pattern) {
if (!isset($choices[$pattern])) {
continue;
}
$option = (object)[
'id' => true,
'description' => get_string('result_sequencing_position', 'mod_h5pactivity', $num),
'correctanswer' => $this->get_answer(parent::TEXT, $choices[$pattern]->description),
'correctanswerid' => 'item_'.$key,
];
if (isset($response[$key])) {
$answerstate = ($response[$key] == $option->correctanswerid) ? parent::PASS : parent::FAIL;
} else {
$answerstate = parent::FAIL;
}
$option->useranswer = $this->get_answer($answerstate);
$options[$key] = $option;
$num ++;
}
return $options;
}
/**
* Return a label for result user options/choices.
*
* @return string to use in options table
*/
protected function get_optionslabel(): string {
return get_string('result_sequencing_choice', 'mod_h5pactivity');
}
/**
* Return a label for result user correct answer.
*
* @return string to use in options table
*/
protected function get_correctlabel(): string {
return get_string('result_sequencing_answer', 'mod_h5pactivity');
}
}

View File

@ -0,0 +1,76 @@
<?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/>.
/**
* Contains class mod_h5pactivity\output\result\truefalse
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\output\result;
defined('MOODLE_INTERNAL') || die();
use mod_h5pactivity\output\result;
use renderer_base;
/**
* Class to display H5P choice result.
*
* @copyright 2020 Ferran Recio
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class truefalse extends result {
/**
* Return the options data structure.
*
* @return array of options
*/
protected function export_options(): ?array {
// This interaction type have only one entry which is the correct option.
$correctpattern = reset($this->correctpattern);
$correctpattern = filter_var(reset($correctpattern), FILTER_VALIDATE_BOOLEAN);
$response = filter_var(reset($this->response), FILTER_VALIDATE_BOOLEAN);
$options = [
(object)[
'id' => true,
'description' => get_string('true', 'mod_h5pactivity'),
],
(object)[
'id' => false,
'description' => get_string('false', 'mod_h5pactivity'),
],
];
foreach ($options as $value) {
$correctstate = ($value->id == $correctpattern) ? parent::CHECKED : parent::UNCHECKED;
if ($value->id == $response) {
$answerstate = ($correctstate == parent::CHECKED) ? parent::PASS : parent::FAIL;
$value->useranswer = $this->get_answer($answerstate);
}
$value->correctanswer = $this->get_answer($correctstate);
}
return $options;
}
}

View File

@ -56,4 +56,15 @@ $capabilities = [
'student' => CAP_ALLOW
],
],
'mod/h5pactivity:reviewattempts' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/grade:manage'
),
];

View File

@ -17,6 +17,7 @@
<FIELD NAME="displayoptions" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="H5P Button display options"/>
<FIELD NAME="enabletracking" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Enable xAPI tracking"/>
<FIELD NAME="grademethod" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Which H5P attempt is used for grading"/>
<FIELD NAME="reviewmode" TYPE="int" LENGTH="4" NOTNULL="false" DEFAULT="1" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>

View File

@ -225,5 +225,20 @@ function xmldb_h5pactivity_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2020041401, 'h5pactivity');
}
if ($oldversion < 2020042202) {
// Define field reviewmode to be added to h5pactivity.
$table = new xmldb_table('h5pactivity');
$field = new xmldb_field('reviewmode', XMLDB_TYPE_INTEGER, '4', null, null, null, '1', 'grademethod');
// Conditionally launch add field reviewmode.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// H5pactivity savepoint reached.
upgrade_mod_savepoint(true, 2020042202, 'h5pactivity');
}
return true;
}

View File

@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_h5pactivity\local\manager;
require(__DIR__.'/../../config.php');
// Course module ID.
@ -34,9 +36,27 @@ $itemnumber = optional_param('itemnumber', 0, PARAM_INT);
// Graded user ID (optional).
$userid = optional_param('userid', 0, PARAM_INT);
require_login();
list ($course, $cm) = get_course_and_cm_from_cmid($id, 'h5pactivity');
// TODO: in the near future this file will redirect to a specific user H5P attempts page.
require_login($course, true, $cm);
// In the simplest case just redirect to the view page.
redirect('view.php?id='.$id);
$manager = manager::create_from_coursemodule($cm);
if (!$manager->can_view_all_attempts() && !$manager->can_view_own_attempts()) {
redirect(new moodle_url('/mod/h5pactivity/view.php', ['id' => $id]));
}
$moduleinstance = $manager->get_instance();
$params = [
'a' => $moduleinstance->id,
'userid' => $userid,
];
$scores = $manager->get_users_scaled_score($userid);
$score = $scores[$userid] ?? null;
if (!empty($score->attemptid)) {
$params['attemptid'] = $score->attemptid;
}
redirect(new moodle_url('/mod/h5pactivity/report.php', $params));

View File

@ -25,14 +25,42 @@
defined('MOODLE_INTERNAL') || die();
$string['all_attempts'] = 'All user attempts';
$string['answer_checked'] = 'Answer checked';
$string['answer_correct'] = 'Your answer is correct';
$string['answer_fail'] = 'Incorrect answer';
$string['answer_incorrect'] = 'Your answer is incorrect';
$string['answer_pass'] = 'Correct answer';
$string['answer_unchecked'] = 'Answer unchecked';
$string['answer_unknown'] = 'Unkown answer';
$string['answer_text'] = 'Answer text';
$string['areapackage'] = 'Package file';
$string['attempt'] = 'Attempt';
$string['attempt_average'] = 'Attempts average scored';
$string['attempt_answer'] = 'Attempt answer';
$string['attempt_completion_no'] = 'This attempt is not marked as completed';
$string['attempt_completion_yes'] = 'This attempt is completed';
$string['attempt_first'] = 'First attempt';
$string['attempt_highest'] = 'Highest score attempt';
$string['attempt_last'] = 'Last attempt';
$string['attempt_none'] = 'No attempts are used for grading';
$string['attempt_number'] = 'Attempt #{$a}';
$string['attempt_success_fail'] = 'Fail';
$string['attempt_success_pass'] = 'Pass';
$string['attempt_success_unknown'] = 'Not reported';
$string['attempts'] = 'Attempts';
$string['attempts_report'] = 'Attempts report';
$string['attempts_none'] = 'This user has no attempts to display.';
$string['choice'] = 'Choice';
$string['completion'] = 'Completion';
$string['correct_answer'] = 'Correct answer';
$string['deleteallattempts'] = 'Delete all H5P attempts';
$string['displayexport'] = 'Allow download';
$string['displayembed'] = 'Embed button';
$string['displaycopyright'] = 'Copyright button';
$string['duration'] = 'Duration';
$string['enabletracking'] = 'Enable attempt tracking';
$string['false'] = 'False';
$string['grade_grademethod'] = 'Grading method';
$string['grade_grademethod_help'] = 'When using point grading, the following methods are available for calculating the final grade:
@ -47,6 +75,7 @@ $string['grade_average_attempt'] = 'Average grade';
$string['grade_last_attempt'] = 'Last attempt';
$string['grade_first_attempt'] = 'First attempt';
$string['h5pactivity:addinstance'] = 'Add a new H5P';
$string['h5pactivity:reviewattempts'] = 'Review H5P attempts';
$string['h5pactivity:submit'] = 'Submit H5P attempts';
$string['h5pactivity:view'] = 'View H5P';
$string['h5pactivityfieldset'] = 'H5P settings';
@ -54,6 +83,7 @@ $string['h5pactivityname'] = 'H5P';
$string['h5pactivitysettings'] = 'Settings';
$string['h5pattempts'] = 'Attempt options';
$string['h5pdisplay'] = 'H5P options';
$string['maxscore'] = 'Max score';
$string['modulename'] = 'H5P';
$string['modulename_help'] = 'H5P is an abbreviation for HTML5 Package - interactive content such as presentations, videos and other multimedia, questions, quizzes, games and more. The H5P activity enables H5P to be uploaded and added to a course.
@ -61,6 +91,10 @@ Any question attempts are marked automatically, and the grade is recorded in the
$string['modulename_link'] = 'mod/h5pactivity/view';
$string['modulenameplural'] = 'H5P';
$string['myattempts'] = 'My attempts';
$string['no_compatible_track'] = 'This interaction ({$a}) does not provide tracking information or the tracking
provided is not compatible with the current activity version.';
$string['noparticipants'] = 'No participants to display';
$string['outcome'] = 'Outcome';
$string['package'] = 'Package file';
$string['package_help'] = 'The package file is a h5p file containing H5P interactive content.';
$string['page-mod-h5pactivity-x'] = 'Any H5P module page';
@ -74,6 +108,27 @@ $string['privacy:metadata:timemodified'] = 'The last time element was tracked';
$string['privacy:metadata:userid'] = 'The ID of the user who accessed the H5P activity';
$string['privacy:metadata:xapi_track'] = 'Attempt tracking information';
$string['privacy:metadata:xapi_track_results'] = 'Attempt results tracking information';
$string['report_viewed'] = 'Report viewed';
$string['result_compound'] = 'Combined partial score';
$string['result_fill-in'] = 'Fill-in text';
$string['result_fill-in_gap'] = 'Gap #{$a}';
$string['result_matching'] = 'Matching choice';
$string['result_other'] = 'Unkown interaction type';
$string['result_sequencing_choice'] = 'Positions';
$string['result_sequencing_answer'] = 'Position value';
$string['result_sequencing_position'] = '#{$a}';
$string['review_all_attempts'] = 'View all attempts ({$a} submitted)';
$string['review_mode'] = 'Review attempts';
$string['review_my_attempts'] = 'View my attempts';
$string['review_user_attempts'] = 'View user attempts ({$a})';
$string['review_none'] = 'Participants cannot review their own attempts';
$string['review_on_completion'] = 'Participants can review their own attempts';
$string['score'] = 'Score';
$string['score_out_of'] = '{$a->rawscore} out of {$a->maxscore}';
$string['startdate'] = 'Start date';
$string['statement_received'] = 'xAPI statement received';
$string['totalscore'] = 'Total score';
$string['tracking_messages'] = 'Some H5P provide attempt tracking data for advanced reporting such as number of attempts, responses and grades. Note: Some H5P don\'t provide attempt tracking data. In such cases, the following settings will have no effect.';
$string['true'] = 'True';
$string['view'] = 'View';
$string['view_report'] = 'View report';

View File

@ -104,6 +104,11 @@ class mod_h5pactivity_mod_form extends moodleform_mod {
$mform->disabledIf('grademethod', 'grade[modgrade_type]', 'neq', 'point');
$mform->addHelpButton('grademethod', 'grade_grademethod', 'mod_h5pactivity');
$options = manager::get_review_modes();
$mform->addElement('select', 'reviewmode', get_string('review_mode', 'mod_h5pactivity'), $options);
$mform->setType('reviewmode', PARAM_INT);
$mform->hideIf('reviewmode', 'enabletracking', 'notchecked');
// Add standard elements.
$this->standard_coursemodule_elements();

130
mod/h5pactivity/report.php Normal file
View File

@ -0,0 +1,130 @@
<?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/>.
/**
* Prints an instance of mod_h5pactivity.
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_h5pactivity\local\manager;
use mod_h5pactivity\event\report_viewed;
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/lib.php');
$userid = optional_param('userid', null, PARAM_INT);
$attemptid = optional_param('attemptid', null, PARAM_INT);
// Attempts have only the instance id information but system events
// have only cmid. To prevent unnecesary db queries, this page accept both.
$id = optional_param('id', null, PARAM_INT);
if (empty($id)) {
$a = required_param('a', PARAM_INT);
list ($course, $cm) = get_course_and_cm_from_instance($a, 'h5pactivity');
} else {
list ($course, $cm) = get_course_and_cm_from_cmid($id, 'h5pactivity');
}
require_login($course, true, $cm);
$manager = manager::create_from_coursemodule($cm);
$report = $manager->get_report($userid, $attemptid);
if (!$report) {
print_error('permissiondenied');
}
$user = $report->get_user();
$attempt = $report->get_attempt();
$moduleinstance = $manager->get_instance();
$context = $manager->get_context();
$params = ['a' => $cm->instance];
if ($user) {
$params['userid'] = $user->id;
}
if ($attempt) {
$params['attemptid'] = $attempt->get_id();
}
$PAGE->set_url('/mod/h5pactivity/report.php', $params);
// Trigger event.
$other = [
'instanceid' => $params['a'],
'userid' => $params['userid'] ?? null,
'attemptid' => $params['attemptid'] ?? null,
];
$event = report_viewed::create([
'objectid' => $moduleinstance->id,
'context' => $context,
'other' => $other,
]);
$event->add_record_snapshot('course', $course);
$event->add_record_snapshot('h5pactivity', $moduleinstance);
$event->trigger();
$shortname = format_string($course->shortname, true, ['context' => $context]);
$pagetitle = strip_tags($shortname.': '.format_string($moduleinstance->name));
$PAGE->set_title(format_string($pagetitle));
$navbar = [];
if ($manager->can_view_all_attempts()) {
// Report navbar have 3 levels for teachers:
// - Participants list
// - Participant attempts list
// - Individual attempt details.
$nav = [get_string('attempts', 'mod_h5pactivity'), null];
if ($user) {
$nav[1] = new moodle_url('/mod/h5pactivity/report.php', ['a' => $cm->instance]);
$navbar[] = $nav;
$nav = [fullname($user), null];
if ($attempt) {
$nav[1] = new moodle_url('/mod/h5pactivity/report.php', ['a' => $cm->instance, 'userid' => $user->id]);
}
}
$navbar[] = $nav;
} else {
// Report navbar have 2 levels for a regular participant:
// - My attempts
// - Individual attempt details.
$nav = [get_string('myattempts', 'mod_h5pactivity'), null];
if ($attempt) {
$nav[1] = new moodle_url('/mod/h5pactivity/report.php', ['a' => $cm->instance]);
}
$navbar[] = $nav;
}
if ($attempt) {
$navbar[] = [get_string('attempt_number', 'mod_h5pactivity', $attempt->get_attempt()), null];
}
foreach ($navbar as $nav) {
$PAGE->navbar->add($nav[0], $nav[1]);
}
$PAGE->set_heading(format_string($course->fullname));
$PAGE->set_context($context);
echo $OUTPUT->header();
echo $report->print();
echo $OUTPUT->footer();

View File

@ -0,0 +1,96 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/attempt
This template will render an attempt basic information inside a H5P activity results report.
Variables required for this template:
* timemodified - Full attempts list
* attempt - Full attempts list
* rawscore - Full attempts list
* maxscore - Full attempts list
* duration - Full attempts list
* completionicon - Full attempts list
* successicon - Full attempts list
* reporturl - Full attempts list
Variables optional for this template:
* user - optional user record
* scored - The scored attempt
Example context (json):
{
"id": 11,
"h5pactivityid": 1,
"userid": 3,
"timecreated": 1587655101,
"timemodified": 1587655101,
"attempt": 2,
"rawscore": 6,
"maxscore": 6,
"duration": "2 minutes 10 seconds",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \" title=\"Attempt completed\" aria-label=\"Attempt completed\"><\/i>",
"success": 1,
"successicon": "<i class=\"icon fa fa-check-circle fa-fw \" title=\"Attempt completed successfully\" aria-label=\"Attempt completed successfully\"><\/i>",
"scaled": 1,
"reporturl": {},
"score": "6 out of 6",
"user": {
"id": "3",
"idnumber": "",
"firstname": "John",
"lastname": "Doe",
"email": "s1@example.com",
"institution": "Moodle HQ",
"department": "Business",
"address": "",
"city": "Barcelona",
"country": "Spain",
"lang": "en",
"picture": "[userpic]",
"fullname": "User Fullname"
}
}
}}
<h2 class="px-0 px-md-3">
{{#user}}
{{{picture}}}
{{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}: {{fullname}}
{{/user}}
{{^user}}
{{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}
{{/user}}
</h2>
<div class="container-fluid mt-4 mb-5 px-0 px-md-3">
<dl class="row">
<dt class="col-12">{{#str}} startdate, mod_h5pactivity {{/str}}</dt>
<dd class="col-12">{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</dd>
<dt class="col-12">{{#str}} completion, mod_h5pactivity {{/str}}</dt>
<dd class="col-12">{{{completiontext}}}</dd>
<dt class="col-12 text-truncate">{{#str}} duration, mod_h5pactivity {{/str}}</dt>
<dd class="col-12">{{duration}}</dd>
<dt class="col-12">{{#str}} outcome, mod_h5pactivity {{/str}}</dt>
<dd class="col-12">{{{successtext}}}</dd>
{{#score}}
<dt class="col-12">{{#str}} totalscore, mod_h5pactivity {{/str}}</dt>
<dd class="col-12">{{score}}</dd>
{{/score}}
</dl>
</div>

View File

@ -0,0 +1,145 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/attempts
This template will render the report link inside a H5P activity.
Variables required for this template:
* attempts - Full attempts list with:
* timemodified - Attempt timemodified
* attempt - Attempt number
* rawscore - Attempt rawscore
* maxscore - Attempt maxscore
* duration - Attempt duration
* completionicon - Completion icon HTML
* successicon - Success icon HTML
* reporturl - Attempt report URL
Variables optional for this template:
* title - optional selected attempt name
* scored - The scored attempt
Example context (json):
{
"attempts": [
{
"id": 7,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654916,
"timemodified": 1587654916,
"attempt": 1,
"rawscore": 6,
"maxscore": 6,
"duration": "14 seconds",
"durationcompact": "14''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 1,
"successicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"scaled": 1,
"reporturl": {},
"score": "6 out of 6"
},
{
"id": 8,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654927,
"timemodified": 1587654927,
"attempt": 2,
"rawscore": 1,
"maxscore": 6,
"duration": "25 seconds",
"durationcompact": "25''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 0,
"successicon": "<i class=\"icon fa fa-circle-o fa-fw \"><\/i>",
"scaled": 0,
"reporturl": {},
"score": "1 out of 6"
},
{
"id": 9,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654942,
"timemodified": 1587654942,
"attempt": 3,
"rawscore": 3,
"maxscore": 6,
"duration": "40 seconds",
"durationcompact": "40''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 0,
"successicon": "<i class=\"icon fa fa-circle-o fa-fw \"><\/i>",
"scaled": 0,
"reporturl": {},
"score": "3 out of 6"
}
]
}
}}
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">{{#str}} date {{/str}}</th>
<th scope="col">{{#str}} score, mod_h5pactivity {{/str}}</th>
<th scope="col" class="d-none d-md-table-cell">{{#str}} maxscore, mod_h5pactivity {{/str}}</th>
<th scope="col" class="d-none d-sm-table-cell">{{#str}} duration, mod_h5pactivity {{/str}}</th>
<th scope="col" class="d-none d-sm-table-cell" class="text-center">{{#str}} completion, mod_h5pactivity {{/str}}</th>
<th scope="col" class="text-center">{{#str}} success {{/str}}</th>
<th scope="col">{{#str}} report {{/str}}</th>
</tr>
</thead>
<tbody>
{{#attempts}}
<tr>
<th scope="row">{{attempt}}</th>
<td>
<span class="d-none d-lg-inline">
{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
</span>
<span class="d-inline d-lg-none">
{{#userdate}} {{timemodified}}, {{#str}} strftimedatetimeshort, core_langconfig {{/str}} {{/userdate}}
</span>
</td>
<td>{{rawscore}}<span class="d-inline d-md-none"> / {{maxscore}}</span></td>
<td class="d-none d-md-table-cell">{{maxscore}}</td>
<td class="d-none d-sm-table-cell">
<span class="d-none d-md-inline">{{duration}}</span>
<span class="d-inline d-md-none">{{durationcompact}}</span>
</td>
<td class="text-center d-none d-sm-table-cell">{{{completionicon}}}</td>
<td class="text-center">{{{successicon}}}</td>
<td>
<span class="d-none d-md-inline">
<a href="{{{reporturl}}}">{{#str}} view_report, mod_h5pactivity {{/str}}</a>
</span>
<span class="d-inline d-md-none">
<a href="{{{reporturl}}}">{{#str}} view {{/str}}</a>
</span>
</td>
</tr>
{{/attempts}}
</tbody>
</table>

View File

@ -0,0 +1,156 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/reportattempts
This template will render the report link inside a H5P activity.
Variables required for this template:
* attempts - Full attempts list
Variables optional for this template:
* title - optional selected attempt name
* scored - The scored attempt
* attemptscount - The current number of attempts
Example context (json):
{
"attempts": [
{
"id": 7,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654916,
"timemodified": 1587654916,
"attempt": 1,
"rawscore": 6,
"maxscore": 6,
"duration": "14 seconds",
"durationcompact": "14''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 1,
"successicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"scaled": 1,
"reporturl": {},
"score": "6 out of 6"
},
{
"id": 8,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654927,
"timemodified": 1587654927,
"attempt": 2,
"rawscore": 1,
"maxscore": 6,
"duration": "25 seconds",
"durationcompact": "25''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 0,
"successicon": "<i class=\"icon fa fa-circle-o fa-fw \"><\/i>",
"scaled": 0,
"reporturl": {},
"score": "1 out of 6"
},
{
"id": 9,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654942,
"timemodified": 1587654942,
"attempt": 3,
"rawscore": 3,
"maxscore": 6,
"duration": "40 seconds",
"durationcompact": "40''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 0,
"successicon": "<i class=\"icon fa fa-circle-o fa-fw \"><\/i>",
"scaled": 0,
"reporturl": {},
"score": "3 out of 6"
}
],
"user": {
"id": "5",
"auth": "manual",
"idnumber": "",
"firstname": "Miguel",
"lastname": "Alonso",
"email": "s3@example.com",
"institution": "Moodle HQ",
"department": "Development",
"address": "",
"city": "Barcelona",
"country": "ES",
"lang": "en",
"picture": "[USERPIC]",
"fullname": "Miguel Alonso"
},
"scored": {
"title": "Highest score attempt",
"attempts": [
{
"id": 7,
"h5pactivityid": 1,
"userid": 5,
"timecreated": 1587654916,
"timemodified": 1587654916,
"attempt": 1,
"rawscore": 6,
"maxscore": 6,
"duration": "14 seconds",
"durationcompact": "14''",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 1,
"successicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"scaled": 1,
"reporturl": {},
"score": "6 out of 6"
}
]
}
}
}}
{{#user}}
<h2 class="mb-4">
{{{picture}}}
{{#title}}{{title}}{{/title}}
{{^title}}{{#str}} attempts, mod_h5pactivity {{/str}}: {{fullname}}{{/title}}
</h2>
{{/user}}
{{#scored}}
<h3>{{title}}</h3>
{{>mod_h5pactivity/attempts}}
{{/scored}}
<h3>{{#str}}all_attempts, mod_h5pactivity{{/str}}</h3>
{{^attemptscount}}
<div class="alert alert-warning">
{{#str}} attempts_none, mod_h5pactivity {{/str}}
</div>
{{/attemptscount}}
{{#attemptscount}}
{{>mod_h5pactivity/attempts}}
{{/attemptscount}}

View File

@ -0,0 +1,35 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/reportlink
This template will render the report link inside a H5P activity.
Variables required for this template:
* url - The URL to the report page
* message - The link message
Example context (json):
{
"url": "#",
"message": "View attempts list (3 submitted)"
}
}}
<div class="reportlink mb-3">
<a href="{{{url}}}">{{message}}</a>
</div>

View File

@ -0,0 +1,85 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/reportresults
This template will render the report results inside a H5P activity.
Variables required for this template:
* attempts - Full attempts list
Variables optional for this template:
* title - optional selected attempt name
* scored - The scored attempt
Example context (json):
{
"attempt": {
"id": 11,
"h5pactivityid": 1,
"userid": 3,
"timecreated": 1587655101,
"timemodified": 1587655101,
"attempt": 2,
"rawscore": 6,
"maxscore": 6,
"duration": "2 minutes 10 seconds",
"completion": 1,
"completionicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"success": 1,
"successicon": "<i class=\"icon fa fa-check-circle fa-fw \"><\/i>",
"scaled": 1,
"reporturl": {},
"score": "6 out of 6",
"user": {
"id": "3",
"username": "s1",
"idnumber": "",
"firstname": "John",
"lastname": "Doe",
"email": "s1@example.com",
"phone2": "",
"institution": "Moodle HQ",
"department": "Business",
"picture": "[USERPIC]",
"fullname": "John Doe"
}
},
"results": [
{
"timecreated": "1587655101",
"interactiontype": "other",
"description": "Example of some results",
"rawscore": "6",
"maxscore": "6",
"duration": "130",
"completion": "1",
"success": "1",
"optionslabel": "Example result",
"score": "6 out of 6",
"content": "<p>See mod_h5pactivity result template for more examples.</p>"
}
]
}
}}
{{#attempt}}
{{>mod_h5pactivity/attempt}}
{{/attempt}}
{{#results}}
{{>mod_h5pactivity/result}}
{{/results}}

View File

@ -0,0 +1,119 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/result
This template will render a result choices inside a H5P activity results report.
Variables required for this template:
* description - Result description text
* score - Score string (for example: 2 out of 4)
Variables optional for this template:
* hasoptions - If an option table must be present
* optionslabel - The right label for available options on this result type
* options - An array of mod_h5pactivity/result/options compatible array
* content - Extra content in HTML
* track - Indicate if the result has displayable tracking
Example context (json):
{
"timecreated": "1587655101",
"interactiontype": "choice",
"description": "Example of some results",
"rawscore": "4",
"maxscore": "4",
"duration": "130",
"completion": "1",
"success": "1",
"hasoptions": true,
"optionslabel": "Choice",
"options": [
{
"description": "Choice 1 text",
"id": 0,
"useranswer": {
"answer": "Correct answer",
"pass": true
},
"correctanswer": {
"answer": "Answer checked",
"checked": true
}
},
{
"description": "Choice 2 text",
"id": 1,
"useranswer": {
"answer": "Wrong answer",
"fail": true
},
"correctanswer": {
"answer": "Answer checked",
"unchecked": true
}
},
{
"description": "Choice 3 text",
"id": 2,
"useranswer": {
"answer": "This was the correct text",
"correct": true
},
"correctanswer": {
"answer": "This was the correct text",
"text": true
}
},
{
"description": "Choice 4 text",
"id": 3,
"correctanswer": {
"answer": "Some text",
"text": true
}
},
{
"description": "Choice 4 text",
"id": 3,
"useranswer": {
"answer": "Some wrong text",
"incorrect": true
}
}
],
"score": "4 out of 4",
"content": "<p>This is an optional extra content in <b>HTML</b>.</p>",
"track": true
}
}}
<div class="container-fluid w-100 my-0 p-0">
<div class="row w-100 py-3 px-1 m-0 p-md-3">
{{>mod_h5pactivity/result/header}}
{{{content}}}
{{#hasoptions}}
{{>mod_h5pactivity/result/options}}
{{/hasoptions}}
{{^track}}
<div class="alert alert-warning w-100" role="alert">
{{#str}}no_compatible_track, mod_h5pactivity, {{interactiontype}}{{/str}}
</div>
{{/track}}
</div>
</div>

View File

@ -0,0 +1,66 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/result/answer
This template render all kind of answers/choice in a results table.
Variables required for this template:
* answer - The answer/choice text
Variables optional for this template:
* pass - The answer pass
* fail - The answer failed
* correct - The answer is correct
* incorrect - The answer is not correct
* text - The choice is just text
* checked - The choice must be checked
* unchecked - The choice must be unchecked
Example context (json):
{
"answer": "This was the ansewer,",
"correct": true,
"incorrect": true,
"text": true,
"checked": true,
"unchecked": true,
"pass": true,
"fail": true
}
}}
{{#correct}}
{{#pix}}i/valid, moodle, {{#str}}answer_correct, mod_h5pactivity{{/str}}{{/pix}}{{answer}}
{{/correct}}
{{#incorrect}}
{{#pix}}i/invalid, moodle, {{#str}}answer_incorrect, mod_h5pactivity{{/str}}{{/pix}}{{answer}}
{{/incorrect}}
{{#text}}
{{answer}}
{{/text}}
{{#checked}}
{{#pix}}i/checkedcircle, moodle, {{#str}}answer_checked, mod_h5pactivity{{/str}}{{/pix}}
{{/checked}}
{{#unchecked}}
{{/unchecked}}
{{#pass}}
{{#pix}}i/valid, moodle, {{#str}}answer_pass, mod_h5pactivity{{/str}}{{/pix}}
{{/pass}}
{{#fail}}
{{#pix}}i/invalid, moodle, {{#str}}answer_fail, mod_h5pactivity{{/str}}{{/pix}}
{{/fail}}

View File

@ -0,0 +1,44 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/result/header
This template will render a results header inside mod_h5pactivity results report.
Variables required for this template:
* description - Result description
Variables optional for this template:
* success - If the result is marked as success
* score - The result score string
Example context (json):
{
"timecreated": "1587655101",
"interactiontype": "choice",
"description": "Example of some results\n",
"rawscore": "4",
"maxscore": "4",
"duration": "130",
"success": "1",
"score": "4 out of 4"
}
}}
<h3 class="w-100">
{{description}}
</h3>

View File

@ -0,0 +1,118 @@
{{!
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/>.
}}
{{!
@template mod_h5pactivity/result/options
This template will render a choices table inside a H5P activity results report.
Variables required for this template:
* options - An array of options
* optionslabel - The right label for options column
* correctlabel - The right label for correct answer column
* answerlabel - The right label for the attempt answer column
Example context (json):
{
"optionslabel": "Choice",
"correctlabel": "Correct answer",
"answerlabel": "Attempt answer",
"options": [
{
"description": "Choice 1 text",
"id": 0,
"useranswer": {
"answer": "Correct answer",
"pass": true
},
"correctanswer": {
"answer": "Answer checked",
"checked": true
}
},
{
"description": "Choice 2 text",
"id": 1,
"useranswer": {
"answer": "Wrong answer",
"fail": true
},
"correctanswer": {
"answer": "Answer checked",
"unchecked": true
}
},
{
"description": "Choice 3 text",
"id": 2,
"useranswer": {
"answer": "This was the correct text",
"correct": true
},
"correctanswer": {
"answer": "This was the correct text",
"text": true
}
},
{
"description": "Choice 4 text",
"id": 3,
"correctanswer": {
"answer": "Some text",
"text": true
}
},
{
"description": "Choice 4 text",
"id": 3,
"useranswer": {
"answer": "Some wrong text",
"incorrect": true
}
}
]
}
}}
<table class="table table-striped">
<thead>
<tr>
<th scope="col">{{optionslabel}}</th>
<th scope="col">{{correctlabel}}</th>
<th scope="col">{{answerlabel}}</th>
</tr>
</thead>
<tbody>
{{#options}}
<tr>
<td>{{description}}</td>
<td>{{#correctanswer}}{{>mod_h5pactivity/result/answer}}{{/correctanswer}}</td>
<td>{{#useranswer}}{{>mod_h5pactivity/result/answer}}{{/useranswer}}</td>
</tr>
{{/options}}
{{#score}}
<tr class="table-light">
<td colspan="2" class="d-none d-sm-table-cell border-top-3 border-dark"></td>
<td class="d-none d-sm-table-cell border-top border-dark">
<strong>{{#str}}score, mod_h5pactivity{{/str}}: {{score}}</strong>
</td>
<td class="d-table-cell d-sm-none text-right border-top border-dark" colspan="3">
<strong>{{#str}}score, mod_h5pactivity{{/str}}: {{score}}</strong>
</td>
</tr>
{{/score}}
</tbody>
</table>

View File

@ -0,0 +1,147 @@
<?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/>.
/**
* Events test.
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\event;
use advanced_testcase;
use moodle_url;
use coding_exception;
use context_module;
defined('MOODLE_INTERNAL') || die();
/**
* H5P activity events test cases.
*
* @package mod_h5pactivity
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_viewed_testcase extends advanced_testcase {
/**
* Test report_viewed event.
*
* @dataProvider report_viewed_data
* @param bool $usea if a (instanceid) will be used in the event
* @param bool $useattemptid if attemptid will be used in the event
* @param bool $useuserid if user id will be used in the event
* @param bool $exception if exception is expected
*/
public function test_report_viewed(bool $usea, bool $useattemptid, bool $useuserid, bool $exception) {
$this->resetAfterTest();
// Must be a non-guest user to create h5pactivities.
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course->id]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity');
// Create a user with 1 attempt.
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
$params = ['cmid' => $activity->cmid, 'userid' => $user->id];
$attempt = $generator->create_content($activity, $params);
$other = [];
$urlparams = [];
if ($usea) {
$other['instanceid'] = $activity->id;
$urlparams['a'] = $activity->id;
}
if ($useuserid) {
$other['userid'] = $user->id;
$urlparams['userid'] = $user->id;
}
if ($useattemptid) {
$other['attemptid'] = $attempt->id;
$urlparams['attemptid'] = $attempt->id;
}
$params = [
'context' => context_module::instance($activity->cmid),
'objectid' => $activity->id,
'other' => $other,
];
if ($exception) {
$this->expectException(coding_exception::class);
}
$event = report_viewed::create($params);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_h5pactivity\event\report_viewed', $event);
$this->assertEquals(context_module::instance($activity->cmid), $event->get_context());
$this->assertEquals($activity->id, $event->objectid);
$eventurl = $event->get_url();
$url = new moodle_url('/mod/h5pactivity/report.php', $urlparams);
$this->assertTrue($eventurl->compare($url));
}
/**
* Data provider for data request creation tests.
*
* @return array
*/
public function report_viewed_data(): array {
return [
// Exception cases.
'Event withour other data (exception)' => [
false, false, false, true
],
'Event with only userid (exception)' => [
false, false, true, true
],
'Event with only attemptid (exception)' => [
false, true, false, true
],
'Event with attemptid and userid (exception)' => [
false, true, true, true
],
// Correct cases.
'Event with instance id' => [
true, false, false, false
],
'Event with instance id and attempt id' => [
true, false, true, false
],
'Event with instance id and userid' => [
true, true, false, false
],
'Event with instance id, user id and attemptid' => [
true, true, true, false
],
];
}
}

View File

@ -25,7 +25,6 @@
namespace mod_h5pactivity\event;
use advanced_testcase;
use context_course;
use context_module;
/**
@ -38,7 +37,7 @@ use context_module;
class statement_received_testcase extends advanced_testcase {
/**
* Test course_module_viewed event.
* Test statement_recieved event.
*/
public function test_statement_received() {
global $USER;

View File

@ -71,6 +71,9 @@ class mod_h5pactivity_generator extends testing_module_generator {
if (!isset($record->grademethod)) {
$record->grademethod = manager::GRADEHIGHESTATTEMPT;
}
if (!isset($record->reviewmode)) {
$record->reviewmode = manager::REVIEWCOMPLETION;
}
// The 'packagefile' value corresponds to the draft file area ID. If not specified, create from packagefilepath.
if (empty($record->packagefile)) {

View File

@ -63,6 +63,7 @@ class mod_h5pactivity_generator_testcase extends advanced_testcase {
$this->assertEquals(6, $activity->displayoptions);
$this->assertEquals(0, $activity->enabletracking);
$this->assertEquals(manager::GRADELASTATTEMPT, $activity->grademethod);
$this->assertEquals(manager::REVIEWCOMPLETION, $activity->reviewmode);
$this->assertEquals(2, count($records));
$this->assertEquals('Another h5pactivity', $records[$activity->id]->name);

View File

@ -276,9 +276,9 @@ class attempt_testcase extends \advanced_testcase {
attempt::delete_all_attempts($cm, $user);
// Check data.
for ($i = 0; $i < 4; $i++) {
$count = $attempts[$i]->count_results();
$this->assertEquals($results[$i], $count);
for ($assert = 0; $assert < 4; $assert++) {
$count = $attempts[$assert]->count_results();
$this->assertEquals($results[$assert], $count);
}
$count = $DB->count_records('h5pactivity_attempts');
$this->assertEquals($results[4], $count);

View File

@ -264,6 +264,80 @@ class manager_testcase extends \advanced_testcase {
$this->assertNotEmpty($methods[manager::GRADEMANUAL]);
}
/**
* Test static get_selected_attempt.
*
* @dataProvider get_selected_attempt_data
* @param int $enabletracking if tracking is enabled
* @param int $gradingmethod new grading method
* @param int $result the expected result
*/
public function test_get_selected_attempt(int $enabletracking, int $gradingmethod, int $result) {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => $enabletracking, 'grademethod' => $gradingmethod]);
$manager = manager::create_from_instance($activity);
$selected = $manager->get_selected_attempt();
$this->assertEquals($result, $selected[0]);
$this->assertNotEmpty($selected[1]);
}
/**
* Data provider for get_users_scaled_score.
*
* @return array
*/
public function get_selected_attempt_data(): array {
return [
'Tracking with max attempt method' => [
1, manager::GRADEHIGHESTATTEMPT, manager::GRADEHIGHESTATTEMPT
],
'Tracking with average attempt method' => [
1, manager::GRADEAVERAGEATTEMPT, manager::GRADEAVERAGEATTEMPT
],
'Tracking with last attempt method' => [
1, manager::GRADELASTATTEMPT, manager::GRADELASTATTEMPT
],
'Tracking with first attempt method' => [
1, manager::GRADEFIRSTATTEMPT, manager::GRADEFIRSTATTEMPT
],
'Tracking with manual attempt grading' => [
1, manager::GRADEMANUAL, manager::GRADEMANUAL
],
'No tracking with max attempt method' => [
0, manager::GRADEHIGHESTATTEMPT, manager::GRADEMANUAL
],
'No tracking with average attempt method' => [
0, manager::GRADEAVERAGEATTEMPT, manager::GRADEMANUAL
],
'No tracking with last attempt method' => [
0, manager::GRADELASTATTEMPT, manager::GRADEMANUAL
],
'No tracking with first attempt method' => [
0, manager::GRADEFIRSTATTEMPT, manager::GRADEMANUAL
],
'No tracking with manual attempt grading' => [
0, manager::GRADEMANUAL, manager::GRADEMANUAL
],
];
}
/**
* Test static get_review_modes.
*/
public function test_get_review_modes() {
$methods = manager::get_review_modes();
$this->assertCount(2, $methods);
$this->assertNotEmpty($methods[manager::REVIEWCOMPLETION]);
$this->assertNotEmpty($methods[manager::REVIEWNONE]);
}
/**
* Test get_grader method.
*/
@ -282,6 +356,388 @@ class manager_testcase extends \advanced_testcase {
$this->assertInstanceOf('mod_h5pactivity\local\grader', $grader);
}
/**
* Test static can_view_all_attempts.
*
* @dataProvider can_view_all_attempts_data
* @param int $enabletracking if tracking is enabled
* @param bool $usestudent if test must be done with a user role
* @param bool $useloggedin if test must be done with the loggedin user
* @param bool $result the expected result
*/
public function test_can_view_all_attempts(int $enabletracking, bool $usestudent, bool $useloggedin, bool $result) {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => $enabletracking]);
$manager = manager::create_from_instance($activity);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
$loggedin = $USER;
// We want to test what when the method is called to check a different user than $USER.
if (!$usestudent) {
$loggedin = $user;
$user = $USER;
}
if ($useloggedin) {
$this->setUser($user);
$user = null;
} else {
$this->setUser($loggedin);
}
$this->assertEquals($result, $manager->can_view_all_attempts($user));
}
/**
* Data provider for test_can_view_all_attempts.
*
* @return array
*/
public function can_view_all_attempts_data(): array {
return [
// No tracking cases.
'No tracking with admin using $USER' => [
0, false, false, false
],
'No tracking with student using $USER' => [
0, true, false, false
],
'No tracking with admin loggedin' => [
0, false, true, false
],
'No tracking with student loggedin' => [
0, true, true, false
],
// Tracking enabled cases.
'Tracking with admin using $USER' => [
1, false, false, true
],
'Tracking with student using $USER' => [
1, true, false, false
],
'Tracking with admin loggedin' => [
1, false, true, true
],
'Tracking with student loggedin' => [
1, true, true, false
],
];
}
/**
* Test static can_view_own_attempts.
*
* @dataProvider can_view_own_attempts_data
* @param int $enabletracking if tracking is enabled
* @param int $reviewmode the attempt review mode
* @param bool $useloggedin if test must be done with the loggedin user
* @param bool $hasattempts if the student have attempts
* @param bool $result the expected result
*/
public function test_can_view_own_attempts(int $enabletracking, int $reviewmode,
bool $useloggedin, bool $hasattempts, bool $result) {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
$manager = manager::create_from_instance($activity);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
if ($hasattempts) {
$this->generate_fake_attempts($activity, $user, 1);
}
if ($useloggedin) {
$this->setUser($user);
$user = null;
}
$this->assertEquals($result, $manager->can_view_own_attempts($user));
}
/**
* Data provider for test_can_view_own_attempts.
*
* @return array
*/
public function can_view_own_attempts_data(): array {
return [
// No tracking cases.
'No tracking, review none, using $USER, without attempts' => [
0, manager::REVIEWNONE, false, false, false
],
'No tracking, review enabled, using $USER, without attempts' => [
0, manager::REVIEWCOMPLETION, false, false, false
],
'No tracking, review none, loggedin, without attempts' => [
0, manager::REVIEWNONE, true, false, false
],
'No tracking, review enabled, loggedin, without attempts' => [
0, manager::REVIEWCOMPLETION, true, false, false
],
'No tracking, review none, using $USER, with attempts' => [
0, manager::REVIEWNONE, false, true, false
],
'No tracking, review enabled, using $USER, with attempts' => [
0, manager::REVIEWCOMPLETION, false, true, false
],
'No tracking, review none, loggedin, with attempts' => [
0, manager::REVIEWNONE, true, true, false
],
'No tracking, review enabled, loggedin, with attempts' => [
0, manager::REVIEWCOMPLETION, true, true, false
],
// Tracking enabled cases.
'Tracking enabled, review none, using $USER, without attempts' => [
1, manager::REVIEWNONE, false, false, false
],
'Tracking enabled, review enabled, using $USER, without attempts' => [
1, manager::REVIEWCOMPLETION, false, false, true
],
'Tracking enabled, review none, loggedin, without attempts' => [
1, manager::REVIEWNONE, true, false, false
],
'Tracking enabled, review enabled, loggedin, without attempts' => [
1, manager::REVIEWCOMPLETION, true, false, true
],
'Tracking enabled, review none, using $USER, with attempts' => [
1, manager::REVIEWNONE, false, true, false
],
'Tracking enabled, review enabled, using $USER, with attempts' => [
1, manager::REVIEWCOMPLETION, false, true, true
],
'Tracking enabled, review none, loggedin, with attempts' => [
1, manager::REVIEWNONE, true, true, false
],
'Tracking enabled, review enabled, loggedin, with attempts' => [
1, manager::REVIEWCOMPLETION, true, true, true
],
];
}
/**
* Test static count_attempts.
*/
public function test_count_attempts() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course]);
$manager = manager::create_from_instance($activity);
// User without attempts.
$user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
// User with 1 attempt.
$user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->generate_fake_attempts($activity, $user2, 1);
// User with 2 attempts.
$user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->generate_fake_attempts($activity, $user3, 1);
// Incomplete user2 and 3 has only 3 attempts completed.
$this->assertEquals(0, $manager->count_attempts($user1->id));
$this->assertEquals(3, $manager->count_attempts($user2->id));
$this->assertEquals(3, $manager->count_attempts($user3->id));
}
/**
* Test static count_attempts.
*/
public function test_count_users_attempts() {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course]);
$manager = manager::create_from_instance($activity);
// User without attempts.
$user1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
// User with 1 attempt.
$user2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->generate_fake_attempts($activity, $user2, 1);
// User with 2 attempts.
$user3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$this->generate_fake_attempts($activity, $user3, 1);
$attempts = $manager->count_users_attempts();
$this->assertArrayNotHasKey($user1->id, $attempts);
$this->assertArrayHasKey($user2->id, $attempts);
$this->assertEquals(4, $attempts[$user2->id]);
$this->assertArrayHasKey($user3->id, $attempts);
$this->assertEquals(4, $attempts[$user3->id]);
}
/**
* Test static get_report.
*
* @dataProvider get_report_data
* @param int $enabletracking if tracking is enabled
* @param int $reviewmode the attempt review mode
* @param bool $createattempts if the student have attempts
* @param string $role the user role (student or editingteacher)
* @param array $results the expected classname (or null)
*/
public function test_get_report(int $enabletracking, int $reviewmode, bool $createattempts,
string $role, array $results) {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('h5pactivity',
['course' => $course, 'enabletracking' => $enabletracking, 'reviewmode' => $reviewmode]);
$manager = manager::create_from_instance($activity);
$cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
$users = [
'editingteacher' => $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'),
'student' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
'otheruser' => $this->getDataGenerator()->create_and_enrol($course, 'student'),
];
$attempts = [];
if ($createattempts) {
$this->generate_fake_attempts($activity, $users['student'], 1);
$this->generate_fake_attempts($activity, $users['otheruser'], 2);
$attempts['student'] = attempt::last_attempt($users['student'], $cm);
$attempts['otheruser'] = attempt::last_attempt($users['otheruser'], $cm);
}
$classnamebase = 'mod_h5pactivity\\local\\report\\';
$attemptid = null;
if (isset($attempts['student'])) {
$attemptid = $attempts['student']->get_id() ?? null;
}
$userid = $users['student']->id;
// Check reports.
$this->setUser($users[$role]);
$report = $manager->get_report(null, null);
if ($results[0] === null) {
$this->assertNull($report);
} else {
$this->assertEquals($classnamebase.$results[0], get_class($report));
}
$report = $manager->get_report($userid, null);
if ($results[1] === null) {
$this->assertNull($report);
} else {
$this->assertEquals($classnamebase.$results[1], get_class($report));
}
$report = $manager->get_report($userid, $attemptid);
if ($results[2] === null) {
$this->assertNull($report);
} else {
$this->assertEquals($classnamebase.$results[2], get_class($report));
}
// Check that student cannot access another student reports.
if ($role == 'student') {
$attemptid = null;
if (isset($attempts['otheruser'])) {
$attemptid = $attempts['otheruser']->get_id() ?? null;
}
$userid = $users['otheruser']->id;
$report = $manager->get_report($userid, null);
$this->assertNull($report);
$report = $manager->get_report($userid, $attemptid);
$this->assertNull($report);
}
}
/**
* Data provider for test_get_report.
*
* @return array
*/
public function get_report_data(): array {
return [
// No tracking scenarios.
'No tracking, review none, no attempts, teacher' => [
0, manager::REVIEWNONE, false, 'editingteacher', [null, null, null]
],
'No tracking, review own, no attempts, teacher' => [
0, manager::REVIEWCOMPLETION, false, 'editingteacher', [null, null, null]
],
'No tracking, review none, no attempts, student' => [
0, manager::REVIEWNONE, false, 'student', [null, null, null]
],
'No tracking, review own, no attempts, student' => [
0, manager::REVIEWCOMPLETION, false, 'student', [null, null, null]
],
'No tracking, review none, with attempts, teacher' => [
0, manager::REVIEWNONE, true, 'editingteacher', [null, null, null]
],
'No tracking, review own, with attempts, teacher' => [
0, manager::REVIEWCOMPLETION, true, 'editingteacher', [null, null, null]
],
'No tracking, review none, with attempts, student' => [
0, manager::REVIEWNONE, true, 'student', [null, null, null]
],
'No tracking, review own, with attempts, student' => [
0, manager::REVIEWCOMPLETION, true, 'student', [null, null, null]
],
// Tracking enabled scenarios.
'Tracking enabled, review none, no attempts, teacher' => [
1, manager::REVIEWNONE, false, 'editingteacher', ['participants', 'attempts', 'attempts']
],
'Tracking enabled, review own, no attempts, teacher' => [
1, manager::REVIEWCOMPLETION, false, 'editingteacher', ['participants', 'attempts', 'attempts']
],
'Tracking enabled, review none, no attempts, student' => [
1, manager::REVIEWNONE, false, 'student', [null, null, null]
],
'Tracking enabled, review own, no attempts, student' => [
1, manager::REVIEWCOMPLETION, false, 'student', ['attempts', 'attempts', 'attempts']
],
'Tracking enabled, review none, with attempts, teacher' => [
1, manager::REVIEWNONE, true, 'editingteacher', ['participants', 'attempts', 'results']
],
'Tracking enabled, review own, with attempts, teacher' => [
1, manager::REVIEWCOMPLETION, true, 'editingteacher', ['participants', 'attempts', 'results']
],
'Tracking enabled, review none, with attempts, student' => [
1, manager::REVIEWNONE, true, 'student', [null, null, null]
],
'Tracking enabled, review own, with attempts, student' => [
1, manager::REVIEWCOMPLETION, true, 'student', ['attempts', 'attempts', 'results']
],
];
}
/**
* Insert fake attempt data into h5pactiviyt_attempts.
*

View File

@ -25,5 +25,5 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'mod_h5pactivity';
$plugin->version = 2020041401;
$plugin->version = 2020042202;
$plugin->requires = 2020013000;

View File

@ -81,6 +81,19 @@ $PAGE->set_context($context);
echo $OUTPUT->header();
echo $OUTPUT->heading(format_string($moduleinstance->name));
// Attempts review.
if ($manager->can_view_all_attempts()) {
$reviewurl = new moodle_url('report.php', ['a' => $cm->instance]);
$reviewmessage = get_string('review_all_attempts', 'mod_h5pactivity', $manager->count_attempts());
} else if ($manager->can_view_own_attempts() && $manager->count_attempts($USER->id)) {
$reviewurl = new moodle_url('report.php', ['a' => $cm->instance, 'userid' => $USER->id]);
$reviewmessage = get_string('review_my_attempts', 'mod_h5pactivity');
}
if (isset($reviewurl)) {
$widget = new mod_h5pactivity\output\reportlink($reviewurl, $reviewmessage);
echo $OUTPUT->render($widget);
}
if ($manager->is_tracking_enabled()) {
$trackcomponent = 'mod_h5pactivity';
} else {