Compare commits

...

129 commits

Author SHA1 Message Date
Kumi 6ac6ba53c1
Current status 2022-09-07 13:54:03 +00:00
Kumi bd2a78b94b Add CLI script to batch-apply templates 2021-11-09 14:27:32 +00:00
Kumi 87b79503c8 Implemented PIN replacement 2021-11-09 14:23:04 +00:00
Kumi 7c33e659b4 Current changes 2021-11-09 13:43:08 +00:00
Kumi 8cc6693b24 Current changes 2021-11-09 13:42:54 +00:00
Kumi 6bb72f655f Current changes 2021-11-09 13:42:47 +00:00
Kumi b9c5e95f7a Add custom HTML field which takes precedence over template pages 2021-11-05 08:02:24 +00:00
Mark Nelson 311e243ca5 Update CHANGES.md 2021-10-27 16:46:50 +08:00
Mark Nelson 5ba1b2b40f Respect multiple languages in manage template page title (#467) 2021-10-27 16:44:49 +08:00
Mark Nelson 4ca9f05fc1 Updated CHANGES.md 2021-10-27 15:31:53 +08:00
hieuvu 2a9640b5a3 Fix error message when we have custom profile fields (#465) 2021-10-27 15:30:13 +08:00
Mark Nelson 18eab896bd Change JS to please GHA 2021-10-27 13:30:04 +08:00
Mark Nelson 9e38411434 Changes to make GHA happy and added usages of the coalescing operator (#121) 2021-10-27 13:15:32 +08:00
Mark Nelson 8464ba6259 Add issue number to CHANGES.md 2021-10-27 12:23:50 +08:00
Mark Nelson 92a3bcb0c3 Updated CHANGES.md 2021-10-25 16:14:18 +08:00
Jesús Alonso Abad 3b1cd64703 Added alignment to activity backup/cloning (#121) 2021-10-17 20:54:42 +08:00
Jesús Alonso Abad 383054be39 Added elements alignment support (#121) 2021-10-17 20:44:55 +08:00
Mark Nelson 80dc7395c3 Do not encode html entities in emails (#457) 2021-09-28 22:17:53 +08:00
Mark Nelson b684fee9f4 Update CHANGES.md 2021-08-06 10:56:43 +08:00
Andrew Madden 95274bfad6 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:21 +08:00
Mark Nelson 75fe6b4cdd Update CHANGES.md 2021-08-04 11:37:11 +08:00
Mark Nelson 8b8a358790 Minor code changes (#415) 2021-08-04 11:28:03 +08:00
Sameer Ahmed a1f0e6dec7 Display the course short name (#415)
- Added a new select box to choose from course name or short description to display.
2021-08-03 18:56:25 +08:00
Mark Nelson f1cf599f31 GHA: ROW_FORMAT=COMPRESSED deprecated in MariaDB 10.6
See MDL-72131.
2021-08-03 17:56:54 +08:00
Mark Nelson afcea89c82 Add new lines at end of files 2021-08-03 17:39:34 +08:00
Mark Nelson 5e3135324c Remove unnecessary new line 2021-08-03 16:53:24 +08:00
Mark Nelson c6c4e6f6a7 Minor changes and CHANGES.md note (#433) 2021-08-03 16:30:45 +08:00
Michael Milette 25d398ce22 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-07-27 10:54:18 +08:00
Mark Nelson cf11f765b0 Fix GHA failing 2021-07-06 17:18:35 +08:00
Dani Palou 6d966df56c Adapt mobile app code to Ionic 5 (#431) 2021-07-06 17:03:49 +08:00
Mark Nelson e4458872fa Fix release date 2021-06-13 14:26:57 +08:00
Mark Nelson 26802e9262 Bump version 2021-06-13 14:04:59 +08:00
Mark Nelson 0de67035a3 Use 'cron_setup_user' when sending emails (#414) 2021-06-13 14:04:18 +08:00
Mark Nelson 11225ff1fa Set proper context when sending emails (#402) 2021-06-13 13:36:02 +08:00
hieuvu bd361d2434 Add actions title to elements table (#425) 2021-06-08 17:41:38 +08:00
Mark Nelson 659e065a2f Fix failing PHPUnit test 2021-05-28 00:50:25 +08:00
Mark Nelson 991ef9b0e3 Fix failing Behat tests 2021-05-28 00:33:52 +08:00
Mark Nelson a68f1b9487 Remove usage of deprecated user fields 2021-05-27 23:14:10 +08:00
Mark Nelson 8145e020e4 Add type hint and return type 2021-05-27 22:49:57 +08:00
Mark Nelson e65cf7e9c2 Add new line at end of file 2021-05-27 22:48:30 +08:00
Mark Nelson 345c6e438b Removed usaged of deprecated functions (#423) 2021-05-27 22:46:53 +08:00
Mark Nelson 1343d64310 Fix Moodle Code Checker complaints 2021-05-27 22:13:31 +08:00
Mark Nelson 0208efa585 Removed usaged of deprecated functions (#423) 2021-05-27 21:37:50 +08:00
Mark Nelson 1a6d370d3f Removed .travis.yml file 2021-05-27 19:21:11 +08:00
Mark Nelson 69a3436faa Fix Moodle Code Checker complaints 2021-05-27 18:24:01 +08:00
Mark Nelson 9f320984c6 Update github/workflows/moodle-ci.yml 2021-05-27 14:28:16 +08:00
Mark Nelson 0122cc1138 Update travis.yml 2021-05-27 14:11:54 +08:00
Mark Nelson 4ca04d204d Bump for version 3.11 2021-05-27 14:05:04 +08:00
Mark Nelson ff16abfd7c Update CHANGES.md 2021-04-16 14:42:31 +08:00
Mikhail Golenkov 1859ffa651 Fix the issue with displaying PDF when debugging is ON (#420) 2021-04-16 14:36:15 +08:00
Mark Nelson 47061f8737 Add type hinting to get_course_time() 2021-04-05 20:11:02 +08:00
Mark Nelson 6e89c1d921 Use instance of moodle_url() in the function notice() 2021-04-05 20:08:25 +08:00
Mark Nelson 22081dffb0 Fix get_course_time() allowing users to view certificate early (#403) 2021-04-05 19:55:27 +08:00
Mark Nelson 2e58b24daa Updated CHANGES.md 2021-04-05 16:51:26 +08:00
Mark Nelson 8446a2e10a Add ability to choose how to deliver the certificate (#401) 2021-04-05 16:49:10 +08:00
Mark Nelson ae2df6c9ed Updated CHANGES.md 2021-04-03 11:27:32 +08:00
Mark Nelson 2e881e32c9 Add ability to show description on course page (#406) 2021-04-03 10:37:15 +08:00
Marina Glancy dcbb07e2f3 Add github actions (#407) 2021-04-02 20:48:49 +08:00
Toni Förster 350b18dda6 Allow managers to download certificates (#412)
Currently, it is not possible for editing teachers
and managers to download certificates for users because
we only check for $canreceive.
2021-04-02 20:45:42 +08:00
Mark Nelson a9f83a2579 Fix PHPDocs for the method get_course_field_value() 2020-11-27 20:13:33 +08:00
Mark Nelson a81b6e4fd5 Update .travis.yml 2020-11-27 20:13:28 +08:00
Mark Nelson 8515c5bd9e Add missing full-stop to comment 2020-11-27 16:02:07 +08:00
Mark Nelson b70224c0e4 Make tests compatible with new version of PHPUnit 2020-11-27 16:01:04 +08:00
Mark Nelson a38678c691 3.10 release 2020-11-27 14:40:56 +08:00
Mark Nelson 35350fe454 3.9 release 2020-11-27 14:25:02 +08:00
Mark Nelson fd4d992602 Rebuilt moodle-mod_customcert-rearrange-min.js 2020-11-27 14:25:02 +08:00
Mark Nelson 361b156f4a Fixed size of 'Changed' in CHANGES.md 2020-11-26 23:30:30 +08:00
Mark Nelson e9b30dac79 Bumped version 2020-11-26 22:48:49 +08:00
Mark Nelson be202676c9 Update CHANGES.md (#390) 2020-11-26 22:08:06 +08:00
uvigo-atic da49799857 Added username to userfield form element (#390) 2020-11-26 22:06:31 +08:00
Mark Nelson 842b0910e2 Updated CHANGES.md (#276) 2020-11-26 21:41:24 +08:00
Mark Nelson 8c0e00419c Update 'emailteachers_help' and 'emailothers_help' language strings (#276) 2020-11-26 21:34:05 +08:00
Mark Nelson c1615cbd16 Do not email out certificates with no elements (#276) 2020-11-26 19:54:58 +08:00
Mark Nelson 438dfd868f Use null coalescing operator in element factory 2020-11-26 17:52:01 +08:00
Mark Nelson a262adfc6e Update 'emailstudents_help' language string (#276) 2020-11-25 22:38:28 +08:00
Mark Nelson 2b4f385333 Bump .travis.yml to use v3 of moodle-plugin-ci 2020-11-09 15:32:31 +01:00
Mark Nelson 62dcd9576d Update repo used by Travis 2020-11-05 20:05:56 +01:00
Mark Nelson 9a2edca684 Update CHANGES.md 2020-11-04 20:21:02 +01:00
Brendan Heywood 0c89ff9547 PDFs should be inline not attachments #153 2020-11-04 19:03:57 +01:00
Mark Nelson 27f5a74cb3 Add CSS class for mobile app (#378) 2020-10-12 13:34:04 +02:00
Mark Nelson a8eca647fe Removed multiple empty lines 2020-09-20 16:00:34 +02:00
Mark Nelson 695798ca6f Updated CHANGES.md 2020-09-20 15:51:04 +02:00
Mark Nelson 632dba8fb2 Fixed issue with PDF being generated without a name (#333) 2020-09-20 15:39:53 +02:00
Mark Nelson 0a390df7ff Fixed exception loading template image that has no image selected (#369) 2020-09-20 14:55:48 +02:00
Mark Nelson 46ca744d32 Do not email those who can manage the certificate (#376) 2020-09-20 14:24:50 +02:00
Guillermo Gomez 60b247f441 Implement get_objectid_mapping (#374) 2020-09-20 14:13:27 +02:00
Mark Nelson 6a19ec83e9 Updated CHANGES.md 2020-08-08 19:16:54 +02:00
Mark Nelson e52422b922 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:08:02 +02:00
Sergey Evstegneiev 2dc05b8b81 Adding enrollment start & end date options (#328) 2020-08-08 18:57:05 +02:00
Mark Nelson c95b058049 Fix stale file warning 2020-08-08 18:10:29 +02:00
Mark Nelson c69d4822cc Ignore multiple certificate codes (#363) 2020-08-08 15:52:10 +02:00
Mark Nelson a884f948d3 Remove trailing spaces 2020-08-08 15:46:08 +02:00
Thong Bui 6af1757084 Fix get position when browser shrinking (#343) 2020-08-08 15:23:15 +02:00
Steven Tsvetkov 0f62eb30b5 Fix custom fields not displaying properly #359 2020-07-20 16:02:39 +02:00
Mark Nelson 66148c70f6 Updated CHANGES.md 2020-06-28 18:36:51 +02:00
Mark Nelson 98307310cf Update node version used in .travis.yml 2020-05-30 13:13:10 +02:00
Mark Nelson 05f12a3dde Mark certificates as viewed in mobile app (#342) 2020-05-30 13:03:02 +02:00
Mark Nelson 7107b67856 Refactor element_helper::get_grade_items() to be nicer to read 2020-05-08 21:23:36 +02:00
Mark Nelson 9e386f19c3 Fix docs in Grade element name 2020-05-08 17:40:26 +02:00
Mark Nelson eec256cd01 The Grade item name element works with all grade items (#346) 2020-05-08 17:22:50 +02:00
Mark Nelson 4ab2c04b29 Extend unit test (#329) 2020-05-08 17:22:50 +02:00
Mark Nelson 784ca08fb1 Add the ability to select Outcomes in the Grade element (#329) 2020-05-08 17:22:44 +02:00
Mark Nelson 31d1f9a36d Removed 'exampledata' string 2020-05-08 15:56:41 +02:00
Mark Nelson 05c1c00cc0 Revert "Add new Outcome element (#329)"
This reverts commit 3e26a6b7c1.

This logic should be in the Grade element.
2020-05-06 14:57:27 +02:00
Mark Nelson 3e26a6b7c1 Add new Outcome element (#329) 2020-05-06 13:44:20 +02:00
Mark Nelson 4679c79e37 Bumped version 2020-03-17 00:20:30 +01:00
Mark Nelson 15b8061261 Fixed broken logic (#298) 2020-03-16 17:04:08 +01:00
Mark Nelson 2a16dbe1c0 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:42:36 +01:00
Mark Nelson f2524610f0 Updated CHANGES.md 2020-03-12 14:42:54 +01:00
Mark Nelson db10a589f1 Added extra Behat steps for new elements (#309) 2020-03-11 14:10:22 +01:00
Mark Nelson a0bc5ffa04 Avoid use of old array syntax (#331) 2020-03-10 17:29:37 +01:00
Sergey d5ba284e64 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:55:50 +01:00
Mark Nelson f68658d675 Do not allow '0' as a value for width or height in QR code (#321) 2020-03-09 14:50:33 +01:00
Mark Nelson 28975d4625 Fix foreign key violation (#331) 2020-03-09 14:05:21 +01:00
Mark Nelson 2cdae3f45c Added missing full-stop 2019-12-16 20:56:24 +01:00
Mark Nelson 9cf95f5104 Bumped version 2019-12-16 20:53:06 +01:00
Mark Nelson f0a25193ba Updated CHANGES.md 2019-12-16 20:53:00 +01:00
Mark Nelson bd135ef579 Define subplugins.json (#312) 2019-12-16 20:39:08 +01:00
Mark Nelson 6a1f55c98a Fixed typo in Behat file name 2019-12-16 17:03:47 +01:00
Mark Nelson 9df6c9d71e Re-add code column (#264) 2019-12-16 16:55:33 +01:00
mwithheld 8ba89c18ce Do not fail if multiple certificate issues (#304)
Fix another location of this problem.
2019-12-16 15:59:57 +01:00
mwithheld a5eea57644 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 15:55:42 +01:00
Mark Nelson f172ad029b Changed references to old github name 2019-12-16 15:00:11 +01:00
Mark Nelson 59b87d19ac Bump with JS fix 2019-12-14 14:51:30 +01:00
Mark Nelson 849d6645fd Recompile JS for 3.8 2019-12-14 14:50:53 +01:00
Mark Nelson ac8a49bcca Bumped version 2019-12-14 14:23:31 +01:00
Mark Nelson f6b9e48691 Bumped version in travis 2019-12-14 14:19:51 +01:00
Arnaud Trouvé 080e53d13a Add userfullname variable for email subject (#316) 2019-12-14 14:17:49 +01:00
Mark Nelson b89010ffd1 Bumped version for 3.8 2019-12-14 14:14:50 +01:00
94 changed files with 1978 additions and 471 deletions

120
.github/workflows/moodle-ci.yml vendored Normal file
View file

@ -0,0 +1,120 @@
name: Moodle Plugin CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-18.04
services:
postgres:
image: postgres:9.6
env:
POSTGRES_USER: 'postgres'
POSTGRES_HOST_AUTH_METHOD: 'trust'
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3
mariadb:
image: mariadb:10.5
env:
MYSQL_USER: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3
strategy:
fail-fast: false
matrix:
include:
- php: '7.4'
moodle-branch: 'MOODLE_311_STABLE'
database: pgsql
- php: '7.4'
moodle-branch: 'MOODLE_311_STABLE'
database: mariadb
- php: '7.3'
moodle-branch: 'MOODLE_311_STABLE'
database: pgsql
- php: '7.3'
moodle-branch: 'MOODLE_311_STABLE'
database: mariadb
steps:
- name: Check out repository code
uses: actions/checkout@v2
with:
path: plugin
- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- name: Initialise moodle-plugin-ci
run: |
composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
echo $(cd ci/bin; pwd) >> $GITHUB_PATH
echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
sudo locale-gen en_AU.UTF-8
echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV
- name: Install moodle-plugin-ci
run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1
env:
DB: ${{ matrix.database }}
MOODLE_BRANCH: ${{ matrix.moodle-branch }}
MUSTACHE_IGNORE_NAMES: 'mobile_*.mustache'
- name: PHP Lint
if: ${{ always() }}
run: moodle-plugin-ci phplint
- name: PHP Copy/Paste Detector
continue-on-error: true # This step will show errors but will not fail
if: ${{ always() }}
run: moodle-plugin-ci phpcpd
- name: PHP Mess Detector
continue-on-error: true # This step will show errors but will not fail
if: ${{ always() }}
run: moodle-plugin-ci phpmd
- name: Moodle Code Checker
if: ${{ always() }}
run: moodle-plugin-ci codechecker --max-warnings 0
- name: Moodle PHPDoc Checker
if: ${{ always() }}
run: moodle-plugin-ci phpdoc
- name: Validating
if: ${{ always() }}
run: moodle-plugin-ci validate
- name: Check upgrade savepoints
if: ${{ always() }}
run: moodle-plugin-ci savepoints
- name: Mustache Lint
if: ${{ always() }}
run: moodle-plugin-ci mustache
- name: Grunt
if: ${{ always() }}
run: moodle-plugin-ci grunt --max-lint-warnings 0
- name: PHPUnit tests
if: ${{ always() }}
run: |
moodle-plugin-ci phpunit
cd moodle
vendor/bin/phpunit --fail-on-risky --disallow-test-output --filter tool_dataprivacy_metadata_registry_testcase
vendor/bin/phpunit --fail-on-risky --disallow-test-output --filter core_externallib_testcase
vendor/bin/phpunit --fail-on-risky --disallow-test-output --testsuite core_privacy_testsuite --filter provider_testcase
- name: Behat features
if: ${{ always() }}
run: moodle-plugin-ci behat --profile chrome

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
vendor/
*.swp

View file

@ -1,52 +0,0 @@
language: php
# For javascript behat tests we need sudo
sudo: true
dist: trusty
cache:
directories:
- $HOME/.composer/cache
- $HOME/.npm
php:
- 7.1
- 7.2
addons:
firefox: 47.0.1
postgresql: 9.4
apt:
packages:
- openjdk-8-jre-headless
env:
global:
- MOODLE_BRANCH=MOODLE_37_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
- nvm install 8.9
- nvm use 8.9
- cd ../..
- composer create-project -n --no-dev --prefer-dist blackboard-open-source/moodle-plugin-ci ci ^2
- 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 -t stylelint:css -t js
- moodle-plugin-ci phpunit
- moodle-plugin-ci behat

View file

@ -2,7 +2,101 @@
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.11.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).
- Fix error message when we have custom profile fields (#465).
- 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.11.1] - 2021-06-13
### Fixed
- Usage of deprecated functions (#423)
## [3.10.1] - 2021-06-13
### Added
- Usage of github actions (#407).
- The ability to show the description on the course page (#406).
- The ability to choose how to deliver the certificate (#401).
### Fixed
- Managers are now able to download their students' certificates (#412).
- Users being able to view the certificate before the required time set (#403).
- 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
### 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.8.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.8.3] - 2020-03-09
### Fixed
- Fixed foreign key violation (#331).
## [3.8.2] - 2019-12-16
### Added
- Added subplugins.json file (#312).
- 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

@ -6,6 +6,8 @@ This activity allows the dynamic generation of PDF certificates with complete cu
There are two installation methods that are available.
First, make sure that wkhtmltopdf is installed on your server and available on $PATH.
Follow one of these, then log into your Moodle site as an administrator and visit the notifications page to complete the install.
### Git
@ -15,7 +17,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.
@ -36,4 +38,4 @@ Visit the [wiki page](https://docs.moodle.org/en/Custom_certificate_module "Wiki
## License
Licensed under the [GNU GPL License](http://www.gnu.org/copyleft/gpl.html).
Licensed under the [GNU GPL License](http://www.gnu.org/copyleft/gpl.html).

View file

@ -54,4 +54,4 @@ foreach ($values as $value) {
$element->posx = $value->posx;
$element->posy = $value->posy;
$DB->update_record('customcert_elements', $element);
}
}

View file

@ -1 +1,2 @@
define(["core/yui"],function(a){var b=function(b,c,d,e,f){this.yuiDialogue=null;var g=this;"undefined"==typeof f&&(f=!1),a.use("moodle-core-notification","timers",function(){var h="480px";f&&(h="800px"),g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h}),g.yuiDialogue.after("visibleChange",function(b){b.newVal?"undefined"!=typeof d&&a.soon(function(){d(g),g.yuiDialogue.centerDialogue()}):"undefined"!=typeof e&&a.soon(function(){e(g)})}),g.yuiDialogue.show()})};return b.prototype.close=function(){this.yuiDialogue.hide(),this.yuiDialogue.destroy()},b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()},b});
define ("mod_customcert/dialogue",["core/yui"],function(a){var b=function(b,c,d,e,f){this.yuiDialogue=null;var g=this;if("undefined"==typeof f){f=!1}a.use("moodle-core-notification","timers",function(){var h="480px";if(f){h="800px"}g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h});g.yuiDialogue.after("visibleChange",function(b){if(b.newVal){if("undefined"!=typeof d){a.soon(function(){d(g);g.yuiDialogue.centerDialogue()})}}else{if("undefined"!=typeof e){a.soon(function(){e(g)})}}});g.yuiDialogue.show()})};b.prototype.close=function(){this.yuiDialogue.hide();this.yuiDialogue.destroy()};b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()};return b});
//# sourceMappingURL=dialogue.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -1 +1,2 @@
define(["jquery","core/yui","core/fragment","mod_customcert/dialogue","core/notification","core/str","core/templates","core/ajax"],function(a,b,c,d,e,f,g,h){var i=function(b){this._node=a(b),this._setEvents()};return i.prototype.CUSTOMCERT_REF_POINT_TOPLEFT=0,i.prototype.CUSTOMCERT_REF_POINT_TOPCENTER=1,i.prototype.CUSTOMCERT_REF_POINT_TOPRIGHT=2,i.prototype.PIXELSINMM=3.779527559055,i.prototype._setEvents=function(){this._node.on("click",".element",this._editElement.bind(this))},i.prototype._editElement=function(a){var g=a.currentTarget.id.substr(8),h=this._node.attr("data-contextid"),i={elementid:g};c.loadFragment("mod_customcert","editelement",h,i).done(function(a,c){f.get_string("editelement","mod_customcert").done(function(e){b.use("moodle-core-formchangechecker",function(){new d(e,"<div id='elementcontent'></div>",this._editElementDialogueConfig.bind(this,g,a,c),(void 0),(!0))}.bind(this))}.bind(this))}.bind(this)).fail(e.exception)},i.prototype._editElementDialogueConfig=function(b,c,d,f){g.replaceNode("#elementcontent",c,d),this._setPositionInForm(b);var h=a(f.getContent());h.on("click","#id_submitbutton",function(c){M.core_formchangechecker.reset_form_dirty_state(),this._saveElement(b).then(function(){this._getElementHTML(b).done(function(c){var d=this._node.find("#element-"+b),e=parseInt(a("#id_refpoint").val()),g="";e==this.CUSTOMCERT_REF_POINT_TOPLEFT?g="refpoint-left":e==this.CUSTOMCERT_REF_POINT_TOPCENTER?g="refpoint-center":e==this.CUSTOMCERT_REF_POINT_TOPRIGHT&&(g="refpoint-right"),d.empty().append(c),d.removeClass(),d.addClass("element "+g),d.attr("data-refpoint",e);var h=a("#editelementform #id_posx").val(),i=a("#editelementform #id_posy").val();this._setPosition(b,e,h,i),f.close()}.bind(this))}.bind(this)).fail(e.exception),c.preventDefault()}.bind(this)),h.on("click","#id_cancel",function(a){f.close(),a.preventDefault()})},i.prototype._setPosition=function(a,c,d,e){var f=b.one("#element-"+a);d=b.one("#pdf").getX()+d*this.PIXELSINMM,e=b.one("#pdf").getY()+e*this.PIXELSINMM;var g=parseFloat(f.getComputedStyle("width")),h=f.width*this.PIXELSINMM;switch(h&&g>h&&(g=h),c){case this.CUSTOMCERT_REF_POINT_TOPCENTER:d-=g/2;break;case this.CUSTOMCERT_REF_POINT_TOPRIGHT:d=d-g+2}f.setX(d),f.setY(e)},i.prototype._setPositionInForm=function(c){var d=a("#editelementform #id_posx"),e=a("#editelementform #id_posy");if(d.length&&e.length){var f=b.one("#element-"+c),g=f.getX()-b.one("#pdf").getX(),h=f.getY()-b.one("#pdf").getY(),i=parseInt(f.getData("refpoint")),j=parseFloat(f.getComputedStyle("width"));switch(i){case this.CUSTOMCERT_REF_POINT_TOPCENTER:g+=j/2;break;case this.CUSTOMCERT_REF_POINT_TOPRIGHT:g+=j}g=Math.round(parseFloat(g/this.PIXELSINMM)),h=Math.round(parseFloat(h/this.PIXELSINMM)),d.val(g),e.val(h)}},i.prototype._getElementHTML=function(a){var b=this._node.attr("data-templateid"),c=h.call([{methodname:"mod_customcert_get_element_html",args:{templateid:b,elementid:a}}]);return c[0]},i.prototype._saveElement=function(b){var c=this._node.attr("data-templateid"),d=a("#editelementform").serializeArray(),e=h.call([{methodname:"mod_customcert_save_element",args:{templateid:c,elementid:b,values:d}}]);return e[0]},{init:function(a){new i(a)}}});
define ("mod_customcert/rearrange-area",["jquery","core/yui","core/fragment","mod_customcert/dialogue","core/notification","core/str","core/templates","core/ajax"],function(a,b,c,d,f,g,h,i){var j=function(b){this._node=a(b);this._setEvents()};j.prototype.CUSTOMCERT_REF_POINT_TOPLEFT=0;j.prototype.CUSTOMCERT_REF_POINT_TOPCENTER=1;j.prototype.CUSTOMCERT_REF_POINT_TOPRIGHT=2;j.prototype.PIXELSINMM=3.779527559055;j.prototype._setEvents=function(){this._node.on("click",".element",this._editElement.bind(this))};j.prototype._editElement=function(a){var e=a.currentTarget.id.substr(8),h=this._node.attr("data-contextid");c.loadFragment("mod_customcert","editelement",h,{elementid:e}).done(function(a,c){g.get_string("editelement","mod_customcert").done(function(f){b.use("moodle-core-formchangechecker",function(){new d(f,"<div id='elementcontent'></div>",this._editElementDialogueConfig.bind(this,e,a,c),void 0,!0)}.bind(this))}.bind(this))}.bind(this)).fail(f.exception)};j.prototype._editElementDialogueConfig=function(b,c,d,g){h.replaceNode("#elementcontent",c,d);this._setPositionInForm(b);var i=a(g.getContent());i.on("click","#id_submitbutton",function(c){M.core_formchangechecker.reset_form_dirty_state();this._saveElement(b).then(function(){this._getElementHTML(b).done(function(c){var d=this._node.find("#element-"+b),e=parseInt(a("#id_refpoint").val()),f="";if(e==this.CUSTOMCERT_REF_POINT_TOPLEFT){f="refpoint-left"}else if(e==this.CUSTOMCERT_REF_POINT_TOPCENTER){f="refpoint-center"}else if(e==this.CUSTOMCERT_REF_POINT_TOPRIGHT){f="refpoint-right"}d.empty().append(c);d.removeClass();d.addClass("element "+f);d.attr("data-refpoint",e);var h=a("#editelementform #id_posx").val(),i=a("#editelementform #id_posy").val();this._setPosition(b,e,h,i);g.close()}.bind(this))}.bind(this)).fail(f.exception);c.preventDefault()}.bind(this));i.on("click","#id_cancel",function(a){g.close();a.preventDefault()})};j.prototype._setPosition=function(a,c,d,e){var f=b.one("#element-"+a);d=b.one("#pdf").getX()+d*this.PIXELSINMM;e=b.one("#pdf").getY()+e*this.PIXELSINMM;var g=parseFloat(f.getComputedStyle("width")),h=f.width*this.PIXELSINMM;if(h&&g>h){g=h}switch(c){case this.CUSTOMCERT_REF_POINT_TOPCENTER:d-=g/2;break;case this.CUSTOMCERT_REF_POINT_TOPRIGHT:d=d-g+2;break;}f.setX(d);f.setY(e)};j.prototype._setPositionInForm=function(c){var d=a("#editelementform #id_posx"),e=a("#editelementform #id_posy");if(d.length&&e.length){var f=b.one("#element-"+c),g=f.getX()-b.one("#pdf").getX(),h=f.getY()-b.one("#pdf").getY(),i=parseInt(f.getData("refpoint")),j=parseFloat(f.getComputedStyle("width"));switch(i){case this.CUSTOMCERT_REF_POINT_TOPCENTER:g+=j/2;break;case this.CUSTOMCERT_REF_POINT_TOPRIGHT:g+=j;break;}g=Math.round(parseFloat(g/this.PIXELSINMM));h=Math.round(parseFloat(h/this.PIXELSINMM));d.val(g);e.val(h)}};j.prototype._getElementHTML=function(a){var b=this._node.attr("data-templateid"),c=i.call([{methodname:"mod_customcert_get_element_html",args:{templateid:b,elementid:a}}]);return c[0]};j.prototype._saveElement=function(b){var c=this._node.attr("data-templateid"),d=a("#editelementform").serializeArray(),e=i.call([{methodname:"mod_customcert_save_element",args:{templateid:c,elementid:b,values:d}}]);return e[0]};return{init:function init(a){new j(a)}}});
//# sourceMappingURL=rearrange-area.min.js.map

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.
*
* @module mod_customcert/dialogue
* @package mod_customcert
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @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.
*
* @module mod_customcert/rearrange-area
* @package mod_customcert
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @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
* @return mixed|string
*/
static public function encode_content_links($content) {
public static function encode_content_links($content) {
global $CFG;
$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(
'pageid', 'name', 'element', 'data', 'font', 'fontsize',
'colour', 'posx', 'posy', 'width', 'refpoint', 'sequence',
'timecreated', 'timemodified'));
'alignment', 'timecreated', 'timemodified'));
// The 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.
*/
static public function define_decode_contents() {
public static function define_decode_contents() {
$contents = array();
$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.
*/
static public function define_decode_rules() {
public static function define_decode_rules() {
$rules = array();
$rules[] = new restore_decode_rule('CUSTOMCERTVIEWBYID', '/mod/customcert/view.php?id=$1', 'course_module');
@ -75,12 +75,12 @@ class restore_customcert_activity_task extends restore_activity_task {
}
/**
* Define the restore log rules that will be applied by the {@link restore_logs_processor} when restoring
* customcert logs. It must return one array of {@link restore_log_rule} objects.
* Define the restore log rules that will be applied by the {@see restore_logs_processor} when restoring
* customcert logs. It must return one array of {@see restore_log_rule} objects.
*
* @return array the restore log rules
*/
static public function define_restore_log_rules() {
public static function define_restore_log_rules() {
$rules = array();
$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->timecreated = $this->apply_date_offset($data->timecreated);
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('customcert_issues', $data);
$this->set_mapping('customcert_issue', $oldid, $newitemid);

View file

@ -80,4 +80,4 @@ class admin_setting_link extends \admin_setting_configtext {
return format_admin_setting($this, $this->visiblename,
\html_writer::link($this->link, $this->linkname), $this->description, true, '', null, $query);
}
}
}

View file

@ -37,6 +37,16 @@ defined('MOODLE_INTERNAL') || die();
*/
class certificate {
/**
* Send the file inline to the browser.
*/
const DELIVERY_OPTION_INLINE = 'I';
/**
* Send to the browser and force a file download
*/
const DELIVERY_OPTION_DOWNLOAD = 'D';
/**
* @var string the print protection variable
*/
@ -165,7 +175,7 @@ class certificate {
* @param int $userid
* @return int the total time spent in seconds
*/
public static function get_course_time($courseid, $userid = 0) {
public static function get_course_time(int $courseid, int $userid = 0): int {
global $CFG, $DB, $USER;
if (empty($userid)) {
@ -217,7 +227,7 @@ class certificate {
$totaltime = 0;
}
$delay = $log->$timefield - $lasthit;
if ($delay > ($CFG->sessiontimeout * 60)) {
if ($delay > $CFG->sessiontimeout) {
// The difference between the last log and the current log is more than
// the timeout Register session value so that we have found a session!
$login = $log->$timefield;
@ -260,12 +270,20 @@ class certificate {
$allparams = $conditionsparams + array('customcertid' => $customcertid);
// Return the issues.
$extrafields = get_extra_user_fields(\context_module::instance($cm->id));
$ufields = \user_picture::fields('u', $extrafields);
$sql = "SELECT $ufields, ci.id as issueid, ci.code, ci.timecreated
$context = \context_module::instance($cm->id);
$extrafields = \core_user\fields::for_identity($context)->get_required_fields();
$ufields = \core_user\fields::for_userpic()->including(...$extrafields);
[
'selects' => $userfieldsselects,
'joins' => $userfieldsjoin,
'params' => $userfieldsparams
] = (array) $ufields->get_sql('u', true);
$allparams = array_merge($allparams, $userfieldsparams);
$sql = "SELECT ci.id as issueid, ci.code, ci.timecreated $userfieldsselects
FROM {user} u
INNER JOIN {customcert_issues} ci
ON u.id = ci.userid
INNER JOIN {customcert_issues} ci ON u.id = ci.userid
$userfieldsjoin
WHERE u.deleted = 0
AND ci.customcertid = :customcertid
$conditionssql";

View file

@ -247,7 +247,7 @@ class edit_form extends \moodleform {
// Create a table to display these elements.
$table = new \html_table();
$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');
// Loop through and add the elements to the table.
foreach ($elements as $element) {

View file

@ -37,6 +37,21 @@ defined('MOODLE_INTERNAL') || die();
*/
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.
*/
@ -97,6 +112,11 @@ abstract class element {
*/
protected $refpoint;
/**
* @var string The alignment.
*/
protected $alignment;
/**
* @var bool $showposxy Show position XY form elements?
*/
@ -130,6 +150,7 @@ abstract class element {
$this->width = $element->width;
$this->refpoint = $element->refpoint;
$this->showposxy = isset($showposxy) && $showposxy;
$this->set_alignment($element->alignment ?? self::ALIGN_LEFT);
}
/**
@ -231,6 +252,31 @@ abstract class element {
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.
* 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_refpoint($mform);
element_helper::render_form_element_alignment($mform);
}
/**
@ -265,7 +312,8 @@ abstract class element {
'posx' => $this->posx,
'posy' => $this->posy,
'width' => $this->width,
'refpoint' => $this->refpoint
'refpoint' => $this->refpoint,
'alignment' => $this->get_alignment()
];
foreach ($properties as $property => $value) {
if (!is_null($value) && $mform->elementExists($property)) {
@ -311,15 +359,16 @@ abstract class element {
$element = new \stdClass();
$element->name = $data->name;
$element->data = $this->save_unique_data($data);
$element->font = (isset($data->font)) ? $data->font : null;
$element->fontsize = (isset($data->fontsize)) ? $data->fontsize : null;
$element->colour = (isset($data->colour)) ? $data->colour : null;
$element->font = $data->font ?? null;
$element->fontsize = $data->fontsize ?? null;
$element->colour = $data->colour ?? null;
if ($this->showposxy) {
$element->posx = (isset($data->posx)) ? $data->posx : null;
$element->posy = (isset($data->posy)) ? $data->posy : null;
$element->posx = $data->posx ?? null;
$element->posy = $data->posy ?? null;
}
$element->width = (isset($data->width)) ? $data->width : null;
$element->refpoint = (isset($data->refpoint)) ? $data->refpoint : null;
$element->width = $data->width ?? null;
$element->refpoint = $data->refpoint ?? null;
$element->alignment = $data->alignment ?? self::ALIGN_LEFT;
$element->timemodified = time();
// 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 \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.
@ -389,7 +438,7 @@ abstract class element {
*
* @return string the html
*/
public abstract function render_html();
abstract public function render_html();
/**
* Handles deleting any data this element may have introduced.

View file

@ -47,18 +47,19 @@ 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;
$data->alignment = $element->alignment ?? null;
// Ensure the necessary class exists.
if (class_exists($classname)) {

View file

@ -74,6 +74,7 @@ class element_helper {
$w = $element->get_width();
$refpoint = $element->get_refpoint();
$actualwidth = $pdf->GetStringWidth($content);
$alignment = $element->get_alignment();
if ($w and $w < $actualwidth) {
$actualwidth = $w;
@ -104,7 +105,7 @@ class element_helper {
$w += 0.0001;
}
$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');
}
/**
* 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.
*
@ -462,70 +480,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);
}
$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,23 @@ class course_module_viewed extends \core\event\course_module_viewed {
$this->data['objecttable'] = 'customcert';
parent::init();
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public static function get_objectid_mapping() {
return array('db' => 'customcert', 'restore' => 'customcert');
}
/**
* {@inheritdoc}
*
* @return bool
*/
public static function get_other_mapping() {
// No need to map.
return false;
}
}

64
classes/helper.php Normal file
View file

@ -0,0 +1,64 @@
<?php
// This file is part of the customcert module for 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/>.
/**
* Provides helper functionality.
*
* @package mod_customcert
* @copyright 2021 Mark Nelson <mdjnelson@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_customcert;
use core_user\fields;
defined('MOODLE_INTERNAL') || die();
/**
* Class helper.
*
* Helper functionality for this module.
*
* @package mod_customcert
* @copyright 2021 Mark Nelson <mdjnelson@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* A centralised location for the all name fields.
*
* Returns a sql string snippet.
*
* @param string $tableprefix table query prefix to use in front of each field.
* @return string All name fields.
*/
public static function get_all_user_name_fields(string $tableprefix = ''): string {
$alternatenames = [];
foreach (fields::get_name_fields() as $field) {
$alternatenames[$field] = $field;
}
if ($tableprefix) {
foreach ($alternatenames as $key => $altname) {
$alternatenames[$key] = $tableprefix . '.' . $altname;
}
}
return implode(',', $alternatenames);
}
}

View file

@ -47,19 +47,23 @@ class load_template_form extends \moodleform {
// Get the context.
$context = $this->_customdata['context'];
$syscontext = \context_system::instance();
$mform->addElement('header', 'loadtemplateheader', get_string('loadtemplate', 'customcert'));
// 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'),
get_string('managetemplates', 'customcert'));
$mform->addElement('static', 'managetemplates', '', $link);
}
$templates = $DB->get_records_menu('customcert_templates',
array('contextid' => \context_system::instance()->id), 'name ASC', 'id, name');
if ($templates) {
$arrtemplates = $DB->get_records_menu('customcert_templates', ['contextid' => $syscontext->id], 'name ASC', 'id, name');
if ($arrtemplates) {
$templates = [];
foreach ($arrtemplates as $key => $template) {
$templates[$key] = format_string($template, true, ['context' => $context]);
}
$group = array();
$group[] = $mform->createElement('select', 'ltid', '', $templates);
$group[] = $mform->createElement('submit', 'loadtemplatesubmit', get_string('load', 'customcert'));

View file

@ -77,7 +77,7 @@ class manage_templates_table extends \table_sql {
* @return string
*/
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;
$args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$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.
$cm = get_coursemodule_from_id('customcert', $cmid);
@ -114,6 +115,7 @@ class mobile {
'showreport' => $showreport,
'hasrecipients' => !empty($recipients),
'recipients' => array_values($recipients),
'numrecipients' => count($recipients),
'currenttimestamp' => time()
];
@ -121,11 +123,13 @@ class mobile {
'templates' => [
[
'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' => '',
'otherdata' => ''
'otherdata' => [
'group' => $groupid,
]
];
}

View file

@ -66,7 +66,7 @@ class report_table extends \table_sql {
parent::__construct('mod_customcert_report_table');
$context = \context_module::instance($cm->id);
$extrafields = get_extra_user_fields($context);
$extrafields = \core_user\fields::for_identity($context)->get_required_fields();
$columns = [];
$columns[] = 'fullname';
@ -74,13 +74,15 @@ class report_table extends \table_sql {
$columns[] = $extrafield;
}
$columns[] = 'timecreated';
$columns[] = 'code';
$headers = [];
$headers[] = get_string('fullname');
foreach ($extrafields as $extrafield) {
$headers[] = get_user_field_name($extrafield);
$headers[] = \core_user\fields::get_display_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

@ -23,6 +23,8 @@
*/
namespace mod_customcert\task;
use mod_customcert\helper;
defined('MOODLE_INTERNAL') || die();
/**
@ -69,9 +71,24 @@ 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);
// 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.
$userfrom = \core_user::get_noreply_user();
@ -90,7 +107,7 @@ class email_certificate_task extends \core\task\scheduled_task {
$info->certificatename = $certificatename;
// Get a list of all the issues.
$userfields = get_all_user_name_fields(true, 'u');
$userfields = helper::get_all_user_name_fields('u');
$sql = "SELECT u.id, u.username, $userfields, u.email, ci.id as issueid, ci.emailed
FROM {customcert_issues} ci
JOIN {user} u
@ -112,6 +129,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 +149,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);
@ -159,7 +181,11 @@ class email_certificate_task extends \core\task\scheduled_task {
// Now, email the people we need to.
foreach ($issuedusers as $user) {
// Set up the user.
cron_setup_user($user);
$userfullname = fullname($user);
$info->userfullname = $userfullname;
// Now, get the PDF.
$template = new \stdClass();
@ -187,7 +213,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$subject = get_string('emailstudentsubject', 'customcert', $info);
$message = $textrenderer->render($renderable);
$messagehtml = $htmlrenderer->render($renderable);
email_to_user($user, fullname($userfrom), $subject, $message, $messagehtml, $tempfile, $filename);
email_to_user($user, fullname($userfrom), html_entity_decode($subject), $message, $messagehtml,
$tempfile, $filename);
}
if ($customcert->emailteachers) {
@ -198,8 +225,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$message = $textrenderer->render($renderable);
$messagehtml = $htmlrenderer->render($renderable);
foreach ($teachers as $teacher) {
email_to_user($teacher, fullname($userfrom), $subject, $message, $messagehtml, $tempfile,
$filename);
email_to_user($teacher, fullname($userfrom), html_entity_decode($subject), $message, $messagehtml,
$tempfile, $filename);
}
}
@ -218,8 +245,8 @@ class email_certificate_task extends \core\task\scheduled_task {
$emailuser = new \stdClass();
$emailuser->id = -1;
$emailuser->email = $email;
email_to_user($emailuser, fullname($userfrom), $subject, $message, $messagehtml, $tempfile,
$filename);
email_to_user($emailuser, fullname($userfrom), html_entity_decode($subject), $message,
$messagehtml, $tempfile, $filename);
}
}
}

View file

@ -18,13 +18,19 @@
* Class represents a customcert template.
*
* @package mod_customcert
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @copyright 2016 Mark Nelson <markn@moodle.com>, 2022 Kumi Systems e.U. <office@kumi.systems>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_customcert;
defined('MOODLE_INTERNAL') || die();
error_reporting(E_ALL);
require_once(__DIR__ . "/../vendor/autoload.php");
require_once($CFG->dirroot.'/user/profile/lib.php');
use mikehaertl\wkhtmlto\Pdf;
#defined('MOODLE_INTERNAL') || die();
/**
* Class represents a customcert template.
@ -50,6 +56,8 @@ class template {
*/
protected $contextid;
protected $html;
/**
* The constructor.
*
@ -59,6 +67,7 @@ class template {
$this->id = $template->id;
$this->name = $template->name;
$this->contextid = $template->contextid;
$this->html = $template->html;
}
/**
@ -253,8 +262,8 @@ class template {
* @param bool $return Do we want to return the contents of the PDF?
* @return string|void Can return the PDF in string format if specified.
*/
public function generate_pdf($preview = false, $userid = null, $return = false) {
global $CFG, $DB, $USER;
public function generate_pdf(bool $preview = false, int $userid = null, bool $return = false) {
global $CFG, $DB, $USER, $SITE;
if (empty($userid)) {
$user = $USER;
@ -264,25 +273,44 @@ class template {
require_once($CFG->libdir . '/pdflib.php');
profile_load_data($user);
// Get the pages for the template, there should always be at least one page for each template.
if ($pages = $DB->get_records('customcert_pages', array('templateid' => $this->id), 'sequence ASC')) {
// Create the pdf object.
$pdf = new \pdf();
$customcert = $DB->get_record('customcert', ['templateid' => $this->id]);
// If the template belongs to a certificate then we need to check what permissions we set for it.
if ($protection = $DB->get_field('customcert', 'protection', array('templateid' => $this->id))) {
if (!empty($protection)) {
$protection = explode(', ', $protection);
$pdf->SetProtection($protection);
}
if (!empty($customcert->protection)) {
$protection = explode(', ', $customcert->protection);
$pdf->SetProtection($protection);
}
if (empty($customcert->deliveryoption)) {
$deliveryoption = certificate::DELIVERY_OPTION_INLINE;
} else {
$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->setPrintFooter(false);
$pdf->SetTitle($this->name);
$pdf->SetTitle($filename);
$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) {
@ -293,22 +321,91 @@ class template {
$orientation = 'P';
}
$pdf->AddPage($orientation, array($page->width, $page->height));
$pdf->SetMargins($page->leftmargin, 0, $page->rightmargin);
// Get the elements for the page.
if ($elements = $DB->get_records('customcert_elements', array('pageid' => $page->id), 'sequence ASC')) {
// Loop through and display.
foreach ($elements as $element) {
// Get an instance of the element class.
if ($e = \mod_customcert\element_factory::get_element_instance($element)) {
$e->render($pdf, $preview, $user);
}
}
}
if ($this->html) {
$pdf = new Pdf(array(
"disable-smart-shrinking",
"margin-bottom" => "0",
"margin-right" => "0",
"margin-left" => "0",
"margin-top" => "0"
));
$html = $this->html;
$context = \context_user::instance($user->id);
$fs = get_file_storage();
$files = $fs->get_area_files($context->id, 'user', 'icon', 0);
$file = null;
$content = "";
foreach ($files as $filefound) {
if (!$filefound->is_directory()) {
$file = $filefound;
break;
}
}
if ($file) {
$location = make_request_directory() . '/target';
$content = $file->get_content();
} else if ($preview) {
}
$html = str_replace("__PROFILEPIC__", 'data: ' . mime_content_type($file) . ';base64,' . $content, $html);
$html = str_replace("__NAME__", $user->firstname . " " . $user->lastname, $html);
if ($preview) {
$code = \mod_customcert\certificate::generate_code();
} else {
$issue = $DB->get_record('customcert_issues', array('userid' => $user->id, 'customcertid' => $customcert->id),
'*', IGNORE_MULTIPLE);
$code = $issue->code;
}
$html = str_replace("__CERTNUM__", $code, $html);
if ($preview) {
$courseid = $SITE->id;
} else {
$courseid = $customcert->course;
}
$course = get_course($courseid);
$coursename = $course->fullname;
$html = str_replace("__COURSE__", $coursename, $html);
$date = $issue->timecreated;
$html = str_replace("__DATE__", userdate($date, '%B %d, %Y'), $html);
$html = str_replace("__PIN__", $user->username, $html);
$pdf->addPage($html);
$pdf->send();
die($pdf->getError());
} else {
$pdf->SetMargins($page->leftmargin, 0, $page->rightmargin);
// Get the elements for the page.
if ($elements = $DB->get_records('customcert_elements', array('pageid' => $page->id), 'sequence ASC')) {
// Loop through and display.
foreach ($elements as $element) {
// Get an instance of the element class.
if ($e = \mod_customcert\element_factory::get_element_instance($element)) {
$e->render($pdf, $preview, $user);
}
}
}
}
}
if ($return) {
if ($return && !$this->html) {
return $pdf->Output('', 'S');
}
$pdf->Output($filename, 'D');
if (!$this->html) {
$pdf->Output($filename, $deliveryoption);
}
}
}
@ -320,6 +417,10 @@ class template {
public function copy_to_template($copytotemplateid) {
global $DB;
$copytotemplate = $DB->get_record('customcert_templates', array('id' => $copytotemplateid));
$copytotemplate->html = $this->html;
$DB->update_record('customcert_templates', $copytotemplate);
// Get the pages for the template, there should always be at least one page for each template.
if ($templatepages = $DB->get_records('customcert_pages', array('templateid' => $this->id))) {
// Loop through the pages.
@ -412,6 +513,10 @@ class template {
return $this->name;
}
public function get_html() {
return $this->html;
}
/**
* Returns the context id.
*

View file

@ -1,8 +1,9 @@
{
"name": "markn86/moodle-mod_customcert",
"name": "mdjnelson/moodle-mod_customcert",
"type": "moodle-mod",
"require": {
"composer/installers": "~1.0"
"composer/installers": "~1.0",
"mikehaertl/phpwkhtmltopdf": "^2.5"
},
"extra": {
"installer-name": "customcert"

311
composer.lock generated Normal file
View file

@ -0,0 +1,311 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "749e39445f7c33f767b710400da4e9c6",
"packages": [
{
"name": "composer/installers",
"version": "v1.12.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"replace": {
"roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
"composer/composer": "1.6.* || ^2.0",
"composer/semver": "^1 || ^3",
"phpstan/phpstan": "^0.12.55",
"phpstan/phpstan-phpunit": "^0.12.16",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.3"
},
"type": "composer-plugin",
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
"homepage": "https://composer.github.io/installers/",
"keywords": [
"Craft",
"Dolibarr",
"Eliasis",
"Hurad",
"ImageCMS",
"Kanboard",
"Lan Management System",
"MODX Evo",
"MantisBT",
"Mautic",
"Maya",
"OXID",
"Plentymarkets",
"Porto",
"RadPHP",
"SMF",
"Starbug",
"Thelia",
"Whmcs",
"WolfCMS",
"agl",
"aimeos",
"annotatecms",
"attogram",
"bitrix",
"cakephp",
"chef",
"cockpit",
"codeigniter",
"concrete5",
"croogo",
"dokuwiki",
"drupal",
"eZ Platform",
"elgg",
"expressionengine",
"fuelphp",
"grav",
"installer",
"itop",
"joomla",
"known",
"kohana",
"laravel",
"lavalite",
"lithium",
"magento",
"majima",
"mako",
"mediawiki",
"miaoxing",
"modulework",
"modx",
"moodle",
"osclass",
"pantheon",
"phpbb",
"piwik",
"ppi",
"processwire",
"puppet",
"pxcms",
"reindex",
"roundcube",
"shopware",
"silverstripe",
"sydes",
"sylius",
"symfony",
"tastyigniter",
"typo3",
"wordpress",
"yawik",
"zend",
"zikula"
],
"support": {
"issues": "https://github.com/composer/installers/issues",
"source": "https://github.com/composer/installers/tree/v1.12.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-09-13T08:19:44+00:00"
},
{
"name": "mikehaertl/php-shellcommand",
"version": "1.6.4",
"source": {
"type": "git",
"url": "https://github.com/mikehaertl/php-shellcommand.git",
"reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikehaertl/php-shellcommand/zipball/3488d7803df1e8f1a343d3d0ca452d527ad8d5e5",
"reference": "3488d7803df1e8f1a343d3d0ca452d527ad8d5e5",
"shasum": ""
},
"require": {
"php": ">= 5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">4.0 <=9.4"
},
"type": "library",
"autoload": {
"psr-4": {
"mikehaertl\\shellcommand\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Härtl",
"email": "haertl.mike@gmail.com"
}
],
"description": "An object oriented interface to shell commands",
"keywords": [
"shell"
],
"support": {
"issues": "https://github.com/mikehaertl/php-shellcommand/issues",
"source": "https://github.com/mikehaertl/php-shellcommand/tree/1.6.4"
},
"time": "2021-03-17T06:54:33+00:00"
},
{
"name": "mikehaertl/php-tmpfile",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/mikehaertl/php-tmpfile.git",
"reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikehaertl/php-tmpfile/zipball/70a5b70b17bc0d9666388e6a551ecc93d0b40a10",
"reference": "70a5b70b17bc0d9666388e6a551ecc93d0b40a10",
"shasum": ""
},
"require-dev": {
"php": ">=5.3.0",
"phpunit/phpunit": ">4.0 <=9.4"
},
"type": "library",
"autoload": {
"psr-4": {
"mikehaertl\\tmp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Härtl",
"email": "haertl.mike@gmail.com"
}
],
"description": "A convenience class for temporary files",
"keywords": [
"files"
],
"support": {
"issues": "https://github.com/mikehaertl/php-tmpfile/issues",
"source": "https://github.com/mikehaertl/php-tmpfile/tree/1.2.1"
},
"time": "2021-03-01T18:26:25+00:00"
},
{
"name": "mikehaertl/phpwkhtmltopdf",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/mikehaertl/phpwkhtmltopdf.git",
"reference": "17ee71341591415d942774eda2c98d8ba7ea9e90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikehaertl/phpwkhtmltopdf/zipball/17ee71341591415d942774eda2c98d8ba7ea9e90",
"reference": "17ee71341591415d942774eda2c98d8ba7ea9e90",
"shasum": ""
},
"require": {
"mikehaertl/php-shellcommand": "^1.5.0",
"mikehaertl/php-tmpfile": "^1.2.1",
"php": ">=5.0.0"
},
"require-dev": {
"phpunit/phpunit": ">4.0 <9.4"
},
"type": "library",
"autoload": {
"psr-4": {
"mikehaertl\\wkhtmlto\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Haertl",
"email": "haertl.mike@gmail.com"
}
],
"description": "A slim PHP wrapper around wkhtmltopdf with an easy to use and clean OOP interface",
"homepage": "http://mikehaertl.github.com/phpwkhtmltopdf/",
"keywords": [
"pdf",
"wkhtmltoimage",
"wkhtmltopdf"
],
"support": {
"issues": "https://github.com/mikehaertl/phpwkhtmltopdf/issues",
"source": "https://github.com/mikehaertl/phpwkhtmltopdf/tree/2.5.0"
},
"time": "2021-03-01T19:41:06+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/customcert/db" VERSION="20170530" COMMENT="XMLDB file for Moodle mod/customcert"
<XMLDB PATH="mod/customcert/db" VERSION="20211105" COMMENT="XMLDB file for Moodle mod/customcert"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -10,10 +10,11 @@
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="templateid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="intro" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="requiredtime" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="verifyany" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="deliveryoption" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="emailstudents" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="emailteachers" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="emailothers" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
@ -23,7 +24,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">
@ -33,6 +34,7 @@
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="References contextid."/>
<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="html" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for customcert_template"/>
@ -67,7 +69,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">
@ -76,7 +78,7 @@
<FIELD NAME="pageid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="element" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="data" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="data" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="font" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="fontsize" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="colour" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false"/>
@ -84,6 +86,7 @@
<FIELD NAME="posy" 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="alignment" TYPE="char" LENGTH="1" NOTNULL="true" DEFAULT="L" 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="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>

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.

5
db/subplugins.json Normal file
View file

@ -0,0 +1,5 @@
{
"plugintypes": {
"customcertelement": "mod\/customcert\/element"
}
}

View file

@ -1,27 +0,0 @@
<?php
// This file is part of the customcert module for 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/>.
/**
* Definition of sub-plugins.
*
* @package mod_customcert
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$subplugins = array('customcertelement' => 'mod/customcert/element');

View file

@ -146,5 +146,58 @@ function xmldb_customcert_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2018051705, 'customcert');
}
if ($oldversion < 2019111803) {
$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, 2019111803, 'customcert');
}
if ($oldversion < 2020110901) {
$table = new xmldb_table('customcert');
$field = new xmldb_field('deliveryoption', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'verifyany');
// Conditionally launch add field.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2020110901, 'customcert');
}
if ($oldversion < 2021051702) {
$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, 2021051702, 'customcert');
}
if ($oldversion < 2021110501) {
$table = new xmldb_table('customcert_templates');
$field = new xmldb_field('html', XMLDB_TYPE_TEXT, null, null, null, null, null, 'timemodified');
// Conditionally launch add field html.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2021110501, 'customcert');
}
return true;
}

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_bgimage';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_border';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_categoryname';

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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_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

@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
$plugin->version = 2019041100; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2019041000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_coursefield';

View file

@ -35,6 +35,42 @@ defined('MOODLE_INTERNAL') || die();
*/
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.
*
@ -43,7 +79,7 @@ class element extends \mod_customcert\element {
* @param \stdClass $user the user we are rendering this for
*/
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
*/
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
*/
protected function get_course_name() : string {
protected function get_course_name_detail(): string {
$courseid = \mod_customcert\element_helper::get_courseid($this->get_id());
$course = get_course($courseid);
$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
*/
$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['privacy:metadata'] = 'The Course name plugin does not store any personal data.';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_coursename';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_date';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_daterange';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_digitalsignature';

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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_grade';

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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_gradeitemname';

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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_image';

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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_qrcode';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_studentname';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_teachername';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_text';

View file

@ -24,6 +24,8 @@
namespace customcertelement_userfield;
use core_user\fields;
defined('MOODLE_INTERNAL') || die();
/**
@ -43,29 +45,26 @@ class element extends \mod_customcert\element {
public function render_form_elements($mform) {
// Get the user profile fields.
$userfields = array(
'firstname' => get_user_field_name('firstname'),
'lastname' => get_user_field_name('lastname'),
'email' => get_user_field_name('email'),
'city' => get_user_field_name('city'),
'country' => get_user_field_name('country'),
'url' => get_user_field_name('url'),
'icq' => get_user_field_name('icq'),
'skype' => get_user_field_name('skype'),
'aim' => get_user_field_name('aim'),
'yahoo' => get_user_field_name('yahoo'),
'msn' => get_user_field_name('msn'),
'idnumber' => get_user_field_name('idnumber'),
'institution' => get_user_field_name('institution'),
'department' => get_user_field_name('department'),
'phone1' => get_user_field_name('phone1'),
'phone2' => get_user_field_name('phone2'),
'address' => get_user_field_name('address')
'firstname' => fields::get_display_name('firstname'),
'lastname' => fields::get_display_name('lastname'),
'username' => fields::get_display_name('username'),
'email' => fields::get_display_name('email'),
'city' => fields::get_display_name('city'),
'country' => fields::get_display_name('country'),
'url' => fields::get_display_name('url'),
'skype' => fields::get_display_name('skype'),
'idnumber' => fields::get_display_name('idnumber'),
'institution' => fields::get_display_name('institution'),
'department' => fields::get_display_name('department'),
'phone1' => fields::get_display_name('phone1'),
'phone2' => fields::get_display_name('phone2'),
'address' => fields::get_display_name('address')
);
// Get the user custom fields.
$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

@ -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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_userfield';

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021051700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$plugin->component = 'customcertelement_userpicture';

View file

@ -22,8 +22,14 @@
* @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['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['cannotverifyallcertificates'] = 'You do not have the permission to verify all certificates on the site.';
$string['certificate'] = 'Certificate';
@ -56,6 +62,9 @@ $string['deleteissueconfirm'] = 'Are you sure you want to delete this certificat
$string['deleteissuedcertificates'] = 'Delete issued certificates';
$string['deletepageconfirm'] = 'Are you sure you want to delete this certificate page?';
$string['deletetemplateconfirm'] = 'Are you sure you want to delete this certificate template?';
$string['deliveryoptiondownload'] = 'Send to the browser and force a file download';
$string['deliveryoptioninline'] = 'Send the file inline to the browser';
$string['deliveryoptions'] = 'Delivery options';
$string['description'] = 'Description';
$string['duplicate'] = 'Duplicate';
$string['duplicateconfirm'] = 'Duplicate confirmation';
@ -83,12 +92,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 +105,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

@ -264,6 +264,8 @@ function customcert_supports($feature) {
return true;
case FEATURE_MOD_INTRO:
return true;
case FEATURE_SHOW_DESCRIPTION:
return true;
case FEATURE_COMPLETION_TRACKS_VIEWS:
return true;
case FEATURE_BACKUP_MOODLE2:

View file

@ -97,4 +97,4 @@ $PAGE->navbar->add(get_string('loadtemplate', 'customcert'));
echo $OUTPUT->header();
echo $OUTPUT->heading($heading);
echo $OUTPUT->confirm(get_string('loadtemplatemsg', 'customcert'), $yesurl, $nourl);
echo $OUTPUT->footer();
echo $OUTPUT->footer();

57
load_template_cli.php Normal file
View file

@ -0,0 +1,57 @@
<?php
// This file is part of the customcert module for 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/>.
define('CLI_SCRIPT', true);
require_once('../../config.php');
$tid = $argv[1];
$ltid = $argv[2];
$template = $DB->get_record('customcert_templates', array('id' => $tid), '*', MUST_EXIST);
$template = new \mod_customcert\template($template);
$loadtemplate = $DB->get_record('customcert_templates', array('id' => $ltid), '*', MUST_EXIST);
$loadtemplate = new \mod_customcert\template($loadtemplate);
$cm = $template->get_cm();
if ($template->get_context()->contextlevel == CONTEXT_MODULE) {
$customcert = $DB->get_record('customcert', ['id' => $cm->instance], '*', MUST_EXIST);
$title = $customcert->name;
$heading = format_string($title);
} else {
$title = $SITE->fullname;
$heading = $title;
}
$sql = "SELECT e.*
FROM {customcert_elements} e
INNER JOIN {customcert_pages} p
ON e.pageid = p.id
WHERE p.templateid = :templateid";
if ($elements = $DB->get_records_sql($sql, array('templateid' => $template->get_id()))) {
foreach ($elements as $element) {
if ($e = \mod_customcert\element_factory::get_element_instance($element)) {
$e->delete();
}
}
}
$DB->delete_records('customcert_pages', array('templateid' => $template->get_id()));
$loadtemplate->copy_to_template($template->get_id());

View file

@ -47,7 +47,6 @@ require_login();
require_capability('mod/customcert:manage', $context);
$title = $SITE->fullname;
$heading = $title;
// Set up the page.
$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'));
}
$heading = format_string($title, true, ['context' => $context]);
if ($tid) {
if ($action && confirm_sesskey()) {
$nourl = new moodle_url('/mod/customcert/manage_templates.php');

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

@ -22,6 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_customcert\certificate;
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
require_once($CFG->dirroot.'/course/moodleform_mod.php');
@ -55,14 +57,20 @@ class mod_customcert_mod_form extends moodleform_mod {
$this->standard_intro_elements(get_string('description', 'customcert'));
$optionsheader = $mform->createElement('header', 'options', get_string('options', 'customcert'));
$mform->addElement('header', 'options', get_string('options', 'customcert'));
$deliveryoptions = [
certificate::DELIVERY_OPTION_INLINE => get_string('deliveryoptioninline', 'customcert'),
certificate::DELIVERY_OPTION_DOWNLOAD => get_string('deliveryoptiondownload', 'customcert')
];
$mform->addElement('select', 'deliveryoption', get_string('deliveryoptions', 'customcert'), $deliveryoptions);
$mform->setDefault('deliveryoption', certificate::DELIVERY_OPTION_INLINE);
if (has_capability('mod/customcert:manageemailstudents', $this->get_context())) {
$mform->addElement('selectyesno', 'emailstudents', get_string('emailstudents', 'customcert'));
$mform->setDefault('emailstudents', get_config('customcert', 'emailstudents'));
$mform->addHelpButton('emailstudents', 'emailstudents', 'customcert');
$mform->setType('emailstudents', PARAM_INT);
$firstoption = 'emailstudents';
}
if (has_capability('mod/customcert:manageemailteachers', $this->get_context())) {
@ -70,7 +78,6 @@ class mod_customcert_mod_form extends moodleform_mod {
$mform->setDefault('emailteachers', get_config('customcert', 'emailteachers'));
$mform->addHelpButton('emailteachers', 'emailteachers', 'customcert');
$mform->setType('emailteachers', PARAM_INT);
$firstoption = empty($firstoption) ? 'emailteachers' : $firstoption;
}
if (has_capability('mod/customcert:manageemailothers', $this->get_context())) {
@ -78,7 +85,6 @@ class mod_customcert_mod_form extends moodleform_mod {
$mform->addHelpButton('emailothers', 'emailothers', 'customcert');
$mform->setDefault('emailothers', get_config('customcert', 'emailothers'));
$mform->setType('emailothers', PARAM_TEXT);
$firstoption = empty($firstoption) ? 'emailothers' : $firstoption;
}
if (has_capability('mod/customcert:manageverifyany', $this->get_context())) {
@ -86,7 +92,6 @@ class mod_customcert_mod_form extends moodleform_mod {
$mform->addHelpButton('verifyany', 'verifycertificateanyone', 'customcert');
$mform->setDefault('verifyany', get_config('customcert', 'verifyany'));
$mform->setType('verifyany', PARAM_INT);
$firstoption = empty($firstoption) ? 'verifyany' : $firstoption;
}
if (has_capability('mod/customcert:managerequiredtime', $this->get_context())) {
@ -94,7 +99,6 @@ class mod_customcert_mod_form extends moodleform_mod {
$mform->addHelpButton('requiredtime', 'coursetimereq', 'customcert');
$mform->setDefault('requiredtime', get_config('customcert', 'requiredtime'));
$mform->setType('requiredtime', PARAM_INT);
$firstoption = empty($firstoption) ? 'requiredtime' : $firstoption;
}
if (has_capability('mod/customcert:manageprotection', $this->get_context())) {
@ -106,11 +110,6 @@ class mod_customcert_mod_form extends moodleform_mod {
$mform->setType('protection_print', PARAM_BOOL);
$mform->setType('protection_modify', PARAM_BOOL);
$mform->setType('protection_copy', PARAM_BOOL);
$firstoption = empty($firstoption) ? 'protection_print' : $firstoption;
}
if (!empty($firstoption)) {
$mform->insertElementBefore($optionsheader, $firstoption);
}
$this->standard_coursemodule_elements();
@ -227,13 +226,13 @@ class mod_customcert_mod_form extends moodleform_mod {
$protection = explode(', ', $protection);
if (in_array(\mod_customcert\certificate::PROTECTION_PRINT, $protection)) {
if (in_array(certificate::PROTECTION_PRINT, $protection)) {
$data->protection_print = 1;
}
if (in_array(\mod_customcert\certificate::PROTECTION_MODIFY, $protection)) {
if (in_array(certificate::PROTECTION_MODIFY, $protection)) {
$data->protection_modify = 1;
}
if (in_array(\mod_customcert\certificate::PROTECTION_COPY, $protection)) {
if (in_array(certificate::PROTECTION_COPY, $protection)) {
$data->protection_copy = 1;
}

View file

@ -49,9 +49,15 @@ $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 (($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_context(context_user::instance($userid));
$PAGE->set_title(get_string('mycertificates', 'customcert'));
$PAGE->set_pagelayout('standard');
$PAGE->navigation->extend_for_user($user);
// Check if we requested to download a certificate.
if ($downloadcert) {
$template = $DB->get_record('customcert_templates', array('id' => $customcert->templateid), '*', MUST_EXIST);
@ -68,12 +74,6 @@ if ($table->is_downloading()) {
exit();
}
$PAGE->set_url($pageurl);
$PAGE->set_context(context_user::instance($userid));
$PAGE->set_title(get_string('mycertificates', 'customcert'));
$PAGE->set_pagelayout('standard');
$PAGE->navigation->extend_for_user($user);
// Additional page setup.
$PAGE->navbar->add(get_string('profile'), new moodle_url('/user/profile.php', array('id' => $userid)));
$PAGE->navbar->add(get_string('mycertificates', 'customcert'));

View file

@ -112,6 +112,18 @@ if ($elements) {
default:
$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,
'data-refpoint' => $element->refpoint, 'id' => 'element-' . $element->id));
}

View file

@ -57,6 +57,18 @@
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 {
border-style: solid;
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

@ -8,11 +8,9 @@ Feature: Being able to correctly display options on the certificate activity edi
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| manager1 | Manager | 1 | Manager1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| manager1 | C1 | manager |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| customcert | Custom certificate 1 | Custom certificate 1 intro | C1 | customcert1 |
@ -29,40 +27,7 @@ Feature: Being able to correctly display options on the certificate activity edi
And I should see "Required minutes in course"
And I should see "Set protection"
Scenario: Create an activity as an Editing Teacher I can see all custom certificate options
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Custom certificate" to section "1"
And I should see "Email students"
And I should see "Email teachers"
And I should see "Email others"
And I should see "Allow anyone to verify a certificate"
And I should see "Required minutes in course"
And I should see "Set protection"
Scenario: Add an activity as a Manager I can see all custom certificate options
And I log in as "manager1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Custom certificate" to section "1"
And I should see "Email students"
And I should see "Email teachers"
And I should see "Email others"
And I should see "Allow anyone to verify a certificate"
And I should see "Required minutes in course"
And I should see "Set protection"
Scenario: Edit an activity as a Manager Teacher I can see all custom certificate options
And I log in as "manager1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Custom certificate 1"
And I navigate to "Edit settings" in current page administration
And I should see "Email students"
And I should see "Email teachers"
And I should see "Email others"
And I should see "Allow anyone to verify a certificate"
And I should see "Required minutes in course"
And I should see "Set protection"
@javascript
Scenario: Create an activity as an Editing Teacher without required capabilities I can't see all custom certificate options
And I log in as "admin"
And I set the following system permissions of "Teacher" role:
@ -106,8 +71,9 @@ Feature: Being able to correctly display options on the certificate activity edi
And I should not see "Required minutes in course"
And I should not see "Set protection"
@javascript
Scenario: Add an activity using default custom certificate options
And I log in as "manager1"
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Custom certificate" to section "0"
And the field "emailstudents" matches value "0"
@ -119,6 +85,7 @@ Feature: Being able to correctly display options on the certificate activity edi
And the field "protection_modify" matches value "0"
And the field "protection_copy" matches value "0"
@javascript
Scenario: Add an activity using configured custom certificate options
And the following config values are set as admin:
| emailstudents | 1 | customcert |
@ -129,7 +96,7 @@ Feature: Being able to correctly display options on the certificate activity edi
| protection_print | 1 | customcert |
| protection_modify | 1 | customcert |
| protection_copy | 1 | customcert |
And I log in as "manager1"
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Custom certificate" to section "1"
And the field "emailstudents" matches value "1"

View file

@ -40,7 +40,7 @@ class behat_mod_customcert extends behat_base {
/**
* Adds an element to the specified page of a template.
*
* @codingStandardsIgnoreLine
* phpcs:ignore
* @Given /^I add the element "(?P<element_name>(?:[^"]|\\")*)" to page "(?P<page_number>\d+)" of the "(?P<template_name>(?:[^"]|\\")*)" certificate template$/
* @param string $elementname
* @param int $pagenum

View file

@ -79,8 +79,8 @@ Feature: Being able to manage elements in a certificate template
| 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
# 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 |
@ -88,8 +88,8 @@ Feature: Being able to manage elements in a certificate template
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
And I should see "Course name" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Course name" "table_row"
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 |
@ -97,6 +97,26 @@ Feature: Being able to manage elements in a certificate template
| 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:
| Font | Helvetica |
| Type | Short name |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
And I should see "Course name" in the "elementstable" "table"
And I click on ".edit-icon" "css_element" in the "Course name" "table_row"
And the following fields match these values:
| Font | Helvetica |
| Type | Short name |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# Date.
And I add the element "Date" to page "1" of the "Custom certificate 1" certificate template
And I set the following fields to these values:
@ -119,6 +139,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,44 +200,44 @@ 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 format | Percentage |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Grade item | Activity : Assignment 1 |
| Grade format | Percentage |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
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 format | Percentage |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Grade item | Activity : Assignment 1 |
| Grade format | Percentage |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# 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 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Grade item | Activity : Assignment 2 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
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 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
| Grade item | Activity : Assignment 2 |
| Font | Helvetica |
| Size | 20 |
| Colour | #045ECD |
| Width | 20 |
| Reference point location | Top left |
And I press "Save changes"
# Image.
And I add the element "Image" to page "1" of the "Custom certificate 1" certificate template
@ -287,6 +343,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

@ -21,6 +21,8 @@ Feature: Being able to view the certificates that have been issued
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| customcert | Custom certificate 1 | Custom certificate 1 intro | C1 | customcert1 |
And the following config values are set as admin:
| showuseridentity | username,email |
Scenario: View the issued certificates
And I log in as "student1"
@ -38,6 +40,12 @@ Feature: Being able to view the certificates that have been issued
And I follow "Custom certificate 1"
And I should see "Student 1"
And I should see "Student 2"
And I should see "Email address"
And I should see "Username"
And "Student 1" row "Email address" column of "generaltable" table should contain "student1@example.com"
And "Student 1" row "Username" column of "generaltable" table should contain "student1"
And "Student 2" row "Email address" column of "generaltable" table should contain "student2@example.com"
And "Student 2" row "Username" column of "generaltable" table should contain "student2"
Scenario: Delete an issued certificate
And I log in as "student1"

View file

@ -40,7 +40,7 @@ class mod_customcert_element_helper_testcase extends advanced_testcase {
/**
* Test set up.
*/
public function setUp() {
public function setUp(): void {
$this->resetAfterTest();
}
@ -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.
*
@ -40,10 +38,36 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
/**
* Test set up.
*/
public function setUp() {
public function setUp(): void {
$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();
@ -296,4 +416,4 @@ class mod_customcert_task_email_certificate_task_testcase extends advanced_testc
// Confirm no emails were sent.
$this->assertCount(0, $emails);
}
}
}

View file

@ -40,7 +40,7 @@ class mod_customcert_external_test_testcase extends advanced_testcase {
/**
* Test set up.
*/
public function setUp() {
public function setUp(): void {
$this->resetAfterTest();
}

View file

@ -98,9 +98,9 @@ class mod_customcert_privacy_provider_testcase extends \core_privacy\tests\provi
// Check this time there are 2 users.
$this->assertCount(2, $userlist->get_userids());
$this->assertContains($user1->id, $userlist->get_userids());
$this->assertContains($user2->id, $userlist->get_userids());
$this->assertNotContains($user3->id, $userlist->get_userids());
$this->assertContains((int) $user1->id, $userlist->get_userids());
$this->assertContains((int) $user2->id, $userlist->get_userids());
$this->assertNotContains((int) $user3->id, $userlist->get_userids());
}
/**

View file

@ -93,7 +93,7 @@ if ($code) {
$result->issues = array();
// Ok, now check if the code is valid.
$userfields = get_all_user_name_fields(true, 'u');
$userfields = \mod_customcert\helper::get_all_user_name_fields('u');
$sql = "SELECT ci.id, u.id as userid, $userfields, co.id as courseid,
co.fullname as coursefullname, c.id as certificateid,
c.name as certificatename, c.verifyany

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->requires = 2019052000; // Requires this Moodle version (3.7).
$plugin->version = 2021110501; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021051700; // Requires this Moodle version (3.11).
$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.11.1-seachefs"; // User-friendly version number.

View file

@ -18,7 +18,7 @@
* Handles viewing a customcert.
*
* @package mod_customcert
* @copyright 2013 Mark Nelson <markn@moodle.com>
* @copyright 2013 Mark Nelson <markn@moodle.com>, 2022 Kumi Systems e.U. <office@kumi.systems>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@ -56,7 +56,8 @@ if ($customcert->requiredtime && !$canmanage) {
if (\mod_customcert\certificate::get_course_time($course->id) < ($customcert->requiredtime * 60)) {
$a = new stdClass;
$a->requiredtime = $customcert->requiredtime;
notice(get_string('requiredtimenotmet', 'customcert', $a), "$CFG->wwwroot/course/view.php?id=$course->id");
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
notice(get_string('requiredtimenotmet', 'customcert', $a), $url);
die;
}
}
@ -139,8 +140,9 @@ 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->add_action(new \popup_action('click', $link));
$downloadbutton = $OUTPUT->render($downloadbutton);
}
@ -158,7 +160,7 @@ if (!$downloadown && !$downloadissue) {
}
echo $OUTPUT->footer($course);
exit();
} else if ($canreceive) { // Output to pdf.
} else if ($canreceive || $canmanage) { // Output to pdf.
// Set the userid value of who we are downloading the certificate for.
$userid = $USER->id;
if ($downloadown) {
@ -179,6 +181,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(r,t){var s=function(){s.superclass.constructor.apply(this,[arguments])};r.extend(s,r.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.setPdfDimensions(),this.setBoundaries(),this.setpositions(),this.createevents(),window.addEventListener("resize",this.checkWindownResize.bind(this))},setpositions:function(){var t,e,i,s,n,o;for(t in this.elements){switch(e=this.elements[t],i=this.pdfx+e.posx*this.pixelsinmm,s=this.pdfy+e.posy*this.pixelsinmm,n=parseFloat(r.one("#element-"+e.id).getComputedStyle("width")),(o=e.width*this.pixelsinmm)&&o<n&&(n=o),e.refpoint){case"1":i-=n/2;break;case"2":i=i-n+2}r.one("#element-"+e.id).setX(i),r.one("#element-"+e.id).setY(s)}},setPdfDimensions:function(){this.pdfx=r.one("#pdf").getX(),this.pdfy=r.one("#pdf").getY(),this.pdfwidth=parseFloat(r.one("#pdf").getComputedStyle("width")),this.pdfheight=parseFloat(r.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(){r.one(".savepositionsbtn [type=submit]").on("click",function(t){this.savepositions(t)},this),r.one(".applypositionsbtn [type=submit]").on("click",function(t){this.savepositions(t),t.preventDefault()},this);var e=new r.DD.Delegate({container:"#pdf",nodes:".element"});e.on("drag:start",function(){var t=e.get("currentNode");this.elementxy=t.getXY()},this),e.on("drag:end",function(){var t=e.get("currentNode");this.isoutofbounds(t)&&t.setXY(this.elementxy)},this)},isoutofbounds:function(t){var e=parseFloat(t.getComputedStyle("width")),i=parseFloat(t.getComputedStyle("height")),s=t.getX(),n=s+e,o=t.getY(),a=o+i;return s<this.pdfleftboundary||n>this.pdfrightboundary||(o<this.pdfy||a>this.pdfy+this.pdfheight)},savepositions:function(o){var t,e,i,s,n,a,d,p={tid:this.templateid,values:[]};for(t in this.elements){switch(e=this.elements[t],s=(i=r.one("#element-"+e.id)).getX()-this.pdfx,n=i.getY()-this.pdfy,a=i.getData("refpoint"),d=parseFloat(i.getComputedStyle("width")),a){case"1":s+=d/2;break;case"2":s+=d}p.values.push({id:e.id,posx:Math.round(parseFloat(s/this.pixelsinmm)),posy:Math.round(parseFloat(n/this.pixelsinmm))})}p.values=JSON.stringify(p.values),r.io(M.cfg.wwwroot+"/mod/customcert/ajax.php",{method:"POST",data:p,on:{failure:function(t,e){this.ajaxfailure(e)},success:function(){var t,e,i=o.currentTarget.ancestor("form",!0),s=i.getAttribute("action"),n=i.one("[name=pid]");n?(t=n.get("value"),window.location=s+"?pid="+t):(e=i.one("[name=tid]").get("value"),window.location=s+"?tid="+e)}},context:this}),o.preventDefault()},ajaxfailure:function(t){var e={name:t.status+" "+t.statusText,message:t.responseText};return new M.core.exception(e)}}),r.namespace("M.mod_customcert.rearrange").init=function(t,e,i){new s(t,e,i)}},"@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.
*/