MDL-67814 core_h5p: added renderer and editor classes
authorVíctor Déniz Falcón <victor@moodle.com>
Sun, 12 Apr 2020 21:30:38 +0000 (22:30 +0100)
committerAdrian Greeve <abgreeve@gmail.com>
Thu, 16 Apr 2020 03:29:35 +0000 (11:29 +0800)
h5p/amd/build/editor_display.min.js [new file with mode: 0644]
h5p/amd/build/editor_display.min.js.map [new file with mode: 0644]
h5p/amd/src/editor_display.js [new file with mode: 0644]
h5p/classes/editor.php [new file with mode: 0644]
h5p/classes/helper.php
h5p/classes/output/h5peditor.php [new file with mode: 0644]
h5p/classes/output/renderer.php [new file with mode: 0644]
h5p/templates/h5peditor.mustache [new file with mode: 0644]
h5p/tests/editor_test.php [new file with mode: 0644]

diff --git a/h5p/amd/build/editor_display.min.js b/h5p/amd/build/editor_display.min.js
new file mode 100644 (file)
index 0000000..8d7d442
Binary files /dev/null and b/h5p/amd/build/editor_display.min.js differ
diff --git a/h5p/amd/build/editor_display.min.js.map b/h5p/amd/build/editor_display.min.js.map
new file mode 100644 (file)
index 0000000..8c65694
Binary files /dev/null and b/h5p/amd/build/editor_display.min.js.map differ
diff --git a/h5p/amd/src/editor_display.js b/h5p/amd/src/editor_display.js
new file mode 100644 (file)
index 0000000..9adbfde
--- /dev/null
@@ -0,0 +1,61 @@
+// 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 module handles the display of the H5P authoring tool.
+ *
+ * @module     core_h5p/editor_display
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import $ from 'jquery';
+
+/**
+ * Display the H5P authoring tool.
+ *
+ * @param {String} elementId Root element.
+ */
+export const init = (elementId) => {
+    const editorwrapper = $('#' + elementId);
+    const editor = $('.h5p-editor');
+    const mform = editor.closest("form");
+    const editorupload = $("h5p-editor-upload");
+    const h5plibrary = $('input[name="h5plibrary"]');
+    const h5pparams = $('input[name="h5pparams"]');
+    const inputname = $('input[name="name"]');
+    const h5paction = $('input[name="h5paction"]');
+
+    // Cancel validation and submission of form if clicking cancel button.
+    const cancelSubmitCallback = function($button) {
+        return $button.is('[name="cancel"]');
+    };
+
+    h5paction.val("create");
+
+    H5PEditor.init(
+        mform,
+        h5paction,
+        editorupload,
+        editorwrapper,
+        editor,
+        h5plibrary,
+        h5pparams,
+        '',
+        inputname,
+        cancelSubmitCallback
+    );
+};
diff --git a/h5p/classes/editor.php b/h5p/classes/editor.php
new file mode 100644 (file)
index 0000000..9ad479f
--- /dev/null
@@ -0,0 +1,453 @@
+<?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/>.
+
+/**
+ * H5P editor class.
+ *
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p;
+
+use core_h5p\local\library\autoloader;
+use core_h5p\output\h5peditor as editor_renderer;
+use H5PCore;
+use H5peditor;
+use stdClass;
+use coding_exception;
+use MoodleQuickForm;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * H5P editor class, for editing local H5P content.
+ *
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class editor {
+
+    /**
+     * @var core The H5PCore object.
+     */
+    private $core;
+
+    /**
+     * @var H5peditor $h5peditor The H5P Editor object.
+     */
+    private $h5peditor;
+
+    /**
+     * @var int Id of the H5P content from the h5p table.
+     */
+    private $id = null;
+
+    /**
+     * @var array Existing H5P content instance before edition.
+     */
+    private $oldcontent = null;
+
+    /**
+     * @var stored_file File of ane existing H5P content before edition.
+     */
+    private $oldfile = null;
+
+    /**
+     * @var array File area to save the file of a new H5P content.
+     */
+    private $filearea = null;
+
+    /**
+     * @var string H5P Library name
+     */
+    private $library = null;
+
+    /**
+     * Inits the H5P editor.
+     */
+    public function __construct() {
+        autoloader::register();
+
+        $factory = new factory();
+        $this->h5peditor = $factory->get_editor();
+        $this->core = $factory->get_core();
+    }
+
+    /**
+     * Loads an existing content for edition.
+     *
+     * If the H5P content or its file can't be retrieved, it is not possible to edit the content.
+     *
+     * @param int $id Id of the H5P content from the h5p table.
+     *
+     * @return void
+     */
+    public function set_content(int $id): void {
+        $this->id = $id;
+
+        // Load the present content.
+        $this->oldcontent = $this->core->loadContent($id);
+        if ($this->oldcontent === null) {
+            print_error('invalidelementid');
+        }
+
+        // Identify the content type library.
+        $this->library = H5PCore::libraryToString($this->oldcontent['library']);
+
+        // Get current file and its file area.
+        $pathnamehash = $this->oldcontent['pathnamehash'];
+        $fs = get_file_storage();
+        $oldfile = $fs->get_file_by_hash($pathnamehash);
+        if (!$oldfile) {
+            print_error('invalidelementid');
+        }
+        $this->set_filearea(
+            $oldfile->get_contextid(),
+            $oldfile->get_component(),
+            $oldfile->get_filearea(),
+            $oldfile->get_itemid(),
+            $oldfile->get_filepath(),
+            $oldfile->get_filename(),
+            $oldfile->get_userid()
+        );
+        $this->oldfile = $oldfile;
+    }
+
+    /**
+     * Sets the content type library and the file area to create a new H5P content.
+     *
+     * Note: this method must be used to create new content, to edit an existing
+     * H5P content use only set_content with the ID from the H5P table.
+     *
+     * @param string $library Library of the H5P content type to create.
+     * @param int $contextid Context where the file of the H5P content will be stored.
+     * @param string $component Component where the file of the H5P content will be stored.
+     * @param string $filearea File area where the file of the H5P content will be stored.
+     * @param int $itemid Item id file of the H5P content.
+     * @param string $filepath File path where the file of the H5P content will be stored.
+     * @param null|string $filename H5P content file name.
+     * @param null|int $userid H5P content file owner userid (default will use $USER->id).
+     *
+     * @return void
+     */
+    public function set_library(string $library, int $contextid, string $component, string $filearea,
+            ?int $itemid = 0, string $filepath = '/', ?string $filename = null, ?int $userid = null): void {
+
+        $this->library = $library;
+        $this->set_filearea($contextid, $component, $filearea, $itemid, $filepath, $filename, $userid);
+    }
+
+    /**
+     * Sets the Moodle file area where the file of a new H5P content will be stored.
+     *
+     * @param int $contextid Context where the file of the H5P content will be stored.
+     * @param string $component Component where the file of the H5P content will be stored.
+     * @param string $filearea File area where the file of the H5P content will be stored.
+     * @param int $itemid Item id file of the H5P content.
+     * @param string $filepath File path where the file of the H5P content will be stored.
+     * @param null|string $filename H5P content file name.
+     * @param null|int $userid H5P content file owner userid (default will use $USER->id).
+     *
+     * @return void
+     */
+    private function set_filearea(int $contextid, string $component, string $filearea,
+            int $itemid, string $filepath = '/', ?string $filename = null, ?int $userid = null): void {
+        global $USER;
+
+        $this->filearea = [
+            'contextid' => $contextid,
+            'component' => $component,
+            'filearea' => $filearea,
+            'itemid' => $itemid,
+            'filepath' => $filepath,
+            'filename' => $filename,
+            'userid' => $userid ?? $USER->id,
+        ];
+    }
+
+    /**
+     * Adds an H5P editor to a form.
+     *
+     * @param MoodleQuickForm $mform Moodle Quick Form
+     *
+     * @return void
+     */
+    public function add_editor_to_form(MoodleQuickForm $mform): void {
+        global $PAGE;
+
+        $this->add_assets_to_page();
+
+        $data = $this->data_preprocessing();
+
+        // Hidden fields used bu H5P editor.
+        $mform->addElement('hidden', 'h5plibrary', $data->h5plibrary);
+        $mform->setType('h5plibrary', PARAM_RAW);
+
+        $mform->addElement('hidden', 'h5pparams', $data->h5pparams);
+        $mform->setType('h5pparams', PARAM_RAW);
+
+        $mform->addElement('hidden', 'h5paction');
+        $mform->setType('h5paction', PARAM_ALPHANUMEXT);
+
+        // Render H5P editor.
+        $ui = new editor_renderer($data);
+        $editorhtml = $PAGE->get_renderer('core_h5p')->render($ui);
+        $mform->addElement('html', $editorhtml);
+    }
+
+    /**
+     * Creates or updates an H5P content.
+     *
+     * @param stdClass $content Object containing all the necessary data.
+     *
+     * @return int Content id
+     */
+    public function save_content(stdClass $content): int {
+
+        if (empty($content->h5pparams)) {
+            throw new coding_exception('Missing H5P params.');
+        }
+
+        if (!isset($content->h5plibrary)) {
+            throw new coding_exception('Missing H5P library.');
+        }
+
+        if ($content->h5plibrary != $this->library) {
+            throw new coding_exception("Wrong H5P library.");
+        }
+
+        $content->params = $content->h5pparams;
+
+        if (!empty($this->oldcontent)) {
+            $content->id = $this->oldcontent['id'];
+            // Get old parameters for comparison.
+            $oldparams = json_decode($this->oldcontent['params']) ?? null;
+            // Keep the existing display options.
+            $content->disable = $this->oldcontent['disable'];
+            $oldlib = $this->oldcontent['library'];
+        } else {
+            $oldparams = null;
+            $oldlib = null;
+        }
+
+        // Prepare library data to be save.
+        $content->library = H5PCore::libraryFromString($content->h5plibrary);
+        $content->library['libraryId'] = $this->core->h5pF->getLibraryId($content->library['machineName'],
+            $content->library['majorVersion'],
+            $content->library['minorVersion']);
+
+        // Prepare current parameters.
+        $params = json_decode($content->params);
+
+        $modified = false;
+        if (empty($params->metadata)) {
+            $params->metadata = new stdClass();
+            $modified = true;
+        }
+        if (empty($params->metadata->title)) {
+            // Use a default string if not available.
+            $params->metadata->title = 'Untitled';
+            $modified = true;
+        }
+        if (!isset($content->title)) {
+            $content->title = $params->metadata->title;
+        }
+        if ($modified) {
+            $content->params = json_encode($params);
+        }
+
+        // Save content.
+        $content->id = $this->core->saveContent((array)$content);
+
+        // Move any uploaded images or files. Determine content dependencies.
+        $this->h5peditor->processParameters($content, $content->library, $params->params, $oldlib, $oldparams);
+
+        $this->update_h5p_file($content);
+
+        return $content->id;
+    }
+
+    /**
+     * Creates or updates the H5P file and the related database data.
+     *
+     * @param stdClass $content Object containing all the necessary data.
+     *
+     * @return void
+     */
+    private function update_h5p_file(stdClass $content): void {
+        global $USER;
+
+        // Keep title before filtering params.
+        $title = $content->title;
+        $contentarray = $this->core->loadContent($content->id);
+        $contentarray['title'] = $title;
+
+        // Generates filtered params and export file.
+        $this->core->filterParameters($contentarray);
+
+        $slug = isset($contentarray['slug']) ? $contentarray['slug'] . '-' : '';
+        $filename = $contentarray['id'] ?? $contentarray['title'];
+        $filename = $slug . $filename . '.h5p';
+        $file = $this->core->fs->get_export_file($filename);
+        $fs = get_file_storage();
+
+        if ($file) {
+            $fields['contenthash'] = $file->get_contenthash();
+
+            // Delete old file if any.
+            if (!empty($this->oldfile)) {
+                $this->oldfile->delete();
+            }
+            // Create new file.
+            if (empty($this->filearea['filename'])) {
+                $this->filearea['filename'] = $contentarray['slug'] . '.h5p';
+            }
+            $newfile = $fs->create_file_from_storedfile($this->filearea, $file);
+            if (empty($this->oldcontent)) {
+                $pathnamehash = $newfile->get_pathnamehash();
+            } else {
+                $pathnamehash = $this->oldcontent['pathnamehash'];
+            }
+
+            // Update hash fields in the h5p table.
+            $fields['pathnamehash'] = $pathnamehash;
+            $this->core->h5pF->updateContentFields($contentarray['id'], $fields);
+        }
+    }
+
+    /**
+     * Add required assets for displaying the editor.
+     *
+     * @return void
+     * @throws coding_exception If page header is already printed.
+     */
+    private function add_assets_to_page(): void {
+        global $PAGE, $CFG;
+
+        if ($PAGE->headerprinted) {
+            throw new coding_exception('H5P assets cannot be added when header is already printed.');
+        }
+
+        $context = \context_system::instance();
+
+        $settings = helper::get_core_assets();
+
+        // Use jQuery and styles from core.
+        $assets = [
+            'css' => $settings['core']['styles'],
+            'js' => $settings['core']['scripts']
+        ];
+
+        // Use relative URL to support both http and https.
+        $url = autoloader::get_h5p_editor_library_url()->out();
+        $url = '/' . preg_replace('/^[^:]+:\/\/[^\/]+\//', '', $url);
+
+        // Make sure files are reloaded for each plugin update.
+        $cachebuster = helper::get_cache_buster();
+
+        // Add editor styles.
+        foreach (H5peditor::$styles as $style) {
+            $assets['css'][] = $url . $style . $cachebuster;
+        }
+
+        // Add editor JavaScript.
+        foreach (H5peditor::$scripts as $script) {
+            // We do not want the creator of the iframe inside the iframe.
+            if ($script !== 'scripts/h5peditor-editor.js') {
+                $assets['js'][] = $url . $script . $cachebuster;
+            }
+        }
+
+        // Add JavaScript with library framework integration (editor part).
+        $PAGE->requires->js(autoloader::get_h5p_editor_library_url('scripts/h5peditor-editor.js' . $cachebuster), true);
+        $PAGE->requires->js(autoloader::get_h5p_editor_library_url('scripts/h5peditor-init.js' . $cachebuster), true);
+
+        // Add translations.
+        $language = framework::get_language();
+        $languagescript = "language/{$language}.js";
+
+        if (!file_exists("{$CFG->dirroot}" . autoloader::get_h5p_editor_library_base($languagescript))) {
+            $languagescript = 'language/en.js';
+        }
+        $PAGE->requires->js(autoloader::get_h5p_editor_library_url($languagescript . $cachebuster),
+            true);
+
+        // Add JavaScript settings.
+        $root = $CFG->wwwroot;
+        $filespathbase = "{$root}/pluginfile.php/{$context->id}/core_h5p/";
+
+        $factory = new factory();
+        $contentvalidator = $factory->get_content_validator();
+
+        $editorajaxtoken = core::createToken(editor_ajax::EDITOR_AJAX_TOKEN);
+        $settings['editor'] = [
+            'filesPath' => $filespathbase . 'editor',
+            'fileIcon' => [
+                'path' => $url . 'images/binary-file.png',
+                'width' => 50,
+                'height' => 50,
+            ],
+            'ajaxPath' => $CFG->wwwroot . '/h5p/' . "ajax.php?contextId={$context->id}&token={$editorajaxtoken}&action=",
+            'libraryUrl' => $url,
+            'copyrightSemantics' => $contentvalidator->getCopyrightSemantics(),
+            'metadataSemantics' => $contentvalidator->getMetadataSemantics(),
+            'assets' => $assets,
+            'apiVersion' => H5PCore::$coreApi,
+            'language' => $language,
+        ];
+
+        if (!empty($this->id)) {
+            $settings['editor']['nodeVersionId'] = $this->id;
+
+            // Override content URL.
+            $contenturl = "{$root}/pluginfile.php/{$context->id}/core_h5p/content/{$this->id}";
+            $settings['contents']['cid-' . $this->id]['contentUrl'] = $contenturl;
+        }
+
+        $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
+    }
+
+    /**
+     * Preprocess the data sent through the form to the H5P JS Editor Library.
+     *
+     * @return stdClass
+     */
+    private function data_preprocessing(): stdClass {
+
+        $defaultvalues = [
+            'id' => $this->id,
+            'h5plibrary' => $this->library,
+        ];
+
+        // In case both contentid and library have values, content(edition) takes precedence over library(creation).
+        if (empty($this->oldcontent)) {
+            $maincontentdata = ['params' => (object)[]];
+        } else {
+            $params = $this->core->filterParameters($this->oldcontent);
+            $maincontentdata = ['params' => json_decode($params)];
+            if (isset($this->oldcontent['metadata'])) {
+                $maincontentdata['metadata'] = $this->oldcontent['metadata'];
+            }
+        }
+
+        $defaultvalues['h5pparams'] = json_encode($maincontentdata, true);
+
+        return (object) $defaultvalues;
+    }
+}
index 1eca6a4..f040d52 100644 (file)
@@ -370,100 +370,4 @@ class helper {
 
         return $settings;
     }
