From 6eb8e5b27c35d079d1b4275c15ea7b93fb2efc9c Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Thu, 2 Feb 2017 17:17:41 +0800 Subject: [PATCH] #48 Added task and settings responsible for emailing certificates --- backup/moodle2/backup_customcert_stepslib.php | 6 +- classes/certificate.php | 12 +- classes/output/email/renderer.php | 46 +++ classes/output/email/renderer_textemail.php | 46 +++ classes/output/email_certificate.php | 108 +++++++ classes/output/renderer.php | 11 + classes/task/email_certificate_task.php | 222 +++++++++++++++ classes/template.php | 7 +- db/install.xml | 4 + db/tasks.php | 38 +++ db/upgrade.php | 35 +++ lang/en/customcert.php | 15 + mod_form.php | 12 + templates/email_certificate_html.mustache | 46 +++ templates/email_certificate_text.mustache | 44 +++ tests/email_certificate_task_test.php | 269 ++++++++++++++++++ tests/generator/lib.php | 64 +++++ version.php | 4 +- view.php | 2 +- 19 files changed, 982 insertions(+), 9 deletions(-) create mode 100644 classes/output/email/renderer.php create mode 100644 classes/output/email/renderer_textemail.php create mode 100644 classes/output/email_certificate.php create mode 100644 classes/task/email_certificate_task.php create mode 100644 db/tasks.php create mode 100644 templates/email_certificate_html.mustache create mode 100644 templates/email_certificate_text.mustache create mode 100644 tests/email_certificate_task_test.php create mode 100644 tests/generator/lib.php diff --git a/backup/moodle2/backup_customcert_stepslib.php b/backup/moodle2/backup_customcert_stepslib.php index ac33bba..8ab6a3b 100644 --- a/backup/moodle2/backup_customcert_stepslib.php +++ b/backup/moodle2/backup_customcert_stepslib.php @@ -42,8 +42,8 @@ class backup_customcert_activity_structure_step extends backup_activity_structur // The instance. $customcert = new backup_nested_element('customcert', array('id'), array( - 'templateid', 'name', 'intro', 'introformat', 'requiredtime', 'protection', - 'timecreated', 'timemodified')); + 'templateid', 'name', 'intro', 'introformat', 'requiredtime', 'emailstudents', + 'emailteachers', 'emailothers', 'protection', 'timecreated', 'timemodified')); // The template. $template = new backup_nested_element('template', array('id'), array( @@ -64,7 +64,7 @@ class backup_customcert_activity_structure_step extends backup_activity_structur // The issues. $issues = new backup_nested_element('issues'); $issue = new backup_nested_element('issue', array('id'), array( - 'customcertid', 'userid', 'timecreated', 'code')); + 'customcertid', 'userid', 'timecreated', 'emailed', 'code')); // Build the tree. $customcert->add_child($issues); diff --git a/classes/certificate.php b/classes/certificate.php index c0a48d4..45c2ef5 100644 --- a/classes/certificate.php +++ b/classes/certificate.php @@ -165,14 +165,22 @@ class certificate { * Get the time the user has spent in the course. * * @param int $courseid + * @param int $userid * @return int the total time spent in seconds */ - public static function get_course_time($courseid) { + public static function get_course_time($courseid, $userid = 0) { global $CFG, $DB, $USER; + if (empty($userid)) { + $userid = $USER->id; + } + $logmanager = get_log_manager(); $readers = $logmanager->get_readers(); $enabledreaders = get_config('tool_log', 'enabled_stores'); + if (empty($enabledreaders)) { + return 0; + } $enabledreaders = explode(',', $enabledreaders); // Go through all the readers until we find one that we can use. @@ -201,7 +209,7 @@ class certificate { WHERE userid = :userid AND $coursefield = :courseid ORDER BY $timefield ASC"; - $params = array('userid' => $USER->id, 'courseid' => $courseid); + $params = array('userid' => $userid, 'courseid' => $courseid); $totaltime = 0; if ($logs = $DB->get_recordset_sql($sql, $params)) { foreach ($logs as $log) { diff --git a/classes/output/email/renderer.php b/classes/output/email/renderer.php new file mode 100644 index 0000000..e1295e7 --- /dev/null +++ b/classes/output/email/renderer.php @@ -0,0 +1,46 @@ +. + +/** + * Email certificate as html renderer. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_customcert\output\email; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Email certificate as html renderer. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends \mod_customcert\output\renderer { + + /** + * The template name for this renderer. + * + * @return string + */ + public function get_template_name() { + return 'email_certificate_html'; + } +} diff --git a/classes/output/email/renderer_textemail.php b/classes/output/email/renderer_textemail.php new file mode 100644 index 0000000..7ef8f2c --- /dev/null +++ b/classes/output/email/renderer_textemail.php @@ -0,0 +1,46 @@ +. + +/** + * Email certificate as text renderer. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_customcert\output\email; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Email certificate as text renderer. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer_textemail extends \mod_customcert\output\renderer { + + /** + * The template name for this renderer. + * + * @return string + */ + public function get_template_name() { + return 'email_certificate_text'; + } +} diff --git a/classes/output/email_certificate.php b/classes/output/email_certificate.php new file mode 100644 index 0000000..ceb05ac --- /dev/null +++ b/classes/output/email_certificate.php @@ -0,0 +1,108 @@ +. + +/** + * Email certificate renderable. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_customcert\output; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Email certificate renderable. + * + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class email_certificate implements \renderable, \templatable { + + /** + * @var bool Are we emailing the student? + */ + public $isstudent; + + /** + * @var string The name of the user who owns the certificate. + */ + public $userfullname; + + /** + * @var string The course full name. + */ + public $coursefullname; + + /** + * @var int The certificate name. + */ + public $certificatename; + + /** + * @var int The course module id. + */ + public $cmid; + + /** + * Constructor. + * + * @param bool $isstudent Are we emailing the student? + * @param string $userfullname The name of the user who owns the certificate. + * @param string $coursefullname The name of the course. + * @param string $certificatename The name of the certificate. + * @param string $cmid The course module id. + */ + public function __construct($isstudent, $userfullname, $coursefullname, $certificatename, $cmid) { + $this->isstudent = $isstudent; + $this->userfullname = $userfullname; + $this->coursefullname = $coursefullname; + $this->certificatename = $certificatename; + $this->cmid = $cmid; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param \renderer_base $renderer The render to be used for formatting the email + * @return \stdClass The data ready for use in a mustache template + */ + public function export_for_template(\renderer_base $renderer) { + $data = new \stdClass(); + + // Used for the body text. + $info = new \stdClass(); + $info->userfullname = $this->userfullname; + $info->certificatename = $this->certificatename; + $info->coursefullname = $this->coursefullname; + + if ($this->isstudent) { + $data->emailgreeting = get_string('emailstudentgreeting', 'customcert', $this->userfullname); + $data->emailbody = get_string('emailstudentbody', 'customcert', $info); + $data->emailcertificatelink = new \moodle_url('/mod/customcert/view.php', array('id' => $this->cmid)); + $data->emailcertificatetext = get_string('emailstudentcertificatelinktext', 'customcert'); + } else { + $data->emailgreeting = get_string('emailnonstudentgreeting', 'customcert'); + $data->emailbody = get_string('emailnonstudentbody', 'customcert', $info); + $data->emailcertificatelink = new \moodle_url('/mod/customcert/report.php', array('id' => $this->cmid)); + $data->emailcertificatetext = get_string('emailnonstudentcertificatelinktext', 'customcert'); + } + + return $data; + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index df946b7..c8eeb37 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -49,4 +49,15 @@ class renderer extends plugin_renderer_base { $data = $page->export_for_template($this); return parent::render_from_template('mod_customcert/verify_certificate_results', $data); } + + /** + * Formats the email used to send the certificate by the email_certificate_task. + * + * @param email_certificate $certificate The certificate to email + * @return string + */ + public function render_email_certificate(email_certificate $certificate) { + $data = $certificate->export_for_template($this); + return $this->render_from_template('mod_customcert/' . $this->get_template_name(), $data); + } } diff --git a/classes/task/email_certificate_task.php b/classes/task/email_certificate_task.php new file mode 100644 index 0000000..3493816 --- /dev/null +++ b/classes/task/email_certificate_task.php @@ -0,0 +1,222 @@ +. + +/** + * A scheduled task for emailing certificates. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_customcert\task; + +defined('MOODLE_INTERNAL') || die(); + +/** + * A scheduled task for emailing certificates. + * + * @package mod_customcert + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class email_certificate_task extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('taskemailcertificate', 'customcert'); + } + + /** + * Execute. + */ + public function execute() { + global $DB, $PAGE; + + // Get all the certificates that have requested someone get emailed. + $sql = "SELECT c.*, ct.id as templateid, ct.name as templatename, ct.contextid, co.id as courseid, + co.shortname as coursename + FROM {customcert} c + JOIN {customcert_templates} ct + ON c.templateid = ct.id + JOIN {course} co + ON c.course = co.id + WHERE (c.emailstudents = :emailstudents + OR c.emailteachers = :emailteachers + OR c.emailothers != '')"; + if ($customcerts = $DB->get_records_sql($sql, array('emailstudents' => 1, 'emailteachers' => 1))) { + // The renderers used for sending emails. + $htmlrenderer = $PAGE->get_renderer('mod_customcert', 'email', 'htmlemail'); + $textrenderer = $PAGE->get_renderer('mod_customcert', 'email', 'textemail'); + foreach ($customcerts as $customcert) { + // Get the context. + $context = \context::instance_by_id($customcert->contextid); + + // Get the person we are going to send this email on behalf of. + // Look through the teachers. + if ($teachers = get_users_by_capability($context, 'moodle/course:update', 'u.*', 'u.id ASC', + '', '', '', '', false, true)) { + $teachers = sort_by_roleassignment_authority($teachers, $context); + $userfrom = reset($teachers); + } else { // Ok, no teachers, use administrator name. + $userfrom = get_admin(); + } + + $coursename = format_string($customcert->coursename, true, array('context' => $context)); + $certificatename = format_string($customcert->name, true, array('context' => $context)); + + // Used to create the email subject. + $info = new \stdClass; + $info->coursename = $coursename; + $info->certificatename = $certificatename; + + // Get a list of issues that have not yet been emailed. + $userfields = get_all_user_name_fields(true, 'u'); + $sql = "SELECT u.id, u.username, $userfields, u.email, ci.id as issueid + FROM {customcert_issues} ci + JOIN {user} u + ON ci.userid = u.id + WHERE ci.customcertid = :customcertid + AND emailed = :emailed"; + $issuedusers = $DB->get_records_sql($sql, array('customcertid' => $customcert->id, + 'emailed' => 0)); + + // Now, get a list of users who can access the certificate but have not yet. + $enrolledusers = get_enrolled_users(\context_course::instance($customcert->courseid), 'mod/customcert:view'); + foreach ($enrolledusers as $enroluser) { + // Check if the user has already been issued. + if (in_array($enroluser->id, array_keys((array) $issuedusers))) { + continue; + } + + // Now check if the certificate is not visible to the current user. + $cm = get_fast_modinfo($customcert->courseid, $enroluser->id)->instances['customcert'][$customcert->id]; + if (!$cm->uservisible) { + continue; + } + + // Don't want to email those with the capability to manage the certificate. + if (has_capability('mod/customcert:manage', $context, $enroluser->id)) { + continue; + } + + // Check that they have passed the required time. + if (\mod_customcert\certificate::get_course_time($customcert->courseid, $enroluser->id) < + ($customcert->requiredtime * 60)) { + continue; + } + + // Ok, issue them the certificate. + $customcertissue = new \stdClass(); + $customcertissue->customcertid = $customcert->id; + $customcertissue->userid = $enroluser->id; + $customcertissue->code = \mod_customcert\certificate::generate_code(); + $customcertissue->emailed = 0; + $customcertissue->timecreated = time(); + + // Insert the record into the database. + $issueid = $DB->insert_record('customcert_issues', $customcertissue); + + // Add them to the array so we email them. + $enroluser->issueid = $issueid; + $issuedusers[] = $enroluser; + } + + // Now, email the people we need to. + if ($issuedusers) { + foreach ($issuedusers as $user) { + $userfullname = fullname($user); + + // Create a directory to store the PDF we will be sending. + $tempdir = make_temp_directory('certificate/attachment'); + if (!$tempdir) { + return false; + } + + // Now, get the PDF. + $template = new \stdClass(); + $template->id = $customcert->templateid; + $template->name = $customcert->templatename; + $template->contextid = $customcert->contextid; + $template = new \mod_customcert\template($template); + $filecontents = $template->generate_pdf(false, $user->id, true); + + // Set the name of the file we are going to send. + $filename = $coursename . '_' . $certificatename; + $filename = \core_text::entities_to_utf8($filename); + $filename = strip_tags($filename); + $filename = rtrim($filename, '.'); + $filename = str_replace('&', '_', $filename) . '.pdf'; + + // Create the file we will be sending. + $tempfile = $tempdir . '/' . md5(microtime() . $user->id) . '.pdf'; + file_put_contents($tempfile, $filecontents); + + if ($customcert->emailstudents) { + $renderable = new \mod_customcert\output\email_certificate(true, $userfullname, $coursename, + $certificatename, $customcert->contextid); + + $subject = get_string('emailstudentsubject', 'customcert', $info); + $message = $textrenderer->render($renderable); + $messagehtml = $htmlrenderer->render($renderable); + email_to_user($user, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, $filename); + } + + if ($customcert->emailteachers) { + $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $coursename, + $certificatename, $customcert->contextid); + + $subject = get_string('emailnonstudentsubject', 'customcert', $info); + $message = $textrenderer->render($renderable); + $messagehtml = $htmlrenderer->render($renderable); + foreach ($teachers as $teacher) { + email_to_user($teacher, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, + $filename); + } + } + + if (!empty($customcert->emailothers)) { + $others = explode(',', $customcert->emailothers); + foreach ($others as $email) { + $email = trim($email); + if (validate_email($email)) { + $renderable = new \mod_customcert\output\email_certificate(false, $userfullname, $coursename, + $certificatename, $customcert->contextid); + + $subject = get_string('emailnonstudentsubject', 'customcert', $info); + $message = $textrenderer->render($renderable); + $messagehtml = $htmlrenderer->render($renderable); + + $emailuser = new \stdClass(); + $emailuser->id = -1; + $emailuser->email = $email; + email_to_user($emailuser, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, + $filename); + } + } + } + + // Set the field so that it is emailed. + $DB->set_field('customcert_issues', 'emailed', 1, array('id' => $user->issueid)); + } + } + } + } + } +} diff --git a/classes/template.php b/classes/template.php index 710ba0e..b0cfea3 100644 --- a/classes/template.php +++ b/classes/template.php @@ -250,8 +250,10 @@ class template { * * @param bool $preview true if it is a preview, false otherwise * @param int $userid the id of the user whose certificate we want to view + * @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 generate_pdf($preview = false, $userid = null) { + public function generate_pdf($preview = false, $userid = null, $return = false) { global $CFG, $DB, $USER; if (empty($userid)) { @@ -303,6 +305,9 @@ class template { } } } + if ($return) { + return $pdf->Output('', 'S'); + } $pdf->Output($filename, 'D'); } } diff --git a/db/install.xml b/db/install.xml index 09a9964..e777144 100644 --- a/db/install.xml +++ b/db/install.xml @@ -13,6 +13,9 @@ + + + @@ -41,6 +44,7 @@ + diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000..91f6e2a --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,38 @@ +. + +/** + * Definition of customcert scheduled tasks. + * + * @package mod_customcert + * @category task + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'mod_customcert\task\email_certificate_task', + 'blocking' => 0, + 'minute' => '*', + 'hour' => '*', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*' + ) +); diff --git a/db/upgrade.php b/db/upgrade.php index 91137ec..4499e91 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -45,5 +45,40 @@ function xmldb_customcert_upgrade($oldversion) { upgrade_mod_savepoint(true, 2016052306, 'customcert'); } + if ($oldversion < 2016052308) { + $table = new xmldb_table('customcert'); + $field = new xmldb_field('emailstudents', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'requiredtime'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('emailteachers', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'emailstudents'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('emailothers', XMLDB_TYPE_TEXT, null, null, null, null, null, 'emailteachers'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $table = new xmldb_table('customcert_issues'); + $field = new xmldb_field('emailed', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'code'); + + // Conditionally launch add field. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Savepoint reached. + upgrade_mod_savepoint(true, 2016052308, 'customcert'); + } + return true; } diff --git a/lang/en/customcert.php b/lang/en/customcert.php index 8739fce..78ae56e 100644 --- a/lang/en/customcert.php +++ b/lang/en/customcert.php @@ -57,6 +57,20 @@ $string['elements_help'] = 'This is the list of elements that will be displayed Please note: The elements are rendered in this order. The order can be changed by using the arrows next to each element.'; $string['elementwidth'] = 'Width'; $string['elementwidth_help'] = 'Specify the width of the element - \'0\' means that there is no width constraint.'; +$string['emailnonstudentbody'] = 'Attached is the certificate \'{$a->certificatename}\' for \'{$a->userfullname}\' for the course \'{$a->coursefullname}\'.'; +$string['emailnonstudentcertificatelinktext'] = 'View certificate report'; +$string['emailnonstudentgreeting'] = 'Hi'; +$string['emailnonstudentsubject'] = '{$a->coursename}: {$a->certificatename}'; +$string['emailstudentbody'] = 'Attached is your certificate \'{$a->certificatename}\' for the course \'{$a->coursefullname}\'.'; +$string['emailstudentcertificatelinktext'] = 'View certificate'; +$string['emailstudentgreeting'] = 'Dear {$a}'; +$string['emailstudentsubject'] = '{$a->coursename}: {$a->certificatename}'; +$string['emailstudents'] = 'Email students'; +$string['emailstudents_help'] = 'If set this will email the students a copy of the certificate when it becomes available.'; +$string['emailteachers'] = 'Email teachers'; +$string['emailteachers_help'] = 'If set this will email the teachers a copy of the certificate when it becomes available.'; +$string['emailothers'] = 'Email others'; +$string['emailothers_help'] = 'If set this will email the email addresses listed here (separated by a comma) with a copy of the certificate when it becomes available.'; $string['font'] = 'Font'; $string['font_help'] = 'The font used when generating this element.'; $string['fontcolour'] = 'Colour'; @@ -127,6 +141,7 @@ $string['showposxy_desc'] = 'This will show the X and Y position when editing of This isn\'t required if you plan on solely using the drag and drop interface for this purpose.'; $string['summaryofissue'] = 'Summary of issue'; +$string['taskemailcertificate'] = 'Handles emailing certificates.'; $string['templatename'] = 'Template name'; $string['templatenameexists'] = 'That template name is currently in use, please choose another.'; $string['topcenter'] = 'Center'; diff --git a/mod_form.php b/mod_form.php index be88640..bc445bd 100644 --- a/mod_form.php +++ b/mod_form.php @@ -57,6 +57,18 @@ class mod_customcert_mod_form extends moodleform_mod { $mform->addElement('header', 'options', get_string('options', 'customcert')); + $mform->addElement('selectyesno', 'emailstudents', get_string('emailstudents', 'customcert')); + $mform->setType('emailstudents', 0); + $mform->addHelpButton('emailstudents', 'emailstudents', 'customcert'); + + $mform->addElement('selectyesno', 'emailteachers', get_string('emailteachers', 'customcert')); + $mform->setDefault('emailteachers', 0); + $mform->addHelpButton('emailteachers', 'emailteachers', 'customcert'); + + $mform->addElement('text', 'emailothers', get_string('emailothers', 'customcert'), array('size' => '40')); + $mform->setType('emailothers', PARAM_TEXT); + $mform->addHelpButton('emailothers', 'emailothers', 'customcert'); + $mform->addElement('text', 'requiredtime', get_string('coursetimereq', 'customcert'), array('size' => '3')); $mform->setType('requiredtime', PARAM_INT); $mform->addHelpButton('requiredtime', 'coursetimereq', 'customcert'); diff --git a/templates/email_certificate_html.mustache b/templates/email_certificate_html.mustache new file mode 100644 index 0000000..ecbd6cb --- /dev/null +++ b/templates/email_certificate_html.mustache @@ -0,0 +1,46 @@ +{{! + 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 . +}} +{{! + @template mod_customcert/email_certificate_html + + The certificate verification result + + Classes required for JS: + * None + + Data attibutes required for JS: + * All data attributes are required + + Context variables required for this template: + * emailgreeting + * emailbody + * emailcertificatelink + * emailcertificatelinktext + + Example context (json): + { + "emailgreeting": "Dear Angus MacGyver", + "emailbody": "Attached is your certificate 'The basics' for the course 'Survival as an '80s action hero'", + "emailcertificatelink": "http://yoursite.com/mod/customcert/view.php?id=4", + "emailcertificatelinktext": "Certificate report" + } +}} +{{{emailgreeting}}} + +{{{emailbody}}} + +{{emailcertificatelinktext}}. diff --git a/templates/email_certificate_text.mustache b/templates/email_certificate_text.mustache new file mode 100644 index 0000000..3615b0e --- /dev/null +++ b/templates/email_certificate_text.mustache @@ -0,0 +1,44 @@ +{{! + 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 . +}} +{{! + @template mod_customcert/email_certificate_html + + The certificate verification result + + Classes required for JS: + * None + + Data attibutes required for JS: + * All data attributes are required + + Context variables required for this template: + * emailgreeting + * emailbody + * emailcertificatelink + * emailcertificatelinktext + + Example context (json): + { + "emailgreeting": "Dear Angus MacGyver", + "emailbody": "Attached is your certificate 'The basics' for the course 'Survival as an '80s action hero'", + "emailcertificatelink": "http://yoursite.com/mod/customcert/view.php?id=4", + "emailcertificatelinktext": "Certificate report" + } +}} +{{{emailgreeting}}} + +{{{emailbody}}} diff --git a/tests/email_certificate_task_test.php b/tests/email_certificate_task_test.php new file mode 100644 index 0000000..abbfcf3 --- /dev/null +++ b/tests/email_certificate_task_test.php @@ -0,0 +1,269 @@ +. + +/** + * File contains the unit tests for the email certificate task. + * + * @package mod_customcert + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +/** + * Unit tests for the email certificate task. + * + * @package mod_customcert + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_customcert_task_email_certificate_task_testcase extends advanced_testcase { + + /** + * Test set up. + */ + public function setUp() { + $this->resetAfterTest(); + } + + /** + * Tests the email certificate task for students. + */ + public function test_email_certificates_students() { + global $CFG, $DB; + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create some users. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(array('firstname' => 'Teacher', 'lastname' => 'One')); + + // Enrol two of them in the course as students. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + + // Enrol one of the users as a teacher. + $this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleids['editingteacher']); + + // Create a custom certificate. + $customcert = $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, + 'emailstudents' => 1)); + + // Ok, now issue this to one user. + $customcertissue = new stdClass(); + $customcertissue->customcertid = $customcert->id; + $customcertissue->userid = $user1->id; + $customcertissue->code = \mod_customcert\certificate::generate_code(); + $customcertissue->timecreated = time(); + $customcertissue->emailed = 0; + + // Insert the record into the database. + $DB->insert_record('customcert_issues', $customcertissue); + + // Confirm there is only one entry in this table. + $this->assertEquals(1, $DB->count_records('customcert_issues')); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new \mod_customcert\task\email_certificate_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Get the issues from the issues table now. + $issues = $DB->get_records('customcert_issues'); + $this->assertCount(2, $issues); + + // Confirm that it was marked as emailed and was not issued to the teacher. + foreach ($issues as $issue) { + $this->assertEquals(1, $issue->emailed); + $this->assertNotEquals($user3->id, $issue->userid); + } + + // Confirm that we sent out emails to the two users. + $this->assertCount(2, $emails); + + $this->assertContains(fullname($user3), $emails[0]->header); + $this->assertEquals($CFG->noreplyaddress, $emails[0]->from); + $this->assertEquals($user1->email, $emails[0]->to); + + $this->assertContains(fullname($user3), $emails[1]->header); + $this->assertEquals($CFG->noreplyaddress, $emails[1]->from); + $this->assertEquals($user2->email, $emails[1]->to); + } + + /** + * Tests the email certificate task for teachers. + */ + public function test_email_certificates_teachers() { + global $CFG, $DB; + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create some users. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(array('firstname' => 'Teacher', 'lastname' => 'One')); + + // Enrol two of them in the course as students. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + + // Enrol one of the users as a teacher. + $this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleids['editingteacher']); + + // Create a custom certificate. + $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, + 'emailteachers' => 1)); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new \mod_customcert\task\email_certificate_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Confirm that we only sent out 2 emails, both emails to the teacher for the two students. + $this->assertCount(2, $emails); + + $this->assertContains(fullname($user3), utf8_encode($emails[0]->header)); + $this->assertEquals($CFG->noreplyaddress, $emails[0]->from); + $this->assertEquals($user3->email, $emails[0]->to); + + $this->assertContains(fullname($user3), utf8_encode($emails[1]->header)); + $this->assertEquals($CFG->noreplyaddress, $emails[1]->from); + $this->assertEquals($user3->email, $emails[1]->to); + } + + /** + * Tests the email certificate task for others. + */ + public function test_email_certificates_others() { + global $CFG; + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create some users. + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + // Enrol two of them in the course as students. + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + $this->getDataGenerator()->enrol_user($user2->id, $course->id); + + // Create a custom certificate. + $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, + 'emailothers' => 'testcustomcert@example.com, doo@dah')); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new \mod_customcert\task\email_certificate_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Confirm that we only sent out 2 emails, both emails to the other address that was valid for the two students. + $this->assertCount(2, $emails); + + $this->assertContains(fullname(get_admin()), utf8_encode($emails[0]->header)); + $this->assertEquals($CFG->noreplyaddress, $emails[0]->from); + $this->assertEquals('testcustomcert@example.com', $emails[0]->to); + + $this->assertContains(fullname(get_admin()), utf8_encode($emails[1]->header)); + $this->assertEquals($CFG->noreplyaddress, $emails[1]->from); + $this->assertEquals('testcustomcert@example.com', $emails[1]->to); + } + + /** + * Tests the email certificate task when the certificate is not visible. + */ + public function test_email_certificates_students_not_visible() { + global $DB; + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a user. + $user1 = $this->getDataGenerator()->create_user(); + + // Enrol them in the course. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + + // Create a custom certificate. + $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, 'emailstudents' => 1)); + + // Remove the permission for the user to view the certificate. + assign_capability('mod/customcert:view', CAP_PROHIBIT, $roleids['student'], \context_course::instance($course->id)); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new \mod_customcert\task\email_certificate_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Confirm there are no issues as the user did not have permissions to view it. + $issues = $DB->get_records('customcert_issues'); + $this->assertCount(0, $issues); + + // Confirm no emails were sent. + $this->assertCount(0, $emails); + } + + /** + * Tests the email certificate task when the student has not met the required time for the course. + */ + public function test_email_certificates_students_havent_met_required_time() { + global $DB; + + // Set the standard log to on. + set_config('enabled_stores', 'logstore_standard', 'tool_log'); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a user. + $user1 = $this->getDataGenerator()->create_user(); + + // Enrol them in the course. + $this->getDataGenerator()->enrol_user($user1->id, $course->id); + + // Create a custom certificate. + $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, 'emailstudents' => 1, + 'requiredtime' => '60')); + + // Run the task. + $sink = $this->redirectEmails(); + $task = new \mod_customcert\task\email_certificate_task(); + $task->execute(); + $emails = $sink->get_messages(); + + // Confirm there are no issues as the user did not meet the required time. + $issues = $DB->get_records('customcert_issues'); + $this->assertCount(0, $issues); + + // Confirm no emails were sent. + $this->assertCount(0, $emails); + } +} \ No newline at end of file diff --git a/tests/generator/lib.php b/tests/generator/lib.php new file mode 100644 index 0000000..bcc3d2f --- /dev/null +++ b/tests/generator/lib.php @@ -0,0 +1,64 @@ +. + +/** + * Contains the class responsible for data generation during unit tests + * + * @package mod_customcert + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * The class responsible for data generation during unit tests + * + * @package mod_customcert + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_customcert_generator extends testing_module_generator { + + /** + * Creates an instance of the custom certificate. + * + * @param array|stdClass|null $record + * @param array|null $options. + * @return stdClass. + */ + public function create_instance($record = null, array $options = null) { + $record = (object)(array)$record; + + $defaultsettings = array( + 'requiredtime' => 0, + 'emailstudents' => 0, + 'emailteachers' => 0, + 'emailothers' => '', + 'protection' => '' + ); + + foreach ($defaultsettings as $name => $value) { + if (!isset($record->{$name})) { + $record->{$name} = $value; + } + } + + return parent::create_instance($record, (array)$options); + } +} diff --git a/version.php b/version.php index 64a2a04..05d9dbd 100644 --- a/version.php +++ b/version.php @@ -24,10 +24,10 @@ defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); -$plugin->version = 2016052307; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2016052308; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2016052300; // Requires this Moodle version (3.1). $plugin->cron = 0; // Period for cron to check this module (secs). $plugin->component = 'mod_customcert'; $plugin->maturity = MATURITY_STABLE; -$plugin->release = "3.1 release (Build: 2016052307)"; // User-friendly version number. +$plugin->release = "3.1 release (Build: 2016052308)"; // User-friendly version number. diff --git a/view.php b/view.php index 71e6163..4e6014c 100644 --- a/view.php +++ b/view.php @@ -118,7 +118,7 @@ if (empty($action)) { echo $downloadbutton; echo $OUTPUT->footer($course); exit; -} else { // Output to pdf +} else { // Output to pdf. // Create new customcert issue record if one does not already exist. if (!$DB->record_exists('customcert_issues', array('userid' => $USER->id, 'customcertid' => $customcert->id))) { $customcertissue = new stdClass();