Skip to content

Commit 0637ca1

Browse files
HobieCatmdjnelson
authored andcommitted
Implement new feature keeplocalcopy:
- add keeplocalcopy field to the customcert table - add managekeeplocalcopy, deletelocalcopy capabilities - add keeplocalcopy to the backup structure - add keeplocalcopy setting element to forms - add localfile class to manage local PDF files - add logic to store and serve local PDF files - add deletelocalcopy to the actions column - add deletelocalcopy logic - add 'download all certificates' link to nav menus - add file to download all certificates - italian translation for new lang strings
1 parent 14e91a8 commit 0637ca1

15 files changed

+585
-25
lines changed

backup/moodle2/backup_customcert_stepslib.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected function define_structure() {
4242
$customcert = new backup_nested_element('customcert', ['id'], [
4343
'templateid', 'name', 'intro', 'introformat', 'requiredtime', 'verifyany',
4444
'deliveryoption', 'usecustomfilename', 'customfilenamepattern', 'emailstudents',
45-
'emailteachers', 'emailothers', 'protection', 'language', 'timecreated',
45+
'emailteachers', 'emailothers', 'protection', 'language', 'keeplocalcopy', 'timecreated',
4646
'timemodified']);
4747

4848
// The template.

classes/localfile.php

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
/**
18+
* Class represents a local file of an issued certificate.
19+
*
20+
* @package mod_customcert
21+
* @copyright 2023 Giorgio Consorti <[email protected]>
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
25+
namespace mod_customcert;
26+
27+
use file_exception;
28+
29+
defined('MOODLE_INTERNAL') || die();
30+
31+
/**
32+
* Class represents a local file of an issued certificate.
33+
*
34+
* @package mod_customcert
35+
* @copyright 023 Giorgio Consorti <[email protected]>
36+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37+
*/
38+
class localfile {
39+
40+
/**
41+
* The template representing the content of the file.
42+
*
43+
* @var \mod_customcert\template
44+
*/
45+
protected $template;
46+
47+
/**
48+
* The component name for the file storage.
49+
*/
50+
const component = 'mod_customcert';
51+
52+
/**
53+
* The filearea name for the file storage.
54+
*/
55+
const filearea = 'customcert_issues';
56+
57+
/**
58+
* The constructor.
59+
*
60+
* @param \mod_customcert\template $template
61+
*/
62+
public function __construct(\mod_customcert\template $template) {
63+
$this->template = $template;
64+
}
65+
66+
/**
67+
* Save the PDF to the file storage.
68+
*
69+
* @param string $pdfcontent string content of the pdf
70+
* @param integer|null $userid the id of the user whose certificate we want to save
71+
* @return stored_file|false the stored_file object on success, false on error
72+
*/
73+
public function savePDF(string $pdfcontent, ?int $userid = null) {
74+
global $CFG, $USER;
75+
require_once($CFG->libdir . '/filelib.php');
76+
77+
if (empty($userid)) {
78+
$userid = $USER->id;
79+
}
80+
81+
try {
82+
$file = $this->getPDF($userid);
83+
if (!$file) {
84+
// Create file containing the pdf
85+
$fs = get_file_storage();
86+
$file = $fs->create_file_from_string($this->buildFileInfo($userid), $pdfcontent);
87+
}
88+
return $file;
89+
} catch (file_exception $e) {
90+
// maybe log the exception
91+
return false;
92+
}
93+
}
94+
95+
/**
96+
* Get the PDF from the file storage.
97+
*
98+
* @param integer|null $userid the id of the user whose certificate we want to get
99+
* @return \stored_file|false the stored_file object on success, false on error
100+
*/
101+
public function getPDF(?int $userid = null) {
102+
global $CFG, $USER;
103+
require_once($CFG->libdir . '/filelib.php');
104+
105+
if (empty($userid)) {
106+
$userid = $USER->id;
107+
}
108+
109+
$fileinfo = $this->buildFileInfo($userid);
110+
$fs = get_file_storage();
111+
return $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
112+
$fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
113+
}
114+
115+
/**
116+
* Delete the PDF from the file storage.
117+
*
118+
* @param integer|null $userid the id of the user whose certificate we want to get
119+
* @return bool true on success
120+
*/
121+
public function deletePDF(?int $userid = null) {
122+
global $USER;
123+
124+
if (empty($userid)) {
125+
$userid = $USER->id;
126+
}
127+
128+
try {
129+
$file = $this->getPDF($userid);
130+
if ($file) {
131+
return $file->delete();
132+
}
133+
return false;
134+
} catch (file_exception $e) {
135+
// maybe log the exception
136+
return false;
137+
}
138+
}
139+
140+
/**
141+
* Send the PDF to the browser or return it as a string.
142+
*
143+
* @param int $userid the id of the user whose certificate we want to view
144+
* @param string $deliveryoption the delivery option of the customcert
145+
* @param bool $return Do we want to return the contents of the PDF?
146+
* @return string|void Can return the PDF in string format if specified.
147+
*/
148+
public function sendPDF(?int $userid = NULL, string $deliveryoption = certificate::DELIVERY_OPTION_DOWNLOAD, bool $return = false) {
149+
global $USER;
150+
151+
if (empty($userid)) {
152+
$userid = $USER->id;
153+
}
154+
155+
$file = $this->getPDF($userid);
156+
if ($file) {
157+
if ($return) {
158+
return $file->get_content();
159+
} else {
160+
// send the file to the browser
161+
send_stored_file(
162+
$file,
163+
0,
164+
0,
165+
$deliveryoption == certificate::DELIVERY_OPTION_DOWNLOAD,
166+
['filename' => $file->get_filename()]
167+
);
168+
die();
169+
}
170+
}
171+
}
172+
173+
/**
174+
* Check if a pdf exists in the file storage area.
175+
*
176+
* @param \stdClass $cm the course module
177+
* @param integer|null $userid the id of the user whose PDF we want to check
178+
* @param integer|null $templateid the template id of the customcert we want to check
179+
* @return \stored_file|false the stored_file object on success, false on error
180+
*/
181+
public static function existsPDF($cm, ?int $userid = null, ?int $templateid = null) {
182+
183+
$fileinfo = self::buildFileInfoArr($cm, $userid, $templateid);
184+
$fs = get_file_storage();
185+
return $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
186+
$fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
187+
}
188+
189+
/**
190+
* Build the fileinfo array needed by the file storage.
191+
*
192+
* @param integer|null $userid the id of the user whose fileinfo array we want to generate
193+
* @return array the fileinfo array
194+
*/
195+
protected function buildFileInfo(?int $userid = null) {
196+
197+
return self::buildFileInfoArr($this->template->get_cm(), $userid, $this->template->get_id());
198+
}
199+
200+
/**
201+
* Build the fileinfo array needed by the file storage, static version.
202+
*
203+
* @param \stdClass $cm the course module
204+
* @param integer|null $userid the id of the user whose fileinfo array we want to generate
205+
* @param integer|null $templateid the template id of the customcert of the array we want to generate
206+
* @return array the fileinfo array
207+
*/
208+
private static function buildFileInfoArr ($cm, ?int $userid = null, ?int $templateid = null) {
209+
210+
/** @var \moodle_database $DB */
211+
global $DB, $USER;
212+
213+
if (empty($userid)) {
214+
$userid = $USER->id;
215+
}
216+
217+
if (empty($templateid)) {
218+
$customcert = $DB->get_record('customcert', array('id' => $cm->instance), '*', MUST_EXIST);
219+
$templateid = $customcert->templateid;
220+
}
221+
222+
$course = $DB->get_record('course', ['id' => $cm->course]);
223+
$context = $DB->get_record('context', ['contextlevel' => '50', 'instanceid' => $course->id]);
224+
$user_info = $DB->get_record('user', ['id' => $userid]);
225+
226+
return [
227+
'contextid' => $context->id,
228+
'component' => self::component,
229+
'filearea' => self::filearea,
230+
'itemid' => $templateid,
231+
'userid' => $USER->id,
232+
'author' => fullname($USER),
233+
'filepath' => '/' . $course->id . '/',
234+
'filename' => self::buildFileName($user_info->username, $templateid, $course->shortname),
235+
];
236+
}
237+
238+
/**
239+
* Build the PDF filename.
240+
*
241+
* @param string $username
242+
* @param string $templateid
243+
* @param string $courseShortname
244+
* @return string the PDF file name
245+
*/
246+
public static function buildFileName($username, $templateid, $courseShortname) {
247+
return $username . '_cert-' . $templateid . '_course-' . $courseShortname . '.pdf';
248+
}
249+
}

classes/report_table.php

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,42 @@ public function col_download($user) {
212212
public function col_actions($user) {
213213
global $OUTPUT;
214214

215-
$icon = new \pix_icon('i/delete', get_string('delete'));
216-
$link = new \moodle_url('/mod/customcert/view.php',
215+
$actions = [
217216
[
218-
'id' => $this->cm->id,
219-
'deleteissue' => $user->issueid,
220-
'sesskey' => sesskey(),
217+
'icon' => new \pix_icon('i/delete', get_string('delete')),
218+
'link' => new \moodle_url(
219+
'/mod/customcert/view.php',
220+
[
221+
'id' => $this->cm->id,
222+
'deleteissue' => $user->issueid,
223+
'sesskey' => sesskey()
224+
]
225+
),
226+
'attributes' => ['class' => 'action-icon delete-icon'],
221227
]
222-
);
228+
];
229+
230+
if (has_capability('mod/customcert:deletelocalcopy', \context_module::instance($this->cm->id)) && localfile::existsPDF($this->cm, $user->id)) {
231+
$actions[] = [
232+
'icon' => new \pix_icon('deletelocalcopy', get_string('deletelocalcopy', 'customcert'), 'customcert'),
233+
'link' => new \moodle_url(
234+
'/mod/customcert/view.php',
235+
[
236+
'id' => $this->cm->id,
237+
'deleteissue' => $user->issueid,
238+
'deletelocalcopy' => 1,
239+
'sesskey' => sesskey()
240+
]
241+
),
242+
'attributes' => ['class' => 'action-icon deletelocalcopy-icon'],
243+
];
244+
}
223245

224-
return $OUTPUT->action_icon($link, $icon, null, ['class' => 'action-icon delete-icon']);
246+
return implode(" ", array_map(
247+
fn ($action) =>
248+
$OUTPUT->action_icon($action['link'], $action['icon'], null, $action['attributes'] ?? []),
249+
$actions
250+
));
225251
}
226252

227253
/**

classes/template.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class template {
4848
*/
4949
protected $contextid;
5050

51+
/**
52+
* @var \mod_customcert\localfile the local file for the template.
53+
*/
54+
protected $localfile;
55+
5156
/**
5257
* The constructor.
5358
*
@@ -57,6 +62,7 @@ public function __construct($template) {
5762
$this->id = $template->id;
5863
$this->name = $template->name;
5964
$this->contextid = $template->contextid;
65+
$this->localfile = new localfile($this);
6066
}
6167

6268
/**
@@ -314,7 +320,13 @@ public function generate_pdf(bool $preview = false, ?int $userid = null, bool $r
314320
$deliveryoption = $customcert->deliveryoption;
315321
}
316322

317-
// Set up PDF document properties — no header/footer, auto page break.
323+
if ($customcert->keeplocalcopy) {
324+
$retval = $this->localfile->sendPDF($userid, $deliveryoption, $return);
325+
if ($return && !empty($retval)) {
326+
return $retval;
327+
}
328+
}
329+
318330
$pdf->setPrintHeader(false);
319331
$pdf->setPrintFooter(false);
320332
$pdf->SetAutoPageBreak(true, 0);
@@ -413,6 +425,10 @@ public function generate_pdf(bool $preview = false, ?int $userid = null, bool $r
413425
}
414426
}
415427

428+
if ($customcert->keeplocalcopy) {
429+
$this->localfile->savePDF($pdf->Output('', 'S'), $userid);
430+
}
431+
416432
if ($return) {
417433
return $pdf->Output('', 'S');
418434
}

db/access.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,26 @@
104104
],
105105
],
106106

107+
'mod/customcert:managekeeplocalcopy' => [
108+
'captype' => 'write',
109+
'contextlevel' => CONTEXT_COURSE,
110+
'archetypes' => array(
111+
'editingteacher' => CAP_ALLOW,
112+
'manager' => CAP_ALLOW
113+
),
114+
'clonepermissionsfrom' => 'moodle/course:manageactivities'
115+
],
116+
117+
'mod/customcert:deletelocalcopy' => [
118+
'captype' => 'write',
119+
'contextlevel' => CONTEXT_COURSE,
120+
'archetypes' => array(
121+
'editingteacher' => CAP_ALLOW,
122+
'manager' => CAP_ALLOW
123+
),
124+
'clonepermissionsfrom' => 'moodle/course:manageactivities'
125+
],
126+
107127
'mod/customcert:manageemailstudents' => [
108128
'captype' => 'write',
109129
'contextlevel' => CONTEXT_COURSE,

db/install.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<FIELD NAME="language" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="Force certificate to render with specified langauge"/>
2525
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
2626
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
27+
<FIELD NAME="keeplocalcopy" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Force certificate to keep a local copy of the PDF"/>
2728
</FIELDS>
2829
<KEYS>
2930
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for customcert"/>

0 commit comments

Comments
 (0)