-
-    /**
-     * Add required assets for displaying the editor.
-     *
-     * @param int $id Id of the content being edited. null for creating new content.
-     * @param string $mformid Id of Moodle form
-     *
-     * @return void
-     */
-    public static function add_editor_assets_to_page(?int $id = null, string $mformid = null): void {
-        global $PAGE, $CFG;
-
-        $libeditorpath = 'lib/h5peditor';
-
-        // Require classes from H5P third party library.
-        autoloader::register();
-
-        $context = context_system::instance();
-
-        $settings = self::get_core_assets();
-
-        // Use jQuery and styles from core.
-        $assets = array(
-            'css' => $settings['core']['styles'],
-            'js' => $settings['core']['scripts']
-        );
-
-        // Use relative URL to support both http and https.
-        $url = $CFG->wwwroot . '/'. $libeditorpath . '/';
-        $url = '/' . preg_replace('/^[^:]+:\/\/[^\/]+\//', '', $url);
-
-        // Make sure files are reloaded for each plugin update.
-        $cachebuster = self::get_cache_buster();
-
-        // Add editor styles.
-        foreach (H5peditor::$styles as $style) {
-            $assets['css'][] = $url . $style . $cachebuster;
-        }
-
-        // Add editor JavaScript.
-        foreach (H5peditor::$scripts as $script) {
-            // We do not want the creator of the iframe inside the iframe.
-            if ($script !== 'scripts/h5peditor-editor.js') {
-                $assets['js'][] = $url . $script . $cachebuster;
-            }
-        }
-
-        // Add JavaScript with library framework integration (editor part).
-        $PAGE->requires->js(new moodle_url('/'. $libeditorpath .'/scripts/h5peditor-editor.js' . $cachebuster), true);
-        $PAGE->requires->js(new moodle_url('/'. $libeditorpath .'/scripts/h5peditor-init.js' . $cachebuster), true);
-        $PAGE->requires->js(new moodle_url('/h5p/editor.js' . $cachebuster), true);
-
-        // Add translations.
-        $language = framework::get_language();
-        $languagescript = "language/{$language}.js";
-
-        if (!file_exists("{$CFG->dirroot}/" . $libeditorpath . "/{$languagescript}")) {
-            $languagescript = 'language/en.js';
-        }
-        $PAGE->requires->js(new moodle_url('/' . $libeditorpath .'/' . $languagescript . $cachebuster), true);
-
-        // Add JavaScript settings.
-        $root = $CFG->wwwroot;
-        $filespathbase = "{$root}/pluginfile.php/{$context->id}/core_h5p/";
-
-        $factory = new factory();
-        $contentvalidator = $factory->get_content_validator();
-
-        $editorajaxtoken = H5PCore::createToken(editor_ajax::EDITOR_AJAX_TOKEN);
-        $settings['editor'] = array(
-            'filesPath' => $filespathbase . 'editor',
-            'fileIcon' => array(
-                'path' => $url . 'images/binary-file.png',
-                'width' => 50,
-                'height' => 50,
-            ),
-            'ajaxPath' => $CFG->wwwroot . '/h5p/' . "ajax.php?contextId={$context->id}&token={$editorajaxtoken}&action=",
-            'libraryUrl' => $url,
-            'copyrightSemantics' => $contentvalidator->getCopyrightSemantics(),
-            'metadataSemantics' => $contentvalidator->getMetadataSemantics(),
-            'assets' => $assets,
-            'apiVersion' => H5PCore::$coreApi,
-            'language' => $language,
-            'formId' => $mformid,
-        );
-
-        if ($id !== null) {
-            $settings['editor']['nodeVersionId'] = $id;
-
-            // Override content URL.
-            $contenturl = "{$root}/pluginfile.php/{$context->id}/core_h5p/content/{$id}";
-            $settings['contents']['cid-' . $id]['contentUrl'] = $contenturl;
-        }
-
-        $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
-    }
 }
