#70 Added mobile app support

This allows students to view the activity and download
their certificate. It also allows teachers to view the
list of issued certificates, with the ability to revoke
any.

This is for Moodle Mobile v3.5.0 (not to be confused with
your Moodle site version) and will not work on Mobile
versions earlier than this.

If you are running a Moodle site on version 3.4 or below
you will need to install the local_mobile plugin in order
for this to work.

If you are running a Moodle site on version 3.0 or below
then you will need to upgrade.
This commit is contained in:
Mark Nelson 2018-05-23 14:35:23 +08:00
parent 3b2a45d755
commit 08a385fc85
9 changed files with 791 additions and 2 deletions

View file

@ -174,4 +174,57 @@ class external extends \external_api {
public static function get_element_html_returns() { public static function get_element_html_returns() {
return new \external_value(PARAM_RAW, 'The HTML'); return new \external_value(PARAM_RAW, 'The HTML');
} }
/**
* Returns the delete_issue() parameters.
*
* @return \external_function_parameters
*/
public static function delete_issue_parameters() {
return new \external_function_parameters(
array(
'certificateid' => new \external_value(PARAM_INT, 'The certificate id'),
'issueid' => new \external_value(PARAM_INT, 'The issue id'),
)
);
}
/**
* Handles deleting a customcert issue.
*
* @param int $certificateid The certificate id.
* @param int $issueid The issue id.
* @return bool
*/
public static function delete_issue($certificateid, $issueid) {
global $DB;
$params = [
'certificateid' => $certificateid,
'issueid' => $issueid
];
self::validate_parameters(self::delete_issue_parameters(), $params);
$certificate = $DB->get_record('customcert', ['id' => $certificateid], '*', MUST_EXIST);
$issue = $DB->get_record('customcert_issues', ['id' => $issueid, 'customcertid' => $certificateid], '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('customcert', $certificate->id, 0, false, MUST_EXIST);
// Make sure the user has the required capabilities.
$context = \context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/customcert:manage', $context);
// Delete the issue.
return $DB->delete_records('customcert_issues', ['id' => $issue->id]);
}
/**
* Returns the delete_issue result value.
*
* @return \external_value
*/
public static function delete_issue_returns() {
return new \external_value(PARAM_BOOL, 'True if successful, false otherwise');
}
} }

262
classes/output/mobile.php Normal file
View file

