diff --git a/classes/Report.php b/classes/Report.php index f6ce672..0af30b1 100644 --- a/classes/Report.php +++ b/classes/Report.php @@ -131,22 +131,25 @@ public function has_access(string $wstoken): bool { /** * Get all attempts for all users inside this quiz, excluding previews * + * @param int $userid - If set, only the attempts of the given user are included. * @return array Array of all attempt IDs together with the userid that were * made inside this quiz. Indexed by attemptid. * * @throws \dml_exception */ - public function get_attempts(): array { + public function get_attempts($userid = 0): array { global $DB; - return $DB->get_records_sql( - "SELECT id AS attemptid, userid " . - "FROM {quiz_attempts} " . - "WHERE preview = 0 AND quiz = :quizid", - [ - "quizid" => $this->quiz->id, - ] - ); + $conditions = [ + 'quiz' => $this->quiz->id, + 'preview' => 0, + ]; + + if(!empty($userid)) { + $conditions['userid'] = $userid; + } + + return $DB->get_records('quiz_attempts', $conditions, '', 'id AS attemptid, userid'); } /** diff --git a/classes/output/job_overview_table.php b/classes/output/job_overview_table.php index 64b807c..4b68dbe 100644 --- a/classes/output/job_overview_table.php +++ b/classes/output/job_overview_table.php @@ -48,10 +48,11 @@ class job_overview_table extends \table_sql { * @param int $courseid ID of the course * @param int $cmid ID of the course module * @param int $quizid ID of the quiz + * @param int $userid - If set, the table is limited to the archives created by the user itself. * * @throws \coding_exception */ - public function __construct(string $uniqueid, int $courseid, int $cmid, int $quizid) { + public function __construct(string $uniqueid, int $courseid, int $cmid, int $quizid, int $userid = 0) { parent::__construct($uniqueid); $this->define_columns([ 'timecreated', @@ -71,19 +72,22 @@ public function __construct(string $uniqueid, int $courseid, int $cmid, int $qui '', ]); - $this->set_sql( - 'j.jobid, j.userid, j.timecreated, j.timemodified, j.status, j.statusextras, j.retentiontime, j.artifactfilechecksum, '. - 'f.pathnamehash, f.filesize, u.username', - '{'.ArchiveJob::JOB_TABLE_NAME.'} j '. - 'JOIN {user} u ON j.userid = u.id '. - 'LEFT JOIN {files} f ON j.artifactfileid = f.id', - 'j.courseid = :courseid AND j.cmid = :cmid AND j.quizid = :quizid', - [ - 'courseid' => $courseid, - 'cmid' => $cmid, - 'quizid' => $quizid, - ] - ); + $conditions = [ + 'courseid' => $courseid, + 'cmid' => $cmid, + 'quizid' => $quizid, + ]; + + $fields = 'j.jobid, j.userid, j.timecreated, j.timemodified, j.status, j.statusextras, j.retentiontime, j.artifactfilechecksum, f.pathnamehash, f.filesize, u.username'; + $sql = '{' . ArchiveJob::JOB_TABLE_NAME . '} AS j JOIN {user} AS u ON j.userid = u.id LEFT JOIN {files} AS f ON j.artifactfileid = f.id'; + $where = 'j.courseid = :courseid AND j.cmid = :cmid AND j.quizid = :quizid'; + + if (!empty($userid)) { + $conditions['userid'] = $userid; + $where .= ' AND u.id = :userid'; + } + + $this->set_sql($fields, $sql, $where, $conditions); $this->sortable(true, 'timecreated', SORT_DESC); $this->no_sorting('jobid'); diff --git a/db/access.php b/db/access.php index 68e88b6..12e9485 100644 --- a/db/access.php +++ b/db/access.php @@ -65,4 +65,15 @@ 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => [], ], + // Capability to use the webservice. Required for the webservice user. + 'mod/quiz_archiver:getownarchive' => [ + 'riskbitmask' => (RISK_PERSONAL), + 'captype' => 'read', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'student' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW, + ], + ], ]; diff --git a/lib.php b/lib.php index be610a1..17b19c7 100644 --- a/lib.php +++ b/lib.php @@ -48,9 +48,16 @@ function quiz_archiver_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) { // Check permissions. require_login($course, false, $cm); - require_capability('mod/quiz:grade', $context); - require_capability('quiz/grading:viewstudentnames', $context); - require_capability('quiz/grading:viewidnumber', $context); + + if (!(( + has_capability('mod/quiz:grade', $context) + && has_capability('quiz/grading:viewstudentnames', $context) + && has_capability('quiz/grading:viewidnumber', $context) + ) || + has_capability('mod/quiz_archiver:getownarchive', $context) + )) { + throw new moodle_exception("You have not the capability to download the archive file."); + } // Validate course. if ($args[1] !== $course->id) { diff --git a/report.php b/report.php index 70a748c..97057cc 100644 --- a/report.php +++ b/report.php @@ -89,7 +89,7 @@ public static function quiz_can_be_archived(int $quizid): bool { * @throws moodle_exception */ public function display($quiz, $cm, $course): bool { - global $OUTPUT; + global $OUTPUT, $USER; $this->course = $course; $this->cm = $cm; @@ -263,12 +263,20 @@ protected function initiate_archive_job( string $archivefilenamepattern, string $attemptsfilenamepattern, ?array $imageoptimize = null, - ?int $retentionseconds = null + ?int $retentionseconds = null, + int $userid = 0 ): ?ArchiveJob { global $CFG, $USER; // Check permissions. - require_capability('mod/quiz_archiver:create', $this->context); + if ( + !( + has_capability('mod/quiz_archiver:create', $this->context) + || has_capability('mod/quiz_archiver:getownarchive', $this->context) + ) + ) { + throw new moodle_exception("You have not the capability to generate the archive file."); + } // Check if webservice is configured properly. if (autoinstall::plugin_is_unconfigured()) { @@ -300,7 +308,7 @@ protected function initiate_archive_job( } // Get attempt metadata. - $attempts = $this->report->get_attempts(); + $attempts = $this->report->get_attempts($userid); // Prepare task: Export quiz attempts. $taskarchivequizattempts = null; @@ -609,4 +617,47 @@ protected function base_url_with_alert( return $url; } + /** + * Initialises an archive job for a specific user. + * + * @param int $userid + * @return ArchiveJob|null Created ArchiveJob on success + */ + public function initiate_users_archive_job( + object $quiz, + object $cm, + object $course, + object $context, + bool $exportattempts, + array $reportsections, + bool $reportkeephtmlfiles, + string $paperformat, + bool $exportquizbackup, + bool $exportcoursebackup, + string $archivefilenamepattern, + string $attemptsfilenamepattern, + ?int $retentionseconds = null, + int $userid = 0 + ) { + $this->context = $context; + require_capability('mod/quiz_archiver:getownarchive', $this->context); + + $this->course = $course; + $this->cm = $cm; + $this->quiz = $quiz; + $this->report = new Report($this->course, $this->cm, $this->quiz); + return $this->initiate_archive_job( + $exportattempts, + $reportsections, + $reportkeephtmlfiles, + $paperformat, + $exportquizbackup, + $exportcoursebackup, + $archivefilenamepattern, + $attemptsfilenamepattern, + $retentionseconds, + $userid + ); + } + } diff --git a/version.php b/version.php index bef0818..0758a7a 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ $plugin->component = 'quiz_archiver'; $plugin->release = '2.2.0'; -$plugin->version = 2024102900; +$plugin->version = 2024102901; $plugin->requires = 2022112800; $plugin->supported = [401, 405]; $plugin->maturity = MATURITY_STABLE;