Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/keeplocalcopy #584

Open
wants to merge 37 commits into
base: MOODLE_402_STABLE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
91cf2d1
add keeplocalcopy field to the customcert table
HobieCat Oct 13, 2023
95e9862
add managekeeplocalcopy, deletelocalcopy capabilities
HobieCat Oct 13, 2023
ec00528
add keeplocalcopy to the backup structure
HobieCat Oct 13, 2023
65892a5
add keeplocalcopy setting element to forms
HobieCat Oct 13, 2023
22ca94e
add localfile class to manage local PDF files
HobieCat Oct 13, 2023
8ecc09a
add logic to store and serve local PDF files
HobieCat Oct 13, 2023
b3c1b99
add deletelocalcopy to the actions column
HobieCat Oct 13, 2023
c35d84f
add deletelocalcopy logic
HobieCat Oct 13, 2023
d4828c0
add 'download all certificates' link to nav menus
HobieCat Oct 13, 2023
90f0006
add file to download all certificates
HobieCat Oct 13, 2023
381ee0f
add lang strings
HobieCat Oct 13, 2023
054c01b
Bump version
HobieCat Oct 13, 2023
3948340
italian translation for new lang strings
HobieCat Oct 13, 2023
8d31f9d
fix sendPDF method signature
HobieCat Oct 16, 2023
c25341b
add buildFileName static method
HobieCat Oct 17, 2023
86e95c0
fix to download certs when keeplocalcopy is false
HobieCat Oct 17, 2023
b2ee500
bug fix and obey keeplocalcopy setting
HobieCat Nov 20, 2023
57ce9a6
add keeplocalcopy field to the customcert table
HobieCat Oct 13, 2023
6ba8d3f
add managekeeplocalcopy, deletelocalcopy capabilities
HobieCat Oct 13, 2023
48b5e90
add keeplocalcopy to the backup structure
HobieCat Oct 13, 2023
79d2f4d
add keeplocalcopy setting element to forms
HobieCat Oct 13, 2023
ab1bf52
add localfile class to manage local PDF files
HobieCat Oct 13, 2023
663fc6c
add logic to store and serve local PDF files
HobieCat Oct 13, 2023
b8ae136
add deletelocalcopy to the actions column
HobieCat Oct 13, 2023
37cd6a4
add deletelocalcopy logic
HobieCat Oct 13, 2023
11038d3
add 'download all certificates' link to nav menus
HobieCat Oct 13, 2023
f67cfa9
add file to download all certificates
HobieCat Oct 13, 2023
3420bac
add lang strings
HobieCat Oct 13, 2023
f148bae
Bump version
HobieCat Oct 13, 2023
b10606b
italian translation for new lang strings
HobieCat Oct 13, 2023
9450163
fix sendPDF method signature
HobieCat Oct 16, 2023
6b683ce
add buildFileName static method
HobieCat Oct 17, 2023
69f9dac
fix to download certs when keeplocalcopy is false
HobieCat Oct 17, 2023
a813f8e
bug fix and obey keeplocalcopy setting
HobieCat Nov 20, 2023
369e887
fix minor bug in customcert_extend_navigation_course
HobieCat Feb 3, 2024
a83f1b6
remove duplicate delete_records call
HobieCat Feb 3, 2024
53e7779
Merge branch 'feature/keeplocalcopy' of github.com:HobieCat/moodle-mo…
HobieCat Feb 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backup/moodle2/backup_customcert_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function define_structure() {
// The instance.
$customcert = new backup_nested_element('customcert', ['id'], [
'templateid', 'name', 'intro', 'introformat', 'requiredtime', 'verifyany', 'emailstudents',
'emailteachers', 'emailothers', 'protection', 'timecreated', 'timemodified']);
'emailteachers', 'emailothers', 'protection', 'timecreated', 'timemodified', 'keeplocalcopy']);

