MDL-68448 mod_h5pactivity: Add new ws get_h5pactivities_by_courses

This WS is required by the Moodle app in order to fetch
the activity information for displaying it to app users.
This commit is contained in:
cescobedo
2020-05-25 09:12:20 +02:00
parent 14b463c92a
commit 9fa0e253ae
4 changed files with 570 additions and 0 deletions
@@ -0,0 +1,136 @@
<?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/>.
/**
* This is the external method for returning a list of h5p activities.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/externallib.php');
use external_api;
use external_function_parameters;
use external_value;
use external_single_structure;
use external_multiple_structure;
use external_util;
use external_warnings;
use context_module;
use core_h5p\factory;
/**
* This is the external method for returning a list of h5p activities.
*
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_h5pactivities_by_courses extends external_api {
/**
* Parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Course id'), 'Array of course ids', VALUE_DEFAULT, []
),
]
);
}
/**
* Returns a list of h5p activities in a provided list of courses.
* If no list is provided all h5p activities that the user can view will be returned.
*
* @param array $courseids course ids
* @return array of h5p activities and warnings
* @since Moodle 3.9
*/
public static function execute(array $courseids): array {
global $PAGE;
$warnings = [];
$returnedh5pactivities = [];
$params = external_api::validate_parameters(self::execute_parameters(), [
'courseids' => $courseids
]);
$mycourses = [];
if (empty($params['courseids'])) {
$mycourses = enrol_get_my_courses();
$params['courseids'] = array_keys($mycourses);
}
// Ensure there are courseids to loop through.
if (!empty($params['courseids'])) {
$factory = new factory();
list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses);
$output = $PAGE->get_renderer('core');
// Get the h5p activities in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$h5pactivities = get_all_instances_in_courses('h5pactivity', $courses);
foreach ($h5pactivities as $h5pactivity) {
$context = context_module::instance($h5pactivity->coursemodule);
// Remove fields that are not from the h5p activity (added by get_all_instances_in_courses).
unset($h5pactivity->coursemodule, $h5pactivity->context,
$h5pactivity->visible, $h5pactivity->section,
$h5pactivity->groupmode, $h5pactivity->groupingid);
$exporter = new h5pactivity_summary_exporter($h5pactivity,
['context' => $context, 'factory' => $factory]);
$summary = $exporter->export($output);
$returnedh5pactivities[] = $summary;
}
}
$result = [
'h5pactivities' => $returnedh5pactivities,
'warnings' => $warnings
];
return $result;
}
/**
* Describes the get_h5pactivities_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.9
*/
public static function execute_returns() {
return new external_single_structure(
[
'h5pactivities' => new external_multiple_structure(
h5pactivity_summary_exporter::get_read_structure()
),
'warnings' => new external_warnings(),
]
);
}
}
@@ -0,0 +1,241 @@
<?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/>.
/**
* Class for exporting h5p activity data.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\external;
use core\external\exporter;
use renderer_base;
use external_util;
use external_files;
use core_h5p\factory;
use core_h5p\api;
/**
* Class for exporting h5p activity data.
*
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class h5pactivity_summary_exporter extends exporter {
/**
* Properties definition.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => [
'type' => PARAM_INT,
'description' => 'The primary key of the record.',
],
'course' => [
'type' => PARAM_INT,
'description' => 'Course id this h5p activity is part of.',
],
'name' => [
'type' => PARAM_TEXT,
'description' => 'The name of the activity module instance.',
],
'timecreated' => [
'type' => PARAM_INT,
'description' => 'Timestamp of when the instance was added to the course.',
'optional' => true,
],
'timemodified' => [
'type' => PARAM_INT,
'description' => 'Timestamp of when the instance was last modified.',
'optional' => true,
],
'intro' => [
'default' => '',
'type' => PARAM_RAW,
'description' => 'H5P activity description.',
'null' => NULL_ALLOWED,
],
'introformat' => [
'choices' => [FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN],
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'The format of the intro field.',
],
'grade' => [
'type' => PARAM_INT,
'default' => 0,
'description' => 'The maximum grade for submission.',
'optional' => true,
],
'displayoptions' => [
'type' => PARAM_INT,
'default' => 0,
'description' => 'H5P Button display options.',
],
'enabletracking' => [
'type' => PARAM_INT,
'default' => 1,
'description' => 'Enable xAPI tracking.',
],
'grademethod' => [
'type' => PARAM_INT,
'default' => 1,
'description' => 'Which H5P attempt is used for grading.',
],
'contenthash' => [
'type' => PARAM_ALPHANUM,
'description' => 'Sha1 hash of file content.',
'optional' => true,
],
];
}
/**
* Related objects definition.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
'factory' => 'core_h5p\\factory'
];
}
/**
* Other properties definition.
*
* @return array
*/
protected static function define_other_properties() {
return [
'coursemodule' => [
'type' => PARAM_INT
],
'introfiles' => [
'type' => external_files::get_properties_for_exporter(),
'multiple' => true
],
'package' => [
'type' => external_files::get_properties_for_exporter(),
'multiple' => true
],
'deployedfile' => [
'optional' => true,
'description' => 'H5P file deployed.',
'type' => [
'filename' => array(
'type' => PARAM_FILE,
'description' => 'File name.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
),
'filepath' => array(
'type' => PARAM_PATH,
'description' => 'File path.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
),
'filesize' => array(
'type' => PARAM_INT,
'description' => 'File size.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
),
'fileurl' => array(
'type' => PARAM_URL,
'description' => 'Downloadable file url.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
),
'timemodified' => array(
'type' => PARAM_INT,
'description' => 'Time modified.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
),
'mimetype' => array(
'type' => PARAM_RAW,
'description' => 'File mime type.',
'optional' => true,
'null' => NULL_NOT_ALLOWED,
)
]
],
];
}
/**
* Assign values to the defined other properties.
*
* @param renderer_base $output The output renderer object.
* @return array
*/
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$factory = $this->related['factory'];
$values = [
'coursemodule' => $context->instanceid,
];
$values['introfiles'] = external_util::get_area_files($context->id, 'mod_h5pactivity', 'intro', false, false);
$values['package'] = external_util::get_area_files($context->id, 'mod_h5pactivity', 'package', false, false);
// Only if this H5P activity has been deployed, return the exported file.
$fileh5p = api::get_export_info_from_context_id($context->id, $factory, 'mod_h5pactivity', 'package');
if ($fileh5p) {
$values['deployedfile'] = $fileh5p;
}
return $values;
}
/**
* Get the formatting parameters for the intro.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_intro() {
return [
'component' => 'mod_h5pactivity',
'filearea' => 'intro',
'options' => ['noclean' => true],
];
}
/**
* Get the formatting parameters for the package.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_package() {
return [
'component' => 'mod_h5pactivity',
'filearea' => 'package',
'itemid' => 0,
'options' => ['noclean' => true],
];
}
}
+11
View File
@@ -53,4 +53,15 @@ $functions = [
'capabilities' => 'mod/h5pactivity:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
'mod_h5pactivity_get_h5pactivities_by_courses' => [
'classname' => 'mod_h5pactivity\external\get_h5pactivities_by_courses',
'methodname' => 'execute',
'classpath' => '',
'description' => 'Returns a list of h5p activities in a list of
provided courses, if no list is provided all h5p activities
that the user can view will be returned.',
'type' => 'read',
'capabilities' => 'mod/h5pactivity:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
];
@@ -0,0 +1,182 @@
<?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/>.
/**
* External function test for get_h5pactivities_by_courses.
*
* @package mod_h5pactivity
* @category external
* @since Moodle 3.9
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_h5pactivity\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use external_api;
use externallib_advanced_testcase;
use stdClass;
use context_module;
/**
* External function test for get_h5pactivities_by_courses.
*
* @package mod_h5pactivity
* @copyright 2020 Carlos Escobedo <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_h5pactivities_by_courses_testcase extends externallib_advanced_testcase {
/**
* Test test_get_h5pactivities_by_courses user student.
*/
public function test_get_h5pactivities_by_courses() {
global $CFG, $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Create 2 courses.
// Course 1 -> 2 activities with H5P files package without deploy.
// Course 2 -> 1 activity with H5P file package deployed.
$course1 = $this->getDataGenerator()->create_course();
$params = [
'course' => $course1->id,
'packagefilepath' => $CFG->dirroot.'/h5p/tests/fixtures/filltheblanks.h5p',
'introformat' => 1
];
$activities[] = $this->getDataGenerator()->create_module('h5pactivity', $params);
// Add filename to make easier the asserts.
$activities[0]->filename = 'filltheblanks.h5p';
$params = [
'course' => $course1->id,
'packagefilepath' => $CFG->dirroot.'/h5p/tests/fixtures/greeting-card-887.h5p',
'introformat' => 1
];
$activities[] = $this->getDataGenerator()->create_module('h5pactivity', $params);
// Add filename to make easier the asserts.
$activities[1]->filename = 'greeting-card-887.h5p';
$course2 = $this->getDataGenerator()->create_course();
$params = [
'course' => $course2->id,
'packagefilepath' => $CFG->dirroot.'/h5p/tests/fixtures/guess-the-answer.h5p',
'introformat' => 1
];
$activities[] = $this->getDataGenerator()->create_module('h5pactivity', $params);
$activities[2]->filename = 'guess-the-answer.h5p';
$context = context_module::instance($activities[2]->cmid);
// Create a fake deploy H5P file.
$generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
$deployedfile = $generator->create_export_file($activities[2]->filename, $context->id, 'mod_h5pactivity', 'package');
// Create a user and enrol as student in both courses.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$maninstance1 = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'manual'], '*', MUST_EXIST);
$maninstance2 = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'manual'], '*', MUST_EXIST);
$manual = enrol_get_plugin('manual');
$manual->enrol_user($maninstance1, $user->id, $studentrole->id);
$manual->enrol_user($maninstance2, $user->id, $studentrole->id);
// Check the activities returned by the first course.
$this->setUser($user);
$courseids = [$course1->id];
$result = get_h5pactivities_by_courses::execute($courseids);
$result = external_api::clean_returnvalue(get_h5pactivities_by_courses::execute_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(2, $result['h5pactivities']);
$this->assert_activities($activities, $result);
$this->assertNotContains('deployedfile', $result['h5pactivities'][0]);
$this->assertNotContains('deployedfile', $result['h5pactivities'][1]);
// Call the external function without passing course id.
// Expected result, all the courses, course1 and course2.
$result = get_h5pactivities_by_courses::execute([]);
$result = external_api::clean_returnvalue(get_h5pactivities_by_courses::execute_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(3, $result['h5pactivities']);
// We need to sort the $result by id.
// Because we are not sure how it is ordered with more than one course.
array_multisort(array_map(function($element) {
return $element['id'];
}, $result['h5pactivities']), SORT_ASC, $result['h5pactivities']);
$this->assert_activities($activities, $result);
$this->assertNotContains('deployedfile', $result['h5pactivities'][0]);
$this->assertNotContains('deployedfile', $result['h5pactivities'][1]);
// Only the activity from the second course has been deployed.
$this->assertEquals($deployedfile['filename'], $result['h5pactivities'][2]['deployedfile']['filename']);
$this->assertEquals($deployedfile['filepath'], $result['h5pactivities'][2]['deployedfile']['filepath']);
$this->assertEquals($deployedfile['filesize'], $result['h5pactivities'][2]['deployedfile']['filesize']);
$this->assertEquals($deployedfile['timemodified'], $result['h5pactivities'][2]['deployedfile']['timemodified']);
$this->assertEquals($deployedfile['mimetype'], $result['h5pactivities'][2]['deployedfile']['mimetype']);
$this->assertEquals($deployedfile['fileurl'], $result['h5pactivities'][2]['deployedfile']['fileurl']);
// Unenrol user from second course.
$manual->unenrol_user($maninstance2, $user->id);
// Remove the last activity from the array.
array_pop($activities);
// Call the external function without passing course id.
$result = get_h5pactivities_by_courses::execute([]);
$result = external_api::clean_returnvalue(get_h5pactivities_by_courses::execute_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertCount(2, $result['h5pactivities']);
$this->assert_activities($activities, $result);
// Call for the second course we unenrolled the user from, expected warning.
$result = get_h5pactivities_by_courses::execute([$course2->id]);
$result = external_api::clean_returnvalue(get_h5pactivities_by_courses::execute_returns(), $result);
$this->assertCount(1, $result['warnings']);
$this->assertEquals('1', $result['warnings'][0]['warningcode']);
$this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
}
/**
* Create a scenario to use into the tests.
*
* @param array $activities list of H5P activities.
* @param array $result list of H5P activities by WS.
* @return void
*/
protected function assert_activities(array $activities, array $result): void {
$total = count($result);
for ($i = 0; $i < $total; $i++) {
$this->assertEquals($activities[$i]->id, $result['h5pactivities'][$i]['id']);
$this->assertEquals($activities[$i]->course, $result['h5pactivities'][$i]['course']);
$this->assertEquals($activities[$i]->name, $result['h5pactivities'][$i]['name']);
$this->assertEquals($activities[$i]->timecreated, $result['h5pactivities'][$i]['timecreated']);
$this->assertEquals($activities[$i]->timemodified, $result['h5pactivities'][$i]['timemodified']);
$this->assertEquals($activities[$i]->intro, $result['h5pactivities'][$i]['intro']);
$this->assertEquals($activities[$i]->introformat, $result['h5pactivities'][$i]['introformat']);
$this->assertEquals([], $result['h5pactivities'][$i]['introfiles']);
$this->assertEquals($activities[$i]->grade, $result['h5pactivities'][$i]['grade']);
$this->assertEquals($activities[$i]->displayoptions, $result['h5pactivities'][$i]['displayoptions']);
$this->assertEquals($activities[$i]->enabletracking, $result['h5pactivities'][$i]['enabletracking']);
$this->assertEquals($activities[$i]->grademethod, $result['h5pactivities'][$i]['grademethod']);
$this->assertEquals($activities[$i]->cmid, $result['h5pactivities'][$i]['coursemodule']);
$this->assertEquals($activities[$i]->filename, $result['h5pactivities'][$i]['package'][0]['filename']);
}
}
}