Compare commits

...

55 commits

Author SHA1 Message Date
Mark Nelson
86322e06a9 Fix PHPDocs for the method get_course_field_value() 2020-11-27 20:23:40 +08:00
Mark Nelson
57c4fca204 Fixed size of 'Changed' in CHANGES.md 2020-11-26 23:31:30 +08:00
Mark Nelson
5e661ee325 Bumped version 2020-11-26 22:56:19 +08:00
Mark Nelson
8c0c5bd4a4 Update CHANGES.md (#390) 2020-11-26 22:11:25 +08:00
uvigo-atic
ba71760984 Added username to userfield form element (#390) 2020-11-26 22:11:15 +08:00
Mark Nelson
ba47750c58 Updated CHANGES.md (#276) 2020-11-26 21:42:57 +08:00
Mark Nelson
58d7616a99 Update 'emailteachers_help' and 'emailothers_help' language strings (#276) 2020-11-26 21:35:41 +08:00
Mark Nelson
db3365d56a Do not email out certificates with no elements (#276) 2020-11-26 21:13:38 +08:00
Mark Nelson
deb8d60a89 Use null coalescing operator in element factory 2020-11-26 18:22:33 +08:00
Mark Nelson
b0fdb981ff Update 'emailstudents_help' language string (#276) 2020-11-26 17:55:50 +08:00
Mark Nelson
2cc3e3bb87 Bump .travis.yml to use v3 of moodle-plugin-ci 2020-11-09 16:29:11 +01:00
Mark Nelson
ad52d03b74 Update repo used by Travis 2020-11-09 16:28:50 +01:00
Mark Nelson
9aec9b5e76 Update CHANGES.md 2020-11-04 19:32:45 +01:00
Brendan Heywood
1d3d616935 PDFs should be inline not attachments #153 2020-11-04 19:16:15 +01:00
Mark Nelson
c00b957319 Add CSS class for mobile app (#378) 2020-10-12 13:36:27 +02:00
Mark Nelson
2de7656753 Removed multiple empty lines 2020-09-20 16:00:50 +02:00
Mark Nelson
f4a7c3ed37 Updated CHANGES.md 2020-09-20 15:55:00 +02:00
Mark Nelson
e012b8fb22 Fixed issue with PDF being generated without a name (#333) 2020-09-20 15:40:58 +02:00
Mark Nelson
f3f9f9c169 Fixed exception loading template image that has no image selected (#369) 2020-09-20 14:58:13 +02:00
Mark Nelson
1f1b2f7689 Do not email those who can manage the certificate (#376) 2020-09-20 14:29:26 +02:00
Guillermo Gomez
4a5b000c59 Implement get_objectid_mapping (#374) 2020-09-20 14:17:05 +02:00
Mark Nelson
378396ecf6 Updated CHANGES.md 2020-08-08 19:34:52 +02:00
Mark Nelson
9d70a88e74 Changed 'enrollment' to 'enrolment' to conform to Moodle core (#328)
Also minor changes like ordering strings alphabetically and adding
full-stops.
2020-08-08 19:34:43 +02:00
Sergey Evstegneiev
22b156be8f Adding enrollment start & end date options (#328) 2020-08-08 19:33:02 +02:00
Mark Nelson
007446ab33 Fix stale file warning 2020-08-08 18:11:00 +02:00
Mark Nelson
27ae633528 Ignore multiple certificate codes (#363) 2020-08-08 15:52:52 +02:00
Mark Nelson
d5e41eee61 Remove trailing spaces 2020-08-08 15:46:55 +02:00
Thong Bui
cb34de169e Fix get position when browser shrinking (#343) 2020-08-08 15:28:02 +02:00
Steven Tsvetkov
84bdb0d654 Fix custom fields not displaying properly #359 2020-08-01 13:11:47 +02:00
Mark Nelson
267484e214 Updated CHANGES.md 2020-06-28 18:37:12 +02:00
Mark Nelson
1eb24ae74e Update node version used in .travis.yml 2020-05-30 13:37:01 +02:00
Mark Nelson
7d91af9117 Mark certificates as viewed in mobile app (#342) 2020-05-30 13:10:12 +02:00
Mark Nelson
cc768d73aa Refactor element_helper::get_grade_items() to be nicer to read 2020-05-08 21:24:05 +02:00
Mark Nelson
a279237a69 Fix docs in Grade element name 2020-05-08 17:50:33 +02:00
Mark Nelson
b44500dae8 The Grade item name element works with all grade items (#346) 2020-05-08 17:27:43 +02:00
Mark Nelson
30e2e7b1a9 Extend unit test (#329) 2020-05-08 17:22:11 +02:00
Mark Nelson
95b9ee2acb Add the ability to select Outcomes in the Grade element (#329) 2020-05-08 17:22:05 +02:00
Mark Nelson
05fb3c71d7 Removed 'exampledata' string 2020-05-08 16:57:25 +02:00
Mark Nelson
4921c57b23 Bumped version 2020-03-17 00:21:21 +01:00
Mark Nelson
219cfd1343 Fixed broken logic (#298) 2020-03-16 17:52:15 +01:00
Mark Nelson
595326623f Loading templates copies system images to the course (#298)
Before it would just reference the system images. However, this meant
backup/restore was broken as it did not include the images the template
was using.
2020-03-15 19:52:57 +01:00
Mark Nelson
4b2af9bcca Updated CHANGES.md 2020-03-12 14:49:21 +01:00
Mark Nelson
ec0983c124 Added extra Behat steps for new elements (#309) 2020-03-11 14:17:00 +01:00
Mark Nelson
de7f9a9bd8 Avoid use of old array syntax (#331) 2020-03-10 17:41:25 +01:00
Sergey
ca1b065b65 Fix of showing names of custom user fields (#326)
Show normal human-readable name of custom user profile fields instead of internal shortname
2020-03-09 14:57:23 +01:00
Mark Nelson
d007e56589 Do not allow '0' as a value for width or height in QR code (#321) 2020-03-09 14:53:02 +01:00
Mark Nelson
4e9421c6f9 Fix foreign key violation (#331) 2020-03-09 14:06:34 +01:00
Mark Nelson
3ef47f0cb1 Bumped version 2020-02-01 15:59:57 +01:00
Mark Nelson
fb15859e12 Updated CHANGES.md 2020-02-01 15:53:44 +01:00
Mark Nelson
7cb9bf4778 Fixed typo in Behat file name 2020-02-01 15:48:34 +01:00
Mark Nelson
f2a86d4098 Re-add code column (#264) 2020-02-01 15:47:58 +01:00
mwithheld
e0366eb398 Do not fail if multiple certificate issues (#304)
Fix another location of this problem.
2019-12-16 16:18:20 +01:00
mwithheld
def104602a Do not fail if multiple certificate issues (#295)
Ignore multiple matches for a user-customcert combo in the customcert_issues table.
In our case, the scheduled task died when:
$issueid = $DB->get_field('customcert_issues', 'id', array('userid' => $enroluser->id, 'customcertid' => $customcert->id));
returned 2 rows, which shouldn't happen, but did anyway.

This means in rare circumstances (i.e. the race condition) the user may get multiple certificates (e.g. one via the UI, one by email) but that is better than not getting one at all and the whole job dying.
2019-12-16 16:17:59 +01:00
Mark Nelson
8f50580173 Changed references to old github name 2019-12-16 15:11:32 +01:00
Arnaud Trouvé
fa6270e360 Add userfullname variable for email subject (#316) 2019-12-16 15:01:48 +01:00
36 changed files with 675 additions and 183 deletions

View file

@ -30,10 +30,10 @@ env:
before_install:
- phpenv config-rm xdebug.ini
- nvm install 8.9
- nvm use 8.9
- nvm install 14.0
- nvm use 14.0
- cd ../..
- composer create-project -n --no-dev --prefer-dist blackboard-open-source/moodle-plugin-ci ci ^2
- composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
- export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH"
install:

View file

@ -2,7 +2,68 @@
All notable changes to this project will be documented in this file.
Note - All hash comments refer to the issue number. Eg. #169 refers to https://github.com/markn86/moodle-mod_customcert/issues/169.
Note - All hash comments refer to the issue number. Eg. #169 refers to https://github.com/mdjnelson/moodle-mod_customcert/issues/169.
## [3.7.5] - 2020-11-26
### Added
- Added ability to select outcomes in the Grade element (#329).
- The Grade Item Name element now works with all grade items, whereas before it was just activities (#346).
- Added enrolment start and end dates to the date element (#328).
- Added username to userfield form element (#390).
### Changed
- Removed unnecessary and confusing 'exampledata' string.
- Do not email those who can manage the certificate (#376).
- Do not force the PDF to be downloaded, instead send the file inline to the browser (#153).
- Updated the 'emailstudents_help', 'emailteachers_help' and 'emailothers_help' strings to warn users about prematurely emailing the certificate (#276).
- Do not email out certificates that contain no elements (#276).
### Fixed
- Certificates now get marked as viewed via the mobile app (#342).
- Custom fields not displaying properly (#359).
- Fix repositioning elements page when resizing the browser (#343).
- Prevent error when duplicate issues exist when using the code element (#363).
- Implemented get_objectid_mapping for the course_module_viewed.php event to avoid warning (#374).
- Fixed exception being thrown when loading a template that has an image element but no image selected (#369).
- Fixed issue with PDF being generated without a name (#333).
## [3.7.4] - 2020-03-12
### Added
- Added extra Behat steps for new elements (#309).
### Changed
- When copying a site template the site images are also copied to the course context and then those copied images are used.
Before, the elements would simply point to the site images. However, this meant when performing a backup/restore the
images were not stored in the backup file (#298).
### Fixed
- Fixed the displaying of names of a custom user field (#326).
- Do not allow '0' as a value for width or height in QR code (#321).
## [3.7.3] - 2020-03-09
### Fixed
- Fixed foreign key violation (#331).
## [3.7.2] - 2020-02-01
### Added
- Re-added 'code' column to user report (#264).
- Add 'userfullname' variable for email subject (#316).
### Fixed
- Do not fail if multiple certificate issues (#304) and (#295).
## [3.7.1] - 2019-06-17

View file

@ -15,7 +15,7 @@ This requires Git being installed. If you do not have Git installed, please visi
Once you have Git installed, simply visit your Moodle mod directory and clone the repository using the following command.
```
git clone https://github.com/markn86/moodle-mod_customcert.git customcert
git clone https://github.com/mdjnelson/moodle-mod_customcert.git customcert
```
Then checkout the branch corresponding to the version of Moodle you are using with the following command. Make sure to replace MOODLE_32_STABLE with the version of Moodle you are using.

View file

@ -47,18 +47,18 @@ class element_factory {
$classname = '\\customcertelement_' . $element->element . '\\element';
$data = new \stdClass();
$data->id = isset($element->id) ? $element->id : null;
$data->pageid = isset($element->pageid) ? $element->pageid : null;
$data->name = isset($element->name) ? $element->name : get_string('pluginname', 'customcertelement_' . $element->element);
$data->id = $element->id ?? null;
$data->pageid = $element->pageid ?? null;
$data->name = $element->name ?? get_string('pluginname', 'customcertelement_' . $element->element);
$data->element = $element->element;
$data->data = isset($element->data) ? $element->data : null;
$data->font = isset($element->font) ? $element->font : null;
$data->fontsize = isset($element->fontsize) ? $element->fontsize : null;
$data->colour = isset($element->colour) ? $element->colour : null;
$data->posx = isset($element->posx) ? $element->posx : null;
$data->posy = isset($element->posy) ? $element->posy : null;
$data->width = isset($element->width) ? $element->width : null;
$data->refpoint = isset($element->refpoint) ? $element->refpoint : null;
$data->data = $element->data ?? null;
$data->font = $element->font ?? null;
$data->fontsize = $element->fontsize ?? null;
$data->colour = $element->colour ?? null;
$data->posx = $element->posx ?? null;
$data->posy = $element->posy ?? null;
$data->width = $element->width ?? null;
$data->refpoint = $element->refpoint ?? null;
// Ensure the necessary class exists.
if (class_exists($classname)) {

View file

@ -462,70 +462,40 @@ class element_helper {
* @return array the array of gradeable items in the course
*/
public static function get_grade_items($course) {
global $DB;
// Array to store the grade items.
$modules = array();
// Collect course modules data.
$modinfo = get_fast_modinfo($course);
$mods = $modinfo->get_cms();
$sections = $modinfo->get_section_info_all();
// Create the section label depending on course format.
$sectionlabel = get_string('section');
if ($course->format == 'topics') {
$sectionlabel = get_string('topic');
} else if ($course->format == 'weeks') {
$sectionlabel = get_string('week');
}
// Loop through each course section.
for ($i = 0; $i <= count($sections) - 1; $i++) {
// Confirm the index exists, should always be true.
if (isset($sections[$i])) {
// Get the individual section.
$section = $sections[$i];
// Get the mods for this section.
$sectionmods = explode(",", $section->sequence);
// Loop through the section mods.
foreach ($sectionmods as $sectionmod) {
// Should never happen unless DB is borked.
if (empty($mods[$sectionmod])) {
continue;
}
$mod = $mods[$sectionmod];
$instance = $DB->get_record($mod->modname, array('id' => $mod->instance));
// Get the grade items for this activity.
if ($gradeitems = grade_get_grade_items_for_activity($mod)) {
$moditem = grade_get_grades($course->id, 'mod', $mod->modname, $mod->instance);
$gradeitem = reset($moditem->items);
if (isset($gradeitem->grademax)) {
$modules[$mod->id] = $sectionlabel . ' ' . $section->section . ' : ' . $instance->name;
}
}
}
}
}
$arrgradeitems = array();
// Get other non-module related grade items.
if ($gradeitems = \grade_item::fetch_all(['courseid' => $course->id])) {
$arrgradeitems = [];
foreach ($gradeitems as $gi) {
// Skip the course and mod items since we already have them.
if ($gi->itemtype == 'mod' || $gi->itemtype == 'course') {
continue;
if ($gi->is_course_item()) {
continue; // Skipping for legacy reasons - this was added to individual elements.
}
if ($gi->is_external_item()) {
$cm = get_coursemodule_from_instance($gi->itemmodule, $gi->iteminstance, $course->id);
$modcontext = \context_module::instance($cm->id);
$modname = format_string($cm->name, true, array('context' => $modcontext));
}
if ($gi->is_external_item() && !$gi->is_outcome_item()) {
// Due to legacy reasons we are storing the course module ID here rather than the grade item id.
// If we were to change we would need to provide upgrade steps to convert cm->id to gi->id.
$arrgradeitems[$cm->id] = get_string('activity', 'mod_customcert') . ' : ' . $gi->get_name();
} else if ($gi->is_external_item() && $gi->is_outcome_item()) {
// Get the name of the activity.
$optionname = get_string('gradeoutcome', 'mod_customcert') . ' : ' . $modname . " - " . $gi->get_name();
$arrgradeitems['gradeitem:' . $gi->id] = $optionname;
} else {
$arrgradeitems['gradeitem:' . $gi->id] = get_string('gradeitem', 'grades') . ' : ' . $gi->get_name(true);
}
}
// Alphabetise this.
asort($arrgradeitems);
// Merge results.
$modules = $modules + $arrgradeitems;
}
return $modules;
return $arrgradeitems;
}
/**

View file

@ -42,4 +42,13 @@ class course_module_viewed extends \core\event\course_module_viewed {
$this->data['objecttable'] = 'customcert';
parent::init();
}
public static function get_objectid_mapping() {
return array('db' => 'customcert', 'restore' => 'customcert');
}
public static function get_other_mapping() {
// No need to map.
return false;
}
}

View file

@ -74,6 +74,7 @@ class report_table extends \table_sql {
$columns[] = $extrafield;
}
$columns[] = 'timecreated';
$columns[] = 'code';
$headers = [];
$headers[] = get_string('fullname');
@ -81,6 +82,7 @@ class report_table extends \table_sql {
$headers[] = get_user_field_name($extrafield);
}
$headers[] = get_string('receiveddate', 'customcert');
$headers[] = get_string('code', 'customcert');
// Check if we were passed a filename, which means we want to download it.
if ($download) {
@ -101,6 +103,7 @@ class report_table extends \table_sql {
$this->define_headers($headers);
$this->collapsible(false);
$this->sortable(true);
$this->no_sorting('code');
$this->no_sorting('download');
$this->is_downloadable(true);
@ -135,6 +138,16 @@ class report_table extends \table_sql {
return userdate($user->timecreated);
}
/**
* Generate the code column.
*
* @param \stdClass $user
* @return string
*/
public function col_code($user) {
return $user->code;
}
/**
* Generate the download column.
*

View file

@ -69,6 +69,18 @@ class email_certificate_task extends \core\task\scheduled_task {
$htmlrenderer = $PAGE->get_renderer('mod_customcert', 'email', 'htmlemail');
$textrenderer = $PAGE->get_renderer('mod_customcert', 'email', 'textemail');
foreach ($customcerts as $customcert) {
// Do not process an empty certificate.
$sql = "SELECT ce.*
FROM {customcert_elements} ce
JOIN {customcert_pages} cp
ON cp.id = ce.pageid
JOIN {customcert_templates} ct
ON ct.id = cp.templateid
WHERE ct.contextid = :contextid";
if (!$DB->record_exists_sql($sql, ['contextid' => $customcert->contextid])) {
continue;
}
// Get the context.
$context = \context::instance_by_id($customcert->contextid);
@ -112,6 +124,11 @@ class email_certificate_task extends \core\task\scheduled_task {
continue;
}
// Don't want to email those with the capability to manage the certificate.
if (has_capability('mod/customcert:manage', $context, $enroluser->id)) {
continue;
}
// Only email those with the capability to receive the certificate.
if (!has_capability('mod/customcert:receiveissue', $context, $enroluser->id)) {
continue;
@ -127,7 +144,7 @@ class email_certificate_task extends \core\task\scheduled_task {
// Ensure the cert hasn't already been issued, e.g via the UI (view.php) - a race condition.
$issueid = $DB->get_field('customcert_issues', 'id',
array('userid' => $enroluser->id, 'customcertid' => $customcert->id));
array('userid' => $enroluser->id, 'customcertid' => $customcert->id), IGNORE_MULTIPLE);
if (empty($issueid)) {
// Ok, issue them the certificate.
$issueid = \mod_customcert\certificate::issue_certificate($customcert->id, $enroluser->id);
@ -160,6 +177,7 @@ class email_certificate_task extends \core\task\scheduled_task {
// Now, email the people we need to.
foreach ($issuedusers as $user) {
$userfullname = fullname($user);
$info->userfullname = $userfullname;
// Now, get the PDF.
$template = new \stdClass();

View file

@ -283,6 +283,16 @@ class template {
$pdf->SetAutoPageBreak(true, 0);
// Remove full-stop at the end, if it exists, to avoid "..pdf" being created and being filtered by clean_filename.
$filename = rtrim($this->name, '.');
// This is the logic the TCPDF library uses when processing the name. This makes names
// such as 'الشهادة' become empty, so set a default name in these cases.
$filename = preg_replace('/[\s]+/', '_', $filename);
$filename = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $filename);
if (empty($filename)) {
$filename = get_string('certificate', 'customcert');
}
$filename = clean_filename($filename . '.pdf');
// Loop through the pages and display their content.
foreach ($pages as $page) {
@ -305,10 +315,12 @@ class template {
}
}
}
if ($return) {
return $pdf->Output('', 'S');
}
$pdf->Output($filename, 'D');
$pdf->Output($filename, 'I');
}
}

View file

@ -1,5 +1,5 @@
{
"name": "markn86/moodle-mod_customcert",
"name": "mdjnelson/moodle-mod_customcert",
"type": "moodle-mod",
"require": {
"composer/installers": "~1.0"

View file

@ -23,7 +23,7 @@
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for customcert"/>
<KEY NAME="template" TYPE="foreign" FIELDS="templateid" REFTABLE="customcert_template" REFFIELDS="id"/>
<KEY NAME="template" TYPE="foreign" FIELDS="templateid" REFTABLE="customcert_templates" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="customcert_templates" COMMENT="Stores each customcert template">
@ -67,7 +67,7 @@
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for customcert_pages"/>
<KEY NAME="template" TYPE="foreign" FIELDS="templateid" REFTABLE="customcert_template" REFFIELDS="id"/>
<KEY NAME="template" TYPE="foreign" FIELDS="templateid" REFTABLE="customcert_templates" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="customcert_elements" COMMENT="Stores the elements for a given page">

View file

@ -30,7 +30,7 @@ $addons = [
'issueview' => [ // Handler unique name.
'displaydata' => [
'icon' => $CFG->wwwroot . '/mod/customcert/pix/icon.png',
'class' => '',
'class' => 'core-course-module-customcert-handler',
],
'delegate' => 'CoreCourseModuleDelegate', // Delegate (where to display the link to the plugin).
'method' => 'mobile_view_activity', // Main function in \mod_customcert\output\mobile.

View file

@ -146,5 +146,25 @@ function xmldb_customcert_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2018051705, 'customcert');
}
if ($oldversion < 2019052003) {
$table = new xmldb_table('customcert');
$index = new xmldb_index('templateid', XMLDB_INDEX_UNIQUE, ['templateid']);
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}
$key = new xmldb_key('templateid', XMLDB_KEY_FOREIGN, ['templateid'], 'customcert_templates', ['id']);
$dbman->add_key($table, $key);
$table = new xmldb_table('customcert_pages');
$index = new xmldb_index('templateid', XMLDB_INDEX_UNIQUE, ['templateid']);
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}
$key = new xmldb_key('templateid', XMLDB_KEY_FOREIGN, ['templateid'], 'customcert_templates', ['id']);
$dbman->add_key($table, $key);
upgrade_mod_savepoint(true, 2019052003, 'customcert');
}
return true;
}

View file

@ -54,7 +54,7 @@ class element extends \mod_customcert\element {
$customcert = $DB->get_record('customcert', array('templateid' => $page->templateid), '*', MUST_EXIST);
// Now we can get the issue for this user.
$issue = $DB->get_record('customcert_issues', array('userid' => $user->id, 'customcertid' => $customcert->id),
'*', MUST_EXIST);
'*', IGNORE_MULTIPLE);
$code = $issue->code;
}

View file

@ -121,9 +121,9 @@ class element extends \mod_customcert\element {
}
/**
* Helper function that returns the text.
* Helper function that returns the field value in a human-readable format.
*
* @param \stdClass $user the user we are rendering this for
* @param \stdClass $course the course we are rendering this for
* @param bool $preview Is this a preview?
* @return string
*/
@ -141,7 +141,7 @@ class element extends \mod_customcert\element {
$handler = \core_course\customfield\course_handler::create();
$data = $handler->get_instance_data($course->id, true);
if (!empty($data[$field])) {
$value = $data[$field]->get('value');
$value = $data[$field]->export_value();
}
} else if (!empty($course->$field)) { // Field in the course table.

View file

@ -56,6 +56,16 @@ define('CUSTOMCERT_DATE_COURSE_END', '-4');
*/
define('CUSTOMCERT_DATE_CURRENT_DATE', '-5');
/**
* Date - Enrollment start
*/
define('CUSTOMCERT_DATE_ENROLMENT_START', '-6');
/**
* Date - Entrollment end
*/
define('CUSTOMCERT_DATE_ENROLMENT_END', '-7');
require_once($CFG->dirroot . '/lib/grade/constants.php');
/**
@ -83,6 +93,9 @@ class element extends \mod_customcert\element {
if ($completionenabled) {
$dateoptions[CUSTOMCERT_DATE_COMPLETION] = get_string('completiondate', 'customcertelement_date');
}
$dateoptions[CUSTOMCERT_DATE_ENROLMENT_START] = get_string('enrolmentstartdate', 'customcertelement_date');
$dateoptions[CUSTOMCERT_DATE_ENROLMENT_END] = get_string('enrolmentenddate', 'customcertelement_date');
$dateoptions[CUSTOMCERT_DATE_COURSE_START] = get_string('coursestartdate', 'customcertelement_date');
$dateoptions[CUSTOMCERT_DATE_COURSE_END] = get_string('courseenddate', 'customcertelement_date');
$dateoptions[CUSTOMCERT_DATE_COURSE_GRADE] = get_string('coursegradedate', 'customcertelement_date');
@ -147,7 +160,7 @@ class element extends \mod_customcert\element {
$customcert = $DB->get_record('customcert', array('templateid' => $page->templateid), '*', MUST_EXIST);
// Now we can get the issue for this user.
$issue = $DB->get_record('customcert_issues', array('userid' => $user->id, 'customcertid' => $customcert->id),
'*', MUST_EXIST);
'*', IGNORE_MULTIPLE);
if ($dateitem == CUSTOMCERT_DATE_ISSUE) {
$date = $issue->timecreated;
@ -164,6 +177,26 @@ class element extends \mod_customcert\element {
$date = $timecompleted->timecompleted;
}
}
} else if ($dateitem == CUSTOMCERT_DATE_ENROLMENT_START) {
// Get the enrolment start date.
$sql = "SELECT ue.timestart FROM {enrol} e JOIN {user_enrolments} ue ON ue.enrolid = e.id
WHERE e.courseid = :courseid
AND ue.userid = :userid";
if ($timestart = $DB->get_record_sql($sql, array('userid' => $issue->userid, 'courseid' => $courseid))) {
if (!empty($timestart->timestart)) {
$date = $timestart->timestart;
}
}
} else if ($dateitem == CUSTOMCERT_DATE_ENROLMENT_END) {
// Get the enrolment end date.
$sql = "SELECT ue.timeend FROM {enrol} e JOIN {user_enrolments} ue ON ue.enrolid = e.id
WHERE e.courseid = :courseid
AND ue.userid = :userid";
if ($timeend = $DB->get_record_sql($sql, array('userid' => $issue->userid, 'courseid' => $courseid))) {
if (!empty($timeend->timeend)) {
$date = $timeend->timeend;
}
}
} else if ($dateitem == CUSTOMCERT_DATE_COURSE_START) {
$date = $DB->get_field('course', 'startdate', array('id' => $courseid));
} else if ($dateitem == CUSTOMCERT_DATE_COURSE_END) {
@ -198,12 +231,7 @@ class element extends \mod_customcert\element {
// Ensure that a date has been set.
if (!empty($date)) {
$date = $this->get_date_format_string($date, $dateformat);
// If we are previewing, we want to let the user know it's an example date so they don't get confused.
if ($preview) {
$date = get_string('exampledata', 'customcert', 'date') . ' ' . $date;
}
\mod_customcert\element_helper::render_content($pdf, $this, $date);
\mod_customcert\element_helper::render_content($pdf, $this, $this->get_date_format_string($date, $dateformat));
}
}

View file

@ -26,6 +26,8 @@ $string['completiondate'] = 'Completion date';
$string['courseenddate'] = 'Course end date';
$string['coursegradedate'] = 'Course grade date';
$string['coursestartdate'] = 'Course start date';
$string['enrolmentenddate'] = 'Enrolment end date';
$string['enrolmentstartdate'] = 'Enrolment start date';
$string['currentdate'] = 'Current date';
$string['dateformat'] = 'Date format';
$string['dateformat_help'] = 'This is the format of the date that will be displayed';

View file

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
$plugin->version = 2019052000; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2019052001; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->component = 'customcertelement_date';

View file

@ -107,8 +107,7 @@ class element extends \mod_customcert\element {
// If we are previewing this certificate then just show a demonstration grade.
if ($preview) {
$courseitem = \grade_item::fetch_course_item($courseid);
$grade = grade_format_gradevalue('100', $courseitem, true, $gradeinfo->gradeformat);
$grade = get_string('exampledata', 'customcert', 'grade') . ' ' . $grade;
$grade = grade_format_gradevalue('100', $courseitem, true, $gradeinfo->gradeformat);;
} else {
if ($gradeitem == CUSTOMCERT_GRADE_COURSE) {
$grade = \mod_customcert\element_helper::get_course_grade_info(

View file

@ -110,20 +110,40 @@ class element extends \mod_customcert\element {
}
/**
* Helper function that returns the category name.
* Helper function that returns the grade item name.
*
* @return string
*/
protected function get_grade_item_name() : string {
global $DB;
// Get the course module information.
$cm = $DB->get_record('course_modules', array('id' => $this->get_data()), '*', MUST_EXIST);
$module = $DB->get_record('modules', array('id' => $cm->module), '*', MUST_EXIST);
$gradeitem = $this->get_data();
// Get the name of the item.
$itemname = $DB->get_field($module->name, 'name', array('id' => $cm->instance), MUST_EXIST);
if (strpos($gradeitem, 'gradeitem:') === 0) {
$gradeitemid = substr($gradeitem, 10);
$gradeitem = \grade_item::fetch(['id' => $gradeitemid]);
return format_string($itemname, true, ['context' => \mod_customcert\element_helper::get_context($this->get_id())]);
return $gradeitem->get_name();
} else {
if (!$cm = $DB->get_record('course_modules', array('id' => $gradeitem))) {
return '';
}
if (!$module = $DB->get_record('modules', array('id' => $cm->module))) {
return '';
}
$params = [
'itemtype' => 'mod',
'itemmodule' => $module->name,
'iteminstance' => $cm->instance,
'courseid' => $cm->course,
'itemnumber' => 0
];
$gradeitem = \grade_item::fetch($params);
return $gradeitem->get_name();
}
}
}

View file

@ -382,14 +382,14 @@ class element extends \mod_customcert\element {
// Loop through the files uploaded in the system context.
if ($files = $fs->get_area_files(\context_system::instance()->id, 'mod_customcert', 'image', false, 'filename', false)) {
foreach ($files as $hash => $file) {
$arrfiles[$file->get_id()] = $file->get_filename();
$arrfiles[$file->get_id()] = get_string('systemimage', 'customcertelement_image', $file->get_filename());
}
}
// Loop through the files uploaded in the course context.
if ($files = $fs->get_area_files(\context_course::instance($COURSE->id)->id, 'mod_customcert', 'image', false,
'filename', false)) {
foreach ($files as $hash => $file) {
$arrfiles[$file->get_id()] = $file->get_filename();
$arrfiles[$file->get_id()] = get_string('courseimage', 'customcertelement_image', $file->get_filename());
}
}
@ -398,4 +398,60 @@ class element extends \mod_customcert\element {
return $arrfiles;
}
/**
* This handles copying data from another element of the same type.
*
* @param \stdClass $data the form data
* @return bool returns true if the data was copied successfully, false otherwise
*/
public function copy_element($data) {
global $COURSE, $DB, $SITE;
$imagedata = json_decode($data->data);
// If we are in the site context we don't have to do anything, the image is already there.
if ($COURSE->id == $SITE->id) {
return true;
}
$coursecontext = \context_course::instance($COURSE->id);
$systemcontext = \context_system::instance();
$fs = get_file_storage();
// Check that a file has been selected.
if (isset($imagedata->filearea)) {
// If the course file doesn't exist, copy the system file to the course context.
if (!$coursefile = $fs->get_file(
$coursecontext->id,
'mod_customcert',
$imagedata->filearea,
$imagedata->itemid,
$imagedata->filepath,
$imagedata->filename
)) {
$systemfile = $fs->get_file(
$systemcontext->id,
'mod_customcert',
$imagedata->filearea,
$imagedata->itemid,
$imagedata->filepath,
$imagedata->filename
);
// We want to update the context of the file if it doesn't exist in the course context.
$fieldupdates = [
'contextid' => $coursecontext->id
];
$coursefile = $fs->create_file_from_storedfile($fieldupdates, $systemfile);
}
// Set the image to the copied file in the course.
$imagedata->fileid = $coursefile->get_id();
$DB->set_field('customcert_elements', 'data', $this->save_unique_data($imagedata), ['id' => $this->get_id()]);
}
return true;
}
}

View file

@ -24,6 +24,7 @@
$string['alphachannel'] = 'Alpha channel';
$string['alphachannel_help'] = 'This value determines how transparent the image is. You can set the alpha channel from 0 (fully transparent) to 1 (fully opaque).';
$string['courseimage'] = 'Course image: {$a}';
$string['height'] = 'Height';
$string['height_help'] = 'Height of the image in mm. If equal to zero, it is automatically calculated.';
$string['image'] = 'Image';
@ -31,5 +32,6 @@ $string['invalidheight'] = 'The height has to be a valid number greater than or
$string['invalidwidth'] = 'The width has to be a valid number greater than or equal to 0.';
$string['pluginname'] = 'Image';
$string['privacy:metadata'] = 'The Image plugin does not store any personal data.';
$string['systemimage'] = 'Site image: {$a}';
$string['width'] = 'Width';
$string['width_help'] = 'Width of the image in mm. If equal to zero, it is automatically calculated.';

View file

@ -72,8 +72,12 @@ class element extends \mod_customcert\element {
$errors = [];
// Check if height is not set, or not numeric or less than 0.
if ((!isset($data['height'])) || (!is_numeric($data['height'])) || ($data['height'] < 0)) {
$errors['height'] = get_string('invalidheight', 'customcertelement_qrcode');
if ((!isset($data['height'])) || (!is_numeric($data['height'])) || ($data['height'] <= 0)) {
$errors['height'] = get_string('invalidheight', 'mod_customcert');
}
if ((!isset($data['width'])) || (!is_numeric($data['width'])) || ($data['width'] <= 0)) {
$errors['width'] = get_string('invalidwidth', 'mod_customcert');
}
if ($this->showposxy) {

View file

@ -45,6 +45,7 @@ class element extends \mod_customcert\element {
$userfields = array(
'firstname' => get_user_field_name('firstname'),
'lastname' => get_user_field_name('lastname'),
'username' => get_user_field_name('username'),
'email' => get_user_field_name('email'),
'city' => get_user_field_name('city'),
'country' => get_user_field_name('country'),
@ -65,7 +66,7 @@ class element extends \mod_customcert\element {
$arrcustomfields = \availability_profile\condition::get_custom_profile_fields();
$customfields = array();
foreach ($arrcustomfields as $key => $customfield) {
$customfields[$customfield->id] = $key;
$customfields[$customfield->id] = $customfield->name;
}
// Combine the two.
$fields = $userfields + $customfields;

View file

@ -22,6 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['activity'] = 'Activity';
$string['addcertpage'] = 'Add page';
$string['addelement'] = 'Add element';
$string['awardedto'] = 'Awarded to';
@ -83,12 +84,11 @@ $string['emailstudentcertificatelinktext'] = 'View certificate';
$string['emailstudentgreeting'] = 'Dear {$a}';
$string['emailstudentsubject'] = '{$a->coursefullname}: {$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['emailstudents_help'] = 'If set this will email the students a copy of the certificate when it becomes available. <strong>Warning:</strong> Setting this to \'Yes\' before you have finished creating the certificate will email the student an incomplete certificate.';
$string['emailteachers'] = 'Email teachers';
$string['emailteachers_help'] = 'If set this will email the teachers a copy of the certificate when it becomes available.';
$string['emailteachers_help'] = 'If set this will email the teachers a copy of the certificate when it becomes available. <strong>Warning:</strong> Setting this to \'Yes\' before you have finished creating the certificate will email the teacher an incomplete certificate.';
$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['exampledata'] = 'Example {$a}:';
$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. <strong>Warning:</strong> Setting this field before you have finished creating the certificate will email the addresses an incomplete certificate.';
$string['exampledatawarning'] = 'Some of these values may just be an example to ensure positioning of the elements is possible.';
$string['font'] = 'Font';
$string['font_help'] = 'The font used when generating this element.';
@ -97,6 +97,7 @@ $string['fontcolour_help'] = 'The colour of the font.';
$string['fontsize'] = 'Size';
$string['fontsize_help'] = 'The size of the font in points.';
$string['getcustomcert'] = 'View certificate';
$string['gradeoutcome'] = 'Outcome';
$string['height'] = 'Height';
$string['height_help'] = 'This is the height of the certificate PDF in mm. For reference an A4 piece of paper is 297mm high and a letter is 279mm high.';
$string['invalidcode'] = 'Invalid code supplied.';

View file

@ -34,6 +34,7 @@ define('NO_MOODLE_COOKIES', true);
require_once('../../../config.php');
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->libdir . '/completionlib.php');
require_once($CFG->dirroot . '/webservice/lib.php');
// Allow CORS requests.
@ -54,6 +55,7 @@ if (empty($enabledfiledownload)) {
}
$cm = get_coursemodule_from_instance('customcert', $certificateid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$certificate = $DB->get_record('customcert', ['id' => $certificateid], '*', MUST_EXIST);
$template = $DB->get_record('customcert_templates', ['id' => $certificate->templateid], '*', MUST_EXIST);
@ -80,6 +82,10 @@ if (!$issue) {
}
\mod_customcert\certificate::issue_certificate($certificate->id, $USER->id);
// Set the custom certificate as viewed.
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
// Now we want to generate the PDF.

View file

@ -79,6 +79,24 @@ Feature: Being able to manage elements in a certificate template
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# Course field.
And I add the element "Course field" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
And I should see "Course field" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Course field" "table_row"
And the following fields match these values:
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# Course name.
And I add the element "Course name" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
@ -119,6 +137,42 @@ Feature: Being able to manage elements in a certificate template
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# Date range.
And I add the element "Date range" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
| Date item | Course start date |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Fallback string | {{range_first_year}} |
| id_startdate_0_day | 24 |
| id_startdate_0_month | October |
| id_startdate_0_year | 2015 |
| id_enddate_0_day | 21 |
| id_enddate_0_month | March |
| id_enddate_0_year | 2016 |
| String | Oct to March |
And I press "Save changes"
And I should see "Date range" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Date range" "table_row"
And the following fields match these values:
| Date item | Course start date |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Fallback string | {{range_first_year}} |
| id_startdate_0_day | 24 |
| id_startdate_0_month | October |
| id_startdate_0_year | 2015 |
| id_enddate_0_day | 21 |
| id_enddate_0_month | March |
| id_enddate_0_year | 2016 |
| String | Oct to March |
And I press "Save changes"
# Digital signature.
And I add the element "Digital signature" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
@ -144,7 +198,7 @@ Feature: Being able to manage elements in a certificate template
# Grade.
And I add the element "Grade" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
| Grade item | Topic 0 : Assignment 1 |
| Grade item | Activity : Assignment 1 |
| Grade format | Percentage |
| Font | Helvetica |
| Size | 20 |
@ -155,7 +209,7 @@ Feature: Being able to manage elements in a certificate template
And I should see "Grade" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Grade" "table_row"
And the following fields match these values:
| Grade item | Topic 0 : Assignment 1 |
| Grade item | Activity : Assignment 1 |
| Grade format | Percentage |
| Font | Helvetica |
| Size | 20 |
@ -166,7 +220,7 @@ Feature: Being able to manage elements in a certificate template
# Grade item name.
And I add the element "Grade item name" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
| Grade item | Topic 0 : Assignment 2 |
| Grade item | Activity : Assignment 2 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
@ -176,7 +230,7 @@ Feature: Being able to manage elements in a certificate template
And I should see "Grade item name" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Grade item name" "table_row"
And the following fields match these values:
| Grade item | Topic 0 : Assignment 2 |
| Grade item | Activity : Assignment 2 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
@ -287,6 +341,18 @@ Feature: Being able to manage elements in a certificate template
| Width | 10 |
| Height | 10 |
And I press "Save changes"
# QR Code.
And I add the element "QR code" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
| Width | 25 |
| Height | 15 |
And I press "Save changes"
And I should see "QR code" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "QR code" "table_row"
And the following fields match these values:
| Width | 25 |
| Height | 15 |
And I press "Save changes"
# Just to test there are no exceptions being thrown.
And I follow "Reposition elements"
And I press "Save and close"

View file

@ -180,14 +180,24 @@ class mod_customcert_element_helper_testcase extends advanced_testcase {
$gc = $this->getDataGenerator()->create_grade_category(['courseid' => $course->id]);
$gc = $DB->get_record('grade_items', ['itemtype' => 'category', 'iteminstance' => $gc->id]);
// Create an item attached to an outcome.
$outcome = $this->getDataGenerator()->create_grade_outcome(['courseid' => $course->id, 'shortname' => 'outcome']);
$go = $this->getDataGenerator()->create_grade_item(
[
'courseid' => $course->id,
'outcomeid' => $outcome->id
]
);
// Confirm the function returns the correct number of grade items.
$gradeitems = \mod_customcert\element_helper::get_grade_items($course);
$this->assertCount(5, $gradeitems);
$this->assertCount(6, $gradeitems);
$this->assertArrayHasKey($assign1->cmid, $gradeitems);
$this->assertArrayHasKey($assign2->cmid, $gradeitems);
$this->assertArrayHasKey($assign3->cmid, $gradeitems);
$this->assertArrayHasKey('gradeitem:' . $gi->id, $gradeitems);
$this->assertArrayHasKey('gradeitem:' . $gc->id, $gradeitems);
$this->assertArrayHasKey('gradeitem:' . $go->id, $gradeitems);
}
/**

View file

@ -25,8 +25,6 @@
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Unit tests for the email certificate task.
*
@ -44,6 +42,32 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$this->resetAfterTest();
}
/**
* Tests the email certificate task when there are no elements.
*/
public function test_email_certificates_no_elements() {
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a user.
$user1 = $this->getDataGenerator()->create_user();
// Create a custom certificate with no elements.
$this->getDataGenerator()->create_module('customcert', ['course' => $course->id, 'emailstudents' => 1]);
// Enrol the user as a student
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
// Run the task.
$sink = $this->redirectEmails();
$task = new \mod_customcert\task\email_certificate_task();
$task->execute();
$emails = $sink->get_messages();
// Confirm that we did not send any emails because the certificate has no elements.
$this->assertCount(0, $emails);
}
/**
* Tests the email certificate task for users without a capability to receive a certificate.
*/
@ -65,7 +89,23 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
unassign_capability('mod/customcert:receiveissue', $roleids['student']);
// Create a custom certificate.
$this->getDataGenerator()->create_module('customcert', ['course' => $course->id, 'emailstudents' => 1]);
$customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id, 'emailstudents' => 1]);
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Run the task.
$sink = $this->redirectEmails();
@ -103,6 +143,22 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$customcert = $this->getDataGenerator()->create_module('customcert', array('course' => $course->id,
'emailstudents' => 1));
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Ok, now issue this to one user.
\mod_customcert\certificate::issue_certificate($customcert->id, $user1->id);
@ -169,9 +225,25 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleids['editingteacher']);
// Create a custom certificate.
$this->getDataGenerator()->create_module('customcert', array('course' => $course->id,
$customcert = $this->getDataGenerator()->create_module('customcert', array('course' => $course->id,
'emailteachers' => 1));
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Run the task.
$sink = $this->redirectEmails();
$task = new \mod_customcert\task\email_certificate_task();
@ -192,7 +264,7 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
* Tests the email certificate task for others.
*/
public function test_email_certificates_others() {
global $CFG;
global $CFG, $DB;
// Create a course.
$course = $this->getDataGenerator()->create_course();
@ -206,9 +278,25 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
// Create a custom certificate.
$this->getDataGenerator()->create_module('customcert', array('course' => $course->id,
$customcert = $this->getDataGenerator()->create_module('customcert', array('course' => $course->id,
'emailothers' => 'testcustomcert@example.com, doo@dah'));
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Run the task.
$sink = $this->redirectEmails();
$task = new \mod_customcert\task\email_certificate_task();
@ -242,7 +330,23 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
// Create a custom certificate.
$this->getDataGenerator()->create_module('customcert', array('course' => $course->id, 'emailstudents' => 1));
$customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id, 'emailstudents' => 1]);
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Remove the permission for the user to view the certificate.
assign_capability('mod/customcert:view', CAP_PROHIBIT, $roleids['student'], \context_course::instance($course->id));
@ -280,9 +384,25 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
// Create a custom certificate.
$this->getDataGenerator()->create_module('customcert', array('course' => $course->id, 'emailstudents' => 1,
$customcert = $this->getDataGenerator()->create_module('customcert', array('course' => $course->id, 'emailstudents' => 1,
'requiredtime' => '60'));
// Create template object.
$template = new stdClass();
$template->id = $customcert->templateid;
$template->name = 'A template';
$template->contextid = context_course::instance($course->id)->id;
$template = new \mod_customcert\template($template);
// Add a page to this template.
$pageid = $template->add_page();
// Add an element to the page.
$element = new stdClass();
$element->pageid = $pageid;
$element->name = 'Image';
$DB->insert_record('customcert_elements', $element);
// Run the task.
$sink = $this->redirectEmails();
$task = new \mod_customcert\task\email_certificate_task();

View file

@ -24,10 +24,10 @@
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
$plugin->version = 2019052001; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2019052005; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->cron = 0; // Period for cron to check this module (secs).
$plugin->component = 'mod_customcert';
$plugin->maturity = MATURITY_STABLE;
$plugin->release = "3.7.1"; // User-friendly version number.
$plugin->release = "3.7.5"; // User-friendly version number.

View file

@ -139,7 +139,7 @@ if (!$downloadown && !$downloadissue) {
if ($canreceive) {
$linkname = get_string('getcustomcert', 'customcert');
$link = new moodle_url('/mod/customcert/view.php', array('id' => $cm->id, 'downloadown' => true));
$downloadbutton = new single_button($link, $linkname, 'post', true);
$downloadbutton = new single_button($link, $linkname, 'get', true);
$downloadbutton->class .= ' m-b-1'; // Seems a bit hackish, ahem.
$downloadbutton = $OUTPUT->render($downloadbutton);
}
@ -179,6 +179,8 @@ if (!$downloadown && !$downloadissue) {
redirect(new moodle_url('/mod/customcert/view.php', array('id' => $cm->id)));
}
\core\session\manager::write_close();
// Now we want to generate the PDF.
$template = new \mod_customcert\template($template);
$template->generate_pdf(false, $userid);

View file

@ -80,24 +80,14 @@ Y.extend(Rearrange, Y.Base, {
this.elements = params[2];
// Set the PDF dimensions.
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
this.setPdfDimensions();
// Set the boundaries.
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
this.setBoundaries();
this.setpositions();
this.createevents();
window.addEventListener("resize", this.checkWindownResize.bind(this));
},
/**
@ -130,6 +120,40 @@ Y.extend(Rearrange, Y.Base, {
}
},
/**
* Sets the PDF dimensions.
*/
setPdfDimensions: function() {
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
},
/**
* Sets the boundaries.
*/
setBoundaries: function() {
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
},
/**
* Check browser resize and reset position.
*/
checkWindownResize: function() {
this.setPdfDimensions();
this.setBoundaries();
this.setpositions();
},
/**
* Creates the JS events for changing element positions.
*/

View file

@ -1 +1 @@
YUI.add("moodle-mod_customcert-rearrange",function(e,t){var n=function(){n.superclass.constructor.apply(this,[arguments])};e.extend(n,e.Base,{templateid:0,page:[],elements:[],pdfx:0,pdfy:0,pdfwidth:0,pdfheight:0,elementxy:0,pdfleftboundary:0,pdfrightboundary:0,pixelsinmm:3.779527559055,initializer:function(t){this.templateid=t[0],this.page=t[1],this.elements=t[2],this.pdfx=e.one("#pdf").getX(),this.pdfy=e.one("#pdf").getY(),this.pdfwidth=parseFloat(e.one("#pdf").getComputedStyle("width")),this.pdfheight=parseFloat(e.one("#pdf").getComputedStyle("height")),this.pdfleftboundary=this.pdfx,this.page.leftmargin&&(this.pdfleftboundary+=parseInt(this.page.leftmargin*this.pixelsinmm,10)),this.pdfrightboundary=this.pdfx+this.pdfwidth,this.page.rightmargin&&(this.pdfrightboundary-=parseInt(this.page.rightmargin*this.pixelsinmm,10)),this.setpositions(),this.createevents()},setpositions:function(){for(var t in this.elements){var n=this.elements[t],r=this.pdfx+n.posx*this.pixelsinmm,i=this.pdfy+n.posy*this.pixelsinmm,s=parseFloat(e.one("#element-"+n.id).getComputedStyle("width")),o=n.width*this.pixelsinmm;o&&s>o&&(s=o);switch(n.refpoint){case"1":r-=s/2;break;case"2":r=r-s+2}e.one("#element-"+n.id).setX(r),e.one("#element-"+n.id).setY(i)}},createevents:function(){e.one(".savepositionsbtn [type=submit]").on("click",function(e){this.savepositions(e)},this),e.one(".applypositionsbtn [type=submit]").on("click",function(e){this.savepositions(e),e.preventDefault()},this);var t=new e.DD.Delegate({container:"#pdf",nodes:".element"});t.on("drag:start",function(){var e=t.get("currentNode");this.elementxy=e.getXY()},this),t.on("drag:end",function(){var e=t.get("currentNode");this.isoutofbounds(e)&&e.setXY(this.elementxy)},this)},isoutofbounds:function(e){var t=parseFloat(e.getComputedStyle("width")),n=parseFloat(e.getComputedStyle("height")),r=e.getX(),i=r+t,s=e.getY(),o=s+n;return r<this.pdfleftboundary||i>this.pdfrightboundary?!0:s<this.pdfy||o>this.pdfy+this.pdfheight?!0:!1},savepositions:function(t){var n={tid:this.templateid,values:[]};for(var r in this.elements){var i=this.elements[r],s=e.one("#element-"+i.id),o=s.getX()-this.pdfx,u=s.getY()-this.pdfy,a=s.getData("refpoint"),f=parseFloat(s.getComputedStyle("width"));switch(a){case"1":o+=f/2;break;case"2":o+=f}n.values.push({id:i.id,posx:Math.round(parseFloat(o/this.pixelsinmm)),posy:Math.round(parseFloat(u/this.pixelsinmm))})}n.values=JSON.stringify(n.values),e.io(M.cfg.wwwroot+"/mod/customcert/ajax.php",{method:"POST",data:n,on:{failure:function(e,t){this.ajaxfailure(t)},success:function(){var e=t.currentTarget.ancestor("form",!0),n=e.getAttribute("action"),r=e.one("[name=pid]");if(r){var i=r.get("value");window.location=n+"?pid="+i}else{var s=e.one("[name=tid]").get("value");window.location=n+"?tid="+s}}},context:this}),t.preventDefault()},ajaxfailure:function(e){var t={name:e.status+" "+e.statusText,message:e.responseText};return new M.core.exception(t)}}),e.namespace("M.mod_customcert.rearrange").init=function(e,t,r){new n(e,t,r)}},"@VERSION@",{requires:["dd-delegate","dd-drag"]});
YUI.add("moodle-mod_customcert-rearrange",function(e,t){var n=function(){n.superclass.constructor.apply(this,[arguments])};e.extend(n,e.Base,{templateid:0,page:[],elements:[],pdfx:0,pdfy:0,pdfwidth:0,pdfheight:0,elementxy:0,pdfleftboundary:0,pdfrightboundary:0,pixelsinmm:3.779527559055,initializer:function(e){this.templateid=e[0],this.page=e[1],this.elements=e[2],this.setPdfDimensions(),this.setBoundaries(),this.setpositions(),this.createevents(),window.addEventListener("resize",this.checkWindownResize.bind(this))},setpositions:function(){for(var t in this.elements){var n=this.elements[t],r=this.pdfx+n.posx*this.pixelsinmm,i=this.pdfy+n.posy*this.pixelsinmm,s=parseFloat(e.one("#element-"+n.id).getComputedStyle("width")),o=n.width*this.pixelsinmm;o&&s>o&&(s=o);switch(n.refpoint){case"1":r-=s/2;break;case"2":r=r-s+2}e.one("#element-"+n.id).setX(r),e.one("#element-"+n.id).setY(i)}},setPdfDimensions:function(){this.pdfx=e.one("#pdf").getX(),this.pdfy=e.one("#pdf").getY(),this.pdfwidth=parseFloat(e.one("#pdf").getComputedStyle("width")),this.pdfheight=parseFloat(e.one("#pdf").getComputedStyle("height"))},setBoundaries:function(){this.pdfleftboundary=this.pdfx,this.page.leftmargin&&(this.pdfleftboundary+=parseInt(this.page.leftmargin*this.pixelsinmm,10)),this.pdfrightboundary=this.pdfx+this.pdfwidth,this.page.rightmargin&&(this.pdfrightboundary-=parseInt(this.page.rightmargin*this.pixelsinmm,10))},checkWindownResize:function(){this.setPdfDimensions(),this.setBoundaries(),this.setpositions()},createevents:function(){e.one(".savepositionsbtn [type=submit]").on("click",function(e){this.savepositions(e)},this),e.one(".applypositionsbtn [type=submit]").on("click",function(e){this.savepositions(e),e.preventDefault()},this);var t=new e.DD.Delegate({container:"#pdf",nodes:".element"});t.on("drag:start",function(){var e=t.get("currentNode");this.elementxy=e.getXY()},this),t.on("drag:end",function(){var e=t.get("currentNode");this.isoutofbounds(e)&&e.setXY(this.elementxy)},this)},isoutofbounds:function(e){var t=parseFloat(e.getComputedStyle("width")),n=parseFloat(e.getComputedStyle("height")),r=e.getX(),i=r+t,s=e.getY(),o=s+n;return r<this.pdfleftboundary||i>this.pdfrightboundary?!0:s<this.pdfy||o>this.pdfy+this.pdfheight?!0:!1},savepositions:function(t){var n={tid:this.templateid,values:[]};for(var r in this.elements){var i=this.elements[r],s=e.one("#element-"+i.id),o=s.getX()-this.pdfx,u=s.getY()-this.pdfy,a=s.getData("refpoint"),f=parseFloat(s.getComputedStyle("width"));switch(a){case"1":o+=f/2;break;case"2":o+=f}n.values.push({id:i.id,posx:Math.round(parseFloat(o/this.pixelsinmm)),posy:Math.round(parseFloat(u/this.pixelsinmm))})}n.values=JSON.stringify(n.values),e.io(M.cfg.wwwroot+"/mod/customcert/ajax.php",{method:"POST",data:n,on:{failure:function(e,t){this.ajaxfailure(t)},success:function(){var e=t.currentTarget.ancestor("form",!0),n=e.getAttribute("action"),r=e.one("[name=pid]");if(r){var i=r.get("value");window.location=n+"?pid="+i}else{var s=e.one("[name=tid]").get("value");window.location=n+"?tid="+s}}},context:this}),t.preventDefault()},ajaxfailure:function(e){var t={name:e.status+" "+e.statusText,message:e.responseText};return new M.core.exception(t)}}),e.namespace("M.mod_customcert.rearrange").init=function(e,t,r){new n(e,t,r)}},"@VERSION@",{requires:["dd-delegate","dd-drag"]});

View file

@ -80,24 +80,14 @@ Y.extend(Rearrange, Y.Base, {
this.elements = params[2];
// Set the PDF dimensions.
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
this.setPdfDimensions();
// Set the boundaries.
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
this.setBoundaries();
this.setpositions();
this.createevents();
window.addEventListener("resize", this.checkWindownResize.bind(this));
},
/**
@ -130,6 +120,40 @@ Y.extend(Rearrange, Y.Base, {
}
},
/**
* Sets the PDF dimensions.
*/
setPdfDimensions: function() {
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
},
/**
* Sets the boundaries.
*/
setBoundaries: function() {
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
},
/**
* Check browser resize and reset position.
*/
checkWindownResize: function() {
this.setPdfDimensions();
this.setBoundaries();
this.setpositions();
},
/**
* Creates the JS events for changing element positions.
*/

View file

@ -78,24 +78,14 @@ Y.extend(Rearrange, Y.Base, {
this.elements = params[2];
// Set the PDF dimensions.
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
this.setPdfDimensions();
// Set the boundaries.
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
this.setBoundaries();
this.setpositions();
this.createevents();
window.addEventListener("resize", this.checkWindownResize.bind(this));
},
/**
@ -128,6 +118,40 @@ Y.extend(Rearrange, Y.Base, {
}
},
/**
* Sets the PDF dimensions.
*/
setPdfDimensions: function() {
this.pdfx = Y.one('#pdf').getX();
this.pdfy = Y.one('#pdf').getY();
this.pdfwidth = parseFloat(Y.one('#pdf').getComputedStyle('width'));
this.pdfheight = parseFloat(Y.one('#pdf').getComputedStyle('height'));
},
/**
* Sets the boundaries.
*/
setBoundaries: function() {
this.pdfleftboundary = this.pdfx;
if (this.page.leftmargin) {
this.pdfleftboundary += parseInt(this.page.leftmargin * this.pixelsinmm, 10);
}
this.pdfrightboundary = this.pdfx + this.pdfwidth;
if (this.page.rightmargin) {
this.pdfrightboundary -= parseInt(this.page.rightmargin * this.pixelsinmm, 10);
}
},
/**
* Check browser resize and reset position.
*/
checkWindownResize: function() {
this.setPdfDimensions();
this.setBoundaries();
this.setpositions();
},
/**
* Creates the JS events for changing element positions.
*/