@ -0,0 +1,262 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the mobile output class for the custom certificate.
*
* @package mod_customcert
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_customcert\output;
defined('MOODLE_INTERNAL') || die();
/**
* Mobile output class for the custom certificate.
*
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mobile {
/**
* Returns the initial page when viewing the activity for the mobile app.
*
* @param array $args Arguments from tool_mobile_get_content WS
* @return array HTML, javascript and other data
*/
public static function mobile_view_activity($args) {
global $OUTPUT, $DB, $USER;
$args = (object) $args;
// Get the group variable.
$groupid = empty($args->group) ? 0 : $args->group; // By default, group 0.
$cmid = $args->cmid;
// Capabilities check.
$cm = get_coursemodule_from_id('customcert', $cmid);
$context = \context_module::instance($cm->id);
self::require_capability($cm, $context, 'mod/customcert:view');
// Set some variables we are going to be using.
$certificate = $DB->get_record('customcert', ['id' => $cm->instance], '*', MUST_EXIST);
$certificate->name = format_string($certificate->name);
list($certificate->intro, $certificate->introformat) = external_format_text($certificate->intro,
$certificate->introformat, $context->id, 'mod_customcert', 'intro');
// Get the groups (if any) to display - also sets active group.
$groups = self::get_groups($cm, $groupid, $USER->id);
// Get any issues this person may have.
$issues = $DB->get_records('customcert_issues', ['userid' => $USER->id, 'customcertid' => $certificate->id]);
$candownload = true;
if ($certificate->requiredtime && !has_capability('mod/certificate:manage', $context)) {
if (\mod_customcert\certificate::get_course_time($certificate->course) < ($certificate->requiredtime * 60)) {
$candownload = false;
}
}
$fileurl = "";
if ($candownload) {
$fileurl = new \moodle_url('/mod/customcert/mobile/pluginfile.php', ['certificateid' => $certificate->id,
'userid' => $USER->id]);
$fileurl = $fileurl->out(true);
}
$showreport = false;
$numissues = 0;
if (has_capability('mod/customcert:viewreport', $context)) {
// Get the total number of issues.
$showreport = true;
$groupmode = groups_get_activity_groupmode($cm);
if (has_capability('moodle/site:accessallgroups', $context)) {
$groupmode = 'aag';
}
$numissues = \mod_customcert\certificate::get_number_of_issues($certificate->id, $cm, $groupmode);
}
$data = [
'certificate' => $certificate,
'cmid' => $cm->id,
'groupselected' => $groupid,
'showgroups' => !empty($groups),
'groups' => array_values($groups),
'hasissues' => !empty($issues),
'issues' => array_values($issues),
'candownload' => $candownload,
'fileurl' => $fileurl,
'showreport' => $showreport,
'numissuesinreport' => $numissues,
'currenttimestamp' => time()
];
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_customcert/mobile_view_activity_page', $data),
],
],
'javascript' => '',
'otherdata' => ''
];
}
/**
* Returns the list of issues certificates for the activity for the mobile app.
*
* @param array $args Arguments from tool_mobile_get_content WS
* @return array HTML, javascript and other data
*/
public static function mobile_view_report($args) {
global $DB, $OUTPUT, $USER;
$args = (object) $args;
$cmid = $args->cmid;
$groupid = empty($args->group) ? 0 : $args->group; // By default, group 0.
// Capabilities check.
$cm = get_coursemodule_from_id('customcert', $cmid);
$context = \context_module::instance($cm->id);
self::require_capability($cm, $context, 'mod/customcert:viewreport');
// Get the groups (if any) to display - also sets active group.
$groups = self::get_groups($cm, $groupid, $USER->id);
$certificate = $DB->get_record('customcert', ['id' => $cm->instance], '*', MUST_EXIST);
$certificate->name = format_string($certificate->name);
list($certificate->intro, $certificate->introformat) = external_format_text($certificate->intro,
$certificate->introformat, $context->id, 'mod_customcert', 'intro');
$groupmode = groups_get_activity_groupmode($cm);
if (has_capability('moodle/site:accessallgroups', $context)) {
$groupmode = 'aag';
}
$issues = \mod_customcert\certificate::get_issues($certificate->id, $groupmode, $cm, 0, 0);
foreach ($issues as $issue) {
$issue->displayname = fullname($issue);
$issue->fileurl = new \moodle_url('/mod/customcert/mobile/pluginfile.php', ['certificateid' => $certificate->id,
'userid' => $issue->id]);
}
$data = [
'certificate' => $certificate,
'cmid' => $cmid,
'showgroups' => !empty($groups),
'groups' => array_values($groups),
'canmanage' => has_capability('mod/customcert:manage', $context),
'hasissues' => !empty($issues),
'issues' => array_values($issues),
'currenttimestamp' => time()
];
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_customcert/mobile_report_page', $data),
],
],
'javascript' => '',
'otherdata' => ''
];
}
/**
* Returns an array of groups to be displayed (if applicable) for the activity.
*
* The groups API is a mess hence the hackiness.
*
* @param \stdClass $cm The course module
* @param int $groupid The group id
* @param int $userid The user id
* @return array The array of groups, may be empty.
*/
protected static function get_groups($cm, $groupid, $userid) {
$arrgroups = [];
if ($groupmode = groups_get_activity_groupmode($cm)) {
if ($groups = groups_get_activity_allowed_groups($cm, $userid)) {
$context = \context_module::instance($cm->id);
if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $context)) {
$allparticipants = new \stdClass();
$allparticipants->id = 0;
$allparticipants->name = get_string('allparticipants');
$allparticipants->selected = $groupid === 0;
$arrgroups[0] = $allparticipants;
}
self::update_active_group($groupmode, $groupid, $groups, $cm);
// Detect which group is selected.
foreach ($groups as $gid => $group) {
$group->selected = $gid == $groupid;
$arrgroups[] = $group;
}
}
}
return $arrgroups;
}
/**
* Update the active group in the session.
*
* This is a hack. We can't call groups_get_activity_group to update the active group as it relies
* on optional_param('group' .. which we won't have when using the mobile app.
*
* @param int $groupmode The group mode we are in, eg. NOGROUPS, VISIBLEGROUPS
* @param int $groupid The id of the group that has been selected
* @param array $allowedgroups The allowed groups this user can access
* @param \stdClass $cm The course module
*/
private static function update_active_group($groupmode, $groupid, $allowedgroups, $cm) {
global $SESSION;
$context = \context_module::instance($cm->id);
if (has_capability('moodle/site:accessallgroups', $context)) {
$groupmode = 'aag';
}
if ($groupid == 0) {
// The groups are only all visible in VISIBLEGROUPS mode or if the user can access all groups.
if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $context)) {
$SESSION->activegroup[$cm->course][$groupmode][$cm->groupingid] = 0;
}
} else {
if ($allowedgroups && array_key_exists($groupid, $allowedgroups)) {
$SESSION->activegroup[$cm->course][$groupmode][$cm->groupingid] = $groupid;
}
}
}
/**
* Confirms the user is logged in and has the specified capability.
*
* @param \stdClass $cm
* @param \context $context
* @param string $cap
*/
protected static function require_capability(\stdClass $cm, \context $context, string $cap) {
require_login($cm->course, false, $cm, true, true);
require_capability($cap, $context);
}
}

