From ebec56dac0dbf8ecadc82e9d1ec27ddb9a5087db Mon Sep 17 00:00:00 2001
From: Ferran Recio <ferran@moodle.com>
Date: Thu, 23 Apr 2020 18:06:55 +0200
Subject: [PATCH 1/3] MDL-67791 mod_h5pactivity: adding review attempts
 capability

---
 mod/h5pactivity/db/access.php           | 11 +++++++++++
 mod/h5pactivity/lang/en/h5pactivity.php |  1 +
 2 files changed, 12 insertions(+)

diff --git a/mod/h5pactivity/db/access.php b/mod/h5pactivity/db/access.php
index cef7abaea51..c0240a1d4fc 100644
--- a/mod/h5pactivity/db/access.php
+++ b/mod/h5pactivity/db/access.php
@@ -56,4 +56,15 @@ $capabilities = [
             'student' => CAP_ALLOW
         ],
     ],
+
+    'mod/h5pactivity:reviewattempts' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        ),
+        'clonepermissionsfrom' => 'moodle/grade:manage'
+    ),
 ];
diff --git a/mod/h5pactivity/lang/en/h5pactivity.php b/mod/h5pactivity/lang/en/h5pactivity.php
index c855830763d..51d84a11d14 100644
--- a/mod/h5pactivity/lang/en/h5pactivity.php
+++ b/mod/h5pactivity/lang/en/h5pactivity.php
@@ -47,6 +47,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';

From 9a4307dd7adbece56614802ffee6d9c8bb1e1c09 Mon Sep 17 00:00:00 2001
From: Ferran Recio <ferran@moodle.com>
Date: Fri, 24 Apr 2020 20:17:59 +0200
Subject: [PATCH 2/3] MDL-67791 mod_h5pactivity: adding attempts reports

---
 .../moodle2/backup_h5pactivity_stepslib.php   |   4 +-
 .../classes/event/report_viewed.php           | 131 +++++
 mod/h5pactivity/classes/local/attempt.php     |  38 ++
 mod/h5pactivity/classes/local/manager.php     | 213 +++++++-
 mod/h5pactivity/classes/local/report.php      |  58 +++
 .../classes/local/report/attempts.php         | 139 ++++++
 .../classes/local/report/participants.php     | 204 ++++++++
 .../classes/local/report/results.php          |  98 ++++
 mod/h5pactivity/classes/output/attempt.php    | 207 ++++++++
 .../classes/output/reportattempts.php         | 118 +++++
 mod/h5pactivity/classes/output/reportlink.php |  68 +++
 .../classes/output/reportresults.php          |  90 ++++
 mod/h5pactivity/classes/output/result.php     | 296 ++++++++++++
 .../classes/output/result/choice.php          | 101 ++++
 .../classes/output/result/compound.php        |  56 +++
 .../classes/output/result/fillin.php          | 139 ++++++
 .../classes/output/result/longfillin.php      |  53 ++
 .../classes/output/result/matching.php        | 126 +++++
 .../classes/output/result/other.php           |  54 +++
 .../classes/output/result/sequencing.php      | 101 ++++
 .../classes/output/result/truefalse.php       |  76 +++
 mod/h5pactivity/db/access.php                 |   2 +-
 mod/h5pactivity/db/install.xml                |   1 +
 mod/h5pactivity/db/upgrade.php                |  15 +
 mod/h5pactivity/grade.php                     |  28 +-
 mod/h5pactivity/lang/en/h5pactivity.php       |  52 ++
 mod/h5pactivity/mod_form.php                  |   5 +
 mod/h5pactivity/report.php                    | 130 +++++
 mod/h5pactivity/templates/attempt.mustache    | 104 ++++
 mod/h5pactivity/templates/attempts.mustache   | 145 ++++++
 .../templates/reportattempts.mustache         | 156 ++++++
 mod/h5pactivity/templates/reportlink.mustache |  35 ++
 .../templates/reportresults.mustache          |  85 ++++
 mod/h5pactivity/templates/result.mustache     | 119 +++++
 .../templates/result/answer.mustache          |  66 +++
 .../templates/result/header.mustache          |  55 +++
 .../templates/result/options.mustache         | 107 ++++
 .../tests/event/report_viewed_test.php        | 147 ++++++
 .../tests/event/statement_received_test.php   |   3 +-
 mod/h5pactivity/tests/generator/lib.php       |   3 +
 mod/h5pactivity/tests/generator_test.php      |   1 +
 mod/h5pactivity/tests/local/attempt_test.php  |   6 +-
 mod/h5pactivity/tests/local/manager_test.php  | 456 ++++++++++++++++++
 mod/h5pactivity/version.php                   |   2 +-
 mod/h5pactivity/view.php                      |  13 +
 45 files changed, 4092 insertions(+), 14 deletions(-)
 create mode 100644 mod/h5pactivity/classes/event/report_viewed.php
 create mode 100644 mod/h5pactivity/classes/local/report.php
 create mode 100644 mod/h5pactivity/classes/local/report/attempts.php
 create mode 100644 mod/h5pactivity/classes/local/report/participants.php
 create mode 100644 mod/h5pactivity/classes/local/report/results.php
 create mode 100644 mod/h5pactivity/classes/output/attempt.php
 create mode 100644 mod/h5pactivity/classes/output/reportattempts.php
 create mode 100644 mod/h5pactivity/classes/output/reportlink.php
 create mode 100644 mod/h5pactivity/classes/output/reportresults.php
 create mode 100644 mod/h5pactivity/classes/output/result.php
 create mode 100644 mod/h5pactivity/classes/output/result/choice.php
 create mode 100644 mod/h5pactivity/classes/output/result/compound.php
 create mode 100644 mod/h5pactivity/classes/output/result/fillin.php
 create mode 100644 mod/h5pactivity/classes/output/result/longfillin.php
 create mode 100644 mod/h5pactivity/classes/output/result/matching.php
 create mode 100644 mod/h5pactivity/classes/output/result/other.php
 create mode 100644 mod/h5pactivity/classes/output/result/sequencing.php
 create mode 100644 mod/h5pactivity/classes/output/result/truefalse.php
 create mode 100644 mod/h5pactivity/report.php
 create mode 100644 mod/h5pactivity/templates/attempt.mustache
 create mode 100644 mod/h5pactivity/templates/attempts.mustache
 create mode 100644 mod/h5pactivity/templates/reportattempts.mustache
 create mode 100644 mod/h5pactivity/templates/reportlink.mustache
 create mode 100644 mod/h5pactivity/templates/reportresults.mustache
 create mode 100644 mod/h5pactivity/templates/result.mustache
 create mode 100644 mod/h5pactivity/templates/result/answer.mustache
 create mode 100644 mod/h5pactivity/templates/result/header.mustache
 create mode 100644 mod/h5pactivity/templates/result/options.mustache
 create mode 100644 mod/h5pactivity/tests/event/report_viewed_test.php

