├── pix ├── icon.png └── icon.svg ├── CHANGELOG.md ├── README.md ├── templates ├── issues_report.mustache ├── loading.mustache ├── automaticsend_alert.mustache └── view_page.mustache ├── db ├── events.php ├── tasks.php ├── services.php ├── upgrade.php ├── access.php └── install.xml ├── version.php ├── classes ├── event │ ├── course_module_instance_list_viewed.php │ └── course_module_viewed.php ├── privacy │ └── provider.php ├── output │ ├── renderer.php │ ├── view_page.php │ └── certificate_issues_table.php ├── observer.php ├── task │ └── issue_certificates_task.php ├── permission.php ├── external.php └── helper.php ├── view.php ├── tests ├── privacy_provider_tests.php ├── behat │ ├── self_issue_certificates.feature │ ├── view_issued_certificates.feature │ └── basic.feature ├── observer_test.php ├── generator_test.php ├── generator │ └── lib.php ├── external_test.php ├── issue_certificates_task_test.php ├── helper_test.php ├── permission_test.php └── restore_test.php ├── amd ├── build │ ├── manager.min.js │ └── manager.min.js.map └── src │ └── manager.js ├── backup └── moodle2 │ ├── backup_coursecertificate_activity_task.class.php │ ├── backup_coursecertificate_stepslib.php │ ├── restore_coursecertificate_activity_task.class.php │ └── restore_coursecertificate_stepslib.php ├── index.php ├── .github └── workflows │ └── moodle-ci.yml ├── .gitlab-ci.yml ├── lang └── en │ └── coursecertificate.php ├── lib.php └── mod_form.php /pix/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marinaglancy/moodle-mod_coursecertificate/master/pix/icon.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.10.4 (2021051100) 4 | ### Changed 5 | - Fixes to coding style to make new version of codechecker happy 6 | 7 | ## 3.10.1+ (2021020800) 8 | ### Changed 9 | - Viewing and previewing certificates now open a new browser tab 10 | 11 | ## 3.10+ (2020121700) 12 | ### Changed 13 | - Fixed a bug in how a 'Text area' course custom field is handled in the certificate templates 14 | - For performance reasons the exact number of users who will receive certificate is no longer displayed. 15 | [CONTRIB-8325](https://tracker.moodle.org/browse/CONTRIB-8325) 16 | 17 | ## Previous versions 18 | Changelog was not maintained before version 3.10 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Course certificate 2 | ================== 3 | 4 | Automatically issue digital certificates to course participants. 5 | 6 | The course certificate module provides an opportunity for learners to celebrate achievements 7 | by obtaining certificates. 8 | 9 | It allows you to choose from different certificate templates which will automatically display 10 | user data such as full name, course, etc. 11 | 12 | Users will be able to download a PDF copy of the certificate themselves by accessing this activity, 13 | and there are options to send a PDF copy to them by email automatically. 14 | 15 | If the template used on this activity contains a QR code, users will be able to scan it to validate 16 | their certificates. 17 | 18 | The Course certificate plugin works together with the **Certificate manager plugin (tool_certificate)**. 19 | The Certificate manager plugin has to be installed, it provides the API and UI for 20 | designing the certificate templates on system and course category level. 21 | -------------------------------------------------------------------------------- /templates/issues_report.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_coursecertificate/issues_report 19 | 20 | Template for showing the issues report. 21 | 22 | Example context (json): 23 | { 24 | "table": "
" 25 | } 26 | }} 27 |
28 | {{{table}}} 29 |
-------------------------------------------------------------------------------- /templates/loading.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_coursecertificate/loading 19 | 20 | Template for showing the loading overlay. 21 | 22 | Example context (json): 23 | { 24 | } 25 | }} 26 | -------------------------------------------------------------------------------- /db/events.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Events for tool_certificate. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | $observers = [ 28 | [ 29 | 'eventname' => '\tool_certificate\event\template_deleted', 30 | 'callback' => mod_coursecertificate_observer::class . '::on_template_deleted' 31 | ], 32 | ]; -------------------------------------------------------------------------------- /db/tasks.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Coursecertificate scheduled tasks. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $tasks = [ 28 | [ 29 | 'classname' => 'mod_coursecertificate\task\issue_certificates_task', 30 | 'blocking' => 0, 31 | 'minute' => 'R', 32 | 'hour' => '*', 33 | 'day' => '*', 34 | 'month' => '*', 35 | 'dayofweek' => '*' 36 | ] 37 | ]; -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin version and other meta-data are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $plugin->component = 'mod_coursecertificate'; 28 | $plugin->release = '3.10.4'; 29 | $plugin->version = 2021051100; 30 | $plugin->requires = 2020061502.00; 31 | $plugin->maturity = MATURITY_STABLE; 32 | $plugin->supported = [39, 310]; 33 | $plugin->dependencies = [ 34 | 'tool_certificate' => 2021051100 35 | ]; -------------------------------------------------------------------------------- /classes/event/course_module_instance_list_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin event classes are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The course_module_instance_list_viewed event class. 31 | * 32 | * @package mod_coursecertificate 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { 37 | } -------------------------------------------------------------------------------- /db/services.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Coursecertificate module webservice functions. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | $functions = [ 28 | 'mod_coursecertificate_update_automaticsend' => [ 29 | 'classname' => mod_coursecertificate\external::class, 30 | 'methodname' => 'update_automaticsend', 31 | 'description' => 'Update automaticsend setting for a certificate.', 32 | 'type' => 'write', 33 | 'capabilities' => 'mod/coursecertificate:addinstance', 34 | 'ajax' => true, 35 | ] 36 | ]; 37 | -------------------------------------------------------------------------------- /view.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Prints an instance of mod_coursecertificate. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__.'/../../config.php'); 26 | 27 | $id = required_param('id', PARAM_INT); 28 | $page = optional_param('page', 0, PARAM_INT); 29 | $perpage = optional_param('perpage', 10, PARAM_INT); 30 | 31 | [$course, $cm] = get_course_and_cm_from_cmid($id, 'coursecertificate'); 32 | 33 | require_course_login($course, true, $cm); 34 | 35 | $outputpage = new \mod_coursecertificate\output\view_page($id, $page, $perpage, $course, $cm); 36 | $output = $PAGE->get_renderer('coursecertificate'); 37 | 38 | echo $output->header(); 39 | echo $output->render($outputpage); 40 | echo $output->footer(); -------------------------------------------------------------------------------- /classes/privacy/provider.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Class provider 19 | * 20 | * @package mod_coursecertificate 21 | * @category privacy 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace mod_coursecertificate\privacy; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | /** 31 | * Privacy API implementation for the coursecertificate plugin. 32 | * 33 | * @package mod_coursecertificate 34 | * @copyright 2020 Mikel Martín 35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 | */ 37 | class provider implements \core_privacy\local\metadata\null_provider { 38 | /** 39 | * Get the language string identifier with the component's language 40 | * file to explain why this plugin stores no data. 41 | * 42 | * @return string 43 | */ 44 | public static function get_reason() : string { 45 | return 'privacy:metadata'; 46 | } 47 | } -------------------------------------------------------------------------------- /tests/privacy_provider_tests.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for privacy provider. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | use mod_customcert\privacy\provider; 27 | 28 | defined('MOODLE_INTERNAL') || die(); 29 | 30 | /** 31 | * Privacy provider tests class. 32 | * 33 | * @package mod_coursecertificate 34 | * @category test 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class mod_coursecertificate_privacy_provider_testcase extends \core_privacy\tests\provider_testcase { 39 | 40 | /** 41 | * Test for provider::get_reason(). 42 | */ 43 | public function test_get_reason() { 44 | $this->resetAfterTest(); 45 | 46 | $this->assertEquals('privacy:metadata', \mod_coursecertificate\privacy\provider::get_reason()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /classes/output/renderer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Renderer for coursecertificate. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate\output; 26 | 27 | use plugin_renderer_base; 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | /** 32 | * mod_coursecertificate_renderer class. 33 | * 34 | * @package mod_coursecertificate 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class renderer extends plugin_renderer_base { 39 | /** 40 | * Render the view page. 41 | * 42 | * @param \mod_coursecertificate\output\view_page $page 43 | * 44 | * @return bool|string 45 | */ 46 | protected function render_view_page(\mod_coursecertificate\output\view_page $page) { 47 | $context = $page->export_for_template($this); 48 | return $this->render_from_template('mod_coursecertificate/view_page', $context); 49 | } 50 | } -------------------------------------------------------------------------------- /classes/observer.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Class mod_coursecertificate_observer 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die; 26 | 27 | /** 28 | * Class mod_coursecertificate_observer 29 | * 30 | * @package mod_coursecertificate 31 | * @copyright 2020 Mikel Martín 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class mod_coursecertificate_observer 35 | { 36 | /** 37 | * Template deleted observer 38 | * 39 | * @param \tool_certificate\event\template_deleted $event 40 | */ 41 | public static function on_template_deleted(\tool_certificate\event\template_deleted $event): void { 42 | global $DB; 43 | $records = $DB->get_records('coursecertificate', ['template' => $event->objectid]); 44 | foreach ($records as $record) { 45 | $DB->update_record('coursecertificate', (object)['id' => $record->id, 'template' => 0]); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /db/upgrade.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Upgrade scripts 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * Execute mod_coursecertificate upgrade from the given old version. 29 | * 30 | * @param int $oldversion 31 | * @return bool 32 | */ 33 | function xmldb_coursecertificate_upgrade($oldversion) { 34 | global $DB; 35 | $dbman = $DB->get_manager(); 36 | 37 | if ($oldversion < 2020072201) { 38 | 39 | // Define index automaticsend (not unique) to be added to coursecertificate. 40 | $table = new xmldb_table('coursecertificate'); 41 | $index = new xmldb_index('automaticsend', XMLDB_INDEX_NOTUNIQUE, ['automaticsend']); 42 | 43 | // Conditionally launch add index automaticsend. 44 | if (!$dbman->index_exists($table, $index)) { 45 | $dbman->add_index($table, $index); 46 | } 47 | 48 | // Coursecertificate savepoint reached. 49 | upgrade_mod_savepoint(true, 2020072201, 'coursecertificate'); 50 | } 51 | 52 | return true; 53 | } -------------------------------------------------------------------------------- /tests/behat/self_issue_certificates.feature: -------------------------------------------------------------------------------- 1 | @mod @mod_coursecertificate @moodleworkplace @javascript 2 | Feature: Self issue certificate for coursecertificate template 3 | In order to get a certificate issue 4 | As a student 5 | I need to access the course certificate issued module 6 | 7 | Background: 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | 1 | teacher1@example.com | 11 | | student1 | Student | 1 | student1@example.com | 12 | | manager1 | Manager | 1 | manager1@example.com | 13 | And the following "courses" exist: 14 | | fullname | shortname | format | 15 | | Course 1 | C1 | topics | 16 | And the following "course enrolments" exist: 17 | | user | course | role | 18 | | teacher1 | C1 | editingteacher | 19 | | student1 | C1 | student | 20 | And the following certificate templates exist: 21 | | name | shared | 22 | | Template 01 | 1 | 23 | And the following "activities" exist: 24 | | activity | name | intro | course | idnumber | template | groupmode | 25 | | coursecertificate | Certificate | Certificate intro | C1 | coursecertificate1 | Template 01 | 1 | 26 | 27 | Scenario: Get certificate having the activity requirements when accessing the activity 28 | Then I log in as "student1" 29 | And I am on "Course 1" course homepage 30 | And I follow "Certificate" 31 | And I press the "back" button in the browser 32 | And I click on ".popover-region-notifications" "css_element" 33 | And I should see "Your certificate is available!" 34 | 35 | Scenario: Teacher should not get certificate when accessing the activity 36 | And I log in as "teacher1" 37 | And I am on "Course 1" course homepage 38 | And I follow "Certificate" 39 | And I click on ".popover-region-notifications" "css_element" 40 | And I should not see "Your certificate is available!" 41 | -------------------------------------------------------------------------------- /classes/event/course_module_viewed.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin event classes are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate\event; 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | /** 30 | * The course_module_viewed event class. 31 | * 32 | * @package mod_coursecertificate 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class course_module_viewed extends \core\event\course_module_viewed { 37 | 38 | /** 39 | * Init method. 40 | * 41 | * @return void 42 | */ 43 | protected function init(): void { 44 | $this->data['objecttable'] = 'coursecertificate'; 45 | $this->data['crud'] = 'r'; 46 | $this->data['edulevel'] = self::LEVEL_PARTICIPATING; 47 | } 48 | 49 | /** 50 | * This is used when restoring course logs where it is required that we 51 | * map the objectid to it's new value in the new course. 52 | * 53 | * @return array 54 | */ 55 | public static function get_objectid_mapping() { 56 | return ['db' => 'coursecertificate', 'restore' => 'coursecertificate']; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /db/access.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin capabilities are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @category access 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $capabilities = [ 29 | 30 | 'mod/coursecertificate:view' => [ 31 | 'captype' => 'read', 32 | 'contextlevel' => CONTEXT_MODULE, 33 | 'archetypes' => [ 34 | 'student' => CAP_ALLOW, 35 | 'teacher' => CAP_ALLOW, 36 | 'editingteacher' => CAP_ALLOW, 37 | 'manager' => CAP_ALLOW 38 | ], 39 | ], 40 | 'mod/coursecertificate:viewreport' => [ 41 | 'captype' => 'read', 42 | 'contextlevel' => CONTEXT_MODULE, 43 | 'archetypes' => [ 44 | 'teacher' => CAP_ALLOW, 45 | 'editingteacher' => CAP_ALLOW, 46 | 'manager' => CAP_ALLOW 47 | ], 48 | ], 49 | 'mod/coursecertificate:addinstance' => [ 50 | 'captype' => 'write', 51 | 'contextlevel' => CONTEXT_COURSE, 52 | 'archetypes' => [ 53 | 'manager' => CAP_ALLOW, 54 | 'editingteacher' => CAP_ALLOW, 55 | ], 56 | 'clonepermissionsfrom' => 'moodle/course:manageactivities', 57 | ], 58 | 'mod/coursecertificate:receive' => [ 59 | 'captype' => 'write', 60 | 'contextlevel' => CONTEXT_COURSE, 61 | 'archetypes' => [ 62 | 'student' => CAP_ALLOW 63 | ], 64 | ], 65 | ]; -------------------------------------------------------------------------------- /db/install.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
-------------------------------------------------------------------------------- /templates/automaticsend_alert.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_coursecertificate/automaticsend_alert 19 | 20 | Template for showing the automatic send alert. 21 | 22 | Example context (json): 23 | { 24 | "certificateid": "1", 25 | "automaticsend": true 26 | } 27 | }} 28 | {{> coursecertificate/loading}} 29 | {{#automaticsend}} 30 |
31 | 36 | 39 |
40 | {{/automaticsend}} 41 | {{^automaticsend}} 42 |
43 | 48 | 51 |
52 | {{/automaticsend}} 53 | -------------------------------------------------------------------------------- /amd/build/manager.min.js: -------------------------------------------------------------------------------- 1 | define ("mod_coursecertificate/manager",["core/ajax","core/notification","core/templates","core/str"],function(a,b,c,d){var h={AUTOMATICSENDREGION:"[data-region='automaticsend-alert']",HIDDENWARNING:".hidden-warning",NOAUTOSENDINFO:".noautosend-info",REPORTREGION:"[data-region='issues-report']",TOGGLEAUTOMATICSEND:"[data-action='toggle-automaticsend']",REVOKEISSUE:"[data-action='revoke-issue']",LOADING:".loading-overlay"},i={AUTOMATICSENDALERT:"mod_coursecertificate/automaticsend_alert",ISSUESREPORT:"mod_coursecertificate/issues_report"},j={UPDATEAUTOMATICSEND:"mod_coursecertificate_update_automaticsend",REVOKEISSUE:"tool_certificate_revoke_issue"};function e(a,b){if(b){document.querySelector(a).classList.remove("d-none");document.querySelector(a).classList.remove("invisible")}else{document.querySelector(a).classList.add("d-none");document.querySelector(a).classList.add("invisible")}}function f(f){var g=f.querySelector(h.TOGGLEAUTOMATICSEND).dataset,k=g.certificateid,l=g.automaticsend,m="0"===l,n=m?[{key:"confirmation",component:"admin"},{key:"enableautomaticsendpopup",component:"coursecertificate"},{key:"confirm"},{key:"cancel"}]:[{key:"confirmation",component:"admin"},{key:"disableautomaticsend",component:"coursecertificate"},{key:"confirm"},{key:"cancel"}];d.get_strings(n).then(function(d){b.confirm(d[0],d[1],d[2],d[3],function(){M.util.js_pending("mod_coursecertificate_toggle_automaticsend");e(h.LOADING,!0);a.call([{methodname:j.UPDATEAUTOMATICSEND,args:{id:k,automaticsend:m}}])[0].then(function(a){var d=a.showhiddenwarning,g=a.shownoautosendinfo;c.render(i.AUTOMATICSENDALERT,{certificateid:k,automaticsend:m},"").then(function(a){f.innerHTML=a;e(h.HIDDENWARNING,d);e(h.NOAUTOSENDINFO,g);M.util.js_complete("mod_coursecertificate_toggle_automaticsend");return null}).fail(b.exception);return null}).fail(b.exception)});return null}).fail(b.exception)}function g(c){d.get_strings([{key:"confirmation",component:"admin"},{key:"revokeissue",component:"coursecertificate"},{key:"confirm"},{key:"cancel"}]).then(function(d){b.confirm(d[0],d[1],d[2],d[3],function(){M.util.js_pending("mod_coursecertificate_revoke_issue");a.call([{methodname:j.REVOKEISSUE,args:{id:c}}])[0].then(function(){M.util.js_complete("mod_coursecertificate_revoke_issue");window.location.reload();return null}).fail(b.exception)});return null}).fail(b.exception)}return{init:function init(){var a=document.querySelector(h.AUTOMATICSENDREGION);if(a){a.addEventListener("click",function(b){if(b.target&&b.target.closest(h.TOGGLEAUTOMATICSEND)){b.preventDefault();f(a)}})}var b=document.querySelector(h.REPORTREGION);if(b){b.addEventListener("click",function(a){var b=a.target&&a.target.closest(h.REVOKEISSUE);if(b){a.preventDefault();var c=b.dataset.issueid;g(c)}})}}}}); 2 | //# sourceMappingURL=manager.min.js.map 3 | -------------------------------------------------------------------------------- /pix/icon.svg: -------------------------------------------------------------------------------- 1 | Asset 26 -------------------------------------------------------------------------------- /tests/observer_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for observer class. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | /** 29 | * Unit tests for observer class. 30 | * 31 | * @package mod_coursecertificate 32 | * @category test 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_observer_test_testcase extends advanced_testcase { 37 | /** 38 | * Set up 39 | */ 40 | public function setUp(): void { 41 | $this->resetAfterTest(); 42 | } 43 | 44 | /** 45 | * Get certificate generator 46 | * @return tool_certificate_generator 47 | */ 48 | protected function get_certificate_generator(): tool_certificate_generator { 49 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 50 | } 51 | 52 | /** 53 | * Test coursecertificate template value is changed to 0 when template is deleted. 54 | */ 55 | public function test_course_deleted() { 56 | global $DB; 57 | 58 | // Create course, certificate template and coursecertificate module. 59 | $course = $this->getDataGenerator()->create_course(['shortname' => 'C01', 'customfield_f1' => 'some text']); 60 | 61 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 62 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 63 | ['course' => $course->id, 'template' => $certificate1->get_id()]); 64 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 65 | // Sanity check. 66 | $this->assertEquals($certificate1->get_id(), $mod->template); 67 | 68 | // Delete the template. 69 | $certificate1->delete(); 70 | 71 | // Check coursecertificate 'template' is now '0'. 72 | $coursecertificate = $DB->get_record('coursecertificate', ['id' => $mod->id]); 73 | $this->assertEquals(0, $coursecertificate->template); 74 | } 75 | } -------------------------------------------------------------------------------- /backup/moodle2/backup_coursecertificate_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The task that provides all the steps to perform a complete backup is defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @category backup 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | require_once($CFG->dirroot.'/mod/coursecertificate/backup/moodle2/backup_coursecertificate_stepslib.php'); 29 | 30 | /** 31 | * The class provides all the settings and steps to perform one complete backup of mod_coursecertificate. 32 | * 33 | * @package mod_coursecertificate 34 | * @copyright 2020 Mikel Martín 35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 | */ 37 | class backup_coursecertificate_activity_task extends backup_activity_task { 38 | 39 | /** 40 | * Defines particular settings for the plugin. 41 | */ 42 | protected function define_my_settings() { 43 | return; 44 | } 45 | 46 | /** 47 | * Defines particular steps for the backup process. 48 | */ 49 | protected function define_my_steps() { 50 | $this->add_step(new backup_coursecertificate_activity_structure_step( 51 | 'coursecertificate_structure', 52 | 'coursecertificate.xml') 53 | ); 54 | } 55 | 56 | /** 57 | * Codes the transformations to perform in the activity in order to get transportable (encoded) links. 58 | * 59 | * @param string $content content to encode. 60 | * @return string encoded string 61 | */ 62 | public static function encode_content_links($content) { 63 | global $CFG; 64 | 65 | $base = preg_quote($CFG->wwwroot, "/"); 66 | 67 | // Link to the list of choices. 68 | $search = "/(".$base."\/mod\/coursecertificate\/index.php\?id\=)([0-9]+)/"; 69 | $content = preg_replace($search, '$@COURSECERTIFICATEINDEX*$2@$', $content); 70 | 71 | // Link to choice view by moduleid. 72 | $search = "/(".$base."\/mod\/coursecertificate\/view.php\?id\=)([0-9]+)/"; 73 | $content = preg_replace($search, '$@COURSECERTIFICATEVIEWBYID*$2@$', $content); 74 | 75 | return $content; 76 | } 77 | } -------------------------------------------------------------------------------- /templates/view_page.mustache: -------------------------------------------------------------------------------- 1 | {{! 2 | This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/ 3 | 4 | Moodle is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | Moodle is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with Moodle. If not, see . 16 | }} 17 | {{! 18 | @template mod_coursecertificate/view_page 19 | 20 | Template for showing the view page. 21 | 22 | Example context (json): 23 | { 24 | "certificateid": 1, 25 | "certificatename": "Certificate Name", 26 | "showautomaticsend": true, 27 | "showreport": true, 28 | "automaticsend": true, 29 | "notemplateselected": false, 30 | "showhiddenwarning": false, 31 | "shownoautosendinfo": false, 32 | "table": "
" 33 | } 34 | }} 35 |
36 |

{{certificatename}}

37 | {{#studentview}} 38 | {{#notemplateselected}} 39 |
40 | {{#str}} notemplateselecteduser, coursecertificate {{/str}} 41 |
42 | {{/notemplateselected}} 43 | {{/studentview}} 44 | {{^studentview}} 45 | {{#showautomaticsend}} 46 |
47 | {{> coursecertificate/automaticsend_alert}} 48 |
49 | {{/showautomaticsend}} 50 | {{#notemplateselected}} 51 |
52 | {{#str}} notemplateselected, coursecertificate {{/str}} 53 |
54 | {{/notemplateselected}} 55 |
56 | {{#str}} activityhiddenwarning, coursecertificate {{/str}} 57 |
58 |
59 | {{#str}} automaticsenddisabledalert, coursecertificate {{/str}} 60 |
61 | {{#showreport}} 62 |

{{#str}} certifiedusers, coursecertificate {{/str}}

63 |
64 | {{> coursecertificate/issues_report}} 65 |
66 | {{/showreport}} 67 | {{/studentview}} 68 |
69 | 70 | {{#js}} 71 | {{^studentview}} 72 | require(['mod_coursecertificate/manager'], function(m) { 73 | m.init(); 74 | }); 75 | {{/studentview}} 76 | {{/js}} -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Display information about all the mod_coursecertificate modules in the requested course. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | require(__DIR__.'/../../config.php'); 26 | require_once(__DIR__.'/lib.php'); 27 | 28 | $id = required_param('id', PARAM_INT); 29 | 30 | $course = $DB->get_record('course', ['id' => $id], '*', MUST_EXIST); 31 | require_course_login($course); 32 | 33 | $coursecontext = context_course::instance($course->id); 34 | 35 | $event = \mod_coursecertificate\event\course_module_instance_list_viewed::create(['context' => $coursecontext]); 36 | $event->add_record_snapshot('course', $course); 37 | $event->trigger(); 38 | 39 | $PAGE->set_url('/mod/certificate/index.php', ['id' => $id]); 40 | $PAGE->set_title(format_string($course->fullname)); 41 | $PAGE->set_heading(format_string($course->fullname)); 42 | $PAGE->set_context($coursecontext); 43 | 44 | echo $OUTPUT->header(); 45 | 46 | $modulenameplural = get_string('modulenameplural', 'coursecertificate'); 47 | echo $OUTPUT->heading($modulenameplural); 48 | 49 | $certificates = get_all_instances_in_course('coursecertificate', $course); 50 | 51 | if (empty($certificates)) { 52 | notice(get_string('thereareno', 'moodle', $modulenameplural), new moodle_url('/course/view.php', ['id' => $course->id])); 53 | exit; 54 | } 55 | 56 | $table = new html_table(); 57 | $table->attributes['class'] = 'generaltable mod_index'; 58 | 59 | $align = ['center', 'left']; 60 | if ($course->format == 'weeks') { 61 | $table->head = [get_string('week'), get_string('name')]; 62 | $table->align = ['center', 'left']; 63 | } else if ($course->format == 'topics') { 64 | $table->head = [get_string('topic'), get_string('name')]; 65 | $table->align = ['center', 'left']; 66 | } else { 67 | $table->head = [get_string('name')]; 68 | $table->align = ['left']; 69 | } 70 | 71 | foreach ($certificates as $certificate) { 72 | $attributes = []; 73 | if (!$certificate->visible) { 74 | $attributes['class'] = 'dimmed'; 75 | } 76 | $link = html_writer::link( 77 | new moodle_url('/mod/coursecertificate/view.php', ['id' => $certificate->coursemodule]), 78 | format_string($certificate->name, true), 79 | $attributes); 80 | 81 | if ($course->format == 'weeks' or $course->format == 'topics') { 82 | $table->data[] = [$certificate->section, $link]; 83 | } else { 84 | $table->data[] = [$link]; 85 | } 86 | } 87 | 88 | echo html_writer::table($table); 89 | echo $OUTPUT->footer(); -------------------------------------------------------------------------------- /classes/task/issue_certificates_task.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Issue certificates scheduled task. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace mod_coursecertificate\task; 25 | 26 | use context_module; 27 | use mod_coursecertificate\helper; 28 | 29 | defined('MOODLE_INTERNAL') || die(); 30 | 31 | /** 32 | * Issue certificates scheduled task class. 33 | * 34 | * @package mod_coursecertificate 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class issue_certificates_task extends \core\task\scheduled_task { 39 | /** 40 | * Get a descriptive name for this task. 41 | * 42 | * @return string 43 | * @uses \tool_certificate\template 44 | */ 45 | public function get_name() { 46 | return get_string('taskissuecertificates', 'coursecertificate'); 47 | } 48 | 49 | /** 50 | * Execute. 51 | */ 52 | public function execute() { 53 | global $DB; 54 | 55 | // Get all the coursecertificates with automatic send enabled. 56 | $sql = "SELECT c.* 57 | FROM {coursecertificate} c 58 | JOIN {tool_certificate_templates} ct 59 | ON c.template = ct.id 60 | WHERE c.automaticsend = 1"; 61 | $coursecertificates = $DB->get_records_sql($sql); 62 | foreach ($coursecertificates as $coursecertificate) { 63 | [$course, $cm] = get_course_and_cm_from_instance($coursecertificate->id, 'coursecertificate', 64 | $coursecertificate->course); 65 | if (!$cm->visible) { 66 | // Skip coursecertificate modules not visible. 67 | continue; 68 | } 69 | 70 | $template = \tool_certificate\template::instance($coursecertificate->template); 71 | 72 | // Get all the users with requirements that had not been issued. 73 | $users = helper::get_users_to_issue($coursecertificate, $cm); 74 | 75 | // Issue the certificate. 76 | foreach ($users as $user) { 77 | // Add course data to the issue. 78 | $issuedata = helper::get_issue_data($course, $user); 79 | 80 | $template->issue_certificate( 81 | $user->id, 82 | $coursecertificate->expires, 83 | $issuedata, 84 | 'mod_coursecertificate', 85 | $course->id 86 | ); 87 | mtrace("... issued coursecertificate $coursecertificate->id for user $user->id on course $course->id"); 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /backup/moodle2/backup_coursecertificate_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Define the complete structure for backup, with file and id annotations. 19 | * 20 | * @package mod_coursecertificate 21 | * @category backup 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * THe class defines the complete structure for backup, with file and id annotations. 30 | * 31 | * @package mod_coursecertificate 32 | * @copyright 2020 Mikel Martín 33 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 | */ 35 | class backup_coursecertificate_activity_structure_step extends backup_activity_structure_step { 36 | 37 | /** 38 | * Defines the structure of the resulting xml file. 39 | * 40 | * @return backup_nested_element The structure wrapped by the common 'activity' element. 41 | */ 42 | protected function define_structure() { 43 | global $DB; 44 | 45 | if (!$DB->get_manager()->table_exists('tool_certificate_issues')) { 46 | throw new \dml_exception('tool_certificate_issues table does not exists'); 47 | } 48 | 49 | // Course certificate. 50 | $coursecertificate = new backup_nested_element('coursecertificate', ['id'], ['name', 'timecreated', 'timemodified', 'intro', 51 | 'introformat', 'template', 'automaticsend', 'expires']); 52 | 53 | // Issues. 54 | $issues = new backup_nested_element('issues'); 55 | $issue = new backup_nested_element('issue', ['id'], 56 | ['userid', 'templateid', 'code', 'emailed', 'timecreated', 'expires', 'data', 'component', 'courseid']); 57 | 58 | // Build the tree. 59 | $coursecertificate->add_child($issues); 60 | $issues->add_child($issue); 61 | 62 | // Define the source tables for the elements. 63 | $coursecertificate->set_source_table('coursecertificate', ['id' => backup::VAR_ACTIVITYID]); 64 | 65 | // If we are including user info then save the issues. 66 | if ($this->get_setting_value('userinfo')) { 67 | $issue->set_source_table('tool_certificate_issues', ['courseid' => backup::VAR_COURSEID]); 68 | } 69 | 70 | // Define id annotations. 71 | $issue->annotate_ids('user', 'userid'); 72 | 73 | // Define file annotations. 74 | $coursecertificate->annotate_files('mod_coursecertificate', 'intro', null); // This file area hasn't itemid. 75 | if ($this->get_setting_value('userinfo')) { 76 | $issue->annotate_files('tool_certificate', 'issues', 'id', context_system::instance()->id); 77 | } 78 | 79 | return $this->prepare_activity_structure($coursecertificate); 80 | } 81 | } -------------------------------------------------------------------------------- /classes/permission.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Course certificate related permissions. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate; 26 | 27 | use context_course; 28 | 29 | defined('MOODLE_INTERNAL') || die; 30 | 31 | /** 32 | * Course certificate related permissions class. 33 | * 34 | * @package mod_coursecertificate 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | * @uses \tool_certificate\permission 38 | */ 39 | class permission { 40 | /** 41 | * If a user can manage coursecertificate module. 42 | * 43 | * @param \context $context 44 | * @return bool 45 | */ 46 | public static function can_manage(\context $context): bool { 47 | return has_capability('mod/coursecertificate:addinstance', $context); 48 | } 49 | 50 | /** 51 | * If a user can view coursecertificate issues report. 52 | * 53 | * @param \context $context 54 | * @return bool 55 | */ 56 | public static function can_view_report(\context $context): bool { 57 | return has_capability('mod/coursecertificate:viewreport', $context); 58 | } 59 | 60 | /** 61 | * If a user can verify template issues. 62 | * 63 | * @return bool 64 | */ 65 | public static function can_verify_issues(): bool { 66 | return \tool_certificate\permission::can_verify(); 67 | } 68 | 69 | /** 70 | * If a user can revoke. 71 | * 72 | * @param int $courseid 73 | * @return bool 74 | */ 75 | public static function can_revoke_issues(int $courseid): bool { 76 | if (!$context = context_course::instance($courseid, IGNORE_MISSING)) { 77 | return false; 78 | } 79 | return \tool_certificate\permission::can_issue_to_anybody($context); 80 | } 81 | 82 | /** 83 | * If a user can preview issues. 84 | * 85 | * @param int $courseid 86 | * @return bool 87 | */ 88 | public static function can_view_all_issues(int $courseid): bool { 89 | if (!$context = context_course::instance($courseid, IGNORE_MISSING)) { 90 | return false; 91 | } 92 | return \tool_certificate\permission::can_view_all_certificates($context); 93 | } 94 | 95 | /** 96 | * If a user can receive issues. 97 | * 98 | * @param \context $context 99 | * @return bool 100 | */ 101 | public static function can_receive_issues(\context $context): bool { 102 | return has_capability('mod/coursecertificate:receive', $context); 103 | } 104 | } -------------------------------------------------------------------------------- /tests/generator_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * File contains the unit tests for the mod_coursecertificate generator 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Marina Glancy 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * Unit tests for the mod_coursecertificate generator 29 | * 30 | * @package mod_coursecertificate 31 | * @copyright 2020 Marina Glancy 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class mod_coursecertificate_generator_testcase extends advanced_testcase { 35 | 36 | /** 37 | * Get certificate generator 38 | * @return tool_certificate_generator 39 | */ 40 | protected function get_certificate_generator() : tool_certificate_generator { 41 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 42 | } 43 | 44 | /** 45 | * Test create instance of module 46 | */ 47 | public function test_create_instance() { 48 | global $DB; 49 | $this->resetAfterTest(); 50 | $this->setAdminUser(); 51 | 52 | $course = $this->getDataGenerator()->create_course(); 53 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 54 | 55 | $this->assertFalse($DB->record_exists('coursecertificate', ['course' => $course->id])); 56 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 57 | ['course' => $course->id, 'template' => $certificate1->get_id()]); 58 | $this->assertEquals(1, $DB->count_records('coursecertificate', ['course' => $course->id])); 59 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 60 | $this->assertEquals($certificate1->get_id(), $DB->get_field('coursecertificate', 'template', ['id' => $mod->id])); 61 | 62 | // Create an instance specifying the template by name. 63 | $mod = $this->getDataGenerator()->create_module('coursecertificate', ['course' => $course->id, 64 | 'template' => $certificate1->get_name()]); 65 | $this->assertEquals(2, $DB->count_records('coursecertificate', ['course' => $course->id])); 66 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 67 | $this->assertEquals($certificate1->get_id(), $DB->get_field('coursecertificate', 'template', ['id' => $mod->id])); 68 | 69 | // Create an instance without specifying the certificate, a new one should be created. 70 | $mod = $this->getDataGenerator()->create_module('coursecertificate', ['course' => $course->id]); 71 | $this->assertEquals(3, $DB->count_records('coursecertificate', ['course' => $course->id])); 72 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 73 | $this->assertNotEquals($certificate1->get_id(), $DB->get_field('coursecertificate', 'template', ['id' => $mod->id])); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /backup/moodle2/restore_coursecertificate_activity_task.class.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The task that provides a complete restore of mod_coursecertificate is defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | require_once($CFG->dirroot.'/mod/coursecertificate/backup/moodle2/restore_coursecertificate_stepslib.php'); 28 | 29 | /** 30 | * The class provides a complete restore of mod_coursecertificate. 31 | * 32 | * @package mod_coursecertificate 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class restore_coursecertificate_activity_task extends restore_activity_task { 37 | 38 | /** 39 | * Defines particular settings that this activity can have. 40 | */ 41 | protected function define_my_settings(): void { 42 | return; 43 | } 44 | 45 | /** 46 | * Defines particular steps that this activity can have. 47 | * 48 | * @return base_step. 49 | */ 50 | protected function define_my_steps(): void { 51 | $this->add_step(new restore_coursecertificate_activity_structure_step( 52 | 'coursecertificate_structure', 53 | 'coursecertificate.xml') 54 | ); 55 | } 56 | 57 | /** 58 | * Defines the contents in the activity that must be processed by the link decoder. 59 | * 60 | * @return array. 61 | */ 62 | public static function define_decode_contents(): array { 63 | $contents = []; 64 | 65 | // Define the contents. 66 | $contents[] = new restore_decode_content('coursecertificate', ['intro'], 'coursecertificate'); 67 | 68 | return $contents; 69 | } 70 | 71 | /** 72 | * Defines the decoding rules for links belonging to the activity to be executed by the link decoder. 73 | * 74 | * @return restore_decode_rule[]. 75 | */ 76 | public static function define_decode_rules(): array { 77 | $rules = []; 78 | 79 | $rules[] = new restore_decode_rule('COURSECERTIFICATEVIEWBYID', '/mod/coursecertificate/view.php?id=$1', 'course_module'); 80 | $rules[] = new restore_decode_rule('COURSECERTIFICATEINDEX', '/mod/coursecertificate/index.php?id=$1', 'course'); 81 | 82 | return $rules; 83 | } 84 | 85 | /** 86 | * Defines the restore log rules that will be applied by the 87 | * {@see restore_logs_processor} when restoring mod_coursecertificate logs. It 88 | * must return one array of {@see restore_log_rule} objects. 89 | * 90 | * @return restore_log_rule[]. 91 | */ 92 | public static function define_restore_log_rules(): array { 93 | $rules = []; 94 | 95 | // Define the rules. 96 | $rules[] = new restore_log_rule('coursecertificate', 'view all', 'index.php?id={course}', null); 97 | 98 | return $rules; 99 | } 100 | } -------------------------------------------------------------------------------- /classes/external.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The external API for the Coursecertificate module. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate; 26 | 27 | defined('MOODLE_INTERNAL') || die; 28 | 29 | require_once($CFG->dirroot . '/lib/externallib.php'); 30 | 31 | /** 32 | * The external class for the Coursecertificate module. 33 | * 34 | * @package mod_coursecertificate 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class external extends \external_api { 39 | /** 40 | * Returns the structure of parameters for update_automaticsend function. 41 | * @return \external_function_parameters 42 | */ 43 | protected static function update_automaticsend_parameters() { 44 | $params = [ 45 | 'id' => new \external_value(PARAM_INT, 'The ID of the certificate', VALUE_REQUIRED), 46 | 'automaticsend' => new \external_value(PARAM_BOOL, 'The value of automaticsend setting') 47 | ]; 48 | return new \external_function_parameters($params); 49 | } 50 | 51 | /** 52 | * Update automaticsend setting value. 53 | * 54 | * @param int $id 55 | * @param bool $automaticsend 56 | * @return array 57 | */ 58 | public static function update_automaticsend(int $id, bool $automaticsend) { 59 | global $DB; 60 | 61 | $params = self::validate_parameters(self::update_automaticsend_parameters(), 62 | ['id' => $id, 'automaticsend' => $automaticsend]); 63 | 64 | $certificate = $DB->get_record('coursecertificate', ['id' => $params['id']], '*', MUST_EXIST); 65 | $cm = get_coursemodule_from_instance('coursecertificate', $certificate->id); 66 | $context = \context_module::instance($cm->id); 67 | self::validate_context($context); 68 | 69 | if (permission::can_manage($context)) { 70 | $certificate->automaticsend = $params['automaticsend']; 71 | if ($DB->update_record('coursecertificate', $certificate)) { 72 | \core\event\course_module_updated::create_from_cm($cm, $context)->trigger(); 73 | return [ 74 | 'showhiddenwarning' => $certificate->automaticsend && !$cm->visible, 75 | 'shownoautosendinfo' => !$certificate->automaticsend && $cm->visible, 76 | ]; 77 | } 78 | } 79 | return [ 80 | 'showhiddenwarning' => false, 81 | 'shownoautosendinfo' => false, 82 | ]; 83 | } 84 | 85 | /** 86 | * Describes the return function of update_certificate_automaticsend 87 | * 88 | * @return \external_single_structure 89 | */ 90 | public static function update_automaticsend_returns() { 91 | return new \external_single_structure([ 92 | 'showhiddenwarning' => new \external_value(PARAM_BOOL, 'Desc'), 93 | 'shownoautosendinfo' => new \external_value(PARAM_BOOL, 'Desc'), 94 | ]); 95 | } 96 | } -------------------------------------------------------------------------------- /tests/generator/lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * mod_coursecertificate data generator. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Marina Glancy 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | /** 29 | * mod_coursecertificate data generator class. 30 | * 31 | * @package mod_coursecertificate 32 | * @category test 33 | * @copyright 2020 Marina Glancy 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_generator extends testing_module_generator { 37 | 38 | /** 39 | * @var int keep track of how many chapters have been created. 40 | */ 41 | protected $chaptercount = 0; 42 | 43 | /** 44 | * To be called from data reset code only, 45 | * do not use in tests. 46 | * @return void 47 | */ 48 | public function reset() { 49 | $this->chaptercount = 0; 50 | parent::reset(); 51 | } 52 | 53 | /** 54 | * Looks up template id from it's name or id 55 | * 56 | * @param string $nameorid 57 | * @return int 58 | */ 59 | protected function get_template_id(string $nameorid): int { 60 | /** @var tool_certificate_generator $certificategenerator */ 61 | $certificategenerator = \testing_util::get_data_generator()->get_plugin_generator('tool_certificate'); 62 | return $certificategenerator->lookup_template($nameorid); 63 | } 64 | 65 | 66 | /** 67 | * Creates an instance of the module for testing purposes. 68 | * 69 | * Module type will be taken from the class name. Each module type may overwrite 70 | * this function to add other default values used by it. 71 | * 72 | * @param array|stdClass $record data for module being generated. Requires 'course' key 73 | * (an id or the full object). Also can have any fields from add module form. 74 | * @param null|array $options general options for course module. Since 2.6 it is 75 | * possible to omit this argument by merging options into $record 76 | * @return stdClass record from module-defined table with additional field 77 | * cmid (corresponding id in course_modules table) 78 | */ 79 | public function create_instance($record = null, array $options = null) { 80 | $record = (array)$record; 81 | if (empty($record['template'])) { 82 | $certgenerator = \testing_util::get_data_generator()->get_plugin_generator('tool_certificate'); 83 | $certificate1 = $certgenerator->create_template((object)['name' => 'Certificate 1']); 84 | $record['template'] = $certificate1->get_id(); 85 | } else { 86 | $record['template'] = $this->get_template_id($record['template']); 87 | } 88 | 89 | $defaultsettings = [ 90 | 'automaticsend' => 0, 91 | 'expires' => 0 92 | ]; 93 | foreach ($defaultsettings as $name => $value) { 94 | if (!isset($record[$name])) { 95 | $record[$name] = $value; 96 | } 97 | } 98 | 99 | return parent::create_instance($record, (array)$options); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /.github/workflows/moodle-ci.yml: -------------------------------------------------------------------------------- 1 | name: Moodle Plugin CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-18.04 8 | 9 | services: 10 | postgres: 11 | image: postgres:9.6 12 | env: 13 | POSTGRES_USER: 'postgres' 14 | POSTGRES_HOST_AUTH_METHOD: 'trust' 15 | ports: 16 | - 5432:5432 17 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 18 | mariadb: 19 | image: mariadb:10 20 | env: 21 | MYSQL_USER: 'root' 22 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 23 | ports: 24 | - 3306:3306 25 | options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | include: 31 | - php: '7.4' 32 | moodle-branch: 'MOODLE_310_STABLE' 33 | database: pgsql 34 | - php: '7.3' 35 | moodle-branch: 'MOODLE_310_STABLE' 36 | database: mariadb 37 | - php: '7.2' 38 | moodle-branch: 'MOODLE_39_STABLE' 39 | database: pgsql 40 | 41 | steps: 42 | - name: Check out repository code 43 | uses: actions/checkout@v2 44 | with: 45 | path: plugin 46 | 47 | - name: Setup PHP ${{ matrix.php }} 48 | uses: shivammathur/setup-php@v2 49 | with: 50 | php-version: ${{ matrix.php }} 51 | coverage: none 52 | 53 | - name: Initialise moodle-plugin-ci 54 | run: | 55 | composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 56 | echo $(cd ci/bin; pwd) >> $GITHUB_PATH 57 | echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH 58 | sudo locale-gen en_AU.UTF-8 59 | echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV 60 | 61 | - name: Install moodle-plugin-ci 62 | run: | 63 | moodle-plugin-ci add-plugin moodleworkplace/moodle-tool_certificate 64 | moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 65 | env: 66 | DB: ${{ matrix.database }} 67 | MOODLE_BRANCH: ${{ matrix.moodle-branch }} 68 | 69 | - name: PHP Lint 70 | if: ${{ always() }} 71 | run: moodle-plugin-ci phplint 72 | 73 | - name: PHP Copy/Paste Detector 74 | continue-on-error: true # This step will show errors but will not fail 75 | if: ${{ always() }} 76 | run: moodle-plugin-ci phpcpd 77 | 78 | - name: PHP Mess Detector 79 | continue-on-error: true # This step will show errors but will not fail 80 | if: ${{ always() }} 81 | run: moodle-plugin-ci phpmd 82 | 83 | - name: Moodle Code Checker 84 | if: ${{ always() }} 85 | run: moodle-plugin-ci codechecker --max-warnings 0 86 | 87 | - name: Moodle PHPDoc Checker 88 | if: ${{ always() }} 89 | run: moodle-plugin-ci phpdoc 90 | 91 | - name: Validating 92 | if: ${{ always() }} 93 | run: moodle-plugin-ci validate 94 | 95 | - name: Check upgrade savepoints 96 | if: ${{ always() }} 97 | run: moodle-plugin-ci savepoints 98 | 99 | - name: Mustache Lint 100 | if: ${{ always() }} 101 | run: moodle-plugin-ci mustache 102 | 103 | - name: Grunt 104 | if: ${{ always() }} 105 | run: moodle-plugin-ci grunt --max-lint-warnings 0 106 | 107 | - name: PHPUnit tests 108 | if: ${{ always() }} 109 | run: | 110 | moodle-plugin-ci phpunit 111 | cd moodle 112 | vendor/bin/phpunit --fail-on-risky --disallow-test-output -v admin/tool/dataprivacy/tests/metadata_registry_test.php 113 | vendor/bin/phpunit --fail-on-risky --disallow-test-output -v lib/tests/externallib_test.php 114 | vendor/bin/phpunit --fail-on-risky --disallow-test-output -v privacy/tests/provider_test.php 115 | 116 | - name: Behat features 117 | if: ${{ always() }} 118 | run: moodle-plugin-ci behat --profile chrome 119 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: moodlehq/moodle-workplace-plugin-ci:7.3 2 | 3 | services: 4 | - postgres:9.6 5 | #- mysql:5.7.26 6 | - name: selenium/standalone-chrome:3 7 | alias: selenium-standalone-chrome 8 | 9 | variables: 10 | MOODLE_REPO_WORKPLACE: git@git.in.moodle.com:workplace/workplacedev.git 11 | MOODLE_BRANCH_WORKPLACE: master 12 | MOODLE_REPO_LMS: git@git.in.moodle.com:moodle/moodle.git 13 | MOODLE_BRANCH_LMS: MOODLE_310_STABLE 14 | MOODLE_BEHAT_WDHOST: "http://selenium-standalone-chrome:4444/wd/hub" 15 | POSTGRES_USER: postgres 16 | POSTGRES_PASSWORD: "" 17 | POSTGRES_HOST_AUTH_METHOD: "trust" 18 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 19 | DB: "pgsql" 20 | # DB: "mysqli" 21 | 22 | # Gitlab-ci does not have matrixes, so we have these hidden jobs .setupworkplace and .setuplms to do the set up. 23 | .setupworkplace: &setupworkplace 24 | before_script: 25 | - export MOODLE_REPO=$MOODLE_REPO_WORKPLACE 26 | - export MOODLE_BRANCH=$MOODLE_BRANCH_WORKPLACE 27 | - . prepare-workplace $cibot_sshkey 28 | - cd $CI_PROJECT_DIR/.. 29 | - moodle-plugin-ci add-plugin --clone git@git.in.moodle.com:workplace/moodle-theme_workplace.git 30 | - moodle-plugin-ci add-plugin --clone git@git.in.moodle.com:workplace/moodle-tool_wp.git 31 | - moodle-plugin-ci add-plugin --clone git@git.in.moodle.com:workplace/moodle-tool_certificate.git 32 | - moodle-plugin-ci install --db-host="$DB_HOST" --no-init -vvv 33 | - cd moodle 34 | 35 | .setuplms: &setuplms 36 | before_script: 37 | - export MOODLE_REPO=$MOODLE_REPO_LMS 38 | - export MOODLE_BRANCH=$MOODLE_BRANCH_LMS 39 | # TODO: when all plugins are removed, remove the argument with the sshkey but leave the call to prepare-workplace, it sets up the env 40 | - . prepare-workplace $cibot_sshkey 41 | - cd $CI_PROJECT_DIR/.. 42 | - moodle-plugin-ci add-plugin --clone git@git.in.moodle.com:workplace/moodle-tool_certificate.git 43 | - moodle-plugin-ci install --db-host="$DB_HOST" --no-init -vvv 44 | - cd moodle 45 | 46 | codecheck: 47 | <<: *setupworkplace 48 | script: 49 | - php admin/tool/phpunit/cli/init.php 50 | - php admin/tool/phpunit/cli/util.php --buildcomponentconfigs 51 | - . check-start 52 | - . check version_number 53 | - . check no_workplace_licenses 54 | - . check this_plugin_is_part_of "the mod_coursecertificate plugin for Moodle - http://moodle.org/" --nowplicensecomment 55 | - . check language_file_sorting 56 | - . check moodle-plugin-ci phplint 57 | #- . check moodle-plugin-ci phpcpd 58 | #- . check moodle-plugin-ci phpmd 59 | - . check moodle-plugin-ci codechecker --max-warnings 0 60 | - . check moodle-plugin-ci phpdoc 61 | - . check moodle-plugin-ci validate 62 | - . check moodle-plugin-ci savepoints 63 | - . check moodle-plugin-ci mustache 64 | - . check moodle-plugin-ci grunt --max-lint-warnings 0 65 | - . check vendor/bin/phpunit --fail-on-risky --disallow-test-output -v admin/tool/dataprivacy/tests/metadata_registry_test.php 66 | - . check vendor/bin/phpunit --fail-on-risky --disallow-test-output -v lib/tests/externallib_test.php 67 | - . check vendor/bin/phpunit --fail-on-risky --disallow-test-output -v privacy/tests/provider_test.php 68 | - . check moodle-plugin-ci phpunit --coverage-text 69 | - . check-finish 70 | except: 71 | - tags 72 | 73 | behat: 74 | <<: *setupworkplace 75 | script: 76 | - php -S ${IPADDRESS}:8000 -t $CI_PROJECT_DIR/../moodle > /dev/null 2>&1 & 77 | - php admin/tool/behat/cli/init.php --add-core-features-to-theme --parallel=3 --optimize-runs=@mod_coursecertificate 78 | - . check-start 79 | - . check moodle-plugin-ci behat --suite workplace --profile chrome 80 | - . check-finish 81 | except: 82 | - master 83 | - tags 84 | - /^WORKPLACE_\d+$/ 85 | 86 | lms: 87 | <<: *setuplms 88 | script: 89 | - php admin/tool/phpunit/cli/init.php 90 | - php admin/tool/phpunit/cli/util.php --buildcomponentconfigs 91 | - php -S ${IPADDRESS}:8000 -t $CI_PROJECT_DIR/../moodle > /dev/null 2>&1 & 92 | - php admin/tool/behat/cli/init.php --add-core-features-to-theme --parallel=3 --optimize-runs=@mod_coursecertificate 93 | - . check-start 94 | - . check moodle-plugin-ci phpunit --coverage-text 95 | - . check moodle-plugin-ci behat --suite default --profile chrome 96 | - . check-finish 97 | except: 98 | - tags 99 | -------------------------------------------------------------------------------- /backup/moodle2/restore_coursecertificate_stepslib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * All the steps to restore mod_coursecertificate are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * CLass defines the structure step to restore one mod_coursecertificate activity. 29 | * 30 | * @package mod_coursecertificate 31 | * @copyright 2020 Mikel Martín 32 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 | */ 34 | class restore_coursecertificate_activity_structure_step extends restore_activity_structure_step { 35 | 36 | /** 37 | * Defines the structure to be restored. 38 | * 39 | * @return restore_path_element[]. 40 | */ 41 | protected function define_structure(): array { 42 | $paths = []; 43 | $paths[] = new restore_path_element('coursecertificate', '/activity/coursecertificate'); 44 | 45 | // Check if we want the issues as well. 46 | if ($this->get_setting_value('userinfo')) { 47 | $paths[] = new restore_path_element('tool_certificate_issue', '/activity/coursecertificate/issues/issue'); 48 | } 49 | 50 | return $this->prepare_activity_structure($paths); 51 | } 52 | 53 | /** 54 | * Processes the element restore data. 55 | * 56 | * @param array $data Parsed element data. 57 | */ 58 | protected function process_coursecertificate(array $data): void { 59 | global $DB; 60 | $data = (object) $data; 61 | $data->course = $this->get_courseid(); 62 | // Insert the record. 63 | $newitemid = $DB->insert_record('coursecertificate', $data); 64 | // Immediately after inserting "activity" record, call this. 65 | $this->apply_activity_instance($newitemid); 66 | } 67 | 68 | /** 69 | * Handles restoring a tool_certificate issue. 70 | * 71 | * @param stdClass $data Parsed element data. 72 | */ 73 | protected function process_tool_certificate_issue($data) { 74 | global $DB; 75 | 76 | if (!$DB->get_manager()->table_exists('tool_certificate_issues')) { 77 | throw new \dml_exception('tool_certificate_issues table does not exists'); 78 | } 79 | if (!$DB->get_manager()->table_exists('tool_certificate_templates')) { 80 | throw new \dml_exception('tool_certificate_templates table does not exists'); 81 | } 82 | $data = (object) $data; 83 | 84 | $codefound = $DB->record_exists('tool_certificate_issues', ['code' => $data->code]); 85 | $templatefound = $DB->record_exists('tool_certificate_templates', ['id' => $data->templateid]); 86 | 87 | // TODO WP-1997 For now, we only restore issues if is same site, template exists and same issue code does not exist. 88 | if ($this->task->is_samesite() && $templatefound && !$codefound) { 89 | $oldid = $data->id; 90 | $data->courseid = $this->get_courseid(); 91 | $data->userid = $this->get_mappingid('user', $data->userid); 92 | $newitemid = $DB->insert_record('tool_certificate_issues', $data); 93 | $this->set_mapping('tool_certificate_issue', $oldid, $newitemid, true, $this->task->get_old_system_contextid()); 94 | } 95 | } 96 | 97 | /** 98 | * Defines post-execution actions. 99 | */ 100 | protected function after_execute(): void { 101 | $this->add_related_files('mod_coursecertificate', 'intro', null); 102 | $this->add_related_files('tool_certificate', 'issues', 'tool_certificate_issue', $this->task->get_old_system_contextid()); 103 | } 104 | } -------------------------------------------------------------------------------- /classes/helper.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The helper for the Coursecertificate module. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate; 26 | 27 | use core_availability\info_module; 28 | 29 | defined('MOODLE_INTERNAL') || die; 30 | 31 | require_once($CFG->dirroot . '/grade/querylib.php'); 32 | require_once($CFG->libdir . '/gradelib.php'); 33 | 34 | /** 35 | * The helper for the Coursecertificate module. 36 | * 37 | * @package mod_coursecertificate 38 | * @copyright 2020 Mikel Martín 39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 | */ 41 | class helper { 42 | /** 43 | * Gets users who meet access restrictionss and had not been issued. 44 | * 45 | * @param \stdClass $coursecertificate 46 | * @param \cm_info $cm 47 | * @return array 48 | */ 49 | public static function get_users_to_issue(\stdClass $coursecertificate, \cm_info $cm): array { 50 | global $DB; 51 | 52 | $context = \context_course::instance($coursecertificate->course); 53 | // Get users already issued subquery. 54 | [$usersissuedsql, $usersissuedparams] = self::get_users_issued_select($coursecertificate->course, 55 | $coursecertificate->template); 56 | // Get users enrolled with receive capabilities subquery. 57 | [$enrolledsql, $enrolledparams] = get_enrolled_sql($context, 'mod/coursecertificate:receive', 0, true); 58 | $sql = "SELECT eu.id FROM ($enrolledsql) eu WHERE eu.id NOT IN ($usersissuedsql)"; 59 | $params = array_merge($enrolledparams, $usersissuedparams); 60 | $potentialusers = $DB->get_records_sql($sql, $params); 61 | 62 | // Filter only users with access to the activity {@see info_module::filter_user_list}. 63 | $info = new \core_availability\info_module($cm); 64 | $filteredusers = $info->filter_user_list($potentialusers); 65 | 66 | // Filter only users without 'viewall' capabilities and with access to the activity. 67 | $users = []; 68 | foreach ($filteredusers as $filtereduser) { 69 | if (info_module::is_user_visible($cm, $filtereduser->id, false)) { 70 | $users[] = $filtereduser; 71 | } 72 | } 73 | return $users; 74 | } 75 | 76 | /** 77 | * Returns select for the users that have been already issued 78 | * 79 | * @param int $courseid 80 | * @param int $templateid 81 | * @return array 82 | */ 83 | private static function get_users_issued_select(int $courseid, int $templateid): array { 84 | $sql = "SELECT DISTINCT ci.userid FROM {tool_certificate_issues} ci 85 | WHERE component = :component AND courseid = :courseid AND templateid = :templateid"; 86 | $params = ['component' => 'mod_coursecertificate', 'courseid' => $courseid, 87 | 'templateid' => $templateid]; 88 | return [$sql, $params]; 89 | } 90 | 91 | /** 92 | * Get data for the issue. Important course fields (id, shortname, fullname and URL) and course customfields. 93 | * 94 | * @param \stdClass $course 95 | * @param \stdClass $user 96 | * @return array 97 | */ 98 | public static function get_issue_data(\stdClass $course, \stdClass $user): array { 99 | global $DB; 100 | 101 | // Get user course completion date. 102 | $result = $DB->get_field('course_completions', 'timecompleted', 103 | ['course' => $course->id, 'userid' => $user->id]); 104 | $completiondate = $result ? userdate($result, get_string('strftimedatefullshort')) : ''; 105 | 106 | // Get user course grade. 107 | $grade = grade_get_course_grade($user->id, $course->id); 108 | if ($grade && $grade->grade) { 109 | $gradestr = $grade->str_grade; 110 | } 111 | 112 | $issuedata = [ 113 | 'courseid' => $course->id, 114 | 'courseshortname' => $course->shortname, 115 | 'coursefullname' => $course->fullname, 116 | 'courseurl' => course_get_url($course)->out(), 117 | 'coursecompletiondate' => $completiondate, 118 | 'coursegrade' => $gradestr ?? '', 119 | ]; 120 | // Add course custom fields data. 121 | $handler = \core_course\customfield\course_handler::create(); 122 | foreach ($handler->get_instance_data($course->id, true) as $data) { 123 | $issuedata['coursecustomfield_' . $data->get_field()->get('shortname')] = $data->export_value(); 124 | } 125 | 126 | return $issuedata; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/external_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for the webservices. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | use mod_coursecertificate\external; 27 | 28 | defined('MOODLE_INTERNAL') || die; 29 | 30 | /** 31 | * Unit tests for the webservices. 32 | * 33 | * @package mod_coursecertificate 34 | * @category test 35 | * @copyright 2020 Mikel Martín 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | class mod_coursecertificate_external_test_testcase extends advanced_testcase 39 | { 40 | /** 41 | * Set up 42 | */ 43 | public function setUp(): void { 44 | $this->resetAfterTest(); 45 | } 46 | 47 | /** 48 | * Get certificate generator 49 | * @return tool_certificate_generator 50 | */ 51 | protected function get_certificate_generator() : tool_certificate_generator { 52 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 53 | } 54 | 55 | /** 56 | * Test update automaticsend as editingteacher. 57 | */ 58 | public function test_update_automaticsend() { 59 | global $DB; 60 | 61 | // Create course and user enrolled as 'editingteacher'. 62 | $course = $this->getDataGenerator()->create_course(); 63 | $user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 64 | $this->setUser($user->id); 65 | 66 | // Create certificate template. 67 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 68 | 69 | // Create coursecertificate module. 70 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 71 | ['course' => $course->id, 'template' => $certificate1->get_id(), 'visible' => 0]); 72 | 73 | // Sanity check. 74 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 75 | $this->assertEquals(0, $mod->automaticsend); 76 | 77 | // Enable automaticsend. 78 | $result = external::update_automaticsend($mod->id, true); 79 | $result = external::clean_returnvalue(external::update_automaticsend_returns(), $result); 80 | 81 | $this->assertEquals(1, $DB->get_field('coursecertificate', 'automaticsend', ['id' => $mod->id])); 82 | 83 | $this->assertTrue($result['showhiddenwarning']); 84 | $this->assertFalse($result['shownoautosendinfo']); 85 | 86 | $cm = get_coursemodule_from_instance('coursecertificate', $mod->id); 87 | $DB->update_record('course_modules', (object)['id' => $cm->id, 'visible' => 1]); 88 | 89 | // Disable automaticsend. 90 | $result = external::update_automaticsend($mod->id, false); 91 | $result = external::clean_returnvalue(external::update_automaticsend_returns(), $result); 92 | 93 | $this->assertEquals(0, $DB->get_field('coursecertificate', 'automaticsend', ['id' => $mod->id])); 94 | 95 | $this->assertFalse($result['showhiddenwarning']); 96 | $this->assertTrue($result['shownoautosendinfo']); 97 | 98 | // Enable automaticsend. 99 | $result = external::update_automaticsend($mod->id, true); 100 | $result = external::clean_returnvalue(external::update_automaticsend_returns(), $result); 101 | 102 | $this->assertFalse($result['showhiddenwarning']); 103 | $this->assertFalse($result['shownoautosendinfo']); 104 | } 105 | 106 | /** 107 | * Test update automaticsend as teacher (no capabilities). 108 | */ 109 | public function test_update_automaticsend_without_capabilities() { 110 | global $DB; 111 | 112 | // Create course and user enrolled as 'teacher'. 113 | $course = $this->getDataGenerator()->create_course(); 114 | $user = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 115 | $this->setUser($user->id); 116 | 117 | // Create certificate template. 118 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 119 | 120 | // Create coursecertificate module. 121 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 122 | ['course' => $course->id, 'template' => $certificate1->get_id()]); 123 | 124 | // Sanity check. 125 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 126 | $this->assertEquals(0, $mod->automaticsend); 127 | 128 | // Try to create an existing issue file. 129 | external::update_automaticsend($mod->id, true); 130 | $this->assertEquals(0, $DB->get_field('coursecertificate', 'automaticsend', ['id' => $mod->id])); 131 | } 132 | } -------------------------------------------------------------------------------- /tests/issue_certificates_task_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit test for the task. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | /** 29 | * Unit test for the task. 30 | * 31 | * @package mod_coursecertificate 32 | * @category test 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_task_test_testcase extends advanced_testcase { 37 | /** 38 | * Set up 39 | */ 40 | public function setUp(): void { 41 | $this->resetAfterTest(); 42 | $this->setAdminUser(); 43 | } 44 | 45 | /** 46 | * Get certificate generator 47 | * 48 | * @return tool_certificate_generator 49 | */ 50 | protected function get_certificate_generator() : tool_certificate_generator { 51 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 52 | } 53 | 54 | /** 55 | * Test issue_certificates_task with automaticsend setting enabled. 56 | */ 57 | public function test_issue_certificates_task_automaticsend_enabled() { 58 | global $DB; 59 | 60 | // Create a course customfield. 61 | $catid = $this->getDataGenerator()->create_custom_field_category([])->get('id'); 62 | $field = $this->getDataGenerator()->create_custom_field(['categoryid' => $catid, 'type' => 'text', 'shortname' => 'f1']); 63 | 64 | // Create course, certificate template and coursecertificate module. 65 | $course = $this->getDataGenerator()->create_course(['shortname' => 'C01', 'customfield_f1' => 'some text']); 66 | 67 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 68 | $expirydate = strtotime('+5 day'); 69 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 70 | ['course' => $course->id, 'template' => $certificate1->get_id(), 'expires' => $expirydate]); 71 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 72 | 73 | // Create user with 'student' role. 74 | $user1 = $this->getDataGenerator()->create_and_enrol($course); 75 | // Create user with 'editingteacher' role. 76 | $user2 = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 77 | 78 | $mod->automaticsend = 1; 79 | $DB->update_record('coursecertificate', $mod); 80 | 81 | $task = new mod_coursecertificate\task\issue_certificates_task(); 82 | ob_start(); 83 | $task->execute(); 84 | ob_end_clean(); 85 | 86 | $issues = $DB->get_records('tool_certificate_issues', ['templateid' => $certificate1->get_id(), 87 | 'courseid' => $course->id]); 88 | 89 | // Check certificate issue was created for the user. 90 | $issue = reset($issues); 91 | $this->assertEquals($user1->id, $issue->userid); 92 | $this->assertEquals($expirydate, $issue->expires); 93 | $issuedata = @json_decode($issue->data, true); 94 | $this->assertEquals('C01', $issuedata['courseshortname']); 95 | $this->assertEquals('some text', $issuedata['coursecustomfield_' . $field->get('shortname')]); 96 | $this->assertEmpty($issuedata['coursegrade']); 97 | $this->assertEmpty($issuedata['coursecompletiondate']); 98 | 99 | // Check certificate issue was not created for the teacher. 100 | $adminissues = $DB->get_records('tool_certificate_issues', ['userid' => $user2->id]); 101 | $this->assertEmpty($adminissues); 102 | } 103 | 104 | /** 105 | * Test issue_certificates_task with automaticsend setting disabled. 106 | */ 107 | public function test_issue_certificates_task_automaticsend_disabled() { 108 | global $DB; 109 | 110 | // Create course, certificate template and coursecertificate module. 111 | $course = $this->getDataGenerator()->create_course(); 112 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 113 | $mod = $this->getDataGenerator()->create_module('coursecertificate', 114 | ['course' => $course->id, 'template' => $certificate1->get_id()]); 115 | 116 | // Create user with 'student' role. 117 | $this->getDataGenerator()->create_and_enrol($course); 118 | 119 | // Sanity check. 120 | $this->assertTrue($DB->record_exists('coursecertificate', ['course' => $course->id, 'id' => $mod->id])); 121 | $this->assertEquals(0, $mod->automaticsend); 122 | 123 | // Run the task. 124 | $task = new mod_coursecertificate\task\issue_certificates_task(); 125 | ob_start(); 126 | $task->execute(); 127 | ob_end_clean(); 128 | 129 | // Check no issues were created. 130 | $issues = $DB->get_records('tool_certificate_issues', ['templateid' => $certificate1->get_id(), 131 | 'courseid' => $course->id]); 132 | $this->assertEmpty($issues); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/helper_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for the helper. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | /** 29 | * Unit tests for the helper. 30 | * 31 | * @package mod_coursecertificate 32 | * @category test 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_helper_test_testcase extends advanced_testcase { 37 | /** 38 | * Set up 39 | */ 40 | public function setUp(): void { 41 | $this->resetAfterTest(); 42 | } 43 | 44 | /** 45 | * Get certificate generator 46 | * @return tool_certificate_generator 47 | */ 48 | protected function get_certificate_generator(): tool_certificate_generator { 49 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 50 | } 51 | 52 | /** 53 | * Test get users who meet access restrictions and had not been issued. 54 | */ 55 | public function test_get_users_to_issue() { 56 | // Create course. 57 | $course = $this->getDataGenerator()->create_course(); 58 | 59 | // Create and enrol users. 60 | $user1 = $this->getDataGenerator()->create_and_enrol($course); 61 | $user2 = $this->getDataGenerator()->create_and_enrol($course); 62 | 63 | // Create certificate template. 64 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 65 | 66 | // Create coursecertificate1 module without restrictions. 67 | $coursecertificate1 = $this->getDataGenerator()->create_module('coursecertificate', ['course' => $course->id, 68 | 'template' => $certificate1->get_id()]); 69 | $cm1 = get_fast_modinfo($course)->instances['coursecertificate'][$coursecertificate1->id]; 70 | 71 | // Check both users are retured. 72 | $users = \mod_coursecertificate\helper::get_users_to_issue($coursecertificate1, $cm1); 73 | $this->assertCount(2, $users); 74 | 75 | $certificate1->issue_certificate($user1->id, null, [], 'mod_coursecertificate', $course->id); 76 | 77 | // Check just user2 is returned (user1 was already issued). 78 | $users = \mod_coursecertificate\helper::get_users_to_issue($coursecertificate1, $cm1); 79 | $this->assertCount(1, $users); 80 | $this->assertEquals($users[0]->id, $user2->id); 81 | 82 | // Create coursecertificate2 module with data restriction in the future. 83 | $futuredate = strtotime('+1year'); 84 | $availabilityvalue = '{"op":"&","c":[{"type":"date","d":">=","t":' . $futuredate . '}],"showc":[true]}'; 85 | $coursecertificate2 = $this->getDataGenerator()->create_module('coursecertificate', ['course' => $course->id, 86 | 'template' => $certificate1->get_id(), 'availability' => $availabilityvalue]); 87 | $cm2 = get_fast_modinfo($course)->instances['coursecertificate'][$coursecertificate2->id]; 88 | 89 | // Check no user is returned. 90 | $users = \mod_coursecertificate\helper::get_users_to_issue($coursecertificate2, $cm2); 91 | $this->assertEmpty($users); 92 | } 93 | 94 | /** 95 | * Test get course issue data. 96 | */ 97 | public function test_get_issue_data() { 98 | // Create a course customfield. 99 | $catid = $this->getDataGenerator()->create_custom_field_category([])->get('id'); 100 | $field = $this->getDataGenerator()->create_custom_field(['categoryid' => $catid, 'type' => 'text', 'shortname' => 'f1']); 101 | 102 | // Create course with completion self enabled. 103 | $course = $this->getDataGenerator()->create_course(['shortname' => 'C01', 'fullname' => 'Course 01', 104 | 'enablecompletion' => COMPLETION_ENABLED, 'customfield_f1' => 'some text']); 105 | $criteriadata = new \stdClass(); 106 | $criteriadata->id = $course->id; 107 | $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF; 108 | 109 | /** @var \completion_criteria_self $criterion */ 110 | $criterion = \completion_criteria::factory(['criteriatype' => COMPLETION_CRITERIA_TYPE_SELF]); 111 | $criterion->update_config($criteriadata); 112 | 113 | // Create and enrol user. 114 | $user = $this->getDataGenerator()->create_and_enrol($course); 115 | 116 | // Set user grade to 10.00. 117 | $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]); 118 | $gradeitem2 = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $assign->id, 119 | 'courseid' => $course->id]); 120 | $gradeitem2->update_final_grade($user->id, 10, 'gradebook'); 121 | 122 | // Complete the course. 123 | $this->setUser($user); 124 | \core_completion_external::mark_course_self_completed($course->id); 125 | $ccompletion = new \completion_completion(['course' => $course->id, 'userid' => $user->id]); 126 | $ccompletion->mark_complete(); 127 | 128 | $issuedata = \mod_coursecertificate\helper::get_issue_data($course, $user); 129 | $this->assertEquals($course->id, $issuedata['courseid']); 130 | $this->assertEquals('C01', $issuedata['courseshortname']); 131 | $this->assertEquals('Course 01', $issuedata['coursefullname']); 132 | $this->assertEquals(course_get_url($course)->out(), $issuedata['courseurl']); 133 | $this->assertEquals('some text', $issuedata['coursecustomfield_f1']); 134 | $coursecompletiondate = userdate($ccompletion->timecompleted, get_string('strftimedatefullshort')); 135 | $this->assertEquals($coursecompletiondate, $issuedata['coursecompletiondate']); 136 | $this->assertEquals('10.00', $issuedata['coursegrade']); 137 | } 138 | } -------------------------------------------------------------------------------- /lang/en/coursecertificate.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Plugin strings are defined here. 19 | * 20 | * @package mod_coursecertificate 21 | * @category string 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die(); 27 | 28 | $string['activityhiddenwarning'] = 'This activity is currently hidden. By making it visible, students who meet the activity access restrictions will automatically receive a PDF copy of the certificate.'; 29 | $string['automaticsend_helptitle'] = "Help with automatic sending"; 30 | $string['automaticsenddisabled'] = 'The automatic sending of this certificate is disabled.'; 31 | $string['automaticsenddisabled_help'] = 'By leaving this disabled, students must click on the activity link displayed on the course page to receive the certificate, once they meet this activity\'s access restrictions.

32 | By enabling it, students will automatically receive a PDF copy of the certificate once they meet this activity\'s access restrictions. Note that all students that already meet this activity\'s access restrictions will receive the certificate when enabling this.'; 33 | $string['automaticsenddisabledalert'] = 'Students who meet this activity\'s access restrictions will be issued with their certificate once they access it.'; 34 | $string['automaticsenddisabledinfo'] = 'Currently, {$a} students meet this activity\'s access restrictions and will be issued with their certificate once they access it.'; 35 | $string['automaticsendenabled'] = 'The automatic sending of this certificate is enabled.'; 36 | $string['automaticsendenabled_help'] = 'By leaving this enabled, students will automatically receive a PDF copy of the certificate once they meet this activity\'s access restrictions.

37 | By disabling it, students will need to click on the activity link displayed on the course page to receive the certificate, once they meet this activity\'s access restrictions.'; 38 | $string['certificateissues'] = 'Certificate issues'; 39 | $string['certifiedusers'] = 'Certified users'; 40 | $string['chooseatemplate'] = 'Choose a template...'; 41 | $string['code'] = 'Code'; 42 | $string['coursecertificate:addinstance'] = 'Add a new Course certificate activity'; 43 | $string['coursecertificate:receive'] = 'Receive issued certificates'; 44 | $string['coursecertificate:view'] = 'View Course certificate'; 45 | $string['coursecertificate:viewreport'] = 'View Course certificate issues report'; 46 | $string['coursecompletiondate'] = 'Completion date'; 47 | $string['courseinternalid'] = 'Internal course ID used in URLs'; 48 | $string['courseurl'] = 'Course URL'; 49 | $string['disableautomaticsend'] = 'Students will no longer automatically receive a PDF copy of the certificate as soon as they meet 50 | this activity\'s access restrictions. Instead, they will need to click on the activity link displayed on the course page to receive 51 | the certificate, once they meet this activity\'s access restrictions.'; 52 | $string['enableautomaticsend'] = 'All students will automatically receive a PDF copy of the certificate as soon as they meet this activity\'s access restrictions.

53 | Currently, {$a} students already meet these access restrictions but haven\'t accessed this activity yet. They will immediately receive their copy as well.

54 | Students who have already accessed this activity will not receive the certificate again.'; 55 | $string['enableautomaticsendpopup'] = 'All students will automatically receive a PDF copy of the certificate as soon as they meet this activity\'s access restrictions.

56 | Students who already meet these access restrictions but haven\'t accessed this activity yet will immediately receive their copy as well.

57 | Students who have already accessed this activity will not receive the certificate again.'; 58 | $string['expirydate'] = 'Expiry date'; 59 | $string['issueddate'] = 'Date issued'; 60 | $string['managetemplates'] = 'Manage certificate templates'; 61 | $string['modulename'] = 'Course certificate'; 62 | $string['modulename_help'] = 'The course certificate module provides an opportunity for learners to celebrate achievements by 63 | obtaining certificates.

It allows you to choose from different certificate templates which will automatically display user data 64 | such as full name, course, etc.

Users will be able to download a PDF copy of the certificate themselves by accessing this 65 | activity, and there are options to send a PDF copy to them by email automatically.

If the template used on this activity contains 66 | a QR code, users will be able to scan it to validate their certificates.'; 67 | $string['modulename_link'] = 'mod/certificate/view'; 68 | $string['modulenameplural'] = 'Course certificates'; 69 | $string['notemplateselected'] = 'The selected template can’t be found. Please go to the activity settings and select a new one.'; 70 | $string['notemplateselecteduser'] = 'The certificate is not available. Please contact the course administrator.'; 71 | $string['notemplateswarning'] = 'There are no available templates. Please contact the site administrator.'; 72 | $string['notemplateswarningwithlink'] = 'There are no available templates. Please go to certificate template management page and create a new one.'; 73 | $string['nouserscertified'] = 'No users are certified.'; 74 | $string['page-mod-coursecertificate-x'] = 'Any course certificate module page'; 75 | $string['pluginadministration'] = 'Course certificate administration'; 76 | $string['pluginname'] = 'Course certificate'; 77 | $string['previewcoursefullname'] = 'Course full name'; 78 | $string['previewcourseshortname'] = 'Course short name'; 79 | $string['privacy:metadata'] = 'The course certificate activity does not store personal data.'; 80 | $string['revoke'] = 'Revoke'; 81 | $string['revokeissue'] = 'Are you sure you want to revoke this certificate issue from this user?'; 82 | $string['selectdate'] = 'Select date'; 83 | $string['selecttemplatewarning'] = 'Once this activity issues at least one certificate, this field will be locked and will no longer be editable.'; 84 | $string['status'] = 'Status'; 85 | $string['taskissuecertificates'] = 'Issue course certificates'; 86 | $string['template'] = 'Template'; 87 | -------------------------------------------------------------------------------- /tests/permission_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for permission class. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | defined('MOODLE_INTERNAL') || die; 27 | 28 | /** 29 | * Unit tests for permission class. 30 | * 31 | * @package mod_coursecertificate 32 | * @category test 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_permission_test_testcase extends advanced_testcase { 37 | /** 38 | * Set up 39 | */ 40 | public function setUp(): void { 41 | $this->resetAfterTest(); 42 | } 43 | 44 | /** 45 | * Test can_view_report. 46 | */ 47 | public function test_can_view_report() { 48 | $course = $this->getDataGenerator()->create_course(); 49 | $user = $this->getDataGenerator()->create_and_enrol($course); 50 | $this->setUser($user); 51 | 52 | // User without view report capabilities. 53 | $this->assertFalse(has_capability('mod/coursecertificate:viewreport', context_course::instance($course->id))); 54 | $this->assertFalse(\mod_coursecertificate\permission::can_view_report(context_course::instance($course->id))); 55 | 56 | // Enrol user as teacher (with view report capabilities). 57 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher'); 58 | $this->assertTrue(has_capability('mod/coursecertificate:viewreport', context_course::instance($course->id))); 59 | $this->assertTrue(\mod_coursecertificate\permission::can_view_report(context_course::instance($course->id))); 60 | } 61 | 62 | /** 63 | * Test can_verify_issues. 64 | */ 65 | public function test_can_verify_issues() { 66 | $course = $this->getDataGenerator()->create_course(); 67 | $user = $this->getDataGenerator()->create_and_enrol($course); 68 | $this->setUser($user); 69 | 70 | // Every user can verify. 71 | $this->assertTrue(has_capability('tool/certificate:verify', context_system::instance())); 72 | $this->assertTrue(\mod_coursecertificate\permission::can_verify_issues()); 73 | } 74 | 75 | /** 76 | * Test can_revoke_issues. 77 | */ 78 | public function test_can_revoke_issues() { 79 | $course = $this->getDataGenerator()->create_course(); 80 | $user = $this->getDataGenerator()->create_and_enrol($course); 81 | $this->setUser($user); 82 | 83 | // User without issue capabilities. 84 | $this->assertFalse(has_capability('tool/certificate:issue', context_course::instance($course->id))); 85 | $this->assertFalse(\mod_coursecertificate\permission::can_revoke_issues($course->id)); 86 | 87 | // Enrol user as student (without issue capabilities). 88 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 89 | $this->assertFalse(has_capability('tool/certificate:issue', context_course::instance($course->id))); 90 | $this->assertFalse(\mod_coursecertificate\permission::can_revoke_issues($course->id)); 91 | 92 | // Enrol user as editingteacher (with issue capabilities). 93 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher'); 94 | $this->assertTrue(has_capability('tool/certificate:issue', context_course::instance($course->id))); 95 | $this->assertTrue(\mod_coursecertificate\permission::can_revoke_issues($course->id)); 96 | } 97 | 98 | /** 99 | * Test can_view_all_issues 100 | */ 101 | public function test_can_view_all_issues() { 102 | $course = $this->getDataGenerator()->create_course(); 103 | $user = $this->getDataGenerator()->create_and_enrol($course); 104 | $this->setUser($user); 105 | 106 | // User without view all certificates capabilities. 107 | $this->assertFalse(has_capability('tool/certificate:viewallcertificates', context_course::instance($course->id))); 108 | $this->assertFalse(\mod_coursecertificate\permission::can_view_all_issues($course->id)); 109 | 110 | // Enrol user as student (without view all certificates capabilities). 111 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 112 | $this->assertFalse(has_capability('tool/certificate:viewallcertificates', context_course::instance($course->id))); 113 | $this->assertFalse(\mod_coursecertificate\permission::can_view_all_issues($course->id)); 114 | 115 | // Enrol user as editingteacher (with view all certificates capabilities). 116 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher'); 117 | $this->assertTrue(has_capability('tool/certificate:viewallcertificates', context_course::instance($course->id))); 118 | $this->assertTrue(\mod_coursecertificate\permission::can_view_all_issues($course->id)); 119 | } 120 | 121 | /** 122 | * Test can_receive_issues. 123 | */ 124 | public function test_can_receive_issues() { 125 | $course = $this->getDataGenerator()->create_course(); 126 | $user = $this->getDataGenerator()->create_user(); 127 | $this->setUser($user); 128 | 129 | $this->assertFalse(has_capability('mod/coursecertificate:receive', context_course::instance($course->id))); 130 | $this->assertFalse(\mod_coursecertificate\permission::can_receive_issues(context_course::instance($course->id))); 131 | 132 | // Enrol user as editingteacher (without receive issue capabilities). 133 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); 134 | $this->assertFalse(has_capability('mod/coursecertificate:receive', context_course::instance($course->id))); 135 | $this->assertFalse(\mod_coursecertificate\permission::can_receive_issues(context_course::instance($course->id))); 136 | 137 | // Enrol user as student (with receive issue capabilities). 138 | $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); 139 | $this->assertTrue(has_capability('mod/coursecertificate:receive', context_course::instance($course->id))); 140 | $this->assertTrue(\mod_coursecertificate\permission::can_receive_issues(context_course::instance($course->id))); 141 | } 142 | } -------------------------------------------------------------------------------- /lib.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Library of interface functions and constants. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | /** 28 | * Checks if certificate activity supports a specific feature. 29 | * 30 | * @uses FEATURE_GROUPS 31 | * @uses FEATURE_GROUPINGS 32 | * @uses FEATURE_MOD_INTRO 33 | * @uses FEATURE_SHOW_DESCRIPTION 34 | * @uses FEATURE_COMPLETION_TRACKS_VIEWS 35 | * @uses FEATURE_COMPLETION_HAS_RULES 36 | * @uses FEATURE_MODEDIT_DEFAULT_COMPLETION 37 | * @uses FEATURE_BACKUP_MOODLE2 38 | * @param string $feature FEATURE_xx constant for requested feature 39 | * @return mixed True if module supports feature, false if not, null if doesn't know 40 | */ 41 | function coursecertificate_supports(string $feature): ?bool { 42 | switch($feature) { 43 | case FEATURE_GROUPS: 44 | return true; 45 | case FEATURE_GROUPINGS: 46 | return true; 47 | case FEATURE_MOD_INTRO: 48 | return true; 49 | case FEATURE_SHOW_DESCRIPTION: 50 | return true; 51 | case FEATURE_COMPLETION_TRACKS_VIEWS: 52 | return true; 53 | case FEATURE_MODEDIT_DEFAULT_COMPLETION: 54 | return true; 55 | case FEATURE_BACKUP_MOODLE2: 56 | return true; 57 | default: 58 | return null; 59 | } 60 | } 61 | 62 | /** 63 | * Saves a new instance of the mod_coursecertificate into the database. 64 | * 65 | * Given an object containing all the necessary data, (defined by the form 66 | * in mod_form.php) this function will create a new instance and return the id 67 | * number of the instance. 68 | * 69 | * @param stdClass $data An object from the form. 70 | * @param mod_coursecertificate_mod_form $mform The form. 71 | * @return int The id of the newly inserted record. 72 | */ 73 | function coursecertificate_add_instance(stdClass $data, mod_coursecertificate_mod_form $mform = null): int { 74 | global $DB; 75 | 76 | $data->timecreated = time(); 77 | $cmid = $data->coursemodule; 78 | 79 | $data->id = $DB->insert_record('coursecertificate', $data); 80 | 81 | // We need to use context now, so we need to make sure all needed info is already in db. 82 | $DB->set_field('course_modules', 'instance', $data->id, ['id' => $cmid]); 83 | 84 | return $data->id; 85 | } 86 | 87 | /** 88 | * Updates an instance of the mod_coursecertificate in the database. 89 | * 90 | * Given an object containing all the necessary data (defined in mod_form.php), 91 | * this function will update an existing instance with new data. 92 | * 93 | * @param stdClass $data An object from the form in mod_form.php. 94 | * @param mod_coursecertificate_mod_form $mform The form. 95 | * @return bool True if successful, false otherwise. 96 | */ 97 | function coursecertificate_update_instance(stdClass $data, mod_coursecertificate_mod_form $mform = null): bool { 98 | global $DB; 99 | 100 | $data->timemodified = time(); 101 | $data->id = $data->instance; 102 | 103 | return $DB->update_record('coursecertificate', $data); 104 | } 105 | 106 | /** 107 | * Removes an instance of the mod_coursecertificate from the database. 108 | * 109 | * @param int $id Id of the module instance. 110 | * @return bool True if successful, false on failure. 111 | */ 112 | function coursecertificate_delete_instance(int $id): bool { 113 | global $DB; 114 | 115 | $activity = $DB->get_record('coursecertificate', ['id' => $id]); 116 | if (!$activity) { 117 | return false; 118 | } 119 | 120 | $DB->delete_records('coursecertificate', ['id' => $id]); 121 | 122 | return true; 123 | } 124 | 125 | /** 126 | * Return a list of page types 127 | * 128 | * @param string $pagetype current page type 129 | * @param stdClass $parentcontext Block's parent context 130 | * @param stdClass $currentcontext Current context of block 131 | * @return array array of page types and it's names 132 | */ 133 | function coursecertificate_page_type_list($pagetype, $parentcontext, $currentcontext): array { 134 | $modulepagetype = [ 135 | 'mod-coursecertificate-*' => get_string('page-mod-coursecertificate-x', 'mod_coursecertificate'), 136 | ]; 137 | return $modulepagetype; 138 | } 139 | 140 | /** 141 | * Callback for tool_certificate - the fields available for the certificates 142 | */ 143 | function mod_coursecertificate_tool_certificate_fields() { 144 | global $CFG; 145 | 146 | if (!class_exists('tool_certificate\customfield\issue_handler')) { 147 | return; 148 | } 149 | 150 | $handler = tool_certificate\customfield\issue_handler::create(); 151 | 152 | // TODO: the only currently supported field types are text/textarea (numeric will fallback to text). 153 | $handler->ensure_field_exists('courseid', 'numeric', 154 | get_string('courseinternalid', 'mod_coursecertificate'), false, 1); 155 | $handler->ensure_field_exists('courseshortname', 'text', get_string('shortnamecourse'), 156 | true, get_string('previewcourseshortname', 'mod_coursecertificate')); 157 | $handler->ensure_field_exists('coursefullname', 'text', get_string('fullnamecourse'), 158 | true, get_string('previewcoursefullname', 'mod_coursecertificate')); 159 | $handler->ensure_field_exists('courseurl', 'text', 160 | get_string('courseurl', 'mod_coursecertificate'), 161 | true, $CFG->wwwroot . '/course/view.php?id=1'); 162 | $handler->ensure_field_exists( 163 | 'coursecompletiondate', 164 | 'text', 165 | get_string('course') . ': ' . get_string('coursecompletiondate', 'mod_coursecertificate'), 166 | true, 167 | get_string('coursecompletiondate', 'mod_coursecertificate') 168 | ); 169 | $handler->ensure_field_exists( 170 | 'coursegrade', 171 | 'text', 172 | get_string('course') . ': ' . get_string('grade'), 173 | true, 174 | get_string('grade') 175 | ); 176 | 177 | // Get the course custom fields (note the only supported field types are text/textarea). 178 | $coursehandler = \core_course\customfield\course_handler::create(); 179 | foreach ($coursehandler->get_fields() as $field) { 180 | $handler->ensure_field_exists( 181 | 'coursecustomfield_' . $field->get('shortname'), 182 | $field->get('type'), 183 | get_string('course') . ': ' . $field->get_formatted_name(), 184 | true, 185 | $field->get_formatted_name() 186 | ); 187 | } 188 | } -------------------------------------------------------------------------------- /amd/src/manager.js: -------------------------------------------------------------------------------- 1 | // This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/ 2 | // 3 | // Moodle is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // Moodle is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with Moodle. If not, see . 15 | 16 | /** 17 | * This module instantiates the functionality for actions on course certificates. 18 | * 19 | * @module mod_coursecertificate/manager 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | define([ 26 | 'core/ajax', 27 | 'core/notification', 28 | 'core/templates', 29 | 'core/str' 30 | ], function( 31 | Ajax, 32 | Notification, 33 | Templates, 34 | Str 35 | ) { 36 | 37 | /** @type {Object} The list of selectors for the coursecertificate module. */ 38 | const SELECTORS = { 39 | AUTOMATICSENDREGION: "[data-region='automaticsend-alert']", 40 | HIDDENWARNING: ".hidden-warning", 41 | NOAUTOSENDINFO: ".noautosend-info", 42 | REPORTREGION: "[data-region='issues-report']", 43 | TOGGLEAUTOMATICSEND: "[data-action='toggle-automaticsend']", 44 | REVOKEISSUE: "[data-action='revoke-issue']", 45 | LOADING: ".loading-overlay" 46 | }, 47 | /** @type {Object} The list of templates for the coursecertificate module. */ 48 | TEMPLATES = { 49 | AUTOMATICSENDALERT: 'mod_coursecertificate/automaticsend_alert', 50 | ISSUESREPORT: 'mod_coursecertificate/issues_report' 51 | }, 52 | /** @type {Object} The list of services for the coursecertificate module. */ 53 | SERVICES = { 54 | UPDATEAUTOMATICSEND: 'mod_coursecertificate_update_automaticsend', 55 | REVOKEISSUE: 'tool_certificate_revoke_issue', 56 | }; 57 | 58 | /** 59 | * Show/Hide selector. 60 | * 61 | * @param {string} selector 62 | * @param {boolean} visibile 63 | */ 64 | function setVisibility(selector, visibile) { 65 | if (visibile) { 66 | document.querySelector(selector).classList.remove('d-none'); 67 | document.querySelector(selector).classList.remove('invisible'); 68 | } else { 69 | document.querySelector(selector).classList.add('d-none'); 70 | document.querySelector(selector).classList.add('invisible'); 71 | } 72 | } 73 | 74 | /** 75 | * Toggle the automaticsend setting on/off for coursecertificate. 76 | * 77 | * @param {Element} automaticsendregion 78 | */ 79 | function toggleAutomaticSend(automaticsendregion) { 80 | const {certificateid, automaticsend} = 81 | automaticsendregion.querySelector(SELECTORS.TOGGLEAUTOMATICSEND).dataset; 82 | const newstatus = automaticsend === '0'; 83 | const strings = newstatus 84 | // Load strings depending on newstatus. 85 | ? [{'key': 'confirmation', component: 'admin'}, 86 | {'key': 'enableautomaticsendpopup', component: 'coursecertificate'}, 87 | {'key': 'confirm'}, 88 | {'key': 'cancel'}] 89 | : [{'key': 'confirmation', component: 'admin'}, 90 | {'key': 'disableautomaticsend', component: 'coursecertificate'}, 91 | {'key': 'confirm'}, 92 | {'key': 'cancel'}]; 93 | Str.get_strings(strings).then((s) => { 94 | // Show confirm notification. 95 | Notification.confirm(s[0], s[1], s[2], s[3], () => { 96 | M.util.js_pending('mod_coursecertificate_toggle_automaticsend'); 97 | // Show loading template. 98 | setVisibility(SELECTORS.LOADING, true); 99 | // Call to webservice. 100 | Ajax.call([{methodname: SERVICES.UPDATEAUTOMATICSEND, 101 | args: {id: certificateid, automaticsend: newstatus}}])[0] 102 | // Reload automatic send alert template. 103 | .then((result) => { 104 | let {showhiddenwarning, shownoautosendinfo} = result; 105 | Templates.render(TEMPLATES.AUTOMATICSENDALERT, 106 | {certificateid: certificateid, automaticsend: newstatus}, '') 107 | .then((html) => { 108 | automaticsendregion.innerHTML = html; 109 | setVisibility(SELECTORS.HIDDENWARNING, showhiddenwarning); 110 | setVisibility(SELECTORS.NOAUTOSENDINFO, shownoautosendinfo); 111 | M.util.js_complete('mod_coursecertificate_toggle_automaticsend'); 112 | return null; 113 | }) 114 | .fail(Notification.exception); 115 | return null; 116 | }) 117 | .fail(Notification.exception); 118 | }); 119 | return null; 120 | }).fail(Notification.exception); 121 | } 122 | 123 | /** 124 | * Revoke the issue. 125 | * 126 | * @param {int} issueid 127 | */ 128 | function revokeIssue(issueid) { 129 | const strings = [{'key': 'confirmation', component: 'admin'}, 130 | {'key': 'revokeissue', component: 'coursecertificate'}, 131 | {'key': 'confirm'}, 132 | {'key': 'cancel'}]; 133 | Str.get_strings(strings).then((s) => { 134 | // Show confirm notification. 135 | Notification.confirm(s[0], s[1], s[2], s[3], () => { 136 | M.util.js_pending('mod_coursecertificate_revoke_issue'); 137 | // Call to webservice to revoke issue. 138 | Ajax.call([{methodname: SERVICES.REVOKEISSUE, args: {id: issueid}}])[0] 139 | // Call to webservice to get updated table. 140 | .then(() => { 141 | M.util.js_complete('mod_coursecertificate_revoke_issue'); 142 | window.location.reload(); 143 | return null; 144 | }) 145 | .fail(Notification.exception); 146 | }); 147 | return null; 148 | }).fail(Notification.exception); 149 | } 150 | 151 | return { 152 | init: function() { 153 | const automaticsendregion = document.querySelector(SELECTORS.AUTOMATICSENDREGION); 154 | if (automaticsendregion) { 155 | automaticsendregion.addEventListener('click', (e) => { 156 | if (e.target && e.target.closest(SELECTORS.TOGGLEAUTOMATICSEND)) { 157 | e.preventDefault(); 158 | toggleAutomaticSend(automaticsendregion); 159 | } 160 | }); 161 | } 162 | const reportregion = document.querySelector(SELECTORS.REPORTREGION); 163 | if (reportregion) { 164 | reportregion.addEventListener('click', (e) => { 165 | const target = e.target && e.target.closest(SELECTORS.REVOKEISSUE); 166 | if (target) { 167 | e.preventDefault(); 168 | const {issueid} = target.dataset; 169 | revokeIssue(issueid); 170 | } 171 | }); 172 | } 173 | } 174 | }; 175 | }); -------------------------------------------------------------------------------- /tests/restore_test.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Unit tests for restore. 19 | * 20 | * @package mod_coursecertificate 21 | * @category test 22 | * @copyright 2020 Mikel Martín 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | 27 | defined('MOODLE_INTERNAL') || die(); 28 | 29 | global $CFG; 30 | require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php"); 31 | 32 | /** 33 | * Unit tests for restore. 34 | * 35 | * @package mod_coursecertificate 36 | * @category test 37 | * @copyright 2020 Mikel Martín 38 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 | */ 40 | class mod_coursecertificate_restore_testcase extends restore_date_testcase { 41 | /** 42 | * Set up 43 | */ 44 | public function setUp(): void { 45 | $this->resetAfterTest(); 46 | $this->setAdminUser(); 47 | } 48 | 49 | /** 50 | * Get certificate generator 51 | * 52 | * @return tool_certificate_generator 53 | */ 54 | protected function get_certificate_generator() : tool_certificate_generator { 55 | return $this->getDataGenerator()->get_plugin_generator('tool_certificate'); 56 | } 57 | 58 | /** 59 | * Backs a course up. 60 | * 61 | * @param stdClass $course Course object to backup 62 | */ 63 | protected function backup($course) { 64 | global $USER, $CFG; 65 | 66 | // Turn off file logging, otherwise it can't delete the file (Windows). 67 | $CFG->backup_file_logger_level = backup::LOG_NONE; 68 | 69 | // Do backup with default settings. 70 | set_config('backup_general_users', 1, 'backup'); 71 | $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, 72 | backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 73 | $USER->id); 74 | $bc->execute_plan(); 75 | $results = $bc->get_results(); 76 | $file = $results['backup_destination']; 77 | $fp = get_file_packer('application/vnd.moodle.backup'); 78 | $filepath = $CFG->dataroot . '/temp/backup/test-restore-course'; 79 | $file->extract_to_pathname($fp, $filepath); 80 | $bc->destroy(); 81 | } 82 | 83 | /** 84 | * Restores a course. 85 | * 86 | * @param stdClass $course Course object to restore 87 | * @return int ID of newly restored course 88 | */ 89 | protected function restore($course) { 90 | global $USER; 91 | // Do restore to new course with default settings. 92 | $newcourseid = restore_dbops::create_new_course( 93 | $course->fullname, $course->shortname . '_2', $course->category); 94 | $rc = new restore_controller('test-restore-course', $newcourseid, 95 | backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 96 | backup::TARGET_NEW_COURSE); 97 | 98 | $newdate = $this->restorestartdate; 99 | 100 | $rc->get_plan()->get_setting('course_startdate')->set_value($newdate); 101 | $this->assertTrue($rc->execute_precheck()); 102 | $rc->execute_plan(); 103 | $rc->destroy(); 104 | 105 | return $newcourseid; 106 | } 107 | 108 | /** 109 | * Test restore with existing template and existing issue with same code 110 | */ 111 | public function test_restore_without_issues() { 112 | global $DB; 113 | 114 | // Create course and coursecertificate module. 115 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 116 | [$course, $coursecertificate] = $this->create_course_and_module('coursecertificate', 117 | ['template' => $certificate1->get_id()]); 118 | 119 | // Create user with 'student' role and issue a certificate. 120 | $user = $this->getDataGenerator()->create_and_enrol($course); 121 | $issueid = $certificate1->issue_certificate($user->id, null, [], 'mod_coursecertificate', $course->id); 122 | $DB->get_record('tool_certificate_issues', ['id' => $issueid]); 123 | 124 | // Do backup and restore. 125 | $newcourseid = $this->backup_and_restore($course); 126 | $newcoursecertificate = $DB->get_record('coursecertificate', ['course' => $newcourseid]); 127 | 128 | // Check new coursecertificate data. 129 | $this->assertFieldsNotRolledForward($coursecertificate, $newcoursecertificate, ['timecreated', 'timemodified']); 130 | $this->assertEquals($coursecertificate->name, $newcoursecertificate->name); 131 | $this->assertEquals($coursecertificate->automaticsend, $newcoursecertificate->automaticsend); 132 | 133 | // Check new issue is not generated. 134 | $newissue = $DB->get_record('tool_certificate_issues', ['courseid' => $newcourseid, 'userid' => $user->id, 135 | 'templateid' => $certificate1->get_id()], '*', IGNORE_MISSING); 136 | $this->assertEmpty($newissue); 137 | } 138 | 139 | /** 140 | * Test restore with existing template and non-existing issue with same code. 141 | */ 142 | public function test_restore_with_issues() { 143 | global $DB; 144 | 145 | // Create course and coursecertificate module. 146 | $certificate1 = $this->get_certificate_generator()->create_template((object)['name' => 'Certificate 1']); 147 | [$course, $coursecertificate] = $this->create_course_and_module('coursecertificate', 148 | ['template' => $certificate1->get_id()]); 149 | 150 | // Create user with 'student' role and issue a certificate. 151 | $user = $this->getDataGenerator()->create_and_enrol($course); 152 | $issueid = $certificate1->issue_certificate($user->id, null, [], 'mod_coursecertificate', $course->id); 153 | $issue = $DB->get_record('tool_certificate_issues', ['id' => $issueid]); 154 | 155 | $fs = get_file_storage(); 156 | $files = $fs->get_area_files(context_system::instance()->id, 'tool_certificate', 'issues', 157 | $issue->id, 'itemid', false); 158 | $issuefile = reset($files); 159 | 160 | // Do backup. 161 | $this->backup($course); 162 | 163 | // Delete issue. 164 | $DB->delete_records('tool_certificate_issues', ['id' => $issue->id]); 165 | 166 | // Do restore. 167 | $newcourseid = $this->restore($course); 168 | $newcoursecertificate = $DB->get_record('coursecertificate', ['course' => $newcourseid]); 169 | 170 | // Check new coursecertificate data. 171 | $this->assertFieldsNotRolledForward($coursecertificate, $newcoursecertificate, ['timecreated', 'timemodified']); 172 | $this->assertEquals($coursecertificate->name, $newcoursecertificate->name); 173 | $this->assertEquals($coursecertificate->automaticsend, $newcoursecertificate->automaticsend); 174 | 175 | // Check new issue is generated. 176 | $newissue = $DB->get_record('tool_certificate_issues', ['courseid' => $newcourseid, 'userid' => $user->id, 177 | 'templateid' => $certificate1->get_id()], '*', IGNORE_MISSING); 178 | $this->assertEquals($issue->data, $newissue->data); 179 | 180 | $files = $fs->get_area_files(context_system::instance()->id, 'tool_certificate', 'issues', 181 | $newissue->id, 'itemid', false); 182 | $newissuefile = reset($files); 183 | $this->assertEquals($issuefile->get_contenthash(), $newissuefile->get_contenthash()); 184 | } 185 | } -------------------------------------------------------------------------------- /classes/output/view_page.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Certificate issues report renderable. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate\output; 26 | 27 | use cm_info; 28 | use completion_info; 29 | use context_module; 30 | use mod_coursecertificate\helper; 31 | use mod_coursecertificate\permission; 32 | use moodle_url; 33 | use templatable; 34 | use renderable; 35 | 36 | defined('MOODLE_INTERNAL') || die(); 37 | 38 | /** 39 | * Certificate issues report renderable class. 40 | * 41 | * @package mod_coursecertificate 42 | * @copyright 2020 Mikel Martín 43 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 | */ 45 | class view_page implements templatable, renderable { 46 | 47 | /** @var \stdClass $certificate */ 48 | protected $certificate; 49 | 50 | /** @var int $perpage */ 51 | protected $perpage; 52 | 53 | /** @var certificate_issues_table $table */ 54 | protected $table; 55 | 56 | /** @var bool $canmanage */ 57 | protected $canmanage; 58 | 59 | /** @var bool $canviewreport */ 60 | protected $canviewreport; 61 | 62 | /** @var bool $canreceiveissues */ 63 | protected $canreceiveissues; 64 | 65 | /** @var bool */ 66 | private $canviewall; 67 | 68 | /** @var moodle_url $pageurl */ 69 | protected $pageurl; 70 | 71 | /** @var cm_info $cm */ 72 | protected $cm; 73 | 74 | /** 75 | * Constructor. 76 | * 77 | * @param int $id 78 | * @param int $page 79 | * @param int $perpage 80 | * @param \stdClass $course 81 | * @param cm_info $cm 82 | */ 83 | public function __construct(int $id, int $page, int $perpage, \stdClass $course, cm_info $cm) { 84 | global $DB, $PAGE, $USER; 85 | 86 | $this->perpage = $perpage; 87 | $this->cm = $cm; 88 | $this->pageurl = new moodle_url('/mod/coursecertificate/view.php', ['id' => $id, 89 | 'page' => $page, 'perpage' => $perpage]); 90 | 91 | $context = context_module::instance($this->cm->id); 92 | $this->certificate = $DB->get_record('coursecertificate', ['id' => $this->cm->instance], '*', MUST_EXIST); 93 | $this->canviewreport = permission::can_view_report($context); 94 | $this->canmanage = permission::can_manage($context); 95 | $this->canviewall = permission::can_view_all_issues($course->id); 96 | $this->canreceiveissues = permission::can_receive_issues($context); 97 | 98 | // Trigger the event. 99 | $event = \mod_coursecertificate\event\course_module_viewed::create([ 100 | 'objectid' => $this->certificate->id, 101 | 'context' => $context 102 | ]); 103 | $event->add_record_snapshot('course', $course); 104 | $event->add_record_snapshot('coursecertificate', $this->certificate); 105 | $event->trigger(); 106 | 107 | // Update the completion. 108 | $completion = new completion_info($course); 109 | $completion->set_module_viewed($this->cm); 110 | 111 | // Get the current group. 112 | if (groups_get_activity_groupmode($this->cm)) { 113 | $groupid = groups_get_activity_group($this->cm, true); 114 | } 115 | 116 | // View certificate issue PDF if user can not manage, can receive issues and activity template is correct. 117 | if (!$this->canviewall && $this->canreceiveissues && $this->certificate->template != 0) { 118 | // View certificate PDF only if activity has a template. 119 | $params = ['id' => $this->certificate->template]; 120 | $templaterecord = $DB->get_record('tool_certificate_templates', $params, '*', MUST_EXIST); 121 | if ($templaterecord) { 122 | $issuesqlconditions = [ 123 | 'userid' => $USER->id, 124 | 'templateid' => $templaterecord->id, 125 | 'courseid' => $course->id, 126 | 'component' => 'mod_coursecertificate' 127 | ]; 128 | // If user does not have an issue yet, create it first. 129 | $issuedata = helper::get_issue_data($course, $USER); 130 | if (!$DB->record_exists('tool_certificate_issues', $issuesqlconditions)) { 131 | \tool_certificate\template::instance($templaterecord->id)->issue_certificate( 132 | $USER->id, 133 | $this->certificate->expires, 134 | $issuedata, 135 | 'mod_coursecertificate', 136 | $course->id 137 | ); 138 | } 139 | // Redirect to view issue page. 140 | if ($issue = $DB->get_record('tool_certificate_issues', $issuesqlconditions, '*', MUST_EXIST)) { 141 | $showissueurl = new \moodle_url('/admin/tool/certificate/view.php', 142 | ['code' => $issue->code]); 143 | redirect($showissueurl); 144 | } 145 | } 146 | } 147 | 148 | // Show issues table. 149 | if ($this->canviewreport) { 150 | $this->table = new certificate_issues_table($this->certificate, $this->cm, $groupid ?? null); 151 | $this->table->define_baseurl($this->pageurl); 152 | 153 | if ($this->table->is_downloading()) { 154 | $this->table->download(); 155 | exit(); 156 | } 157 | } 158 | 159 | $PAGE->set_url('/mod/coursecertificate/view.php', ['id' => $this->cm->id]); 160 | $PAGE->set_title(format_string($this->certificate->name)); 161 | $PAGE->set_heading(format_string($course->fullname)); 162 | $PAGE->set_context($context); 163 | } 164 | 165 | /** 166 | * Function to export the renderer data in a format that is suitable for a mustache template. 167 | * 168 | * @param \renderer_base $output Used to do a final render of any components that need to be rendered for export. 169 | * @return \stdClass|array 170 | */ 171 | public function export_for_template(\renderer_base $output) { 172 | $data = []; 173 | $data['certificateid'] = $this->certificate->id; 174 | $data['certificatename'] = $this->certificate->name; 175 | $data['automaticsend'] = $this->certificate->automaticsend; 176 | if (isset($this->table)) { 177 | $data['table'] = $this->render_table($this->table); 178 | } 179 | $data['showautomaticsend'] = $this->canmanage; 180 | $data['showreport'] = $this->canviewreport; 181 | $data['notemplateselected'] = $this->certificate->template == 0; 182 | $data['studentview'] = !$this->canviewall && $this->canreceiveissues; 183 | $data['showhiddenwarning'] = $this->certificate->automaticsend && !$this->cm->visible; 184 | $data['shownoautosendinfo'] = !$this->certificate->automaticsend && $this->cm->visible; 185 | 186 | return $data; 187 | } 188 | 189 | /** 190 | * Renders a table. 191 | * 192 | * @param \table_sql $table 193 | * @return string HTML 194 | */ 195 | private function render_table(\table_sql $table) { 196 | ob_start(); 197 | groups_print_activity_menu($this->cm, $this->pageurl); 198 | $table->out($this->perpage, false); 199 | $output = ob_get_contents(); 200 | ob_end_clean(); 201 | 202 | return $output; 203 | } 204 | } -------------------------------------------------------------------------------- /tests/behat/view_issued_certificates.feature: -------------------------------------------------------------------------------- 1 | @mod @mod_coursecertificate @moodleworkplace @javascript 2 | Feature: View the certificates that have been issued 3 | In order to view the certificates that have been issued 4 | As a teacher 5 | I need to view the certificates issues list 6 | 7 | Background: 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | 01 | teacher01@example.com | 11 | | teacher2 | Teacher | 02 | teacher02@example.com | 12 | | student1 | Student | 01 | student01@example.com | 13 | | student2 | Student | 02 | student02@example.com | 14 | | student3 | Student | 03 | student03@example.com | 15 | | student4 | Student | 04 | student04@example.com | 16 | | student5 | Student | 05 | student05@example.com | 17 | | student6 | Student | 06 | student06@example.com | 18 | | student7 | Student | 07 | student07@example.com | 19 | | student8 | Student | 08 | student08@example.com | 20 | | student9 | Student | 09 | student09@example.com | 21 | | student10 | Student | 10 | student10@example.com | 22 | | student11 | Student | 11 | student11@example.com | 23 | | manager1 | Manager | 1 | manager1@example.com | 24 | And the following "courses" exist: 25 | | fullname | shortname | groupmode | 26 | | Course 1 | C1 | 1 | 27 | And the following "course enrolments" exist: 28 | | user | course | role | 29 | | teacher1 | C1 | editingteacher | 30 | | teacher2 | C1 | teacher | 31 | | manager1 | C1 | editingteacher | 32 | | student1 | C1 | student | 33 | | student2 | C1 | student | 34 | | student3 | C1 | student | 35 | | student4 | C1 | student | 36 | | student5 | C1 | student | 37 | | student6 | C1 | student | 38 | | student7 | C1 | student | 39 | | student8 | C1 | student | 40 | | student9 | C1 | student | 41 | | student10 | C1 | student | 42 | | student11 | C1 | student | 43 | And the following "groups" exist: 44 | | name | course | idnumber | 45 | | Group 1 | C1 | G1 | 46 | | Group 2 | C1 | G2 | 47 | | Group 3 | C1 | G3 | 48 | And the following "group members" exist: 49 | | user | group | 50 | | student1 | G1 | 51 | | student2 | G1 | 52 | | teacher2 | G2 | 53 | | student3 | G2 | 54 | | student4 | G2 | 55 | | teacher2 | G3 | 56 | | student5 | G3 | 57 | And the following "roles" exist: 58 | | shortname | name | archetype | 59 | | certificateissuer | Certificate issuer | | 60 | And the following "role assigns" exist: 61 | | user | role | contextlevel | reference | 62 | | manager1 | certificateissuer | System | | 63 | And the following "permission overrides" exist: 64 | | capability | permission | role | contextlevel | reference | 65 | | tool/certificate:issue | Allow | certificateissuer | System | | 66 | And the following certificate templates exist: 67 | | name | shared | 68 | | Template 01 | 1 | 69 | And the following certificate issues exist: 70 | | template | user | course | component | 71 | | Template 01 | student1 | C1 | mod_coursecertificate | 72 | | Template 01 | student2 | C1 | mod_coursecertificate | 73 | | Template 01 | student3 | C1 | mod_coursecertificate | 74 | | Template 01 | student4 | C1 | mod_coursecertificate | 75 | | Template 01 | student5 | C1 | mod_coursecertificate | 76 | | Template 01 | student6 | C1 | mod_coursecertificate | 77 | | Template 01 | student7 | C1 | mod_coursecertificate | 78 | | Template 01 | student8 | C1 | mod_coursecertificate | 79 | | Template 01 | student9 | C1 | mod_coursecertificate | 80 | | Template 01 | student10 | C1 | mod_coursecertificate | 81 | | Template 01 | student11 | C1 | mod_coursecertificate | 82 | And the following "activities" exist: 83 | | activity | name | intro | course | idnumber | template | groupmode | 84 | | coursecertificate | Certificate | Certificate intro | C1 | coursecertificate1 | Template 01 | 1 | 85 | 86 | Scenario: View the issued certificates list 87 | And I log in as "teacher1" 88 | And I am on "Course 1" course homepage 89 | And I follow "Certificate" 90 | # Test group filtering. 91 | And I set the field "Separate groups" to "Group 1" 92 | And I should see "student01@example.com" 93 | And I should see "student02@example.com" 94 | And I should not see "student03@example.com" 95 | And I set the field "Separate groups" to "All participants" 96 | And I should see "student03@example.com" 97 | # Test sorting. 98 | And I click on "Email address" "link" in the "generaltable" "table" 99 | And I should not see "student01@example.com" 100 | # Test pagination. 101 | And I click on "2" "link" in the ".pagination" "css_element" 102 | And I should see "student01@example.com" 103 | 104 | Scenario: View the issued certificates list as non-editing teacher and separate/visible groups 105 | And I log in as "teacher2" 106 | And I am on "Course 1" course homepage 107 | And I follow "Certificate" 108 | And I should not see "student01@example.com" 109 | # And I click on "Separate groups" "field" 110 | And "Group 1" "option" should not exist in the "Separate groups" "select" 111 | And "Group 2" "option" should exist in the "Separate groups" "select" 112 | And I select "Group 3" from the "Separate groups" singleselect 113 | And I should not see "student03@example.com" 114 | And I should see "student05@example.com" 115 | And I log out 116 | And I log in as "teacher1" 117 | And I am on "Course 1" course homepage with editing mode on 118 | And I open "Certificate" actions menu 119 | And I choose "Edit settings" in the open action menu 120 | And I expand all fieldsets 121 | And I set the field "Group mode" to "Visible groups" 122 | And I log out 123 | And I log in as "teacher2" 124 | And I am on "Course 1" course homepage 125 | And I follow "Certificate" 126 | And I should not see "student01@example.com" 127 | And "Group 1" "option" should not exist in the "Separate groups" "select" 128 | And "Group 2" "option" should exist in the "Separate groups" "select" 129 | And I select "Group 3" from the "Separate groups" singleselect 130 | And I should not see "student03@example.com" 131 | And I should see "student05@example.com" 132 | 133 | Scenario: View issued certificates 134 | And I log in as "teacher1" 135 | And I am on "Course 1" course homepage 136 | And I follow "Certificate" 137 | And I click on "Email address" "link" in the "generaltable" "table" 138 | And I click on "View" "link" in the "student06@example.com" "table_row" 139 | 140 | Scenario: Remove issued certificates 141 | And I log in as "teacher1" 142 | And I am on "Course 1" course homepage 143 | And I follow "Certificate" 144 | And I click on "Email address" "link" in the "generaltable" "table" 145 | And I should see "student06@example.com" 146 | And I click on "Revoke" "link" in the "student06@example.com" "table_row" 147 | And I press "Confirm" 148 | And I should not see "student06@example.com" 149 | 150 | Scenario: Verify issued certificates 151 | And I log in as "teacher1" 152 | And I am on "Course 1" course homepage 153 | And I follow "Certificate" 154 | And I click on "Email address" "link" in the "generaltable" "table" 155 | And I click on "Verify" "link" in the "student06@example.com" "table_row" 156 | 157 | Scenario: Download issued certificates list 158 | And I log in as "teacher1" 159 | And I am on "Course 1" course homepage 160 | And I follow "Certificate" 161 | And I press "Download" 162 | And I log out 163 | -------------------------------------------------------------------------------- /mod_form.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * The main mod_coursecertificate configuration form. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | defined('MOODLE_INTERNAL') || die(); 26 | 27 | require_once($CFG->dirroot.'/course/moodleform_mod.php'); 28 | 29 | /** 30 | * Module instance settings form. 31 | * 32 | * @package mod_coursecertificate 33 | * @copyright 2020 Mikel Martín 34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 | */ 36 | class mod_coursecertificate_mod_form extends moodleform_mod { 37 | 38 | /** 39 | * Defines forms elements 40 | */ 41 | public function definition(): void { 42 | global $CFG, $OUTPUT; 43 | 44 | $mform = $this->_form; 45 | $hasissues = $this->has_issues(); 46 | $canmanagetemplates = \tool_certificate\permission::can_manage_anywhere(); 47 | 48 | // Adding the "general" fieldset, where all the common settings are shown. 49 | $mform->addElement('header', 'general', get_string('general', 'form')); 50 | 51 | // Adding the standard "name" field. 52 | $mform->addElement('text', 'name', get_string('name'), ['size' => '64']); 53 | 54 | if (!empty($CFG->formatstringstriptags)) { 55 | $mform->setType('name', PARAM_TEXT); 56 | } else { 57 | $mform->setType('name', PARAM_CLEANHTML); 58 | } 59 | 60 | $mform->addRule('name', null, 'required', null, 'client'); 61 | $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); 62 | 63 | $this->standard_intro_elements(); 64 | 65 | // Adding the template selector. 66 | if ($hasissues) { 67 | // If coursecertificate has issues, just add the current template to the selector. 68 | $templates = $this->get_current_template(); 69 | } else { 70 | // Get all available templates for the user. 71 | $templates = $this->get_template_select(); 72 | } 73 | $templateoptions = ['' => get_string('chooseatemplate', 'coursecertificate')] + $templates; 74 | $manageurl = new \moodle_url('/admin/tool/certificate/manage_templates.php'); 75 | $elements = [$mform->createElement('select', 'template', get_string('template', 'coursecertificate'), $templateoptions)]; 76 | // Adding "Manage templates" link if user has capabilities to manage templates. 77 | if ($canmanagetemplates && !empty($templates)) { 78 | $elements[] = $mform->createElement('static', 'managetemplates', '', 79 | $OUTPUT->action_link($manageurl, get_string('managetemplates', 'coursecertificate'))); 80 | } 81 | $mform->addGroup($elements, 'template_group', get_string('template', 'coursecertificate'), 82 | \html_writer::div('', 'w-100'), false); 83 | 84 | if (empty($templates)) { 85 | // Adding warning text if there are not templates available. 86 | if ($canmanagetemplates) { 87 | $warningstr = get_string('notemplateswarningwithlink', 'coursecertificate', $manageurl->out()); 88 | } else { 89 | $warningstr = get_string('notemplateswarning', 'coursecertificate'); 90 | } 91 | $html = html_writer::tag('div', $warningstr, ['class' => 'alert alert-warning']); 92 | $mform->addElement('static', 'notemplateswarning', '', $html); 93 | } else { 94 | $warningstr = get_string('selecttemplatewarning', 'mod_coursecertificate'); 95 | $html = html_writer::tag('div', $warningstr, ['class' => 'alert alert-warning']); 96 | $mform->addElement('static', 'selecttemplatewarning', '', $html); 97 | } 98 | if (!$hasissues) { 99 | $rules = []; 100 | $rules['template'][] = [null, 'required', null, 'client']; 101 | $mform->addGroupRule('template_group', $rules); 102 | } 103 | // If Certificate has issues it's not possible to change the template. 104 | $mform->addElement('hidden', 'hasissues', $hasissues); 105 | $mform->setType('hasissues', PARAM_TEXT); 106 | $mform->disabledIf('template', 'hasissues', 'eq', 1); 107 | 108 | // Adding the expirydate selector. 109 | $selectdatestr = get_string('selectdate', 'coursecertificate'); 110 | $neverstr = get_string('never'); 111 | $expirydatestr = get_string('expirydate', 'coursecertificate'); 112 | $expirydateoptions = [ 113 | 0 => $neverstr, 114 | 1 => $selectdatestr, 115 | ]; 116 | $group = []; 117 | $expirydatetype = $mform->createElement('select', 'expirydatetype', '', $expirydateoptions, 118 | ['class' => 'calendar-fix-selector-width']); 119 | $group[] =& $expirydatetype; 120 | $expirydate = $mform->createElement('date_selector', 'expires', ''); 121 | $group[] =& $expirydate; 122 | $mform->addGroup($group, 'expirydategroup', $expirydatestr, ' ', false); 123 | $mform->hideIf('expires', 'expirydatetype', 'noteq', 1); 124 | $mform->disabledIf('expires', 'expirydatetype', 'noteq', 1); 125 | 126 | // Add standard elements. 127 | $this->standard_coursemodule_elements(); 128 | 129 | // Add standard buttons. 130 | $this->add_action_buttons(); 131 | } 132 | 133 | /** 134 | * Enforce validation rules here 135 | * 136 | * @param array $data array of ("fieldname"=>value) of submitted data 137 | * @param array $files array of uploaded files "element_name"=>tmp_file_path 138 | * @return array 139 | **/ 140 | public function validation($data, $files) { 141 | $errors = parent::validation($data, $files); 142 | 143 | return $errors; 144 | } 145 | 146 | /** 147 | * Enforce defaults here. 148 | * 149 | * @param array $defaultvalues Form defaults 150 | * @return void 151 | **/ 152 | public function data_preprocessing(&$defaultvalues) { 153 | if (isset($defaultvalues['expires']) && ($defaultvalues['expires'] != 0)) { 154 | $defaultvalues['expirydatetype'] = 1; 155 | } 156 | } 157 | 158 | /** 159 | * Allows modules to modify the data returned by form get_data(). 160 | * This method is also called in the bulk activity completion form. 161 | * 162 | * Only available on moodleform_mod. 163 | * 164 | * @param stdClass $data passed by reference 165 | */ 166 | public function data_postprocessing($data) { 167 | parent::data_postprocessing($data); 168 | $data->expires = $data->expirydatetype == 0 ? 0 : $data->expires; 169 | } 170 | 171 | /** 172 | * Gets the current coursecertificate template for the template selector. 173 | * 174 | * @return array 175 | */ 176 | private function get_current_template(): array { 177 | global $DB; 178 | $templates = []; 179 | if ($instance = $this->get_instance()) { 180 | $sql = "SELECT ct.id, ct.name 181 | FROM {tool_certificate_templates} ct 182 | JOIN {coursecertificate} c 183 | ON c.template = ct.id 184 | AND c.id = :instance"; 185 | if ($record = $DB->get_record_sql($sql, ['instance' => $instance], IGNORE_MISSING)) { 186 | $templates[$record->id] = format_string($record->name); 187 | } 188 | } 189 | return $templates; 190 | } 191 | 192 | /** 193 | * Gets array options of available templates for the user for the template selector. 194 | * 195 | * @return array 196 | */ 197 | private function get_template_select(): array { 198 | $context = context_course::instance($this->current->course); 199 | $templates = []; 200 | if (!empty($records = \tool_certificate\permission::get_visible_templates($context))) { 201 | foreach ($records as $record) { 202 | $templates[$record->id] = format_string($record->name); 203 | } 204 | } 205 | return $templates; 206 | } 207 | 208 | /** 209 | * Returns "1" if course certificate has been issued. 210 | * 211 | * @return string 212 | * @uses \tool_certificate\certificate 213 | */ 214 | private function has_issues(): string { 215 | global $DB; 216 | 217 | if ($instance = $this->get_instance()) { 218 | $certificate = $certificate = $DB->get_record('coursecertificate', ['id' => $instance], '*', MUST_EXIST); 219 | $courseissues = \tool_certificate\certificate::count_issues_for_course($certificate->template, $certificate->course, 220 | 'mod_coursecertificate', null, null); 221 | if ($courseissues > 0) { 222 | return "1"; 223 | } 224 | } 225 | return "0"; 226 | } 227 | } -------------------------------------------------------------------------------- /classes/output/certificate_issues_table.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Table that displays the certificates issued in a course. 19 | * 20 | * @package mod_coursecertificate 21 | * @copyright 2020 Mikel Martín 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace mod_coursecertificate\output; 26 | 27 | use cm_info; 28 | use context_course; 29 | use context_module; 30 | use context_system; 31 | use mod_coursecertificate\permission; 32 | use tool_certificate\template; 33 | 34 | defined('MOODLE_INTERNAL') || die; 35 | 36 | global $CFG; 37 | 38 | require_once($CFG->libdir . '/tablelib.php'); 39 | 40 | /** 41 | * Class certificate_issues_table. 42 | * 43 | * @package mod_coursecertificate 44 | * @copyright 2020 Mikel Martín 45 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 | */ 47 | class certificate_issues_table extends \table_sql { 48 | /** 49 | * @var \stdClass $certificate Course certificate 50 | */ 51 | protected $certificate; 52 | 53 | /** 54 | * @var \stdClass $cm The course module. 55 | */ 56 | protected $cm; 57 | 58 | /** 59 | * @var int 60 | */ 61 | protected $groupid; 62 | 63 | /** 64 | * @var string 65 | */ 66 | protected $downloadparamname = 'download'; 67 | 68 | /** 69 | * @var bool 70 | */ 71 | protected $canrevoke; 72 | 73 | /** 74 | * @var bool 75 | */ 76 | protected $canviewall; 77 | 78 | /** 79 | * @var bool 80 | */ 81 | protected $canverify; 82 | 83 | /** 84 | * Sets up the table. 85 | * 86 | * @param \stdClass $certificate 87 | * @param cm_info $cm the course module 88 | * @param int|null $groupid 89 | */ 90 | public function __construct(\stdClass $certificate, cm_info $cm, int $groupid = null) { 91 | parent::__construct('mod-coursecertificate-issues-' . $cm->instance); 92 | 93 | $context = \context_module::instance($cm->id); 94 | 95 | $this->certificate = $certificate; 96 | $this->cm = $cm; 97 | $this->groupid = $groupid; 98 | 99 | $this->canverify = permission::can_verify_issues(); 100 | $this->canrevoke = permission::can_revoke_issues($this->certificate->course); 101 | $this->canviewall = permission::can_view_all_issues($this->certificate->course); 102 | 103 | $extrafields = get_extra_user_fields($context); 104 | $columnsheaders = ['fullname' => get_string('fullname')]; 105 | foreach ($extrafields as $extrafield) { 106 | $columnsheaders += [$extrafield => get_user_field_name($extrafield)]; 107 | } 108 | $columnsheaders += [ 109 | 'status' => get_string('status', 'coursecertificate'), 110 | 'expires' => get_string('expirydate', 'coursecertificate'), 111 | 'timecreated' => get_string('issueddate', 'coursecertificate'), 112 | 'code' => get_string('code', 'coursecertificate') 113 | ]; 114 | 115 | $filename = format_string('course-certificate-issues'); 116 | $this->is_downloading(optional_param($this->downloadparamname, 0, PARAM_ALPHA), 117 | $filename, get_string('certificateissues', 'coursecertificate')); 118 | 119 | if (!$this->is_downloading() && ($this->canrevoke || $this->canviewall)) { 120 | $columnsheaders += ['actions' => get_string('actions')]; 121 | } 122 | 123 | $this->define_columns(array_keys($columnsheaders)); 124 | $this->define_headers(array_values($columnsheaders)); 125 | $this->collapsible(false); 126 | $this->sortable(true, 'timecreated', SORT_DESC); 127 | $this->no_sorting('code'); 128 | $this->no_sorting('actions'); 129 | $this->pageable(true); 130 | $this->is_downloadable(true); 131 | $this->show_download_buttons_at([TABLE_P_BOTTOM]); 132 | $this->useridfield = 'userid'; 133 | } 134 | 135 | /** 136 | * Generate the fullname column. 137 | * 138 | * @param \stdClass $certificateissue 139 | * @return string 140 | */ 141 | public function col_fullname($certificateissue) { 142 | global $OUTPUT; 143 | 144 | if (!$this->is_downloading()) { 145 | return $OUTPUT->user_picture($certificateissue) . ' ' . fullname($certificateissue); 146 | } else { 147 | return fullname($certificateissue); 148 | } 149 | } 150 | 151 | /** 152 | * Generate the certificate time created column. 153 | * 154 | * @param \stdClass $certificateissue 155 | * @return string 156 | */ 157 | public function col_timecreated($certificateissue) { 158 | return userdate($certificateissue->timecreated, get_string("strftimedatetime", "langconfig")); 159 | } 160 | 161 | /** 162 | * Generate the code column. 163 | * 164 | * @param \stdClass $certificateissue 165 | * @return string 166 | */ 167 | public function col_code($certificateissue) { 168 | if (!$this->is_downloading() && $this->canverify) { 169 | return \html_writer::link(new \moodle_url('/admin/tool/certificate/index.php', ['code' => $certificateissue->code]), 170 | $certificateissue->code, ['title' => get_string('verify', 'tool_certificate')]); 171 | } 172 | return $certificateissue->code; 173 | } 174 | 175 | /** 176 | * Generate the status column. 177 | * 178 | * @param \stdClass $certificateissue 179 | * @return string 180 | */ 181 | public function col_status($certificateissue) { 182 | $expired = $certificateissue->status == 0; 183 | $expiredstr = get_string('expired', 'tool_certificate'); 184 | $validstr = get_string('valid', 'tool_certificate'); 185 | 186 | return $expired ? $expiredstr : $validstr; 187 | } 188 | 189 | /** 190 | * Generate the expires column. 191 | * 192 | * @param \stdClass $certificateissue 193 | * @return string 194 | */ 195 | public function col_expires($certificateissue) { 196 | if ($certificateissue->expires > 0) { 197 | return userdate($certificateissue->expires, get_string('strftimedatetime', 'langconfig')); 198 | } else { 199 | return get_string('never'); 200 | } 201 | } 202 | 203 | /** 204 | * Generate the actions column. 205 | * 206 | * @param \stdClass $certificateissue 207 | * @return string 208 | */ 209 | public function col_actions($certificateissue) { 210 | global $OUTPUT; 211 | $actions = ''; 212 | if ($this->canviewall) { 213 | $previewicon = new \pix_icon('i/search', get_string('view')); 214 | $previewlink = template::view_url($certificateissue->code); 215 | $previewattributes = [ 216 | 'target' => '_blank', 217 | 'class' => 'action-icon delete-icon', 218 | 'data-action' => 'preview-issue', 219 | 'data-issueid' => $certificateissue->issueid 220 | ]; 221 | $actions .= $OUTPUT->action_icon($previewlink, $previewicon, null, $previewattributes); 222 | } 223 | if ($this->canrevoke) { 224 | $rekoveicon = new \pix_icon('i/delete', get_string('revoke', 'coursecertificate')); 225 | $revokeattributes = [ 226 | 'class' => 'action-icon revoke-icon', 227 | 'data-action' => 'revoke-issue', 228 | 'data-issueid' => $certificateissue->issueid 229 | ]; 230 | $actions .= $OUTPUT->action_icon('#', $rekoveicon, null, $revokeattributes); 231 | } 232 | return $actions; 233 | } 234 | 235 | /** 236 | * Query the reader. 237 | * 238 | * @param int $pagesize size of page for paginated displayed table. 239 | * @param bool $useinitialsbar do you want to use the initials bar. 240 | * @uses \tool_certificate\certificate 241 | */ 242 | public function query_db($pagesize, $useinitialsbar = true) { 243 | $total = \tool_certificate\certificate::count_issues_for_course( 244 | $this->certificate->template, 245 | $this->certificate->course, 246 | 'mod_coursecertificate', 247 | $this->cm->effectivegroupmode, 248 | $this->groupid 249 | ); 250 | $this->pagesize($pagesize, $total); 251 | 252 | $this->rawdata = \tool_certificate\certificate::get_issues_for_course( 253 | $this->certificate->template, 254 | $this->certificate->course, 255 | 'mod_coursecertificate', 256 | $this->cm->effectivegroupmode, 257 | $this->groupid, 258 | $this->get_page_start(), 259 | $this->get_page_size(), 260 | $this->get_sql_sort() 261 | ); 262 | 263 | // Set initial bars. 264 | if ($useinitialsbar) { 265 | $this->initialbars($total > $pagesize); 266 | } 267 | } 268 | 269 | /** 270 | * Download the data. 271 | * 272 | * @uses \tool_certificate\certificate 273 | */ 274 | public function download() { 275 | \core\session\manager::write_close(); 276 | $total = \tool_certificate\certificate::count_issues_for_course( 277 | $this->certificate->template, 278 | $this->certificate->course, 279 | 'mod_coursecertificate', 280 | $this->cm->effectivegroupmode, 281 | $this->groupid 282 | ); 283 | $this->out($total, false); 284 | exit; 285 | } 286 | 287 | /** 288 | * This function is not part of the public api. 289 | */ 290 | public function print_nothing_to_display() { 291 | // Render button to allow user to reset table preferences. 292 | echo $this->render_reset_button(); 293 | 294 | $this->print_initials_bar(); 295 | echo \html_writer::div(get_string('nouserscertified', 'coursecertificate'), 'alert alert-info mt-3'); 296 | } 297 | } 298 | 299 | -------------------------------------------------------------------------------- /amd/build/manager.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/manager.js"],"names":["define","Ajax","Notification","Templates","Str","SELECTORS","AUTOMATICSENDREGION","HIDDENWARNING","NOAUTOSENDINFO","REPORTREGION","TOGGLEAUTOMATICSEND","REVOKEISSUE","LOADING","TEMPLATES","AUTOMATICSENDALERT","ISSUESREPORT","SERVICES","UPDATEAUTOMATICSEND","setVisibility","selector","visibile","document","querySelector","classList","remove","add","toggleAutomaticSend","automaticsendregion","dataset","certificateid","automaticsend","newstatus","strings","component","get_strings","then","s","confirm","M","util","js_pending","call","methodname","args","id","result","showhiddenwarning","shownoautosendinfo","render","html","innerHTML","js_complete","fail","exception","revokeIssue","issueid","window","location","reload","init","addEventListener","e","target","closest","preventDefault","reportregion"],"mappings":"AAwBAA,OAAM,iCAAC,CACH,WADG,CAEH,mBAFG,CAGH,gBAHG,CAIH,UAJG,CAAD,CAKH,SACCC,CADD,CAECC,CAFD,CAGCC,CAHD,CAICC,CAJD,CAKD,CAGE,GAAMC,CAAAA,CAAS,CAAG,CACdC,mBAAmB,CAAE,qCADP,CAEdC,aAAa,CAAE,iBAFD,CAGdC,cAAc,CAAE,kBAHF,CAIdC,YAAY,CAAE,+BAJA,CAKdC,mBAAmB,CAAE,sCALP,CAMdC,WAAW,CAAE,8BANC,CAOdC,OAAO,CAAE,kBAPK,CAAlB,CAUAC,CAAS,CAAG,CACRC,kBAAkB,CAAE,2CADZ,CAERC,YAAY,CAAE,qCAFN,CAVZ,CAeAC,CAAQ,CAAG,CACPC,mBAAmB,CAAE,4CADd,CAEPN,WAAW,CAAE,+BAFN,CAfX,CA0BA,QAASO,CAAAA,CAAT,CAAuBC,CAAvB,CAAiCC,CAAjC,CAA2C,CACvC,GAAIA,CAAJ,CAAc,CACVC,QAAQ,CAACC,aAAT,CAAuBH,CAAvB,EAAiCI,SAAjC,CAA2CC,MAA3C,CAAkD,QAAlD,EACAH,QAAQ,CAACC,aAAT,CAAuBH,CAAvB,EAAiCI,SAAjC,CAA2CC,MAA3C,CAAkD,WAAlD,CACH,CAHD,IAGO,CACHH,QAAQ,CAACC,aAAT,CAAuBH,CAAvB,EAAiCI,SAAjC,CAA2CE,GAA3C,CAA+C,QAA/C,EACAJ,QAAQ,CAACC,aAAT,CAAuBH,CAAvB,EAAiCI,SAAjC,CAA2CE,GAA3C,CAA+C,WAA/C,CACH,CACJ,CAOD,QAASC,CAAAA,CAAT,CAA6BC,CAA7B,CAAkD,OAE1CA,CAAmB,CAACL,aAApB,CAAkCjB,CAAS,CAACK,mBAA5C,EAAiEkB,OAFvB,CACvCC,CADuC,GACvCA,aADuC,CACxBC,CADwB,GACxBA,aADwB,CAGxCC,CAAS,CAAqB,GAAlB,GAAAD,CAH4B,CAIxCE,CAAO,CAAGD,CAAS,CAEvB,CAAC,CAAC,IAAO,cAAR,CAAwBE,SAAS,CAAE,OAAnC,CAAD,CACE,CAAC,IAAO,0BAAR,CAAoCA,SAAS,CAAE,mBAA/C,CADF,CAEE,CAAC,IAAO,SAAR,CAFF,CAGE,CAAC,IAAO,QAAR,CAHF,CAFuB,CAMvB,CAAC,CAAC,IAAO,cAAR,CAAwBA,SAAS,CAAE,OAAnC,CAAD,CACE,CAAC,IAAO,sBAAR,CAAgCA,SAAS,CAAE,mBAA3C,CADF,CAEE,CAAC,IAAO,SAAR,CAFF,CAGE,CAAC,IAAO,QAAR,CAHF,CAV4C,CAc9C7B,CAAG,CAAC8B,WAAJ,CAAgBF,CAAhB,EAAyBG,IAAzB,CAA8B,SAACC,CAAD,CAAO,CAEjClC,CAAY,CAACmC,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BA,CAAC,CAAC,CAAD,CAA5B,CAAiCA,CAAC,CAAC,CAAD,CAAlC,CAAuCA,CAAC,CAAC,CAAD,CAAxC,CAA6C,UAAM,CAC/CE,CAAC,CAACC,IAAF,CAAOC,UAAP,CAAkB,4CAAlB,EAEAtB,CAAa,CAACb,CAAS,CAACO,OAAX,IAAb,CAEAX,CAAI,CAACwC,IAAL,CAAU,CAAC,CAACC,UAAU,CAAE1B,CAAQ,CAACC,mBAAtB,CACP0B,IAAI,CAAE,CAACC,EAAE,CAAEf,CAAL,CAAoBC,aAAa,CAAEC,CAAnC,CADC,CAAD,CAAV,EAC2D,CAD3D,EAGCI,IAHD,CAGM,SAACU,CAAD,CAAY,IACTC,CAAAA,CADS,CACgCD,CADhC,CACTC,iBADS,CACUC,CADV,CACgCF,CADhC,CACUE,kBADV,CAEd5C,CAAS,CAAC6C,MAAV,CAAiBnC,CAAS,CAACC,kBAA3B,CACI,CAACe,aAAa,CAAEA,CAAhB,CAA+BC,aAAa,CAAEC,CAA9C,CADJ,CAC8D,EAD9D,EAEKI,IAFL,CAEU,SAACc,CAAD,CAAU,CACZtB,CAAmB,CAACuB,SAApB,CAAgCD,CAAhC,CACA/B,CAAa,CAACb,CAAS,CAACE,aAAX,CAA0BuC,CAA1B,CAAb,CACA5B,CAAa,CAACb,CAAS,CAACG,cAAX,CAA2BuC,CAA3B,CAAb,CACAT,CAAC,CAACC,IAAF,CAAOY,WAAP,CAAmB,4CAAnB,EACA,MAAO,KACV,CARL,EASKC,IATL,CASUlD,CAAY,CAACmD,SATvB,EAUA,MAAO,KACV,CAhBD,EAiBCD,IAjBD,CAiBMlD,CAAY,CAACmD,SAjBnB,CAkBH,CAvBD,EAwBA,MAAO,KACV,CA3BD,EA2BGD,IA3BH,CA2BQlD,CAAY,CAACmD,SA3BrB,CA4BH,CAOD,QAASC,CAAAA,CAAT,CAAqBC,CAArB,CAA8B,CAK1BnD,CAAG,CAAC8B,WAAJ,CAJgB,CAAC,CAAC,IAAO,cAAR,CAAwBD,SAAS,CAAE,OAAnC,CAAD,CACZ,CAAC,IAAO,aAAR,CAAuBA,SAAS,CAAE,mBAAlC,CADY,CAEZ,CAAC,IAAO,SAAR,CAFY,CAGZ,CAAC,IAAO,QAAR,CAHY,CAIhB,EAAyBE,IAAzB,CAA8B,SAACC,CAAD,CAAO,CAEjClC,CAAY,CAACmC,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BA,CAAC,CAAC,CAAD,CAA5B,CAAiCA,CAAC,CAAC,CAAD,CAAlC,CAAuCA,CAAC,CAAC,CAAD,CAAxC,CAA6C,UAAM,CAC/CE,CAAC,CAACC,IAAF,CAAOC,UAAP,CAAkB,oCAAlB,EAEAvC,CAAI,CAACwC,IAAL,CAAU,CAAC,CAACC,UAAU,CAAE1B,CAAQ,CAACL,WAAtB,CAAmCgC,IAAI,CAAE,CAACC,EAAE,CAAEW,CAAL,CAAzC,CAAD,CAAV,EAAqE,CAArE,EAECpB,IAFD,CAEM,UAAM,CACRG,CAAC,CAACC,IAAF,CAAOY,WAAP,CAAmB,oCAAnB,EACAK,MAAM,CAACC,QAAP,CAAgBC,MAAhB,GACA,MAAO,KACV,CAND,EAOCN,IAPD,CAOMlD,CAAY,CAACmD,SAPnB,CAQH,CAXD,EAYA,MAAO,KACV,CAfD,EAeGD,IAfH,CAeQlD,CAAY,CAACmD,SAfrB,CAgBH,CAED,MAAO,CACHM,IAAI,CAAE,eAAW,CACb,GAAMhC,CAAAA,CAAmB,CAAGN,QAAQ,CAACC,aAAT,CAAuBjB,CAAS,CAACC,mBAAjC,CAA5B,CACA,GAAIqB,CAAJ,CAAyB,CACrBA,CAAmB,CAACiC,gBAApB,CAAqC,OAArC,CAA8C,SAACC,CAAD,CAAO,CACjD,GAAIA,CAAC,CAACC,MAAF,EAAYD,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiB1D,CAAS,CAACK,mBAA3B,CAAhB,CAAiE,CAC7DmD,CAAC,CAACG,cAAF,GACAtC,CAAmB,CAACC,CAAD,CACtB,CACJ,CALD,CAMH,CACD,GAAMsC,CAAAA,CAAY,CAAG5C,QAAQ,CAACC,aAAT,CAAuBjB,CAAS,CAACI,YAAjC,CAArB,CACA,GAAIwD,CAAJ,CAAkB,CACdA,CAAY,CAACL,gBAAb,CAA8B,OAA9B,CAAuC,SAACC,CAAD,CAAO,CAC1C,GAAMC,CAAAA,CAAM,CAAGD,CAAC,CAACC,MAAF,EAAYD,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiB1D,CAAS,CAACM,WAA3B,CAA3B,CACA,GAAImD,CAAJ,CAAY,CACRD,CAAC,CAACG,cAAF,GADQ,GAEDT,CAAAA,CAFC,CAEUO,CAAM,CAAClC,OAFjB,CAED2B,OAFC,CAGRD,CAAW,CAACC,CAAD,CACd,CACJ,CAPD,CAQH,CACJ,CAtBE,CAwBV,CAtJK,CAAN","sourcesContent":["// This file is part of the mod_coursecertificate plugin for Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module instantiates the functionality for actions on course certificates.\n *\n * @module mod_coursecertificate/manager\n * @package mod_coursecertificate\n * @copyright 2020 Mikel Martín \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\n 'core/ajax',\n 'core/notification',\n 'core/templates',\n 'core/str'\n], function(\n Ajax,\n Notification,\n Templates,\n Str\n) {\n\n /** @type {Object} The list of selectors for the coursecertificate module. */\n const SELECTORS = {\n AUTOMATICSENDREGION: \"[data-region='automaticsend-alert']\",\n HIDDENWARNING: \".hidden-warning\",\n NOAUTOSENDINFO: \".noautosend-info\",\n REPORTREGION: \"[data-region='issues-report']\",\n TOGGLEAUTOMATICSEND: \"[data-action='toggle-automaticsend']\",\n REVOKEISSUE: \"[data-action='revoke-issue']\",\n LOADING: \".loading-overlay\"\n },\n /** @type {Object} The list of templates for the coursecertificate module. */\n TEMPLATES = {\n AUTOMATICSENDALERT: 'mod_coursecertificate/automaticsend_alert',\n ISSUESREPORT: 'mod_coursecertificate/issues_report'\n },\n /** @type {Object} The list of services for the coursecertificate module. */\n SERVICES = {\n UPDATEAUTOMATICSEND: 'mod_coursecertificate_update_automaticsend',\n REVOKEISSUE: 'tool_certificate_revoke_issue',\n };\n\n /**\n * Show/Hide selector.\n *\n * @param {string} selector\n * @param {boolean} visibile\n */\n function setVisibility(selector, visibile) {\n if (visibile) {\n document.querySelector(selector).classList.remove('d-none');\n document.querySelector(selector).classList.remove('invisible');\n } else {\n document.querySelector(selector).classList.add('d-none');\n document.querySelector(selector).classList.add('invisible');\n }\n }\n\n /**\n * Toggle the automaticsend setting on/off for coursecertificate.\n *\n * @param {Element} automaticsendregion\n */\n function toggleAutomaticSend(automaticsendregion) {\n const {certificateid, automaticsend} =\n automaticsendregion.querySelector(SELECTORS.TOGGLEAUTOMATICSEND).dataset;\n const newstatus = automaticsend === '0';\n const strings = newstatus\n // Load strings depending on newstatus.\n ? [{'key': 'confirmation', component: 'admin'},\n {'key': 'enableautomaticsendpopup', component: 'coursecertificate'},\n {'key': 'confirm'},\n {'key': 'cancel'}]\n : [{'key': 'confirmation', component: 'admin'},\n {'key': 'disableautomaticsend', component: 'coursecertificate'},\n {'key': 'confirm'},\n {'key': 'cancel'}];\n Str.get_strings(strings).then((s) => {\n // Show confirm notification.\n Notification.confirm(s[0], s[1], s[2], s[3], () => {\n M.util.js_pending('mod_coursecertificate_toggle_automaticsend');\n // Show loading template.\n setVisibility(SELECTORS.LOADING, true);\n // Call to webservice.\n Ajax.call([{methodname: SERVICES.UPDATEAUTOMATICSEND,\n args: {id: certificateid, automaticsend: newstatus}}])[0]\n // Reload automatic send alert template.\n .then((result) => {\n let {showhiddenwarning, shownoautosendinfo} = result;\n Templates.render(TEMPLATES.AUTOMATICSENDALERT,\n {certificateid: certificateid, automaticsend: newstatus}, '')\n .then((html) => {\n automaticsendregion.innerHTML = html;\n setVisibility(SELECTORS.HIDDENWARNING, showhiddenwarning);\n setVisibility(SELECTORS.NOAUTOSENDINFO, shownoautosendinfo);\n M.util.js_complete('mod_coursecertificate_toggle_automaticsend');\n return null;\n })\n .fail(Notification.exception);\n return null;\n })\n .fail(Notification.exception);\n });\n return null;\n }).fail(Notification.exception);\n }\n\n /**\n * Revoke the issue.\n *\n * @param {int} issueid\n */\n function revokeIssue(issueid) {\n const strings = [{'key': 'confirmation', component: 'admin'},\n {'key': 'revokeissue', component: 'coursecertificate'},\n {'key': 'confirm'},\n {'key': 'cancel'}];\n Str.get_strings(strings).then((s) => {\n // Show confirm notification.\n Notification.confirm(s[0], s[1], s[2], s[3], () => {\n M.util.js_pending('mod_coursecertificate_revoke_issue');\n // Call to webservice to revoke issue.\n Ajax.call([{methodname: SERVICES.REVOKEISSUE, args: {id: issueid}}])[0]\n // Call to webservice to get updated table.\n .then(() => {\n M.util.js_complete('mod_coursecertificate_revoke_issue');\n window.location.reload();\n return null;\n })\n .fail(Notification.exception);\n });\n return null;\n }).fail(Notification.exception);\n }\n\n return {\n init: function() {\n const automaticsendregion = document.querySelector(SELECTORS.AUTOMATICSENDREGION);\n if (automaticsendregion) {\n automaticsendregion.addEventListener('click', (e) => {\n if (e.target && e.target.closest(SELECTORS.TOGGLEAUTOMATICSEND)) {\n e.preventDefault();\n toggleAutomaticSend(automaticsendregion);\n }\n });\n }\n const reportregion = document.querySelector(SELECTORS.REPORTREGION);\n if (reportregion) {\n reportregion.addEventListener('click', (e) => {\n const target = e.target && e.target.closest(SELECTORS.REVOKEISSUE);\n if (target) {\n e.preventDefault();\n const {issueid} = target.dataset;\n revokeIssue(issueid);\n }\n });\n }\n }\n };\n});"],"file":"manager.min.js"} -------------------------------------------------------------------------------- /tests/behat/basic.feature: -------------------------------------------------------------------------------- 1 | @mod @mod_coursecertificate @moodleworkplace @javascript 2 | Feature: Basic functionality of course certificate module 3 | In order to issue certificates in a course 4 | As a teacher 5 | I need to be able to create instances of course certificate module 6 | 7 | Background: 8 | Given the following "users" exist: 9 | | username | firstname | lastname | email | 10 | | teacher1 | Teacher | 1 | teacher1@example.com | 11 | | student1 | Student | 1 | student1@example.com | 12 | | manager1 | Manager | 1 | manager1@example.com | 13 | And the following "courses" exist: 14 | | fullname | shortname | format | 15 | | Course 1 | C1 | topics | 16 | And the following "course enrolments" exist: 17 | | user | course | role | 18 | | teacher1 | C1 | editingteacher | 19 | | manager1 | C1 | editingteacher | 20 | | student1 | C1 | student | 21 | And the following "roles" exist: 22 | | shortname | name | archetype | 23 | | certificateissuer | Certificate issuer | | 24 | And the following "role assigns" exist: 25 | | user | role | contextlevel | reference | 26 | | manager1 | certificateissuer | System | | 27 | And the following "permission overrides" exist: 28 | | capability | permission | role | contextlevel | reference | 29 | | tool/certificate:issue | Allow | certificateissuer | System | | 30 | 31 | Scenario: Teacher can create an instance of course certificate module 32 | And the following certificate templates exist: 33 | | name | shared | 34 | | Certificate of participation | 1 | 35 | | Certificate of completion | 0 | 36 | And I log in as "teacher1" 37 | And I am on "Course 1" course homepage with editing mode on 38 | And I add a "Course certificate" to section "1" 39 | And "Manage certificate templates" "link" should not exist 40 | And I click on "Template" "select" 41 | And I should not see "Certificate of completion" 42 | And I set the following fields to these values: 43 | | Name | Your awesome certificate | 44 | | Template | Certificate of participation | 45 | And I press "Save and display" 46 | And I should see "Your awesome certificate" 47 | And I should see "The automatic sending of this certificate is disabled" 48 | And I should see "No users are certified." 49 | And I press "Enable" 50 | And I press "Confirm" 51 | And I should see "The automatic sending of this certificate is enabled" 52 | And I navigate to "Edit settings" in current page administration 53 | And I set the following fields to these values: 54 | | Name | Your super awesome certificate | 55 | And I press "Save and display" 56 | And I should see "Your super awesome certificate" 57 | And I log out 58 | 59 | Scenario: Teacher can duplicate and delete an instance of course certificate module 60 | And the following certificate templates exist: 61 | | name | shared | 62 | | Certificate of participation | 1 | 63 | And the following "activities" exist: 64 | | activity | name | intro | course | idnumber | template | 65 | | coursecertificate | Certificate | Certificate intro | C1 | coursecertificate1 | Certificate of participation | 66 | And I log in as "teacher1" 67 | And I am on "Course 1" course homepage with editing mode on 68 | And I duplicate "Certificate" activity 69 | And I wait until "Certificate (copy)" "link" exists 70 | And I delete "Certificate (copy)" activity 71 | And I should not see "Certificate (copy)" 72 | 73 | Scenario: Manager can create an instance of course certificate module with non shared templates 74 | And the following "permission overrides" exist: 75 | | capability | permission | role | contextlevel | reference | 76 | | tool/certificate:manage | Allow | certificateissuer | System | | 77 | And the following certificate templates exist: 78 | | name | shared | 79 | | Certificate of participation | 1 | 80 | | Certificate of completion | 0 | 81 | And I log in as "manager1" 82 | And I am on "Course 1" course homepage with editing mode on 83 | And I add a "Course certificate" to section "1" 84 | And "Manage certificate templates" "link" should exist 85 | And I set the following fields to these values: 86 | | Name | Your awesome certificate | 87 | | Template | Certificate of completion | 88 | And I press "Save and display" 89 | And I follow "Your awesome certificate" 90 | And I should see "Your awesome certificate" 91 | And I should see "The automatic sending of this certificate is disabled" 92 | And I should see "No users are certified." 93 | And I log out 94 | 95 | Scenario: Teacher can not create course certificate if there are not available templates 96 | And the following certificate templates exist: 97 | | name | shared | 98 | | Certificate of completion | 0 | 99 | And I log in as "teacher1" 100 | And I am on "Course 1" course homepage with editing mode on 101 | And I add a "Course certificate" to section "1" 102 | And I should see "There are no available templates. Please contact the site administrator." 103 | And I press "Save and display" 104 | And I should see "You must supply a value here." 105 | 106 | Scenario: Manager can not create course certificate if there are not available templates 107 | And the following "permission overrides" exist: 108 | | capability | permission | role | contextlevel | reference | 109 | | tool/certificate:manage | Allow | certificateissuer | System | | 110 | And I log in as "manager1" 111 | And I am on "Course 1" course homepage with editing mode on 112 | And I add a "Course certificate" to section "1" 113 | And I should see "There are no available templates. Please go to certificate template management page and create a new one." 114 | And I press "Save and display" 115 | And I should see "You must supply a value here." 116 | And "certificate template management page" "link" should exist in the ".alert-warning" "css_element" 117 | 118 | Scenario: Teacher can not change course certificate template if it has been issued 119 | And the following certificate templates exist: 120 | | name | shared | 121 | | Certificate of participation | 1 | 122 | And the following certificate issues exist: 123 | | template | user | course | component | 124 | | Certificate of participation | student1 | C1 | mod_coursecertificate | 125 | And I log in as "teacher1" 126 | And I am on "Course 1" course homepage with editing mode on 127 | And I add a "Course certificate" to section "1" and I fill the form with: 128 | | Name | Your awesome certificate | 129 | | Template | Certificate of participation | 130 | And I follow "Your awesome certificate" 131 | And I should see "Student 1" 132 | And I click on "Actions menu" "link" 133 | And I click on "Edit settings" "link" 134 | And the "Template" "select" should be disabled 135 | 136 | Scenario: Teacher can revoke a certificate 137 | And the following certificate templates exist: 138 | | name | shared | 139 | | Certificate of participation | 1 | 140 | And the following certificate issues exist: 141 | | template | user | course | component | 142 | | Certificate of participation | student1 | C1 | mod_coursecertificate | 143 | And I log in as "teacher1" 144 | And I am on "Course 1" course homepage with editing mode on 145 | And I add a "Course certificate" to section "1" and I fill the form with: 146 | | Name | Your awesome certificate | 147 | | Template | Certificate of participation | 148 | And I follow "Your awesome certificate" 149 | And I should see "Student 1" 150 | And I click on "Revoke" "link" 151 | And I press "Confirm" 152 | And I should see "No users are certified." 153 | 154 | Scenario: Teacher can manage blocks in the module page 155 | And the following certificate templates exist: 156 | | name | shared | 157 | | Certificate of participation | 1 | 158 | And the following "activities" exist: 159 | | activity | name | intro | course | idnumber | template | 160 | | coursecertificate | Certificate 01 | Certificate intro | C1 | coursecertificate1 | Certificate of participation | 161 | And I log in as "teacher1" 162 | And I am on "Course 1" course homepage with editing mode on 163 | And I follow "Certificate 01" 164 | And I add the "HTML" block 165 | And I configure the "(new HTML block)" block 166 | And I set the following fields to these values: 167 | | HTML block title | My block | 168 | | Content | This is my block | 169 | And I press "Save changes" 170 | And I should see "This is my block" 171 | 172 | Scenario: Display information about all coursecertificate activities 173 | And the following certificate templates exist: 174 | | name | shared | 175 | | Certificate of participation | 1 | 176 | And the following "activities" exist: 177 | | activity | name | intro | course | idnumber | template | 178 | | coursecertificate | Certificate 01 | Certificate intro | C1 | coursecertificate1 | Certificate of participation | 179 | | coursecertificate | Certificate 02 | Certificate intro | C1 | coursecertificate1 | Certificate of participation | 180 | And I log in as "teacher1" 181 | And I am on "Course 1" course homepage with editing mode on 182 | And I add the "Activities" block 183 | And I click on "Course certificates" "link" in the "Activities" "block" 184 | And I should see "Certificate 01" 185 | And I should see "Certificate 02" 186 | And I follow "Certificate 01" 187 | And I should see "No users are certified." 188 | 189 | Scenario: Display course certificate after removing current selected template. 190 | And the following certificate templates exist: 191 | | name | shared | 192 | | Certificate of participation A | 1 | 193 | | Certificate of participation B | 1 | 194 | And the following "activities" exist: 195 | | activity | name | intro | course | idnumber | template | 196 | | coursecertificate | Certificate 01 | Certificate intro | C1 | coursecertificate1 | Certificate of participation A | 197 | And I log in as "admin" 198 | And I navigate to "Certificates > Manage certificate templates" in site administration 199 | And I click on "Delete" "link" in the "Certificate of participation A" "table_row" 200 | And I click on "Delete" "button" in the "Confirm" "dialogue" 201 | And I log out 202 | And I log in as "teacher1" 203 | And I am on "Course 1" course homepage with editing mode on 204 | And I follow "Certificate 01" 205 | And I should see "The selected template can’t be found. Please go to the activity settings and select a new one." 206 | And I log out 207 | And I log in as "student1" 208 | And I am on "Course 1" course homepage 209 | And I follow "Certificate 01" 210 | And I should see "The certificate is not available. Please contact the course administrator." 211 | And I log out 212 | And I log in as "teacher1" 213 | And I am on "Course 1" course homepage with editing mode on 214 | And I follow "Certificate 01" 215 | And I click on "Actions menu" "link" 216 | And I click on "Edit settings" "link" 217 | And I set the following fields to these values: 218 | | Template | Certificate of participation B | 219 | And I press "Save and display" 220 | And I should not see "There is no selected template." 221 | 222 | Scenario: Display activity hidden warning 223 | And the following certificate templates exist: 224 | | name | shared | 225 | | Certificate of participation A | 1 | 226 | And the following "activities" exist: 227 | | activity | name | intro | course | idnumber | template | visible | automaticsend | 228 | | coursecertificate | Certificate 01 | Certificate intro | C1 | coursecertificate1 | Certificate of participation A | 0 | 1 | 229 | And I log in as "teacher1" 230 | And I am on "Course 1" course homepage with editing mode on 231 | And I follow "Certificate 01" 232 | And I should see "This activity is currently hidden. By making it visible, students who meet the activity access restrictions will automatically receive a PDF copy of the certificate." 233 | And I press "Disable" 234 | And I press "Confirm" 235 | And I should not see "This activity is currently hidden. By making it visible, students who meet the activity access restrictions will automatically receive a PDF copy of the certificate." 236 | And I press "Enable" 237 | And I press "Confirm" 238 | And I should see "This activity is currently hidden. By making it visible, students who meet the activity access restrictions will automatically receive a PDF copy of the certificate." 239 | 240 | Scenario: Display automatic sending disabled info 241 | And the following certificate templates exist: 242 | | name | shared | 243 | | Certificate of participation A | 1 | 244 | And the following "activities" exist: 245 | | activity | name | intro | course | idnumber | template | visible | automaticsend | 246 | | coursecertificate | Certificate 01 | Certificate intro | C1 | coursecertificate1 | Certificate of participation A | 1 | 0 | 247 | And I log in as "teacher1" 248 | And I am on "Course 1" course homepage with editing mode on 249 | And I follow "Certificate 01" 250 | And I should see "Students who meet this activity's access restrictions will be issued with their certificate once they access it." 251 | And I press "Enable" 252 | And I press "Confirm" 253 | And I should not see "Students who meet this activity's access restrictions will be issued with their certificate once they access it." 254 | And I press "Disable" 255 | And I press "Confirm" 256 | And I should see "Students who meet this activity's access restrictions will be issued with their certificate once they access it." 257 | --------------------------------------------------------------------------------