// The template.
$template = new backup_nested_element('template', ['id'], [
Expand Down
249 changes: 249 additions & 0 deletions classes/localfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
<?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/>.

/**
* Class represents a local file of an issued certificate.
*
* @package mod_customcert
* @copyright 2023 Giorgio Consorti <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_customcert;

use file_exception;

defined('MOODLE_INTERNAL') || die();

/**
* Class represents a local file of an issued certificate.
*
* @package mod_customcert
* @copyright 023 Giorgio Consorti <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class localfile {

/**
* The template representing the content of the file.
*
* @var \mod_customcert\template
*/
protected $template;

/**
* The component name for the file storage.
*/
const component = 'mod_customcert';

/**
* The filearea name for the file storage.
*/
const filearea = 'customcert_issues';

/**
* The constructor.
*
* @param \mod_customcert\template $template
*/
public function __construct(\mod_customcert\template $template) {
$this->template = $template;
}

/**
* Save the PDF to the file storage.
*
* @param string $pdfcontent string content of the pdf
* @param integer|null $userid the id of the user whose certificate we want to save
* @return stored_file|false the stored_file object on success, false on error
*/
public function savePDF(string $pdfcontent, ?int $userid = null) {
global $CFG, $USER;
require_once($CFG->libdir . '/filelib.php');

if (empty($userid)) {
$userid = $USER->id;
}

try {
$file = $this->getPDF($userid);
if (!$file) {
// Create file containing the pdf
$fs = get_file_storage();
$file = $fs->create_file_from_string($this->buildFileInfo($userid), $pdfcontent);
}
return $file;
} catch (file_exception $e) {
// maybe log the exception
return false;
}
}

/**
* Get the PDF from the file storage.
*
* @param integer|null $userid the id of the user whose certificate we want to get
* @return \stored_file|false the stored_file object on success, false on error
*/
public function getPDF(?int $userid = null) {
global $CFG, $USER;
require_once($CFG->libdir . '/filelib.php');

if (empty($userid)) {
$userid = $USER->id;
}

$fileinfo = $this->buildFileInfo($userid);
$fs = get_file_storage();
return $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
$fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
}

/**
* Delete the PDF from the file storage.
*
* @param integer|null $userid the id of the user whose certificate we want to get
* @return bool true on success
*/
public function deletePDF(?int $userid = null) {
global $USER;

if (empty($userid)) {
$userid = $USER->id;
}

try {
$file = $this->getPDF($userid);
if ($file) {
return $file->delete();
}
return false;
} catch (file_exception $e) {
// maybe log the exception
return false;
}
}

/**
* Send the PDF to the browser or return it as a string.
*
* @param int $userid the id of the user whose certificate we want to view
* @param string $deliveryoption the delivery option of the customcert
* @param bool $return Do we want to return the contents of the PDF?
* @return string|void Can return the PDF in string format if specified.
*/
public function sendPDF(?int $userid = NULL, string $deliveryoption = certificate::DELIVERY_OPTION_DOWNLOAD, bool $return = false) {
global $USER;

if (empty($userid)) {
$userid = $USER->id;
}

$file = $this->getPDF($userid);
if ($file) {
if ($return) {
return $file->get_content();
} else {
// send the file to the browser
send_stored_file(
$file,
0,
0,
$deliveryoption == certificate::DELIVERY_OPTION_DOWNLOAD,
['filename' => $file->get_filename()]
);
die();
}
}
}

/**
* Check if a pdf exists in the file storage area.
*
* @param \stdClass $cm the course module
* @param integer|null $userid the id of the user whose PDF we want to check
* @param integer|null $templateid the template id of the customcert we want to check
* @return \stored_file|false the stored_file object on success, false on error
*/
public static function existsPDF($cm, ?int $userid = null, ?int $templateid = null) {

$fileinfo = self::buildFileInfoArr($cm, $userid, $templateid);
$fs = get_file_storage();
return $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'],
$fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename']);
}

/**
* Build the fileinfo array needed by the file storage.
*
* @param integer|null $userid the id of the user whose fileinfo array we want to generate
* @return array the fileinfo array
*/
protected function buildFileInfo(?int $userid = null) {

return self::buildFileInfoArr($this->template->get_cm(), $userid, $this->template->get_id());
}

/**
* Build the fileinfo array needed by the file storage, static version.
*
* @param \stdClass $cm the course module
* @param integer|null $userid the id of the user whose fileinfo array we want to generate
* @param integer|null $templateid the template id of the customcert of the array we want to generate
* @return array the fileinfo array
*/
private static function buildFileInfoArr ($cm, ?int $userid = null, ?int $templateid = null) {

/** @var \moodle_database $DB */
global $DB, $USER;

if (empty($userid)) {
$userid = $USER->id;
}

if (empty($templateid)) {
$customcert = $DB->get_record('customcert', array('id' => $cm->instance), '*', MUST_EXIST);
$templateid = $customcert->templateid;
}

$course = $DB->get_record('course', ['id' => $cm->course]);
$context = $DB->get_record('context', ['contextlevel' => '50', 'instanceid' => $course->id]);
$user_info = $DB->get_record('user', ['id' => $userid]);

return [
'contextid' => $context->id,
'component' => self::component,
'filearea' => self::filearea,
'itemid' => $templateid,
'userid' => $USER->id,
'author' => fullname($USER),
'filepath' => '/' . $course->id . '/',
'filename' => self::buildFileName($user_info->username, $templateid, $course->shortname),
];
}

/**
* Build the PDF filename.
*
* @param string $username
* @param string $templateid
* @param string $courseShortname
* @return string the PDF file name
*/
public static function buildFileName($username, $templateid, $courseShortname) {
return $username . '_cert-' . $templateid . '_course-' . $courseShortname . '.pdf';
}
}
40 changes: 33 additions & 7 deletions classes/report_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,42 @@ public function col_download($user) {
public function col_actions($user) {
global $OUTPUT;

$icon = new \pix_icon('i/delete', get_string('delete'));
$link = new \moodle_url('/mod/customcert/view.php',
$actions = [
[
'id' => $this->cm->id,
'deleteissue' => $user->issueid,
'sesskey' => sesskey()
'icon' => new \pix_icon('i/delete', get_string('delete')),
'link' => new \moodle_url(
'/mod/customcert/view.php',
[
'id' => $this->cm->id,
'deleteissue' => $user->issueid,
'sesskey' => sesskey()
]
),
'attributes' => ['class' => 'action-icon delete-icon'],
]
);
];

if (has_capability('mod/customcert:deletelocalcopy', \context_module::instance($this->cm->id)) && localfile::existsPDF($this->cm, $user->id)) {
$actions[] = [
'icon' => new \pix_icon('deletelocalcopy', get_string('deletelocalcopy', 'customcert'), 'customcert'),
'link' => new \moodle_url(
'/mod/customcert/view.php',
[
'id' => $this->cm->id,
'deleteissue' => $user->issueid,
'deletelocalcopy' => 1,
'sesskey' => sesskey()
]
),
'attributes' => ['class' => 'action-icon deletelocalcopy-icon'],
];
}

return $OUTPUT->action_icon($link, $icon, null, ['class' => 'action-icon delete-icon']);
return implode(" ", array_map(
fn ($action) =>
$OUTPUT->action_icon($action['link'], $action['icon'], null, $action['attributes'] ?? []),
$actions
));
}

/**
Expand Down
17 changes: 17 additions & 0 deletions classes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class template {
*/
protected $contextid;

/**
* @var \mod_customcert\localfile the local file for the template.
*/
protected $localfile;

/**
* The constructor.
*
Expand All @@ -57,6 +62,7 @@ public function __construct($template) {
$this->id = $template->id;
$this->name = $template->name;
$this->contextid = $template->contextid;
$this->localfile = new localfile($this);
}

/**
Expand Down Expand Up @@ -314,6 +320,13 @@ public function generate_pdf(bool $preview = false, int $userid = null, bool $re
$deliveryoption = $customcert->deliveryoption;
}

if ($customcert->keeplocalcopy) {
$retval = $this->localfile->sendPDF($userid, $deliveryoption, $return);
if ($return && !empty($retval)) {
return $retval;
}
}

// Remove full-stop at the end, if it exists, to avoid "..pdf" being created and being filtered by clean_filename.
$filename = rtrim(format_string($this->name, true, ['context' => $this->get_context()]), '.');

Expand Down Expand Up @@ -362,6 +375,10 @@ public function generate_pdf(bool $preview = false, int $userid = null, bool $re
}
}

if ($customcert->keeplocalcopy) {
$this->localfile->savePDF($pdf->Output('', 'S'), $userid);
}

if ($return) {
return $pdf->Output('', 'S');
}
Expand Down
20 changes: 20 additions & 0 deletions db/access.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@
]
],

'mod/customcert:managekeeplocalcopy' => [
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
],

'mod/customcert:deletelocalcopy' => [
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
],

'mod/customcert:manageemailstudents' => [
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
Expand Down
1 change: 1 addition & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<FIELD NAME="language" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="Force certificate to render with specified langauge"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="keeplocalcopy" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Force certificate to keep a local copy of the PDF"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for customcert"/>
Expand Down
Loading