55
db/mobile.php Normal file
View file

@ -0,0 +1,55 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines mobile handlers.
*
* @package mod_customcert
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$addons = array(
'mod_customcert' => [ // Plugin identifier.
'handlers' => [ // Different places where the plugin will display content.
'issueview' => [ // Handler unique name.
'displaydata' => [
'icon' => $CFG->wwwroot . '/mod/customcert/pix/icon.png',
'class' => '',
],
'delegate' => 'CoreCourseModuleDelegate', // Delegate (where to display the link to the plugin).
'method' => 'mobile_view_activity', // Main function in \mod_customcert\output\mobile.
]
],
'lang' => [ // Language strings that are used in all the handlers.
['code', 'customcert'],
['deleteissueconfirm', 'customcert'],
['file', 'moodle'],
['fullname', 'moodle'],
['getcustomcert', 'customcert'],
['modulenameplural', 'customcert'],
['nothingtodisplay', 'moodle'],
['pluginname', 'customcert'],
['receiveddate', 'customcert'],
['requiredtimenotmet', 'customcert'],
['selectagroup', 'moodle'],
['summaryofissue', 'customcert'],
['viewcustomcertissues', 'customcert']
],
]
);

View file

@ -25,6 +25,15 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$functions = array( $functions = array(
'mod_customcert_delete_issue' => array(
'classname' => 'mod_customcert\external',
'methodname' => 'delete_issue',
'classpath' => '',
'description' => 'Delete an issue for a certificate',
'type' => 'write',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_customcert_save_element' => array( 'mod_customcert_save_element' => array(
'classname' => 'mod_customcert\external', 'classname' => 'mod_customcert\external',
'methodname' => 'save_element', 'methodname' => 'save_element',

88
mobile/pluginfile.php Normal file
View file

@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Serves files for the mobile app.
*
* @package mod_customcert
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* AJAX_SCRIPT - exception will be converted into JSON.
*/
define('AJAX_SCRIPT', true);
/**
* NO_MOODLE_COOKIES - we don't want any cookie.
*/
define('NO_MOODLE_COOKIES', true);
require_once('../../../config.php');
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->dirroot . '/webservice/lib.php');
// Allow CORS requests.
header('Access-Control-Allow-Origin: *');
// Authenticate the user.
$token = required_param('token', PARAM_ALPHANUM);
$certificateid = required_param('certificateid', PARAM_INT);
$userid = required_param('userid', PARAM_INT);
$webservicelib = new webservice();
$authenticationinfo = $webservicelib->authenticate_user($token);
// Check the service allows file download.
$enabledfiledownload = (int) ($authenticationinfo['service']->downloadfiles);
if (empty($enabledfiledownload)) {
throw new webservice_access_exception('Web service file downloading must be enabled in external service settings');
}
$cm = get_coursemodule_from_instance('customcert', $certificateid, 0, false, MUST_EXIST);
$certificate = $DB->get_record('customcert', ['id' => $certificateid], '*', MUST_EXIST);
$template = $DB->get_record('customcert_templates', ['id' => $certificate->templateid], '*', MUST_EXIST);
// Capabilities check.
require_capability('mod/customcert:view', \context_module::instance($cm->id));
if ($userid != $USER->id) {
require_capability('mod/customcert:viewreport', \context_module::instance($cm->id));
} else {
// Make sure the user has met the required time.
if ($certificate->requiredtime) {
if (\mod_customcert\certificate::get_course_time($certificate->course) < ($certificate->requiredtime * 60)) {
exit();
}
}
}
$issue = $DB->get_record('customcert_issues', ['customcertid' => $certificateid, 'userid' => $userid]);
// If we are doing it for the logged in user then we want to issue the certificate.
if (!$issue) {
// If the other user doesn't have an issue, then there is nothing to do.
if ($userid != $USER->id) {
exit();
}
\mod_customcert\certificate::issue_certificate($certificate->id, $USER->id);
}
// Now we want to generate the PDF.
$template = new \mod_customcert\template($template);
$template->generate_pdf(false, $userid);
exit();

View file

@ -0,0 +1,101 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_customcert/mobile_report_page
The page that lists the custom certificates issued
Classes required for JS:
* None
Data attibutes required for JS:
* All data attributes are required
}}
{{=<% %>=}}
<div>
<core-course-module-description description="<% certificate.intro %>" component="mod_customcert" componentId="<% cmid %>"></core-course-module-description>
<%#showgroups%>
<ion-item>
<ion-label>{{ 'plugin.mod_customcert.selectagroup' | translate }}</ion-label>
<ion-select (ionChange)="updateContent({cmid: <% cmid %>, courseid: <% certificate.course %>, group: $event})" interface="popover">
<%#groups%>
<ion-option value="<% id %>" <%#selected%>selected<%/selected%>><% name %></ion-option>
<%/groups%>
</ion-select>
</ion-item>
<%/showgroups%>
<ion-item class="text-center">
<p class="item-heading">{{ 'plugin.mod_customcert.modulenameplural' | translate }}</p>
</ion-item>
<%#hasissues%>
<ion-item>
<ion-grid>
<ion-row>
<ion-col>
<strong>{{ 'plugin.mod_customcert.fullname' | translate }}</strong>
</ion-col>
<ion-col>
<strong>{{ 'plugin.mod_customcert.receiveddate' | translate }}</strong>
</ion-col>
<ion-col>
<strong>{{ 'plugin.mod_customcert.code' | translate }}</strong>
</ion-col>
<ion-col class="text-center">
<strong>{{ 'plugin.mod_customcert.file' | translate }}</strong>
</ion-col>
<%#canmanage%>
<ion-col class="text-center"></ion-col>
<%/canmanage%>
</ion-row>
<%#issues%>
<ion-row>
<ion-col>
<% displayname %>
</ion-col>
<ion-col>
{{ <% timecreated %> | coreToLocaleString }}
</ion-col>
<ion-col>
<% code %>
</ion-col>
<ion-col class="text-center">
<button ion-button icon-only clear [core-download-file]="{fileurl: '<% fileurl %>', timemodified: '<% currenttimestamp %>'}" moduleId="<% cmid %>" courseId="<% certificate.course %>" component="mod_customcert">
<ion-icon name="download"></ion-icon>
</button>
</ion-col>
<%#canmanage%>
<ion-col class="text-center">
<button ion-button icon-only clear core-site-plugins-call-ws name="mod_customcert_delete_issue"
[params]="{certificateid: <% certificate.id %>, issueid: <% issueid %>}"
[preSets]="{getFromCache: 0, saveToCache: 0, typeExpected: 'boolean'}"
confirmMessage="{{ 'plugin.mod_customcert.deleteissueconfirm' | translate }}"
refreshOnSuccess="true">
<ion-icon name="trash"></ion-icon>
</button>
</ion-col>
<%/canmanage%>
</ion-row>
<%/issues%>
</ion-grid>
</ion-item>
<%/hasissues%>
<%^hasissues%>
<ion-item class="text-center">
{{ 'plugin.mod_customcert.nothingtodisplay' | translate }}
</ion-item>
<%/hasissues%>
</div>

