MDL-68448 mod_h5pactivity: Add new ws get_h5pactivities_by_courses
authorcescobedo <carlos.escobedo@gmail.com>
Thu, 21 May 2020 19:13:37 +0000 (21:13 +0200)
committercescobedo <carlos.escobedo@gmail.com>
Mon, 25 May 2020 07:12:20 +0000 (09:12 +0200)
This WS is required by the Moodle app in order to fetch
the activity information for displaying it to app users.

mod/h5pactivity/classes/external/get_h5pactivities_by_courses.php [new file with mode: 0644]
mod/h5pactivity/classes/external/h5pactivity_summary_exporter.php [new file with mode: 0644]
mod/h5pactivity/db/services.php
mod/h5pactivity/tests/external/get_h5pactivities_by_courses_test.php [new file with mode: 0644]

diff --git a/mod/h5pactivity/classes/external/get_h5pactivities_by_courses.php b/mod/h5pactivity/classes/external/get_h5pactivities_by_courses.php
new file mode 100644 (file)
index 0000000..e1546dd
--- /dev/null
@@ -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 <carlos@moodle.com>
+ * @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 <carlos@moodle.com>
+ * @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(),
+            ]
+        );
+    }
+}
\ No newline at end of file
diff --git a/mod/h5pactivity/classes/external/h5pactivity_summary_exporter.php b/mod/h5pactivity/classes/external/h5pactivity_summary_exporter.php
new file mode 100644 (file)
index 0000000..0088715
--- /dev/null
@@ -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 <carlos@moodle.com>
+ * @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 <carlos@moodle.com>
+ * @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],
+        ];
+    }
+}
index db62e5e..ee50c8f 100644 (file)
@@ -53,4 +53,15 @@ $functions = [
         'capabilities'  => 'mod/h5pactivity:view',
         'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE],
     ],
         '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],
+    ],
 ];
 ];
diff --git a/mod/h5pactivity/tests/external/get_h5pactivities_by_courses_test.php b/mod/h5pactivity/tests/external/get_h5pactivities_by_courses_test.php
new file mode 100644 (file)
index 0000000..62050d2
--- /dev/null
@@ -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 <carlos@moodle.com>
+ * @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 <carlos@moodle.com>
+ * @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']);
+        }
+    }
+}