diff --git a/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php b/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php
index 76a45227253..339bb2ce42a 100644
--- a/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php
+++ b/mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php
@@ -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');
diff --git a/mod/h5pactivity/classes/event/report_viewed.php b/mod/h5pactivity/classes/event/report_viewed.php
new file mode 100644
index 00000000000..bf3ed09f151
--- /dev/null
+++ b/mod/h5pactivity/classes/event/report_viewed.php
@@ -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;
+    }
+
+}
diff --git a/mod/h5pactivity/classes/local/attempt.php b/mod/h5pactivity/classes/local/attempt.php
index e037ebbcc01..0333f766f55 100644
--- a/mod/h5pactivity/classes/local/attempt.php
+++ b/mod/h5pactivity/classes/local/attempt.php
@@ -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.
      *
diff --git a/mod/h5pactivity/classes/local/manager.php b/mod/h5pactivity/classes/local/manager.php
index 466995a9925..4f446c67086 100644
--- a/mod/h5pactivity/classes/local/manager.php
+++ b/mod/h5pactivity/classes/local/manager.php
@@ -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;
+    }
 }
diff --git a/mod/h5pactivity/classes/local/report.php b/mod/h5pactivity/classes/local/report.php
new file mode 100644
index 00000000000..4267e49e298
--- /dev/null
+++ b/mod/h5pactivity/classes/local/report.php
@@ -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;
+}
\ No newline at end of file
diff --git a/mod/h5pactivity/classes/local/report/attempts.php b/mod/h5pactivity/classes/local/report/attempts.php
new file mode 100644
index 00000000000..eac8460a608
--- /dev/null
+++ b/mod/h5pactivity/classes/local/report/attempts.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/local/report/participants.php b/mod/h5pactivity/classes/local/report/participants.php
new file mode 100644
index 00000000000..973b422e868
--- /dev/null
+++ b/mod/h5pactivity/classes/local/report/participants.php
@@ -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 '';
+    }
+}
diff --git a/mod/h5pactivity/classes/local/report/results.php b/mod/h5pactivity/classes/local/report/results.php
new file mode 100644
index 00000000000..c5690f9c715
--- /dev/null
+++ b/mod/h5pactivity/classes/local/report/results.php
@@ -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);
+    }
+}
diff --git a/mod/h5pactivity/classes/output/attempt.php b/mod/h5pactivity/classes/output/attempt.php
new file mode 100644
index 00000000000..7c511271150
--- /dev/null
+++ b/mod/h5pactivity/classes/output/attempt.php
@@ -0,0 +1,207 @@
+<?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()),
+            'success' => $attempt->get_success(),
+            'successicon' => $this->success_icon($output, $attempt->get_success()),
+            '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
+     * @return string icon HTML
+     */
+    private function completion_icon(renderer_base $output, int $completion = null): string {
+        if ($completion === null) {
+            return '';
+        }
+        if ($completion) {
+            $alt = get_string('attempt_completion_yes', 'mod_h5pactivity');
+            return $output->pix_icon('i/completion-auto-y', $alt);
+        }
+        $alt = get_string('attempt_completion_no', 'mod_h5pactivity');
+        return $output->pix_icon('i/completion-auto-n', $alt);
+    }
+
+    /**
+     * Return a success icon
+     * @param renderer_base $output the renderer base object
+     * @param int|null $success the current success value
+     * @return string icon HTML
+     */
+    private function success_icon(renderer_base $output, int $success = null): string {
+        if ($success === null) {
+            $alt = get_string('attempt_success_unknown', 'mod_h5pactivity');
+            return $output->pix_icon('i/empty', $alt);
+        }
+        if ($success) {
+            $alt = get_string('attempt_success_pass', 'mod_h5pactivity');
+            return $output->pix_icon('i/checkedcircle', $alt);
+        }
+        $alt = get_string('attempt_success_fail', 'mod_h5pactivity');
+        return $output->pix_icon('i/uncheckedcircle', $alt);
+    }
+
+    /**
+     * 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];
+    }
+}
diff --git a/mod/h5pactivity/classes/output/reportattempts.php b/mod/h5pactivity/classes/output/reportattempts.php
new file mode 100644
index 00000000000..84b15e50f83
--- /dev/null
+++ b/mod/h5pactivity/classes/output/reportattempts.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/reportlink.php b/mod/h5pactivity/classes/output/reportlink.php
new file mode 100644
index 00000000000..771aa0386eb
--- /dev/null
+++ b/mod/h5pactivity/classes/output/reportlink.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/reportresults.php b/mod/h5pactivity/classes/output/reportresults.php
new file mode 100644
index 00000000000..b6efae0c438
--- /dev/null
+++ b/mod/h5pactivity/classes/output/reportresults.php
@@ -0,0 +1,90 @@
+<?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);
+            $data->results[] = $outputresult->export_for_template($output);
+        }
+
+        return $data;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result.php b/mod/h5pactivity/classes/output/result.php
new file mode 100644
index 00000000000..fb1ab06af16
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result.php
@@ -0,0 +1,296 @@
+<?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
+     */
+    public static function create_from_record(stdClass $result): self {
+        $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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/choice.php b/mod/h5pactivity/classes/output/result/choice.php
new file mode 100644
index 00000000000..da4011e70a9
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/choice.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/compound.php b/mod/h5pactivity/classes/output/result/compound.php
new file mode 100644
index 00000000000..2f2f77f17a2
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/compound.php
@@ -0,0 +1,56 @@
+<?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\compound
+ *
+ * @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 compound result.
+ *
+ * @copyright 2020 Ferran Recio
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class compound 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);
+        // A compound without any description is just a partial score so no track needed.
+        if (empty($data->description)) {
+            $data->description = get_string('result_compound', 'mod_h5pactivity');
+            $data->track = true;
+        }
+        return $data;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/fillin.php b/mod/h5pactivity/classes/output/result/fillin.php
new file mode 100644
index 00000000000..0439845ce36
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/fillin.php
@@ -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');
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/longfillin.php b/mod/h5pactivity/classes/output/result/longfillin.php
new file mode 100644
index 00000000000..7297132cd5b
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/longfillin.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/matching.php b/mod/h5pactivity/classes/output/result/matching.php
new file mode 100644
index 00000000000..645afdfdcc6
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/matching.php
@@ -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');
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/other.php b/mod/h5pactivity/classes/output/result/other.php
new file mode 100644
index 00000000000..7d0f66a886b
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/other.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/sequencing.php b/mod/h5pactivity/classes/output/result/sequencing.php
new file mode 100644
index 00000000000..286d1ed10f2
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/sequencing.php
@@ -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');
+    }
+}
diff --git a/mod/h5pactivity/classes/output/result/truefalse.php b/mod/h5pactivity/classes/output/result/truefalse.php
new file mode 100644
index 00000000000..a16cf1a5a35
--- /dev/null
+++ b/mod/h5pactivity/classes/output/result/truefalse.php
@@ -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;
+    }
+}
diff --git a/mod/h5pactivity/db/access.php b/mod/h5pactivity/db/access.php
index c0240a1d4fc..459b8485c34 100644
--- a/mod/h5pactivity/db/access.php
+++ b/mod/h5pactivity/db/access.php
@@ -58,7 +58,7 @@ $capabilities = [
     ],
 
     'mod/h5pactivity:reviewattempts' => array(
-        'captype' => 'write',
+        'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
             'teacher' => CAP_ALLOW,
diff --git a/mod/h5pactivity/db/install.xml b/mod/h5pactivity/db/install.xml
index 0484154eeb0..57cd1e0a085 100644
--- a/mod/h5pactivity/db/install.xml
+++ b/mod/h5pactivity/db/install.xml
@@ -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"/>
diff --git a/mod/h5pactivity/db/upgrade.php b/mod/h5pactivity/db/upgrade.php
index d0fb851f427..1f19284b745 100644
--- a/mod/h5pactivity/db/upgrade.php
+++ b/mod/h5pactivity/db/upgrade.php
@@ -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;
 }