View file

@ -0,0 +1,71 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_customcert/mobile_view_activity_page
The main page to view the custom certificate activity
Classes required for JS:
* None
Data attibutes required for JS:
* All data attributes are required
}}
{{=<% %>=}}
<div>
<core-course-module-description description="<% certificate.intro %>" component="mod_customcert" componentId="<% cmid %>"></core-course-module-description>
<%#showgroups%>
<ion-item>
<ion-label>{{ 'plugin.mod_customcert.selectagroup' | translate }}</ion-label>
<ion-select (ionChange)="updateContent({cmid: <% cmid %>, courseid: <% certificate.course %>, group: $event})" interface="popover">
<%#groups%>
<ion-option value="<% id %>" <%#selected%>selected<%/selected%>><% name %></ion-option>
<%/groups%>
</ion-select>
</ion-item>
<%/showgroups%>
<%#showreport%>
<a ion-item class="text-right" core-site-plugins-new-content component="mod_customcert" method="mobile_view_report" [args]="{cmid: <% cmid %>, group: <% groupselected %>}">
{{ 'plugin.mod_customcert.viewcustomcertissues' | translate: {$a: <% numissuesinreport %>} }}
</a>
<%/showreport%>
<%#hasissues%>
<ion-list>
<ion-list-header class="text-center">
<p class="item-heading">{{ 'plugin.mod_customcert.summaryofissue' | translate }}</p>
</ion-list-header>
<%#issues%>
<ion-item class="text-center">
{{ <% timecreated %> | coreToLocaleString }}
</ion-item>
<%/issues%>
</ion-list>
<%/hasissues%>
<%#candownload%>
<ion-item>
<button ion-button block core-course-download-module-main-file moduleId="<% cmid %>" courseId="<% certificate.course %>" component="mod_customcert" [files]="[{fileurl: '<% fileurl %>', timemodified: '<% currenttimestamp %>'}]">
<ion-icon name="download" item-start></ion-icon>
{{ 'plugin.mod_customcert.getcustomcert' | translate }}
</button>
</ion-item>
<%/candownload%>
<%^candownload%>
<ion-item>
<p>{{ 'plugin.mod_customcert.requiredtimenotmet' | translate: {$a: { requiredtime: <% certificate.requiredtime %>} } }}</p>
</ion-item>
<%/candownload%>
</div>