diff --git a/h5p/classes/output/h5peditor.php b/h5p/classes/output/h5peditor.php
new file mode 100644 (file)
index 0000000..6f1f900
--- /dev/null
@@ -0,0 +1,59 @@
+<?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/>.
+
+/**
+ * Provides {@link \core_h5p\output\h5peditor} class.
+ *
+ * @package   core_h5p
+ * @copyright 2020 Victor Deniz <victor@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Displays the H5P Editor
+ *
+ * @copyright 2020 Victor Deniz <victor@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class h5peditor implements \renderable, \templatable {
+
+    /** @var \stdClass Context in which the H5P Editor is being used. */
+    protected $context;
+
+    /**
+     * h5peditor constructor.
+     *
+     * @param \stdClass $context H5P editor context generated by core_h5p\editor.
+     */
+    public function __construct(\stdClass $context) {
+        $this->context = $context;
+    }
+
+    /**
+     * Exports the data required for the H5P Editor.
+     *
+     * @param \renderer_base $output
+     * @return \stdClass
+     */
+    public function export_for_template(\renderer_base $output) {
+
+        return $this->context;
+    }
+}
diff --git a/h5p/classes/output/renderer.php b/h5p/classes/output/renderer.php
new file mode 100644 (file)
index 0000000..e5720c5
--- /dev/null
@@ -0,0 +1,40 @@
+<?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/>.
+
+/**
+ * Renderer.
+ *
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz {victor@moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_h5p\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use plugin_renderer_base;
+
+/**
+ * Renderer class.
+ *
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz {victor@moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends plugin_renderer_base {
+
+}
\ No newline at end of file
diff --git a/h5p/templates/h5peditor.mustache b/h5p/templates/h5peditor.mustache
new file mode 100644 (file)
index 0000000..6ae4287
--- /dev/null
@@ -0,0 +1,45 @@
+{{!
+    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 core_h5p/h5peditor
+
+    Displays the H5P Editor form.
+
+    Classes required for JS:
+    * editor_display
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * none
+
+    Example context (json):
+    {
+    }
+}}
+<div class="h5p-editor-wrapper" id="editor-region-{{uniqid}}">
+    <div class="h5p-editor">
+        {{> core/loading }}
+    </div>
+</div>
+<div class="h5p-editor-upload"></div>
+{{#js}}
+    require(['core_h5p/editor_display'], function(Editor) {
+        Editor.init("editor-region-{{uniqid}}");
+    });
+{{/js}}
\ No newline at end of file
diff --git a/h5p/tests/editor_test.php b/h5p/tests/editor_test.php
new file mode 100644 (file)
index 0000000..05f5877
--- /dev/null
@@ -0,0 +1,275 @@
+<?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/>.
+
+/**
+ * Testing the Moodle local class for managing the H5P Editor.
+ *
+ * @package    core_h5p
+ * @category   test
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_h5p;
+
+use advanced_testcase;
+use core_h5p\local\library\autoloader;
+use moodleform;
+use MoodleQuickForm;
+use page_requirements_manager;
+
+/**
+ *
+ * Test class covering the editor class.
+ *
+ * @package    core_h5p
+ * @copyright  2020 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ * @runTestsInSeparateProcesses
+ */
+class editor_testcase extends advanced_testcase {
+
+    /**
+     * Test that existing content is properly set.
+     */
+    public function test_set_content() {
+        $this->resetAfterTest();
+
+        autoloader::register();
+
+        // Add H5P content.
+        // 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
+        );
+        $config = new \stdClass();
+
+        $h5pplayer = new player($url->out(), $config);
+
+        // Call the method. We need the id of the new H5P content.
+        $rc = new \ReflectionClass(player::class);
+        $rcp = $rc->getProperty('h5pid');
+        $rcp->setAccessible(true);
+        $h5pid = $rcp->getValue($h5pplayer);
+
+        $editor = new editor();
+        $editor->set_content($h5pid);
+
+        // Check we get the H5P content.
+        $rc = new \ReflectionClass(editor::class);
+        $rcp = $rc->getProperty('oldcontent');
+        $rcp->setAccessible(true);
+        $oldcontent = $rcp->getValue($editor);
+
+        $core = (new factory)->get_core();
+        $this->assertSame($core->loadContent($h5pid), $oldcontent);
+
+        // Check we get the file of the H5P content.
+        $rcp = $rc->getProperty('oldfile');
+        $rcp->setAccessible(true);
+        $oldfile = $rcp->getValue($editor);
+
+        $this->assertSame($file->get_contenthash(), $oldfile->get_contenthash());
+    }
+
+    /**
+     * Tests that library and file area are properly set.
+     */
+    public function test_set_library() {
+        global $USER;
+
+        $library = 'H5P.Accordion 1.5';
+        $contextid = 1;
+        $filearea = 'unittest';
+        $filename = 'export.h5p';
+
+        // Call method.
+        $editor = new editor();
+        $editor->set_library($library, $contextid, file_storage::COMPONENT, $filearea, 0, '/', $filename);
+
+        // Check that the library has the right value.
+        $rc = new \ReflectionClass(editor::class);
+        $rcp = $rc->getProperty('library');
+        $rcp->setAccessible(true);
+        $actual = $rcp->getValue($editor);
+
+        $this->assertSame($library, $actual);
+
+        // Check that the file area has the right value.
+        $expected = [
+            'contextid' => $contextid,
+            'component' => file_storage::COMPONENT,
+            'filearea' => $filearea,
+            'itemid' => 0,
+            'filepath' => '/',
+            'filename' => $filename,
+            'userid' => $USER->id
+        ];
+
+        $rcp = $rc->getProperty('filearea');
+        $rcp->setAccessible(true);
+        $actual = $rcp->getValue($editor);
+
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * Test that required assets (js and css) and form will be loaded in page.
+     */
+    public function test_add_editor_to_form() {
+        global $PAGE, $CFG;
+
+        // Get form data.
+        $form = new temp_form();
+        $mform = $form->getform();
+
+        // Call method.
+        $editor = new editor();
+        $editor->add_editor_to_form($mform);
+
+        // Check $PAGE has the expected css and js scripts.
+        $rc = new \ReflectionClass(page_requirements_manager::class);
+        $rcp = $rc->getProperty('cssurls');
+        $rcp2 = $rc->getProperty('jsincludes');
+        $rcp->setAccessible(true);
+        $rcp2->setAccessible(true);
+        $actualcss = array_keys($rcp->getValue($PAGE->requires));
+        $actualjs = array_keys($rcp2->getValue($PAGE->requires)['head']);
+        $cachebuster = helper::get_cache_buster();
+
+        $h5pcorepath = autoloader::get_h5p_core_library_url()->out();
+
+        $expectedcss = \H5PCore::$styles;
+        $expectedjs = \H5PCore::$scripts;
+
+        array_walk($expectedcss, function(&$item, $key) use ($h5pcorepath, $cachebuster) {
+            $item = $h5pcorepath . $item. $cachebuster;
+
+        });
+
+        array_walk($expectedjs, function(&$item, $key) use ($h5pcorepath, $cachebuster) {
+            $item = $h5pcorepath . $item . $cachebuster;
+        });
+
+        // Add translation script.
+        $language = framework::get_language();
+        $languagescript = "language/{$language}.js";
+
+        if (!file_exists($CFG->dirroot . autoloader::get_h5p_editor_library_base($languagescript))) {
+            $languagescript = 'language/en.js';
+        }
+        $expectedjs[] = autoloader::get_h5p_editor_library_url($languagescript . $cachebuster)->out();
+
+        $expectedjs[] = (new \moodle_url('/h5p/js/h5p_overrides.js' . $cachebuster))->out();
+        $expectedjs[] = autoloader::get_h5p_editor_library_url('scripts/h5peditor-editor.js' . $cachebuster)->out();
+        $expectedjs[] = autoloader::get_h5p_editor_library_url('scripts/h5peditor-init.js' . $cachebuster)->out();
+
+        // Sort arrays before comparison.
+        sort($actualcss);
+        sort($actualjs);
+        sort($expectedcss);
+        sort($expectedjs);
+
+        $this->assertSame($expectedcss, $actualcss);
+        $this->assertSame($expectedjs, $actualjs);
+
+        // H5P Editor expected form fields.
+        $this->assertTrue($mform->elementExists('h5pparams'));
+        $this->assertTrue($mform->elementExists('h5plibrary'));
+        $this->assertTrue($mform->elementExists('h5paction'));
+    }
+
+    /**
+     * Test new content creation.
+     */
+    public function test_save_content() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Fake form data sent during creation.
+        $data = new \stdClass();
+        $data->h5plibrary = "H5P.ArithmeticQuiz 1.1";
+        $data->h5pparams = '{"params":{"quizType":"arithmetic","arithmeticType":"addition","UI":{"score":"Score:","time":"Time: @time"},
+                "intro":"This is a content for testing"},"metadata":{"defaultLanguage":"en","title":"Testing content"}}';
+
+        $title = 'libtest';
+        $library = 'H5P.ArithmeticQuiz 1.1';
+        $machinename = 'H5P.ArithmeticQuiz';
+        $contextid = 1;
+        $filearea = 'unittest';
+        $filename = 'export.h5p';
+
+        // Fake installed library for the H5P content.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+
+        $semantics = json_encode([['type' => 'text', 'name' => 'text', 'label' => 'Plain text', 'description' => 'Some text']]);
+        $generator->create_library_record($machinename, $title, 1, 1, 2, $semantics);
+
+        $editor = new editor();
+        $editor->set_library($library, $contextid, file_storage::COMPONENT, $filearea, 0, '/', $filename);
+        $newcontentid = $editor->save_content($data);
+
+        // Check the H5P content file was created where expected.
+        $fs = get_file_storage();
+        $out = $fs->get_file($contextid, file_storage::COMPONENT, $filearea, 0, '/', $filename);
+        $this->assertNotEmpty($out);
+    }
+}
+
+/**
+ * Form object to be used in test case.
+ */
+class temp_form extends moodleform {
+    /**
+     * Form definition.
+     */
+    public function definition(): void {
+        // No definition required.
+    }
+
+    /**
+     * Returns form reference.
+     *
+     * @return MoodleQuickForm
+     */
+    public function getform() {
+        $mform = $this->_form;
+        // Set submitted flag, to simulate submission.
+        $mform->_flagSubmitted = true;
+        return $mform;
+    }
+}