Compare commits

...

26 commits

Author SHA1 Message Date
Mark Nelson
a228b9a8d4 Update CHANGES.md 2021-10-27 16:47:21 +08:00
Mark Nelson
6eae9d329b Respect multiple languages in manage template page title (#467) 2021-10-27 16:45:59 +08:00
Mark Nelson
ce3e172571 Change JS to please GHA 2021-10-27 13:32:54 +08:00
Mark Nelson
f551c80176 Changes to make GHA happy and added usages of the coalescing operator (#121) 2021-10-27 13:19:59 +08:00
Mark Nelson
40e42a13b8 Add issue number to CHANGES.md 2021-10-27 12:25:23 +08:00
Mark Nelson
5e3caf9e0b Updated CHANGES.md 2021-10-25 17:23:10 +08:00
Jesús Alonso Abad
f8577485d4 Added alignment to activity backup/cloning (#121) 2021-10-25 17:22:31 +08:00
Jesús Alonso Abad
d53a4d89bc Added elements alignment support (#121) 2021-10-25 17:22:23 +08:00
Mark Nelson
0cecdd0ab5 Do not encode html entities in emails (#457) 2021-09-28 22:24:46 +08:00
Mark Nelson
8f72977ac6 Update CHANGES.md 2021-08-06 10:57:36 +08:00
Andrew Madden
79e367864d Closes #449 The user id should be mapped to the equivalent user id in new sites during activity restore
As the userid in the customcert_issues table were using the userid from the site where the activity was backed up, emails were being sent to those that had already received them. Theoretically there wwere also users who should have received an email who didn't get one.
2021-08-06 10:48:55 +08:00
Mark Nelson
c3b5a3d5a1 Update CHANGES.md 2021-08-04 11:46:26 +08:00
Mark Nelson
ce0746ea70 Minor code changes (#415) 2021-08-04 11:45:29 +08:00
Sameer Ahmed
1635adbba3 Display the course short name (#415)
- Added a new select box to choose from course name or short description to display.
2021-08-04 11:45:22 +08:00
Mark Nelson
6d0d84aa90 GHA: ROW_FORMAT=COMPRESSED deprecated in MariaDB 10.6
See MDL-72131.
2021-08-03 18:05:03 +08:00
Mark Nelson
db215a0a7f Add new lines at end of files 2021-08-03 17:52:36 +08:00
Mark Nelson
f82c166440 Remove unnecessary new line 2021-08-03 16:53:41 +08:00
Mark Nelson
ae6e80c3be Minor changes and CHANGES.md note (#433) 2021-08-03 16:33:16 +08:00
Michael Milette
747dcc4c66 Fix for multi-language issues (#433).
- Filename of PDF when viewing/previewing PDF.
- Page title tag when viewing/previewing PDF.
- List of available templates.
- Template Load dropdown list.
2021-08-03 16:32:06 +08:00
Dani Palou
918e3168cd Adapt mobile app code to Ionic 5 (#431) 2021-07-06 17:05:01 +08:00
Mark Nelson
b41fefd989 Bump version 2021-06-13 14:08:05 +08:00
Mark Nelson
6ce7f1fd45 Use 'cron_setup_user' when sending emails (#414) 2021-06-13 14:06:34 +08:00
Mark Nelson
7a45c41cd9 Set proper context when sending emails (#402) 2021-06-13 13:37:04 +08:00
hieuvu
f5afd08bf4 Add actions title to elements table (#425) 2021-06-08 17:47:41 +08:00
Mark Nelson
901c44764f Removed .travis.yml file 2021-05-27 19:21:41 +08:00
Mark Nelson
2e486e02a3 Fix Moodle Code Checker complaints 2021-05-27 18:53:06 +08:00
37 changed files with 444 additions and 116 deletions

View file

@ -16,7 +16,7 @@ jobs:
- 5432:5432 - 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
mariadb: mariadb:
image: mariadb:10 image: mariadb:10.5
env: env:
MYSQL_USER: 'root' MYSQL_USER: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: "true" MYSQL_ALLOW_EMPTY_PASSWORD: "true"

View file

@ -1,54 +0,0 @@
language: php
# For javascript behat tests we need sudo.
sudo: true
cache:
directories:
- $HOME/.composer/cache
- $HOME/.npm
php:
- 7.2
- 7.4
addons:
firefox: 47.0.1
postgresql: "9.6"
apt:
packages:
- openjdk-8-jre-headless
services:
- mysql
- postgresql
env:
global:
- MOODLE_BRANCH=MOODLE_310_STABLE
- IGNORE_NAMES=mobile_*.mustache # Mobile mustache has specific syntax, ignore their templates
matrix:
- DB=pgsql
- DB=mysqli
before_install:
- phpenv config-rm xdebug.ini
- cd ../..
- 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:
- moodle-plugin-ci install
script:
- moodle-plugin-ci phplint
# - moodle-plugin-ci phpcpd # subplugins often have similar code and cause "duplicated code" errors
# - moodle-plugin-ci phpmd # too much noise from this check, maybe, some day...
- moodle-plugin-ci codechecker
- moodle-plugin-ci validate
- moodle-plugin-ci savepoints
- moodle-plugin-ci mustache
- moodle-plugin-ci grunt
# - moodle-plugin-ci phpdoc # Complains about missing PHPDocs when they exist in parent class.
- moodle-plugin-ci phpunit
- moodle-plugin-ci behat

View file

@ -4,7 +4,19 @@ 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/mdjnelson/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.10.1] - 2021-XX-YY ## [3.10.2] - 2021-??-??
### Fixed
- Fix places not using the multi-language filter (#433).
- Fix user IDs in the issue table not being mapped during restore (#449).
- Fix emails displaying HTML entities encoded (#457).
- Respect multiple languages in manage template page title (#467).
### Added
- You can now choose the course short or full name to display (#415).
- You can now select the alignment for all text elements (#121).
## [3.10.1] - 2021-06-13
### Added ### Added
- Usage of github actions (#407). - Usage of github actions (#407).
@ -15,6 +27,8 @@ Note - All hash comments refer to the issue number. Eg. #169 refers to https://g
- Managers are now able to download their students' certificates (#412). - Managers are now able to download their students' certificates (#412).
- Users being able to view the certificate before the required time set (#403). - Users being able to view the certificate before the required time set (#403).
- Fixed the issue with displaying PDF when debugging is ON (#420). - Fixed the issue with displaying PDF when debugging is ON (#420).
- Using incorrect context when sending emails (#402).
- Use `cron_setup_user` when sending emails (#414).
## [3.8.5] - 2020-11-26 ## [3.8.5] - 2020-11-26

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -18,7 +18,6 @@
* use the YUI version in AMD code until it is replaced. * use the YUI version in AMD code until it is replaced.
* *
* @module mod_customcert/dialogue * @module mod_customcert/dialogue
* @package mod_customcert
* @copyright 2016 Mark Nelson <markn@moodle.com> * @copyright 2016 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */

View file

@ -17,7 +17,6 @@
* AMD module used when rearranging a custom certificate. * AMD module used when rearranging a custom certificate.
* *
* @module mod_customcert/rearrange-area * @module mod_customcert/rearrange-area
* @package mod_customcert
* @copyright 2016 Mark Nelson <markn@moodle.com> * @copyright 2016 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */

View file

@ -56,7 +56,7 @@ class backup_customcert_activity_task extends backup_activity_task {
* @param string $content * @param string $content
* @return mixed|string * @return mixed|string
*/ */
static public function encode_content_links($content) { public static function encode_content_links($content) {
global $CFG; global $CFG;
$base = preg_quote($CFG->wwwroot, "/"); $base = preg_quote($CFG->wwwroot, "/");

View file

@ -59,7 +59,7 @@ class backup_customcert_activity_structure_step extends backup_activity_structur
$element = new backup_nested_element('element', array('id'), array( $element = new backup_nested_element('element', array('id'), array(
'pageid', 'name', 'element', 'data', 'font', 'fontsize', 'pageid', 'name', 'element', 'data', 'font', 'fontsize',
'colour', 'posx', 'posy', 'width', 'refpoint', 'sequence', 'colour', 'posx', 'posy', 'width', 'refpoint', 'sequence',
'timecreated', 'timemodified')); 'alignment', 'timecreated', 'timemodified'));
// The issues. // The issues.
$issues = new backup_nested_element('issues'); $issues = new backup_nested_element('issues');

View file

@ -53,7 +53,7 @@ class restore_customcert_activity_task extends restore_activity_task {
/** /**
* Define the contents in the activity that must be processed by the link decoder. * Define the contents in the activity that must be processed by the link decoder.
*/ */
static public function define_decode_contents() { public static function define_decode_contents() {
$contents = array(); $contents = array();
$contents[] = new restore_decode_content('customcert', array('intro'), 'customcert'); $contents[] = new restore_decode_content('customcert', array('intro'), 'customcert');
@ -64,7 +64,7 @@ class restore_customcert_activity_task extends restore_activity_task {
/** /**
* Define the decoding rules for links belonging to the activity to be executed by the link decoder. * Define the decoding rules for links belonging to the activity to be executed by the link decoder.
*/ */
static public function define_decode_rules() { public static function define_decode_rules() {
$rules = array(); $rules = array();
$rules[] = new restore_decode_rule('CUSTOMCERTVIEWBYID', '/mod/customcert/view.php?id=$1', 'course_module'); $rules[] = new restore_decode_rule('CUSTOMCERTVIEWBYID', '/mod/customcert/view.php?id=$1', 'course_module');
@ -80,7 +80,7 @@ class restore_customcert_activity_task extends restore_activity_task {
* *
* @return array the restore log rules * @return array the restore log rules
*/ */
static public function define_restore_log_rules() { public static function define_restore_log_rules() {
$rules = array(); $rules = array();
$rules[] = new restore_log_rule('customcert', 'add', 'view.php?id={course_module}', '{customcert}'); $rules[] = new restore_log_rule('customcert', 'add', 'view.php?id={course_module}', '{customcert}');

View file

@ -159,6 +159,7 @@ class restore_customcert_activity_structure_step extends restore_activity_struct
$data->customcertid = $this->get_new_parentid('customcert'); $data->customcertid = $this->get_new_parentid('customcert');
$data->timecreated = $this->apply_date_offset($data->timecreated); $data->timecreated = $this->apply_date_offset($data->timecreated);
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('customcert_issues', $data); $newitemid = $DB->insert_record('customcert_issues', $data);
$this->set_mapping('customcert_issue', $oldid, $newitemid); $this->set_mapping('customcert_issue', $oldid, $newitemid);

View file

@ -247,7 +247,7 @@ class edit_form extends \moodleform {
// Create a table to display these elements. // Create a table to display these elements.
$table = new \html_table(); $table = new \html_table();
$table->attributes = array('class' => 'generaltable elementstable'); $table->attributes = array('class' => 'generaltable elementstable');
$table->head = array(get_string('name', 'customcert'), get_string('type', 'customcert'), ''); $table->head = array(get_string('name', 'customcert'), get_string('type', 'customcert'), get_string('actions'));
$table->align = array('left', 'left', 'left'); $table->align = array('left', 'left', 'left');
// Loop through and add the elements to the table. // Loop through and add the elements to the table.
foreach ($elements as $element) { foreach ($elements as $element) {

View file

@ -37,6 +37,21 @@ defined('MOODLE_INTERNAL') || die();
*/ */
abstract class element { abstract class element {
/**
* @var string The left alignment constant.
*/
const ALIGN_LEFT = 'L';
/**
* @var string The centered alignment constant.
*/
const ALIGN_CENTER = 'C';
/**
* @var string The right alignment constant.
*/
const ALIGN_RIGHT = 'R';
/** /**
* @var \stdClass $element The data for the element we are adding - do not use, kept for legacy reasons. * @var \stdClass $element The data for the element we are adding - do not use, kept for legacy reasons.
*/ */
@ -97,6 +112,11 @@ abstract class element {
*/ */
protected $refpoint; protected $refpoint;
/**
* @var string The alignment.
*/
protected $alignment;
/** /**
* @var bool $showposxy Show position XY form elements? * @var bool $showposxy Show position XY form elements?
*/ */
@ -130,6 +150,7 @@ abstract class element {
$this->width = $element->width; $this->width = $element->width;
$this->refpoint = $element->refpoint; $this->refpoint = $element->refpoint;
$this->showposxy = isset($showposxy) && $showposxy; $this->showposxy = isset($showposxy) && $showposxy;
$this->set_alignment($element->alignment ?? self::ALIGN_LEFT);
} }
/** /**
@ -231,6 +252,31 @@ abstract class element {
return $this->refpoint; return $this->refpoint;
} }
/**
* Returns the alignment.
*
* @return string The current alignment value.
*/
public function get_alignment() {
return $this->alignment ?? self::ALIGN_LEFT;
}
/**
* Sets the alignment.
*
* @param string $alignment The new alignment.
*
* @throws \InvalidArgumentException if the provided new alignment is not valid.
*/
protected function set_alignment(string $alignment) {
$validvalues = array(self::ALIGN_LEFT, self::ALIGN_CENTER, self::ALIGN_RIGHT);
if (!in_array($alignment, $validvalues)) {
throw new \InvalidArgumentException("'$alignment' is not a valid alignment value. It has to be one of " .
implode(', ', $validvalues));
}
$this->alignment = $alignment;
}
/** /**
* This function renders the form elements when adding a customcert element. * This function renders the form elements when adding a customcert element.
* Can be overridden if more functionality is needed. * Can be overridden if more functionality is needed.
@ -246,6 +292,7 @@ abstract class element {
} }
element_helper::render_form_element_width($mform); element_helper::render_form_element_width($mform);
element_helper::render_form_element_refpoint($mform); element_helper::render_form_element_refpoint($mform);
element_helper::render_form_element_alignment($mform);
} }
/** /**
@ -265,7 +312,8 @@ abstract class element {
'posx' => $this->posx, 'posx' => $this->posx,
'posy' => $this->posy, 'posy' => $this->posy,
'width' => $this->width, 'width' => $this->width,
'refpoint' => $this->refpoint 'refpoint' => $this->refpoint,
'alignment' => $this->get_alignment()
]; ];
foreach ($properties as $property => $value) { foreach ($properties as $property => $value) {
if (!is_null($value) && $mform->elementExists($property)) { if (!is_null($value) && $mform->elementExists($property)) {
@ -311,15 +359,16 @@ abstract class element {
$element = new \stdClass(); $element = new \stdClass();
$element->name = $data->name; $element->name = $data->name;
$element->data = $this->save_unique_data($data); $element->data = $this->save_unique_data($data);
$element->font = (isset($data->font)) ? $data->font : null; $element->font = $data->font ?? null;
$element->fontsize = (isset($data->fontsize)) ? $data->fontsize : null; $element->fontsize = $data->fontsize ?? null;
$element->colour = (isset($data->colour)) ? $data->colour : null; $element->colour = $data->colour ?? null;
if ($this->showposxy) { if ($this->showposxy) {
$element->posx = (isset($data->posx)) ? $data->posx : null; $element->posx = $data->posx ?? null;
$element->posy = (isset($data->posy)) ? $data->posy : null; $element->posy = $data->posy ?? null;
} }
$element->width = (isset($data->width)) ? $data->width : null; $element->width = $data->width ?? null;
$element->refpoint = (isset($data->refpoint)) ? $data->refpoint : null; $element->refpoint = $data->refpoint ?? null;
$element->alignment = $data->alignment ?? self::ALIGN_LEFT;
$element->timemodified = time(); $element->timemodified = time();
// Check if we are updating, or inserting a new element. // Check if we are updating, or inserting a new element.
@ -377,7 +426,7 @@ abstract class element {
* @param bool $preview true if it is a preview, false otherwise * @param bool $preview true if it is a preview, false otherwise
* @param \stdClass $user the user we are rendering this for * @param \stdClass $user the user we are rendering this for
*/ */
public abstract function render($pdf, $preview, $user); abstract public function render($pdf, $preview, $user);
/** /**
* Render the element in html. * Render the element in html.
@ -389,7 +438,7 @@ abstract class element {
* *
* @return string the html * @return string the html
*/ */
public abstract function render_html(); abstract public function render_html();
/** /**
* Handles deleting any data this element may have introduced. * Handles deleting any data this element may have introduced.

View file

@ -59,6 +59,7 @@ class element_factory {
$data->posy = $element->posy ?? null; $data->posy = $element->posy ?? null;
$data->width = $element->width ?? null; $data->width = $element->width ?? null;
$data->refpoint = $element->refpoint ?? null; $data->refpoint = $element->refpoint ?? null;
$data->alignment = $element->alignment ?? null;
// Ensure the necessary class exists. // Ensure the necessary class exists.
if (class_exists($classname)) { if (class_exists($classname)) {

View file

@ -74,6 +74,7 @@ class element_helper {
$w = $element->get_width(); $w = $element->get_width();
$refpoint = $element->get_refpoint(); $refpoint = $element->get_refpoint();
$actualwidth = $pdf->GetStringWidth($content); $actualwidth = $pdf->GetStringWidth($content);
$alignment = $element->get_alignment();
if ($w and $w < $actualwidth) { if ($w and $w < $actualwidth) {
$actualwidth = $w; $actualwidth = $w;
@ -104,7 +105,7 @@ class element_helper {
$w += 0.0001; $w += 0.0001;
} }
$pdf->setCellPaddings(0, 0, 0, 0); $pdf->setCellPaddings(0, 0, 0, 0);
$pdf->writeHTMLCell($w, 0, $x, $y, $content, 0, 0, false, true); $pdf->writeHTMLCell($w, 0, $x, $y, $content, 0, 0, false, true, $alignment);
} }
/** /**
@ -205,6 +206,23 @@ class element_helper {
$mform->addHelpButton('refpoint', 'refpoint', 'customcert'); $mform->addHelpButton('refpoint', 'refpoint', 'customcert');
} }
/**
* Helper function to render the alignment form element.
*
* @param \MoodleQuickForm $mform the edit_form instance.
*/
public static function render_form_element_alignment($mform) {
$alignmentoptions = array();
$alignmentoptions[element::ALIGN_LEFT] = get_string('alignleft', 'customcert');
$alignmentoptions[element::ALIGN_CENTER] = get_string('aligncenter', 'customcert');
$alignmentoptions[element::ALIGN_RIGHT] = get_string('alignright', 'customcert');
$mform->addElement('select', 'alignment', get_string('alignment', 'customcert'), $alignmentoptions);
$mform->setType('alignment', PARAM_ALPHA);
$mform->setDefault('alignment', element::ALIGN_LEFT);
$mform->addHelpButton('alignment', 'alignment', 'customcert');
}
/** /**
* Helper function to performs validation on the colour element. * Helper function to performs validation on the colour element.
* *

View file

@ -47,19 +47,23 @@ class load_template_form extends \moodleform {
// Get the context. // Get the context.
$context = $this->_customdata['context']; $context = $this->_customdata['context'];
$syscontext = \context_system::instance();
$mform->addElement('header', 'loadtemplateheader', get_string('loadtemplate', 'customcert')); $mform->addElement('header', 'loadtemplateheader', get_string('loadtemplate', 'customcert'));
// Display a link to the manage templates page. // Display a link to the manage templates page.
if ($context->contextlevel != CONTEXT_SYSTEM && has_capability('mod/customcert:manage', \context_system::instance())) { if ($context->contextlevel != CONTEXT_SYSTEM && has_capability('mod/customcert:manage', $syscontext)) {
$link = \html_writer::link(new \moodle_url('/mod/customcert/manage_templates.php'), $link = \html_writer::link(new \moodle_url('/mod/customcert/manage_templates.php'),
get_string('managetemplates', 'customcert')); get_string('managetemplates', 'customcert'));
$mform->addElement('static', 'managetemplates', '', $link); $mform->addElement('static', 'managetemplates', '', $link);
} }
$templates = $DB->get_records_menu('customcert_templates', $arrtemplates = $DB->get_records_menu('customcert_templates', ['contextid' => $syscontext->id], 'name ASC', 'id, name');
array('contextid' => \context_system::instance()->id), 'name ASC', 'id, name'); if ($arrtemplates) {
if ($templates) { $templates = [];
foreach ($arrtemplates as $key => $template) {
$templates[$key] = format_string($template, true, ['context' => $context]);
}
$group = array(); $group = array();
$group[] = $mform->createElement('select', 'ltid', '', $templates); $group[] = $mform->createElement('select', 'ltid', '', $templates);
$group[] = $mform->createElement('submit', 'loadtemplatesubmit', get_string('load', 'customcert')); $group[] = $mform->createElement('submit', 'loadtemplatesubmit', get_string('load', 'customcert'));

View file

@ -77,7 +77,7 @@ class manage_templates_table extends \table_sql {
* @return string * @return string
*/ */
public function col_name($template) { public function col_name($template) {
return $template->name; return format_string($template->name, true, ['context' => $this->context]);
} }
/** /**

View file

@ -44,9 +44,10 @@ class mobile {
global $OUTPUT, $DB, $USER; global $OUTPUT, $DB, $USER;
$args = (object) $args; $args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$cmid = $args->cmid; $cmid = $args->cmid;
$groupid = empty($args->group) ? 0 : $args->group; // By default, group 0. $groupid = empty($args->group) ? 0 : (int) $args->group; // By default, group 0.
// Capabilities check. // Capabilities check.
$cm = get_coursemodule_from_id('customcert', $cmid); $cm = get_coursemodule_from_id('customcert', $cmid);
@ -114,6 +115,7 @@ class mobile {
'showreport' => $showreport, 'showreport' => $showreport,
'hasrecipients' => !empty($recipients), 'hasrecipients' => !empty($recipients),
'recipients' => array_values($recipients), 'recipients' => array_values($recipients),
'numrecipients' => count($recipients),
'currenttimestamp' => time() 'currenttimestamp' => time()
]; ];
@ -121,11 +123,13 @@ class mobile {
'templates' => [ 'templates' => [
[ [
'id' => 'main', 'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_customcert/mobile_view_activity_page', $data), 'html' => $OUTPUT->render_from_template("mod_customcert/mobile_view_activity_page_$versionname", $data),
], ],
], ],
'javascript' => '', 'javascript' => '',
'otherdata' => '' 'otherdata' => [
'group' => $groupid,
]
]; ];
} }

View file

@ -84,6 +84,9 @@ class email_certificate_task extends \core\task\scheduled_task {
// Get the context. // Get the context.
$context = \context::instance_by_id($customcert->contextid); $context = \context::instance_by_id($customcert->contextid);
// Set the $PAGE context - this ensure settings, such as language, are kept and don't default to the site settings.
$PAGE->set_context($context);
// Get the person we are going to send this email on behalf of. // Get the person we are going to send this email on behalf of.
$userfrom = \core_user::get_noreply_user(); $userfrom = \core_user::get_noreply_user();
@ -176,6 +179,9 @@ class email_certificate_task extends \core\task\scheduled_task {
// Now, email the people we need to. // Now, email the people we need to.
foreach ($issuedusers as $user) { foreach ($issuedusers as $user) {
// Set up the user.
cron_setup_user($user);
$userfullname = fullname($user); $userfullname = fullname($user);
$info->userfullname = $userfullname; $info->userfullname = $userfullname;
@ -205,7 +211,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$subject = get_string('emailstudentsubject', 'customcert', $info); $subject = get_string('emailstudentsubject', 'customcert', $info);
$message = $textrenderer->render($renderable); $message = $textrenderer->render($renderable);
$messagehtml = $htmlrenderer->render($renderable); $messagehtml = $htmlrenderer->render($renderable);
email_to_user($user, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, $filename); email_to_user($user, fullname($userfrom), html_entity_decode($subject), $message, $messagehtml,
$tempfile, $filename);
} }
if ($customcert->emailteachers) { if ($customcert->emailteachers) {
@ -216,8 +223,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$message = $textrenderer->render($renderable); $message = $textrenderer->render($renderable);
$messagehtml = $htmlrenderer->render($renderable); $messagehtml = $htmlrenderer->render($renderable);
foreach ($teachers as $teacher) { foreach ($teachers as $teacher) {
email_to_user($teacher, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, email_to_user($teacher, fullname($userfrom), html_entity_decode($subject), $message, $messagehtml,
$filename); $tempfile, $filename);
} }
} }
@ -236,8 +243,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$emailuser = new \stdClass(); $emailuser = new \stdClass();
$emailuser->id = -1; $emailuser->id = -1;
$emailuser->email = $email; $emailuser->email = $email;
email_to_user($emailuser, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, email_to_user($emailuser, fullname($userfrom), html_entity_decode($subject), $message,
$filename); $messagehtml, $tempfile, $filename);
} }
} }
} }

View file

@ -283,12 +283,13 @@ class template {
$deliveryoption = $customcert->deliveryoption; $deliveryoption = $customcert->deliveryoption;
} }
// Remove full-stop at the end, if it exists, to avoid "..pdf" being created and being filtered by clean_filename.
$filename = rtrim(format_string($this->name, true, ['context' => $this->get_context()]), '.');
$pdf->setPrintHeader(false); $pdf->setPrintHeader(false);
$pdf->setPrintFooter(false); $pdf->setPrintFooter(false);
$pdf->SetTitle($this->name); $pdf->SetTitle($filename);
$pdf->SetAutoPageBreak(true, 0); $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 // 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. // such as 'الشهادة' become empty, so set a default name in these cases.

View file

@ -85,6 +85,7 @@
<FIELD NAME="posy" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="posy" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="width" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="width" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="refpoint" TYPE="int" LENGTH="4" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="refpoint" TYPE="int" LENGTH="4" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="alignment" TYPE="char" LENGTH="1" NOTNULL="true" DEFAULT="L" SEQUENCE="false"/>
<FIELD NAME="sequence" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/> <FIELD NAME="sequence" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/> <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/> <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>

View file

@ -178,5 +178,14 @@ function xmldb_customcert_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2020110901, 'customcert'); upgrade_mod_savepoint(true, 2020110901, 'customcert');
} }
if ($oldversion < 2020110903) {
$table = new xmldb_table('customcert_elements');
$field = new xmldb_field('alignment', XMLDB_TYPE_CHAR, '1', null, XMLDB_NOTNULL, null, 'L', 'refpoint');
$dbman->add_field($table, $field);
upgrade_mod_savepoint(true, 2020110903, 'customcert'); // Replace with the actual version number.
}
return true; return true;
} }

View file

@ -35,6 +35,42 @@ defined('MOODLE_INTERNAL') || die();
*/ */
class element extends \mod_customcert\element { class element extends \mod_customcert\element {
/**
* The course short name.
*/
const COURSE_SHORT_NAME = 1;
/**
* The course fullname.
*/
const COURSE_FULL_NAME = 2;
/**
* This function renders the form elements when adding a customcert element.
*
* @param \MoodleQuickForm $mform the edit_form instance
*/
public function render_form_elements($mform) {
// The course name display options.
$mform->addElement('select', 'coursenamedisplay', get_string('coursenamedisplay', 'customcertelement_coursename'),
self::get_course_name_display_options());
$mform->setType('coursenamedisplay', PARAM_INT);
$mform->addHelpButton('coursenamedisplay', 'coursenamedisplay', 'customcertelement_coursename');
parent::render_form_elements($mform);
}
/**
* This will handle how form data will be saved into the data column in the
* customcert_elements table.
*
* @param \stdClass $data the form data
* @return string the text
*/
public function save_unique_data($data) {
return $data->coursenamedisplay;
}
/** /**
* Handles rendering the element on the pdf. * Handles rendering the element on the pdf.
* *
@ -43,7 +79,7 @@ class element extends \mod_customcert\element {
* @param \stdClass $user the user we are rendering this for * @param \stdClass $user the user we are rendering this for
*/ */
public function render($pdf, $preview, $user) { public function render($pdf, $preview, $user) {
\mod_customcert\element_helper::render_content($pdf, $this, $this->get_course_name()); \mod_customcert\element_helper::render_content($pdf, $this, $this->get_course_name_detail());
} }
/** /**
@ -55,19 +91,52 @@ class element extends \mod_customcert\element {
* @return string the html * @return string the html
*/ */
public function render_html() { public function render_html() {
return \mod_customcert\element_helper::render_html_content($this, $this->get_course_name()); return \mod_customcert\element_helper::render_html_content($this, $this->get_course_name_detail());
} }
/** /**
* Helper function that returns the category name. * Sets the data on the form when editing an element.
*
* @param \MoodleQuickForm $mform the edit_form instance
*/
public function definition_after_data($mform) {
if (!empty($this->get_data())) {
$element = $mform->getElement('coursenamedisplay');
$element->setValue($this->get_data());
}
parent::definition_after_data($mform);
}
/**
* Helper function that returns the selected course name detail (i.e. name or short description) for display.
* *
* @return string * @return string
*/ */
protected function get_course_name() : string { protected function get_course_name_detail(): string {
$courseid = \mod_customcert\element_helper::get_courseid($this->get_id()); $courseid = \mod_customcert\element_helper::get_courseid($this->get_id());
$course = get_course($courseid); $course = get_course($courseid);
$context = \mod_customcert\element_helper::get_context($this->get_id()); $context = \mod_customcert\element_helper::get_context($this->get_id());
return format_string($course->fullname, true, ['context' => $context]); // The name field to display.
$field = $this->get_data();
// The name value to display.
$value = $course->fullname;
if ($field == self::COURSE_SHORT_NAME) {
$value = $course->shortname;
}
return format_string($value, true, ['context' => $context]);
}
/**
* Helper function to return all the possible name display options.
*
* @return array returns an array of name options
*/
public static function get_course_name_display_options(): array {
return [
self::COURSE_FULL_NAME => get_string('coursefullname', 'customcertelement_coursename'),
self::COURSE_SHORT_NAME => get_string('courseshortname', 'customcertelement_coursename')
];
} }
} }

View file

@ -22,5 +22,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
$string['coursenamedisplay'] = 'Type';
$string['coursenamedisplay_help'] = 'Display the course full name or short name?';
$string['coursefullname'] = 'Full name';
$string['courseshortname'] = 'Short name';
$string['pluginname'] = 'Course name'; $string['pluginname'] = 'Course name';
$string['privacy:metadata'] = 'The Course name plugin does not store any personal data.'; $string['privacy:metadata'] = 'The Course name plugin does not store any personal data.';

View file

@ -25,6 +25,11 @@
$string['activity'] = 'Activity'; $string['activity'] = 'Activity';
$string['addcertpage'] = 'Add page'; $string['addcertpage'] = 'Add page';
$string['addelement'] = 'Add element'; $string['addelement'] = 'Add element';
$string['aligncenter'] = 'Centered';
$string['alignleft'] = 'Left alignment';
$string['alignment'] = 'Alignment';
$string['alignment_help'] = 'This property sets the horizontal alignment of the element. Some elements may not support this, while the behaviour of others may differ.';
$string['alignright'] = 'Right alignment';
$string['awardedto'] = 'Awarded to'; $string['awardedto'] = 'Awarded to';
$string['cannotverifyallcertificates'] = 'You do not have the permission to verify all certificates on the site.'; $string['cannotverifyallcertificates'] = 'You do not have the permission to verify all certificates on the site.';
$string['certificate'] = 'Certificate'; $string['certificate'] = 'Certificate';

View file

@ -47,7 +47,6 @@ require_login();
require_capability('mod/customcert:manage', $context); require_capability('mod/customcert:manage', $context);
$title = $SITE->fullname; $title = $SITE->fullname;
$heading = $title;
// Set up the page. // Set up the page.
$pageurl = new moodle_url('/mod/customcert/manage_templates.php'); $pageurl = new moodle_url('/mod/customcert/manage_templates.php');
@ -61,6 +60,8 @@ if ($tid && $action && confirm_sesskey()) {
$PAGE->navbar->add(get_string('managetemplates', 'customcert')); $PAGE->navbar->add(get_string('managetemplates', 'customcert'));
} }
$heading = format_string($title, true, ['context' => $context]);
if ($tid) { if ($tid) {
if ($action && confirm_sesskey()) { if ($action && confirm_sesskey()) {
$nourl = new moodle_url('/mod/customcert/manage_templates.php'); $nourl = new moodle_url('/mod/customcert/manage_templates.php');

View file

@ -49,7 +49,7 @@ $user = \core_user::get_user($userid, '*', MUST_EXIST);
// If we are viewing certificates that are not for the currently logged in user then do a capability check. // If we are viewing certificates that are not for the currently logged in user then do a capability check.
if (($userid != $USER->id) && !has_capability('mod/customcert:viewallcertificates', context_system::instance())) { if (($userid != $USER->id) && !has_capability('mod/customcert:viewallcertificates', context_system::instance())) {
print_error('You are not allowed to view these certificates'); throw new moodle_exception('You are not allowed to view these certificates');
} }
$PAGE->set_url($pageurl); $PAGE->set_url($pageurl);

View file

@ -112,6 +112,18 @@ if ($elements) {
default: default:
$class = 'element refpoint-left'; $class = 'element refpoint-left';
} }
switch ($element->alignment) {
case \mod_customcert\element::ALIGN_CENTER:
$class .= ' align-center';
break;
case \mod_customcert\element::ALIGN_RIGHT:
$class .= ' align-right';
break;
case \mod_customcert\element::ALIGN_LEFT:
default:
$class .= ' align-left';
break;
}
$html .= html_writer::tag('div', $e->render_html(), array('class' => $class, $html .= html_writer::tag('div', $e->render_html(), array('class' => $class,
'data-refpoint' => $element->refpoint, 'id' => 'element-' . $element->id)); 'data-refpoint' => $element->refpoint, 'id' => 'element-' . $element->id));
} }

View file

@ -57,6 +57,18 @@
margin: -4px -5px -5px 4px; margin: -4px -5px -5px 4px;
} }
#page-mod-customcert-rearrange .element.align-left {
text-align: left;
}
#page-mod-customcert-rearrange .element.align-center {
text-align: center;
}
#page-mod-customcert-rearrange .element.align-right {
text-align: right;
}
#page-mod-customcert-rearrange #pdf { #page-mod-customcert-rearrange #pdf {
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;

View file

@ -0,0 +1,169 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_customcert/mobile_view_activity_page
The main page to view the custom certificate activity
Classes required for JS:
* None
Data attibutes required for JS:
* All data attributes are required
Context variables required for this template:
* certificate
* cmid
* hasissues
* issues
* showgroups
* groups
* canmanage
* requiredtimemet
* hasrecipients
* recipients
* fileurl
* showreport
* currenttimestamp
Example context (json):
{
"certificate": {
"id": "1",
"course": "2",
"name": "A rad certificate name!",
"intro": "A certificate",
"requiredtime": "60"
},
"cmid": "25",
"issue": {
"timecreated": "1528370177"
},
"showgroups": "true",
"groups": [
{
"id": "2",
"selected": "false",
"name": "Group A"
}
],
"canmanage": "true",
"requiredtimemet": "true",
"fileurl": "http://yoursite.com/mod/customcert/mobile/pluginfile.php?id=4",
"showreport": "true",
"hasrecipients": "true",
"recipients": [
{
"id": "2",
"issueid": "3",
"displayname": "Michaelangelo (Mickey)",
"fileurl": "http://yoursite.com/mod/customcert/mobile/pluginfile.php?id=4",
"timecreated": "1528370177"
}
],
"currenttimestamp": "1528370177"
}
}}
{{=<% %>=}}
<core-course-module-description description="<% certificate.intro %>" component="mod_customcert" componentId="<% cmid %>"></core-course-module-description>
<ion-list>
<%^canmanage%>
<%#requiredtimemet%>
<ion-item>
<ion-label>
{{ 'plugin.mod_customcert.receiveddate' | translate }}
<br />
<div class="timerewarded">
<%#issue%>
{{ <% timecreated %> | coreToLocaleString }}
<%/issue%>
<%^issue%>
{{ 'plugin.mod_customcert.notissued' | translate }}
<%/issue%>
</div>
</ion-label>
<div slot="end" class="flex-row">
<ion-button fill="clear" [core-download-file]="{fileurl: '<% fileurl %>', timemodified: '<% currenttimestamp %>'}" moduleId="<% cmid %>" courseId="<% certificate.course %>" component="mod_customcert" [attr.aria-label]="'core.download' | translate">
<ion-icon name="cloud-download" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</div>
</ion-item>
<%/requiredtimemet%>
<%^requiredtimemet%>
<ion-item>
<ion-label>
<p>{{ 'plugin.mod_customcert.requiredtimenotmet' | translate: {$a: { requiredtime: <% certificate.requiredtime %>} } }}</p>
</ion-label>
</ion-item>
<%/requiredtimemet%>
<%/canmanage%>
<%#canmanage%>
<ion-button expand="block" class="ion-margin" core-course-download-module-main-file moduleId="<% cmid %>" courseId="<% certificate.course %>" component="mod_customcert" [files]="[{fileurl: '<% fileurl %>', timemodified: '<% currenttimestamp %>'}]">
<ion-icon name="cloud-download" slot="start" aria-hidden="true"></ion-icon>
{{ 'plugin.mod_customcert.getcustomcert' | translate }}
</ion-button>
<%/canmanage%>
<%#showreport%>
<ion-item>
<ion-label>
{{ 'plugin.mod_customcert.listofissues' | translate: { $a: <% numrecipients %> } }}
</ion-label>
</ion-item>
<%#showgroups%>
<ion-item>
<ion-label>{{ 'plugin.mod_customcert.selectagroup' | translate }}</ion-label>
<ion-select [(ngModel)]="CONTENT_OTHERDATA.group" name="group" (ionChange)="updateContent({cmid: <% cmid %>, courseid: <% certificate.course %>, group: CONTENT_OTHERDATA.group})" interface="popover">
<%#groups%>
<ion-select-option [value]="<% id %>"><% name %></ion-select-option>
<%/groups%>
</ion-select>
</ion-item>
<%/showgroups%>
<%#hasrecipients%>
<%#recipients%>
<ion-item>
<ion-label>
<% displayname %>
<br />
<div class="timerewarded">{{ <% timecreated %> | coreToLocaleString }}</div>
</ion-label>
<div slot="end" class="flex-row">
<ion-button fill="clear" [core-download-file]="{fileurl: '<% fileurl %>', timemodified: '<% currenttimestamp %>'}" moduleId="<% cmid %>" courseId="<% certificate.course %>" component="mod_customcert" [attr.aria-label]="'core.download' | translate">
<ion-icon name="cloud-download" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
<%#canmanage%>
<ion-button fill="clear" core-site-plugins-call-ws name="mod_customcert_delete_issue"
[params]="{certificateid: <% certificate.id %>, issueid: <% issueid %>}"
[preSets]="{getFromCache: 0, saveToCache: 0, typeExpected: 'boolean'}"
confirmMessage="{{ 'plugin.mod_customcert.deleteissueconfirm' | translate }}"
refreshOnSuccess="true" [attr.aria-label]="'core.delete' | translate">
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
<%/canmanage%>
</div>
</ion-item>
<%/recipients%>
<%/hasrecipients%>
<%^hasrecipients%>
<ion-item>
<ion-label>
{{ 'plugin.mod_customcert.nothingtodisplay' | translate }}
</ion-label>
</ion-item>
<%/hasrecipients%>
<%/showreport%>
</ion-list>

View file

@ -101,6 +101,7 @@ Feature: Being able to manage elements in a certificate template
And I add the element "Course name" to page "1" of the "Custom certificate 1" certificate template 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: And I set the following fields to these values:
| Font | Helvetica | | Font | Helvetica |
| Type | Short name |
| Size | 20 | | Size | 20 |
| Colour | #045ECD | | Colour | #045ECD |
| Width | 20 | | Width | 20 |
@ -110,6 +111,7 @@ Feature: Being able to manage elements in a certificate template
And I click on ".edit-icon" "css_element" in the "Course name" "table_row" And I click on ".edit-icon" "css_element" in the "Course name" "table_row"
And the following fields match these values: And the following fields match these values:
| Font | Helvetica | | Font | Helvetica |
| Type | Short name |
| Size | 20 | | Size | 20 |
| Colour | #045ECD | | Colour | #045ECD |
| Width | 20 | | Width | 20 |

View file

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