Skip to content

Conversation

HobieCat
Copy link

Hello there,
I've added the feature to save generated PDF to the local filesystem, and to serve them when downloading.
It came from a customer request to have a (sort of) archived PDF certificates at the time it was first viewed/generated.

Also, a 'download all certificates' link was added to the navigation menus of the course (to download a zip with all of the course certificates) and the activity itself (to download all the certificates of the single activity).

If of any interest to anyone, please merge at your convenience.

@mdjnelson
Copy link
Owner

Sorry I haven't been able to look at this until now. Can you rebase and then solve the issues reported by GHA? Thanks for your hard work.

@HobieCat
Copy link
Author

HobieCat commented Feb 3, 2024

Hi, looks like I've fixed the issues.

@mdjnelson
Copy link
Owner

mdjnelson commented Jun 23, 2025

Sorry. I have not been paying attention to the PRs and issues for a while on this plugin unless they are major breakages to Moodle versions. I appreciate your effort on this PR. Can you squash and rebased on the latested MOODLE_404_STABLE branch?

@HobieCat HobieCat force-pushed the feature/keeplocalcopy branch from 53e7779 to 60daa6d Compare June 24, 2025 14:33
@HobieCat HobieCat changed the base branch from MOODLE_402_STABLE to MOODLE_404_STABLE June 24, 2025 14:34
@HobieCat
Copy link
Author

HobieCat commented Jun 24, 2025

Hello. Should have done! Not so skilled at squashing and rebasing, hope I didn't mess it all up.

@mdjnelson
Copy link
Owner

Taking a look now, thanks!

@mdjnelson
Copy link
Owner

Just fixing conflicts and will browse code. I didn't get as much time as I wanted this weekend and I was also looking at other issues.

Just for reference this should solve #38 which would be a huge win.

- 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
@mdjnelson
Copy link
Owner

I cherry-picked this to my branch and fixed conflicts, see https://github.com/mdjnelson/moodle-mod_customcert/commits/develop/. Perhaps you would like to use this to save you troubles.

Copy link
Owner

@mdjnelson mdjnelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have commented on a few things but am confused about what the aim is here. I was thinking there it would be a certificate setting where we keep the PDFs on the server and just serve the already saved ones instead of generating when this setting is on. What exactly is this doing? Seems to be a few things.

*
* @var \mod_customcert\template
*/
protected $template;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the type here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get this, how do you need the code to look like?

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep consistent with Moodle function naming can you call this save_pdf().

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please call this get_pdf().

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, delete_pdf().

}
return false;
} catch (file_exception $e) {
// maybe log the exception
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment please.

// Trigger event.
$cm = get_coursemodule_from_instance('customcert', $customcert->id, 0, false, MUST_EXIST);
$context = \context_module::instance($cm->id);
$event = \mod_customcert\event\issue_deleted::create([
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we now removing this event?

// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Handles zip and download of certificates.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have the file https://github.com/mdjnelson/moodle-mod_customcert/blob/MOODLE_500_STABLE/download_all_certificates.php. Is this just a copy of that?

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get this, how do you need the code to look like?

@mdjnelson
Copy link
Owner

Also please address issues found in https://github.com/mdjnelson/moodle-mod_customcert/actions/runs/16251043172.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please call does_pdf_exist().

* @return array the fileinfo array
*/
protected function buildFileInfo(?int $userid = null) {

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No space needed here.

* @return array the fileinfo array
*/
private static function buildFileInfoArr ($cm, ?int $userid = null, ?int $templateid = null) {

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No space needed here.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you not call this build_file_info and specify the return type as array?

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

* @param string $courseShortname
* @return string the PDF file name
*/
public static function buildFileName($username, $templateid, $courseShortname) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify return type.

lib.php Outdated
* @param \course_context $context
* @return \navigation_node|null null if there are no certifcates available for the download
*/
function build_downloadall_node(bool $isCourseNav, stdClass $course, \context_course $context = null) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this already exists in the certificate settings.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

@mdjnelson
Copy link
Owner

What we want here is to have a certificate setting to save the PDFs to the server and serve those instead of generating them each time. The parts to do with bulk downloading have already been done in #205. If we can address the issues and just keep this as a simple "keep local copy" setting as described earlier that would be great. Thanks!

@HobieCat
Copy link
Author

HobieCat commented Jul 13, 2025

  • At the time I made this PR, there was no download_all_certificates.php, at least in the branch I was using at that time.
    Ok, I can remove that code.
  • The "event removed" is my mistake. As said, I'm not so good at rebasing and squasing.
  • For other comments, I can try to go through each one and fix code as requested.

At this point, don't you think it would be better to make a new PR? From what branch should I start?

@mdjnelson
Copy link
Owner

Thanks @HobieCat. I apologise as the problem is on my end and I did not properly pay attention to this until now. Removing the code for downloading all certificates and addressing https://github.com/mdjnelson/moodle-mod_customcert/actions/runs/16251043172 as well as any points I made not covered by GHA will mean we are getting much closer. :)

@mdjnelson
Copy link
Owner

mdjnelson commented Jul 13, 2025

You can stay on the same branch and run git fetch followed by git reset --hard <marksremote>/develop - just note this will overwrite local changes. That gives you a clean history to work from, with your work rebased on top of the latest MOODLE_500_STABLE. You’ll need to force push afterward.

If you want to completely reset and discard your branch’s changes, use git reset --hard <marksremote>/MOODLE_500_STABLE instead.

@HobieCat HobieCat force-pushed the feature/keeplocalcopy branch from 60daa6d to 0637ca1 Compare July 20, 2025 20:13
@HobieCat
Copy link
Author

Hi,
should have done as per your suggestion with a hard reset and forced push, thanks.
Afterwards I commited the required changes, but unexpected conflicts and changed files are now showing up.
If this is ok at your side, I'm ok as well. Please let me know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants