MDL-67043 core_h5p: Add new ws to get the H5P trusted file
authorcescobedo <carlos.escobedo@gmail.com>
Thu, 7 Nov 2019 07:47:25 +0000 (08:47 +0100)
committercescobedo <carlos.escobedo@gmail.com>
Thu, 7 Nov 2019 07:47:25 +0000 (08:47 +0100)
h5p/classes/external.php [new file with mode: 0644]
h5p/classes/player.php
h5p/tests/external_test.php [new file with mode: 0644]
h5p/tests/fixtures/find-the-words.h5p [new file with mode: 0644]
lib/db/services.php

diff --git a/h5p/classes/external.php b/h5p/classes/external.php
new file mode 100644 (file)
index 0000000..d1e5d25
--- /dev/null
@@ -0,0 +1,155 @@
+<?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 API for this component.
+ *
+ * @package    core_h5p
+ * @copyright  2019 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p;
+
+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_files;
+use external_warnings;
+
+/**
+ * This is the external API for this component.
+ *
+ * @copyright  2019 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class external extends external_api {
+    /**
+     * get_trusted_h5p_file parameters.
+     *
+     * @since  Moodle 3.8
+     * @return external_function_parameters
+     */
+    public static function get_trusted_h5p_file_parameters() {
+        return new external_function_parameters(
+            [
+                'url' => new external_value(PARAM_URL, 'H5P file url.', VALUE_REQUIRED),
+                'frame' => new external_value(PARAM_INT, 'The frame allow to show the bar options below the content', VALUE_DEFAULT, 0),
+                'export' => new external_value(PARAM_INT, 'The export allow to download the package', VALUE_DEFAULT, 0),
+                'embed' => new external_value(PARAM_INT, 'The embed allow to copy the code to your site', VALUE_DEFAULT, 0),
+                'copyright' => new external_value(PARAM_INT, 'The copyright option', VALUE_DEFAULT, 0)
+            ]
+        );
+    }
+
+    /**
+     * Return the H5P file trusted.
+     *
+     * The Mobile App needs to work with an H5P package which can trust. 
+     * And this H5P package is only trusted by the Mobile App once it's been processed 
+     * by the core checking the right caps, validating the H5P package 
+     * and doing any clean-up process involved.
+     *
+     * @since  Moodle 3.8
+     * @param  string $url H5P file url
+     * @param  int $frame The frame allow to show the bar options below the content
+     * @param  int $export The export allow to download the package
+     * @param  int $embed The embed allow to copy the code to your site
+     * @param  int $copyright The copyright option
+     * @return array
+     * @throws \moodle_exception
+     */
+    public static function get_trusted_h5p_file(string $url, int $frame, int $export, int $embed, int $copyright) {
+
+        $result = [];
+        $warnings = [];
+        $params = external_api::validate_parameters(self::get_trusted_h5p_file_parameters(), [
+            'url' => $url,
+            'frame' => $frame,
+            'export' => $export,
+            'embed' => $embed,
+            'copyright' => $copyright
+        ]);
+        $url = $params['url'];
+        $config = new \stdClass();
+        $config->frame = $params['frame'];
+        $config->export = $params['export'];
+        $config->embed = $params['embed'];
+        $config->copyright = $params['copyright'];
+        try {
+            $h5pplayer = new player($url, $config);
+            $messages = $h5pplayer->get_messages();
+        } catch (\moodle_exception $e) {
+            $messages = (object) [
+                'exception' => $e->getMessage(),
+                'code' => $e->getCode(),
+            ];
+        }
+
+        if (empty($messages->error) && empty($messages->exception)) {
+            // Add H5P assets to the page.
+            $h5pplayer->add_assets_to_page();
+            // Check if there is some error when adding assets to the page.
+            $messages = $h5pplayer->get_messages();
+            if (empty($messages->error)) {
+                $fileh5p = $h5pplayer->get_export_file();
+                $result[] = $fileh5p;
+            }
+        }
+        if (!empty($messages->error)) {
+            foreach ($messages->error as $error) {
+                // We have to apply clean_param because warningcode is a PARAM_ALPHANUM.
+                // And H5P has some error code like 'not-in-whitelist'.
+                $warnings[] = [
+                    'item' => $url,
+                    'warningcode' => clean_param($error->code, PARAM_ALPHANUM),
+                    'message' => $error->message
+                ];
+            }
+        } else if (!empty($messages->exception)) {
+            $warnings[] = [
+                'item' => $url,
+                'warningcode' => $messages->code,
+                'message' => $messages->exception
+            ];
+        }
+
+        return [
+            'files' => $result,
+            'warnings' => $warnings
+        ];
+    }
+
+    /**
+     * get_trusted_h5p_file return
+     *
+     * @since  Moodle 3.8
+     * @return external_description
+     */
+    public static function get_trusted_h5p_file_returns() {
+        return new external_single_structure(
+            [
+                'files'    => new external_files('H5P file trusted.'),
+                'warnings' => new external_warnings()
+            ]
+        );
+    }
+}
index 5605a97..2d0b4d2 100644 (file)
@@ -137,11 +137,11 @@ class player {
 
         $contenturl = \moodle_url::make_pluginfile_url($systemcontext->id, \core_h5p\file_storage::COMPONENT,
             \core_h5p\file_storage::CONTENT_FILEAREA, $this->h5pid, null, null);
-
+        $exporturl = $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]);
         $contentsettings = [
             'library'         => core::libraryToString($this->content['library']),
             'fullScreen'      => $this->content['library']['fullscreen'],
-            'exportUrl'       => $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]),
+            'exportUrl'       => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '',
             'embedCode'       => $this->get_embed_code($this->url->out(),
                 $displayoptions[ core::DISPLAY_OPTION_EMBED ]),
             'resizeCode'      => $this->get_resize_code(),
@@ -444,12 +444,12 @@ class player {
      *
      * @param bool $downloadenabled Whether the option to export the H5P content is enabled.
      *
-     * @return string The URL of the exported file.
+     * @return \moodle_url|null The URL of the exported file.
      */
-    private function get_export_settings(bool $downloadenabled) : string {
+    private function get_export_settings(bool $downloadenabled) : ?\moodle_url {
 
         if ( ! $downloadenabled) {
-            return '';
+            return null;
         }
 
         $systemcontext = \context_system::instance();
@@ -463,7 +463,7 @@ class player {
             "{$slug}{$this->content['id']}.h5p"
         );
 
-        return $url->out();
+        return $url;
     }
 
     /**
@@ -660,4 +660,37 @@ class player {
     public static function get_embed_url(string $url) : \moodle_url {
         return new \moodle_url('/h5p/embed.php', ['url' => $url]);
     }
+
+    /**
+     * Return the export file for Mobile App.
+     *
+     * @return array
+     */
+    public function get_export_file() : array {
+        // Get the export url.
+        $exporturl = $this->get_export_settings(true);
+        // Get the filename of the export url.
+        $path = $exporturl->out_as_local_url();
+        $parts = explode('/', $path);
+        $filename = array_pop($parts);
+        // Get the the export file.
+        $systemcontext = \context_system::instance();
+        $fs = get_file_storage();
+        $fileh5p = $fs->get_file($systemcontext->id,
+            \core_h5p\file_storage::COMPONENT,
+            \core_h5p\file_storage::EXPORT_FILEAREA,
+            0,
+            '/',
+            $filename);
+        // Get the options that the Mobile App needs.
+        $file = [];
+        $file['filename'] = $fileh5p->get_filename();
+        $file['filepath'] = $fileh5p->get_filepath();
+        $file['mimetype'] = $fileh5p->get_mimetype();
+        $file['filesize'] = $fileh5p->get_filesize();
+        $file['timemodified'] = $fileh5p->get_timemodified();
+        $file['fileurl'] = $exporturl->out(false);
+
+        return $file;
+    }
 }
diff --git a/h5p/tests/external_test.php b/h5p/tests/external_test.php
new file mode 100644 (file)
index 0000000..70f613a
--- /dev/null
@@ -0,0 +1,180 @@
+<?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/>.
+
+/**
+ * Core h5p external functions tests.
+ *
+ * @package    core_h5p
+ * @category   external
+ * @copyright  2019 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.8
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+use core_h5p\external;
+use core_h5p\file_storage;
+use core_h5p\autoloader;
+
+/**
+ * Core h5p external functions tests
+ *
+ * @package    core_h5p
+ * @category   external
+ * @copyright  2019 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.8
+ */
+class core_h5p_external_testcase extends externallib_advanced_testcase {
+
+    protected function setUp() {
+        parent::setUp();
+        autoloader::register();
+    }
+
+    /**
+     * test_get_trusted_h5p_file description
+     */
+    public function test_get_trusted_h5p_file() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        // This is a valid .H5P file.
+        $filename = 'find-the-words.h5p';
+        $path = __DIR__ . '/fixtures/'.$filename;
+        $syscontext = \context_system::instance();
+        $filerecord = [
+            'contextid' => $syscontext->id,
+            'component' => \core_h5p\file_storage::COMPONENT,
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/',
+            'filename'  => $filename,
+        ];
+        // Load the h5p file into DB.
+        $fs = get_file_storage();
+        $file = $fs->create_file_from_pathname($filerecord, $path);
+        // Make the URL to pass to the WS.
+        $url  = \moodle_url::make_pluginfile_url(
+            $syscontext->id,
+            \core_h5p\file_storage::COMPONENT,
+            'unittest',
+            0,
+            '/',
+            $filename
+        );
+        // Call the WS.
+        $result = external::get_trusted_h5p_file($url->out(), 0, 0, 0, 0);
+        $result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
+        // Expected result: Just 1 record on files and none on warnings.
+        $this->assertCount(1, $result['files']);
+        $this->assertCount(0, $result['warnings']);
+        // Get the export file in the DB to compare with the ws's results.
+        $fileh5p = $this->get_export_file($filename, $file->get_pathnamehash());
+        $fileh5purl  = \moodle_url::make_pluginfile_url(
+            $syscontext->id,
+            \core_h5p\file_storage::COMPONENT,
+            \core_h5p\file_storage::EXPORT_FILEAREA,
+            '',
+            '',
+            $fileh5p->get_filename()
+        );
+        $this->assertEquals($fileh5p->get_filepath(), $result['files'][0]['filepath']);
+        $this->assertEquals($fileh5p->get_mimetype(), $result['files'][0]['mimetype']);
+        $this->assertEquals($fileh5p->get_filesize(), $result['files'][0]['filesize']);
+        $this->assertEquals($fileh5p->get_timemodified(), $result['files'][0]['timemodified']);
+        $this->assertEquals($fileh5p->get_filename(), $result['files'][0]['filename']);
+        $this->assertEquals($fileh5purl->out(), $result['files'][0]['fileurl']);
+    }
+
+    /**
+     * test_h5p_invalid_url description
+     */
+    public function test_h5p_invalid_url() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Create an empty url.
+        $urlempty = '';
+        $result = external::get_trusted_h5p_file($urlempty, 0, 0, 0, 0);
+        $result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
+        // Expected result: Just 1 record on warnings and none on files.
+        $this->assertCount(0, $result['files']);
+        $this->assertCount(1, $result['warnings']);
+        // Check the warnings to be sure that h5pinvalidurl is the message by moodle_exception.
+        $this->assertEquals($urlempty, $result['warnings'][0]['item']);
+        $this->assertEquals(get_string('h5pinvalidurl', 'core_h5p'), $result['warnings'][0]['message']);
+    }
+
+    /**
+     * test_h5p_file_not_found description
+     */
+    public function test_h5p_file_not_found() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Create a valid url with an h5pfile which doesn't exist in DB.
+        $syscontext = \context_system::instance();
+        $filenotfoundurl  = \moodle_url::make_pluginfile_url(
+            $syscontext->id,
+            \core_h5p\file_storage::COMPONENT,
+            'unittest',
+            0,
+            '/',
+            'notfound.h5p'
+        );
+        // Call the ws.
+        $result = external::get_trusted_h5p_file($filenotfoundurl->out(), 0, 0, 0, 0);
+        $result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
+        // Expected result: Just 1 record on warnings and none on files.
+        $this->assertCount(0, $result['files']);
+        $this->assertCount(1, $result['warnings']);
+        // Check the warnings to be sure that h5pfilenotfound is the message by h5p error.
+        $this->assertEquals($filenotfoundurl->out(), $result['warnings'][0]['item']);
+        $this->assertEquals(get_string('h5pfilenotfound', 'core_h5p'), $result['warnings'][0]['message']);
+    }
+
+    /**
+     * Get the H5P export file.
+     *
+     * @param string $filename
+     * @param string $pathnamehash
+     * @return stored_file
+     */
+    protected function get_export_file($filename, $pathnamehash) {
+        global $DB;
+
+        // Simulate the filenameexport using slug as H5P does.
+        $id = $DB->get_field('h5p', 'id', ['pathnamehash' => $pathnamehash]);
+        $filenameexport = basename($filename, '.h5p').'-'.$id.'-'.$id.'.h5p';
+        $syscontext = \context_system::instance();
+        $fs = get_file_storage();
+        $fileh5p = $fs->get_file($syscontext->id,
+            \core_h5p\file_storage::COMPONENT,
+            \core_h5p\file_storage::EXPORT_FILEAREA,
+            0,
+            '/',
+            $filenameexport);
+        return $fileh5p;
+    }
+}
diff --git a/h5p/tests/fixtures/find-the-words.h5p b/h5p/tests/fixtures/find-the-words.h5p
new file mode 100644 (file)
index 0000000..f7308b1
Binary files /dev/null and b/h5p/tests/fixtures/find-the-words.h5p differ
index 15adc29..845a24c 100644 (file)
@@ -2701,7 +2701,17 @@ $functions = array(
         'description' => 'Drag and drop categories',
         'type'        => 'write',
         'ajax'        => 'true'
-    )
+    ),
+    'core_h5p_get_trusted_h5p_file' => [
+        'classname'     => 'core_h5p\external',
+        'methodname'    => 'get_trusted_h5p_file',
+        'classpath'     => '',
+        'description'   => 'Get the H5P file cleaned for Mobile App.',
+        'type'          => 'read',
+        'ajax'          => 'true',
+        'capabilities'  => '',
+        'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE],
+    ],
 );
 
 $services = array(