150
tests/external_test.php Normal file
View file

@ -0,0 +1,150 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* File contains the unit tests for the webservices.
*
* @package mod_customcert
* @category test
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Unit tests for the webservices.
*
* @package mod_customcert
* @category test
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_customcert_external_test_testcase extends advanced_testcase {
/**
* Test set up.
*/
public function setUp() {
$this->resetAfterTest();
}
/**
* Test the delete_issue web service.
*/
public function test_delete_issue() {
global $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a custom certificate in the course.
$customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id]);
// Create two users.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
// Enrol them into the course.
$this->getDataGenerator()->enrol_user($student1->id, $course->id);
$this->getDataGenerator()->enrol_user($student2->id, $course->id);
// Issue them both certificates.
$i1 = \mod_customcert\certificate::issue_certificate($customcert->id, $student1->id);
$i2 = \mod_customcert\certificate::issue_certificate($customcert->id, $student2->id);
$this->assertEquals(2, $DB->count_records('customcert_issues'));
$result = \mod_customcert\external::delete_issue($customcert->id, $i2);
// We need to execute the return values cleaning process to simulate the web service server.
external_api::clean_returnvalue(\mod_customcert\external::delete_issue_returns(), $result);
$issues = $DB->get_records('customcert_issues');
$this->assertCount(1, $issues);
$issue = reset($issues);
$this->assertEquals($student1->id, $issue->userid);
}
/**
* Test the delete_issue web service.
*/
public function test_delete_issue_no_login() {
global $DB;
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a custom certificate in the course.
$customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id]);
// Create two users.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
// Enrol them into the course.
$this->getDataGenerator()->enrol_user($student1->id, $course->id);
$this->getDataGenerator()->enrol_user($student2->id, $course->id);
// Issue them both certificates.
$i1 = \mod_customcert\certificate::issue_certificate($customcert->id, $student1->id);
$i2 = \mod_customcert\certificate::issue_certificate($customcert->id, $student2->id);
$this->assertEquals(2, $DB->count_records('customcert_issues'));
// Try and delete without logging in.
$this->expectException('require_login_exception');
\mod_customcert\external::delete_issue($customcert->id, $i2);
}
/**
* Test the delete_issue web service.
*/
public function test_delete_issue_no_capability() {
global $DB;
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create a custom certificate in the course.
$customcert = $this->getDataGenerator()->create_module('customcert', ['course' => $course->id]);
// Create two users.
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$this->setUser($student1);
// Enrol them into the course.
$this->getDataGenerator()->enrol_user($student1->id, $course->id);
$this->getDataGenerator()->enrol_user($student2->id, $course->id);
// Issue them both certificates.
$i1 = \mod_customcert\certificate::issue_certificate($customcert->id, $student1->id);
$i2 = \mod_customcert\certificate::issue_certificate($customcert->id, $student2->id);
$this->assertEquals(2, $DB->count_records('customcert_issues'));
// Try and delete without the required capability.
$this->expectException('required_capability_exception');
\mod_customcert\external::delete_issue($customcert->id, $i2);
}
}

View file

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