diff --git a/certificates.php b/certificates.php
index 7ccddbfa..3ab2b0ad 100644
--- a/certificates.php
+++ b/certificates.php
@@ -63,6 +63,14 @@
$outputpage = new \tool_certificate\output\issues_page($template->get_id());
$data = $outputpage->export_for_template($PAGE->get_renderer('core'));
+
+$downloadform = new \tool_certificate\download_issues_form($template->get_id());
+if ($downloadissues = $downloadform->get_data()) {
+ $outputpage->output_issues_pdf($template, $downloadissues);
+ die();
+}
+$data['content'] .= $downloadform->render();
+
$data += ['heading' => get_string('issuedcertificates', 'tool_certificate')];
if ($template->can_issue_to_anybody()) {
$data += ['addbutton' => true, 'addbuttontitle' => get_string('issuecertificates', 'tool_certificate'),
diff --git a/classes/download_issues_form.php b/classes/download_issues_form.php
new file mode 100644
index 00000000..37814a99
--- /dev/null
+++ b/classes/download_issues_form.php
@@ -0,0 +1,56 @@
+templateid = $templateid;
+ }
+
+ public function render(): string {
+ return <<
+
+
+
+
+
+
+
+
+HTML;
+ }
+
+ public function get_data() {
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ return false;
+ }
+
+ $downloadissues = required_param('downloadissues', PARAM_ALPHA);
+ return $downloadissues;
+ }
+
+ protected function definition(): void {
+ $this->_form->setAttributes(['class' => 'form-inline']);
+
+ $templateid = $this->_customdata['templateid'];
+ $this->_form->addElement('hidden', 'templateid', $templateid);
+ $this->_form->setType('id', PARAM_INT);
+
+ $options =
+ [
+ 'pdf' => 'Merged PDF',
+ 'pdfdecollate' => 'Merged PDF (De-collated)',
+ ];
+ $this->_form->addElement('select', 'downloadissues', 'Download issued PDFs as', $options);
+ $this->_form->setType('downloadissues', PARAM_TEXT);
+
+ $submit = $this->_form->addElement('submit', 'submit', 'Download PDFs');
+ $submit->setAttributes(['class' => 'btn btn-secondary']);
+ }
+}
\ No newline at end of file
diff --git a/classes/output/issues_page.php b/classes/output/issues_page.php
index bbd5d147..3169bb2c 100644
--- a/classes/output/issues_page.php
+++ b/classes/output/issues_page.php
@@ -33,6 +33,11 @@ class issues_page implements \templatable, \renderable {
/** @var int */
protected $templateid;
+ /**
+ * @var \stdClass[] The rows that were displayed in the table
+ */
+ public array $rows;
+
/**
* templates_page constructor.
*
@@ -53,6 +58,112 @@ public function export_for_template(renderer_base $output): array {
$report = system_report_factory::create(issues::class, $context,
'', '', 0, ['templateid' => $this->templateid]);
- return ['content' => $report->output()];
+ $result = ['content' => $report->output()];
+ if (isset($report->rows)) {
+ $this->rows = $report->rows;
+ }
+ else
+ {
+ $this->rows = [];
+ }
+ return $result;
+ }
+
+ /**
+ * @throws \InvalidArgumentException
+ * @throws \setasign\Fpdi\PdfParser\PdfParserException
+ * @throws \setasign\Fpdi\PdfParser\PdfParserException
+ */
+ public function output_issues_pdf(template $template, string $type): void {
+ global $CFG;
+ $files = [];
+ $handles = [];
+ foreach ($this->rows as $row) {
+ $file = $template->get_issue_file($row);
+ $files[] = $file;
+ $handles[$file->get_id()] = $file->get_content_file_handle();
+ }
+
+ $debug = optional_param('debug', false, PARAM_BOOL);
+
+ require_once($CFG->libdir . '/pdflib.php');
+ require_once($CFG->dirroot . '/mod/assign/feedback/editpdf/fpdi/autoload.php');
+
+ // end all output buffers if any
+ while (ob_get_level())
+ {
+ ob_get_clean();
+ }
+
+ try {
+ $pdf = new \setasign\Fpdi\Tcpdf\Fpdi();
+ $count = count($files);
+ $name = clean_filename($template->get_name());
+ $at = date('Y-m-d H-i-s');
+ $name = "$name - $count certificate(s) - $at";
+
+ if ($type == 'pdf') {
+ $position = 0;
+ foreach ($files as $file) {
+ $position++;
+ $filePages = $pdf->setSourceFile($handles[$file->get_id()]);
+ for ($pageNumber = 1; $pageNumber <= $filePages; $pageNumber++) {
+ $sourcePage = $pdf->importPage($pageNumber);
+ $size = $pdf->getTemplateSize($sourcePage);
+ $pdf->AddPage($size['orientation'], array($size['width'], $size['height']));
+
+ $pdf->useTemplate($sourcePage);
+
+ if ($debug) {
+ $pdf->SetFont('Helvetica');
+ $pdf->SetTextColor(200, 0, 0);
+ $pdf->SetXY(5, 5);
+ $pdf->Write(2, "PDF $position/$count, Page $pageNumber/$filePages");
+ }
+ }
+ }
+
+ $pdf->Output("$name.pdf");
+ }
+ else if ($type == 'pdfdecollate') {
+ $pageCount = 1;
+ for ($pageNumber = 1; $pageNumber <= $pageCount; $pageNumber++) {
+ $position = 0;
+ foreach ($files as $file) {
+ $position++;
+ $filePages = $pdf->setSourceFile($handles[$file->get_id()]);
+ if ($pageNumber > $filePages) {
+ continue;
+ }
+ if ($filePages > $pageCount) {
+ $pageCount = $filePages;
+ }
+
+ $sourcePage = $pdf->importPage($pageNumber);
+ $size = $pdf->getTemplateSize($sourcePage);
+ $pdf->AddPage($size['orientation'], array($size['width'], $size['height']));
+
+ $pdf->useTemplate($sourcePage);
+
+ if ($debug) {
+ $pdf->SetFont('Helvetica');
+ $pdf->SetTextColor(200, 0, 0);
+ $pdf->SetXY(5, 5);
+ $pdf->Write(2, "PDF $position/$count, Page $pageNumber/$filePages");
+ }
+ }
+ }
+
+ $pdf->Output("$name - ordered.pdf");
+ }
+ else {
+ throw new \InvalidArgumentException("Unknown download type: $type");
+ }
+ }
+ finally {
+ foreach ($handles as $handle) {
+ fclose($handle);
+ }
+ }
}
}
diff --git a/classes/reportbuilder/local/systemreports/issues.php b/classes/reportbuilder/local/systemreports/issues.php
index 9ca11a7a..8fac8620 100644
--- a/classes/reportbuilder/local/systemreports/issues.php
+++ b/classes/reportbuilder/local/systemreports/issues.php
@@ -245,8 +245,18 @@ protected function add_actions(): void {
*/
public function row_callback(stdClass $row): void {
$this->userid = (int) $row->userid;
+
+ if (!isset($this->rows)) {
+ $this->rows = [];
+ }
+ $this->rows[] = $row;
}
+ /**
+ * @var stdClass[]
+ */
+ public array $rows;
+
/**
* Callback for the fullname to display badge for archived issues.
*