diff --git a/mod/h5pactivity/grade.php b/mod/h5pactivity/grade.php
index ccf6c4af1ec..f4b30e4c955 100644
--- a/mod/h5pactivity/grade.php
+++ b/mod/h5pactivity/grade.php
@@ -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));
diff --git a/mod/h5pactivity/lang/en/h5pactivity.php b/mod/h5pactivity/lang/en/h5pactivity.php
index 51d84a11d14..9bd018bc386 100644
--- a/mod/h5pactivity/lang/en/h5pactivity.php
+++ b/mod/h5pactivity/lang/en/h5pactivity.php
@@ -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'] = 'Attempt marked as failed';
+$string['attempt_success_pass'] = 'Attempt completed successfully';
+$string['attempt_success_unknown'] = 'Unkown attempt success state';
 $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:
 
@@ -55,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.
 
@@ -62,6 +91,9 @@ 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['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';
@@ -75,6 +107,26 @@ $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['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';
diff --git a/mod/h5pactivity/mod_form.php b/mod/h5pactivity/mod_form.php
index d3c20d0dc4b..090a1a33fb2 100644
--- a/mod/h5pactivity/mod_form.php
+++ b/mod/h5pactivity/mod_form.php
@@ -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();
 
diff --git a/mod/h5pactivity/report.php b/mod/h5pactivity/report.php
new file mode 100644
index 00000000000..e1ed52a8b69
--- /dev/null
+++ b/mod/h5pactivity/report.php
@@ -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();
diff --git a/mod/h5pactivity/templates/attempt.mustache b/mod/h5pactivity/templates/attempt.mustache
new file mode 100644
index 00000000000..42ee0bf5f64
--- /dev/null
+++ b/mod/h5pactivity/templates/attempt.mustache
@@ -0,0 +1,104 @@
+{{!
+    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="w-100 d-flex flex-column-reverse flex-md-row">
+    <span class="flex-grow-1">
+        {{#user}}
+          {{{picture}}}
+          {{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}: {{fullname}}
+        {{/user}}
+        {{^user}}
+          {{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}
+        {{/user}}
+    </span>
+    <span class="align-self-start badge p-3 mb-3 mb-md-0
+            {{#success}}badge-success{{/success}} {{^success}}badge-secondary{{/success}}">
+        {{#success}}
+            {{#pix}}i/checkedcircle, moodle, {{#str}}answer_pass, mod_h5pactivity{{/str}}{{/pix}}
+        {{/success}}
+        {{^success}}
+            {{#pix}}i/uncheckedcircle, moodle, {{#str}}answer_fail, mod_h5pactivity{{/str}}{{/pix}}
+        {{/success}}
+        {{#score}}{{#str}}score, mod_h5pactivity{{/str}}: {{score}}{{/score}}
+    </span>
+</h2>
+<div class="container-fluid">
+    <dl class="row">
+        <dt class="col-sm-3">{{#str}} startdate, mod_h5pactivity {{/str}}</dt>
+        <dd class="col-sm-3">{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</dd>
+        <dt class="col-sm-3">{{#str}} completion, mod_h5pactivity {{/str}}</dt>
+        <dd class="col-sm-3">{{{completionicon}}}</dd>
+        <dt class="col-sm-3 text-truncate">{{#str}} duration, mod_h5pactivity {{/str}}</dt>
+        <dd class="col-sm-3">{{duration}}</dd>
+        <dt class="col-sm-3">{{#str}} success {{/str}}</dt>
+        <dd class="col-sm-3">{{{successicon}}}</dd>
+    </dl>
+</div>
diff --git a/mod/h5pactivity/templates/attempts.mustache b/mod/h5pactivity/templates/attempts.mustache
new file mode 100644
index 00000000000..2f1f549bd62
--- /dev/null
+++ b/mod/h5pactivity/templates/attempts.mustache
@@ -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>
diff --git a/mod/h5pactivity/templates/reportattempts.mustache b/mod/h5pactivity/templates/reportattempts.mustache
new file mode 100644
index 00000000000..7dd697b95dd
--- /dev/null
+++ b/mod/h5pactivity/templates/reportattempts.mustache
@@ -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}}
diff --git a/mod/h5pactivity/templates/reportlink.mustache b/mod/h5pactivity/templates/reportlink.mustache
new file mode 100644
index 00000000000..14fd2ea0560
--- /dev/null
+++ b/mod/h5pactivity/templates/reportlink.mustache
@@ -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>
diff --git a/mod/h5pactivity/templates/reportresults.mustache b/mod/h5pactivity/templates/reportresults.mustache
new file mode 100644
index 00000000000..167a7af1962
--- /dev/null
+++ b/mod/h5pactivity/templates/reportresults.mustache
@@ -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}}
diff --git a/mod/h5pactivity/templates/result.mustache b/mod/h5pactivity/templates/result.mustache
new file mode 100644
index 00000000000..f4e3920a6fb
--- /dev/null
+++ b/mod/h5pactivity/templates/result.mustache
@@ -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 border border-secondary my-3">
+    <div class="row w-100 py-3 px-1 p-md-3 m-0">
+        {{>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>
diff --git a/mod/h5pactivity/templates/result/answer.mustache b/mod/h5pactivity/templates/result/answer.mustache
new file mode 100644
index 00000000000..c6ba3e12891
--- /dev/null
+++ b/mod/h5pactivity/templates/result/answer.mustache
@@ -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}}
diff --git a/mod/h5pactivity/templates/result/header.mustache b/mod/h5pactivity/templates/result/header.mustache
new file mode 100644
index 00000000000..4b6ca485e19
--- /dev/null
+++ b/mod/h5pactivity/templates/result/header.mustache
@@ -0,0 +1,55 @@
+{{!
+    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 d-flex flex-column-reverse flex-md-row">
+    <span class="flex-grow-1">
+        {{description}}
+    </span>
+    {{#maxscore}}
+        <span class="align-self-start badge p-3 mb-3 mb-md-0
+                {{#success}}badge-success{{/success}} {{^success}}badge-secondary{{/success}}">
+            {{#success}}
+                {{#pix}}i/checkedcircle, moodle, {{#str}}answer_pass, mod_h5pactivity{{/str}}{{/pix}}
+            {{/success}}
+            {{#score}}{{#str}}score, mod_h5pactivity{{/str}}: {{score}}{{/score}}
+        </span>
+    {{/maxscore}}
+</h3>
diff --git a/mod/h5pactivity/templates/result/options.mustache b/mod/h5pactivity/templates/result/options.mustache
new file mode 100644
index 00000000000..5c9927303a0
--- /dev/null
+++ b/mod/h5pactivity/templates/result/options.mustache
@@ -0,0 +1,107 @@
+{{!
+    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">
+  <thead>
+    <tr>
+        <th scope="col" class="">{{optionslabel}}</th>
+        <th scope="col" class="text-center">{{correctlabel}}</th>
+        <th scope="col" class="text-center ">{{answerlabel}}</th>
+    </tr>
+  </thead>
+  <tbody>
+    {{#options}}
+    <tr>
+      <td>{{description}}</td>
+      <td class="text-center">{{#correctanswer}}{{>mod_h5pactivity/result/answer}}{{/correctanswer}}</td>
+      <td class="text-center">{{#useranswer}}{{>mod_h5pactivity/result/answer}}{{/useranswer}}</td>
+    </tr>
+    {{/options}}
+  </tbody>
+</table>
diff --git a/mod/h5pactivity/tests/event/report_viewed_test.php b/mod/h5pactivity/tests/event/report_viewed_test.php
new file mode 100644
index 00000000000..342580bc424
--- /dev/null
+++ b/mod/h5pactivity/tests/event/report_viewed_test.php
@@ -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
+            ],
+        ];
+    }
+}
diff --git a/mod/h5pactivity/tests/event/statement_received_test.php b/mod/h5pactivity/tests/event/statement_received_test.php
index 021043d6127..b4eaa7034fd 100644
--- a/mod/h5pactivity/tests/event/statement_received_test.php
+++ b/mod/h5pactivity/tests/event/statement_received_test.php
@@ -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;
diff --git a/mod/h5pactivity/tests/generator/lib.php b/mod/h5pactivity/tests/generator/lib.php
index b12ffad212d..a9721632dc7 100644
--- a/mod/h5pactivity/tests/generator/lib.php
+++ b/mod/h5pactivity/tests/generator/lib.php
@@ -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)) {
diff --git a/mod/h5pactivity/tests/generator_test.php b/mod/h5pactivity/tests/generator_test.php
index dbb7cfa3dfc..0fd5cab3e5e 100644
--- a/mod/h5pactivity/tests/generator_test.php
+++ b/mod/h5pactivity/tests/generator_test.php
@@ -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);
 
diff --git a/mod/h5pactivity/tests/local/attempt_test.php b/mod/h5pactivity/tests/local/attempt_test.php
index 6cec3c2b067..2ba93798458 100644
--- a/mod/h5pactivity/tests/local/attempt_test.php
+++ b/mod/h5pactivity/tests/local/attempt_test.php
@@ -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);
diff --git a/mod/h5pactivity/tests/local/manager_test.php b/mod/h5pactivity/tests/local/manager_test.php
index e1b63f84740..ff2e8b6149f 100644
--- a/mod/h5pactivity/tests/local/manager_test.php
+++ b/mod/h5pactivity/tests/local/manager_test.php
@@ -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.
      *
diff --git a/mod/h5pactivity/version.php b/mod/h5pactivity/version.php
index 2d8caf89b67..ad71475886a 100644
--- a/mod/h5pactivity/version.php
+++ b/mod/h5pactivity/version.php
@@ -25,5 +25,5 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component = 'mod_h5pactivity';
-$plugin->version = 2020041401;
+$plugin->version = 2020042202;
 $plugin->requires = 2020013000;
diff --git a/mod/h5pactivity/view.php b/mod/h5pactivity/view.php
index 7a47edb3839..67bdc505bcd 100644
--- a/mod/h5pactivity/view.php
+++ b/mod/h5pactivity/view.php
@@ -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 {

From fd7ef55acb05cc87cd965d21b44257f7b666f52f Mon Sep 17 00:00:00 2001
From: Ferran Recio <ferran@moodle.com>
Date: Thu, 14 May 2020 16:45:08 +0200
Subject: [PATCH 3/3] MDL-67791 mod_h5pactivity: attempt report UI ajustments

---
 mod/h5pactivity/classes/output/attempt.php    | 42 ++++++++++----
 .../classes/output/reportresults.php          |  4 +-
 mod/h5pactivity/classes/output/result.php     |  8 ++-
 .../classes/output/result/compound.php        | 56 -------------------
 mod/h5pactivity/lang/en/h5pactivity.php       |  8 ++-
 mod/h5pactivity/templates/attempt.mustache    | 50 +++++++----------
 mod/h5pactivity/templates/result.mustache     |  4 +-
 .../templates/result/header.mustache          | 15 +----
 .../templates/result/options.mustache         | 25 ++++++---
 9 files changed, 88 insertions(+), 124 deletions(-)
 delete mode 100644 mod/h5pactivity/classes/output/result/compound.php

diff --git a/mod/h5pactivity/classes/output/attempt.php b/mod/h5pactivity/classes/output/attempt.php
index 7c511271150..ac433b44886 100644
--- a/mod/h5pactivity/classes/output/attempt.php
+++ b/mod/h5pactivity/classes/output/attempt.php
@@ -86,8 +86,10 @@ class attempt implements renderable, templatable {
             '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()
@@ -117,37 +119,55 @@ class attempt implements renderable, templatable {
      *
      * @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): string {
+    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');
-            return $output->pix_icon('i/completion-auto-y', $alt);
+            $icon = 'i/completion-auto-y';
+        } else {
+            $alt = get_string('attempt_completion_no', 'mod_h5pactivity');
+            $icon = 'i/completion-auto-n';
         }
-        $alt = get_string('attempt_completion_no', 'mod_h5pactivity');
-        return $output->pix_icon('i/completion-auto-n', $alt);
+        $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): string {
+    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');
-            return $output->pix_icon('i/empty', $alt);
-        }
-        if ($success) {
+            if ($showtext) {
+                return $alt;
+            }
+            $icon = 'i/empty';
+        } else if ($success) {
             $alt = get_string('attempt_success_pass', 'mod_h5pactivity');
-            return $output->pix_icon('i/checkedcircle', $alt);
+            $icon = 'i/checkedcircle';
+        } else {
+            $alt = get_string('attempt_success_fail', 'mod_h5pactivity');
+            $icon = 'i/uncheckedcircle';
         }
-        $alt = get_string('attempt_success_fail', 'mod_h5pactivity');
-        return $output->pix_icon('i/uncheckedcircle', $alt);
+        $text = '';
+        if ($showtext) {
+            $text = $alt;
+            $alt = '';
+        }
+        return $output->pix_icon($icon, $alt).$text;
     }
 
     /**
diff --git a/mod/h5pactivity/classes/output/reportresults.php b/mod/h5pactivity/classes/output/reportresults.php
index b6efae0c438..7627a30cea9 100644
--- a/mod/h5pactivity/classes/output/reportresults.php
+++ b/mod/h5pactivity/classes/output/reportresults.php
@@ -82,7 +82,9 @@ class reportresults implements renderable, templatable {
         $data->results = [];
         foreach ($results as $key => $result) {
             $outputresult = output_result::create_from_record($result);
-            $data->results[] = $outputresult->export_for_template($output);
+            if ($outputresult) {
+                $data->results[] = $outputresult->export_for_template($output);
+            }
         }
 
         return $data;
diff --git a/mod/h5pactivity/classes/output/result.php b/mod/h5pactivity/classes/output/result.php
index fb1ab06af16..fc8b53af5ca 100644
--- a/mod/h5pactivity/classes/output/result.php
+++ b/mod/h5pactivity/classes/output/result.php
@@ -100,9 +100,13 @@ class result implements renderable, templatable {
      * return the correct result output depending on the interactiontype
      *
      * @param stdClass $result h5pactivity_attempts_results record
-     * @return result
+     * @return result|null the result output class if any
      */
-    public static function create_from_record(stdClass $result): self {
+    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)) {
diff --git a/mod/h5pactivity/classes/output/result/compound.php b/mod/h5pactivity/classes/output/result/compound.php
deleted file mode 100644
index 2f2f77f17a2..00000000000
--- a/mod/h5pactivity/classes/output/result/compound.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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\compound
- *
- * @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 compound result.
- *
- * @copyright 2020 Ferran Recio
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class compound 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);
-        // A compound without any description is just a partial score so no track needed.
-        if (empty($data->description)) {
-            $data->description = get_string('result_compound', 'mod_h5pactivity');
-            $data->track = true;
-        }
-        return $data;
-    }
-}
diff --git a/mod/h5pactivity/lang/en/h5pactivity.php b/mod/h5pactivity/lang/en/h5pactivity.php
index 9bd018bc386..35fa58e810f 100644
--- a/mod/h5pactivity/lang/en/h5pactivity.php
+++ b/mod/h5pactivity/lang/en/h5pactivity.php
@@ -45,9 +45,9 @@ $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'] = 'Attempt marked as failed';
-$string['attempt_success_pass'] = 'Attempt completed successfully';
-$string['attempt_success_unknown'] = 'Unkown attempt success state';
+$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.';
@@ -94,6 +94,7 @@ $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';
@@ -126,6 +127,7 @@ $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';
diff --git a/mod/h5pactivity/templates/attempt.mustache b/mod/h5pactivity/templates/attempt.mustache
index 42ee0bf5f64..faa97b39a05 100644
--- a/mod/h5pactivity/templates/attempt.mustache
+++ b/mod/h5pactivity/templates/attempt.mustache
@@ -69,36 +69,28 @@
     }
 
 }}
-<h2 class="w-100 d-flex flex-column-reverse flex-md-row">
-    <span class="flex-grow-1">
-        {{#user}}
-          {{{picture}}}
-          {{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}: {{fullname}}
-        {{/user}}
-        {{^user}}
-          {{#str}}attempt, mod_h5pactivity{{/str}} #{{attempt}}
-        {{/user}}
-    </span>
-    <span class="align-self-start badge p-3 mb-3 mb-md-0
-            {{#success}}badge-success{{/success}} {{^success}}badge-secondary{{/success}}">
-        {{#success}}
-            {{#pix}}i/checkedcircle, moodle, {{#str}}answer_pass, mod_h5pactivity{{/str}}{{/pix}}
-        {{/success}}
-        {{^success}}
-            {{#pix}}i/uncheckedcircle, moodle, {{#str}}answer_fail, mod_h5pactivity{{/str}}{{/pix}}
-        {{/success}}
-        {{#score}}{{#str}}score, mod_h5pactivity{{/str}}: {{score}}{{/score}}
-    </span>
+<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">
+<div class="container-fluid mt-4 mb-5 px-0 px-md-3">
     <dl class="row">
-        <dt class="col-sm-3">{{#str}} startdate, mod_h5pactivity {{/str}}</dt>
-        <dd class="col-sm-3">{{#userdate}} {{timemodified}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</dd>
-        <dt class="col-sm-3">{{#str}} completion, mod_h5pactivity {{/str}}</dt>
-        <dd class="col-sm-3">{{{completionicon}}}</dd>
-        <dt class="col-sm-3 text-truncate">{{#str}} duration, mod_h5pactivity {{/str}}</dt>
-        <dd class="col-sm-3">{{duration}}</dd>
-        <dt class="col-sm-3">{{#str}} success {{/str}}</dt>
-        <dd class="col-sm-3">{{{successicon}}}</dd>
+        <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>
diff --git a/mod/h5pactivity/templates/result.mustache b/mod/h5pactivity/templates/result.mustache
index f4e3920a6fb..4309275433c 100644
--- a/mod/h5pactivity/templates/result.mustache
+++ b/mod/h5pactivity/templates/result.mustache
@@ -103,8 +103,8 @@
 
 }}
 
-<div class="container-fluid w-100 border border-secondary my-3">
-    <div class="row w-100 py-3 px-1 p-md-3 m-0">
+<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}}
diff --git a/mod/h5pactivity/templates/result/header.mustache b/mod/h5pactivity/templates/result/header.mustache
index 4b6ca485e19..a5592daa471 100644
--- a/mod/h5pactivity/templates/result/header.mustache
+++ b/mod/h5pactivity/templates/result/header.mustache
@@ -39,17 +39,6 @@
     }
 
 }}
-<h3 class="w-100 d-flex flex-column-reverse flex-md-row">
-    <span class="flex-grow-1">
-        {{description}}
-    </span>
-    {{#maxscore}}
-        <span class="align-self-start badge p-3 mb-3 mb-md-0
-                {{#success}}badge-success{{/success}} {{^success}}badge-secondary{{/success}}">
-            {{#success}}
-                {{#pix}}i/checkedcircle, moodle, {{#str}}answer_pass, mod_h5pactivity{{/str}}{{/pix}}
-            {{/success}}
-            {{#score}}{{#str}}score, mod_h5pactivity{{/str}}: {{score}}{{/score}}
-        </span>
-    {{/maxscore}}
+<h3 class="w-100">
+    {{description}}
 </h3>
diff --git a/mod/h5pactivity/templates/result/options.mustache b/mod/h5pactivity/templates/result/options.mustache
index 5c9927303a0..4be6d11dd4b 100644
--- a/mod/h5pactivity/templates/result/options.mustache
+++ b/mod/h5pactivity/templates/result/options.mustache
@@ -87,21 +87,32 @@
     }
 
 }}
-<table class="table">
+<table class="table table-striped">
   <thead>
     <tr>
-        <th scope="col" class="">{{optionslabel}}</th>
-        <th scope="col" class="text-center">{{correctlabel}}</th>
-        <th scope="col" class="text-center ">{{answerlabel}}</th>
+        <th scope="col">{{optionslabel}}</th>
+        <th scope="col">{{correctlabel}}</th>
+        <th scope="col">{{answerlabel}}</th>
     </tr>
   </thead>
   <tbody>
     {{#options}}
     <tr>
-      <td>{{description}}</td>
-      <td class="text-center">{{#correctanswer}}{{>mod_h5pactivity/result/answer}}{{/correctanswer}}</td>
-      <td class="text-center">{{#useranswer}}{{>mod_h5pactivity/result/answer}}{{/useranswer}}</td>
+        <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>