MDL-67814 core_h5p: implemented H5P Core/Editor library interfaces
This commit is contained in:
committed by
Adrian Greeve
parent
8d702491bc
commit
6da050d72a
+115
@@ -0,0 +1,115 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Responsible for handling AJAX requests related to H5P.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>, based on code by Joubel AS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_h5p\factory;
|
||||
use core_h5p\framework;
|
||||
|
||||
define('AJAX_SCRIPT', true);
|
||||
|
||||
require(__DIR__ . '/../config.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
require_login();
|
||||
|
||||
$action = required_param('action', PARAM_ALPHA);
|
||||
$contextid = required_param('contextId', PARAM_INT);
|
||||
|
||||
$context = context::instance_by_id($contextid);
|
||||
|
||||
if (!has_capability('moodle/h5p:updatelibraries', $context)) {
|
||||
H5PCore::ajaxError(get_string('nopermissiontoedit', 'h5p'));
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
$factory = new factory();
|
||||
$editor = $factory->get_editor();
|
||||
|
||||
switch ($action) {
|
||||
// Load list of libraries or details for library.
|
||||
case 'libraries':
|
||||
// Get parameters.
|
||||
$name = optional_param('machineName', '', PARAM_TEXT);
|
||||
$major = optional_param('majorVersion', 0, PARAM_INT);
|
||||
$minor = optional_param('minorVersion', 0, PARAM_INT);
|
||||
|
||||
$language = optional_param('default-language', null, PARAM_ALPHA);
|
||||
|
||||
if (!empty($name)) {
|
||||
$editor->ajax->action(H5PEditorEndpoints::SINGLE_LIBRARY, $name,
|
||||
$major, $minor, framework::get_language(), '', '', $language);
|
||||
} else {
|
||||
$editor->ajax->action(H5PEditorEndpoints::LIBRARIES);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Load content type cache list to display available libraries in hub.
|
||||
case 'contenttypecache':
|
||||
$editor->ajax->action(H5PEditorEndpoints::CONTENT_TYPE_CACHE);
|
||||
break;
|
||||
|
||||
// Handle file upload through the editor.
|
||||
case 'files':
|
||||
$token = required_param('token', PARAM_RAW);
|
||||
$contentid = required_param('contentId', PARAM_INT);
|
||||
|
||||
$editor->ajax->action(H5PEditorEndpoints::FILES, $token, $contentid);
|
||||
break;
|
||||
|
||||
// Install libraries from H5P and retrieve content json.
|
||||
case 'libraryinstall':
|
||||
$token = required_param('token', PARAM_RAW);
|
||||
$machinename = required_param('id', PARAM_TEXT);
|
||||
$editor->ajax->action(H5PEditorEndpoints::LIBRARY_INSTALL, $token, $machinename);
|
||||
break;
|
||||
|
||||
// Handle file upload through the editor.
|
||||
case 'libraryupload':
|
||||
$token = required_param('token', PARAM_RAW);
|
||||
|
||||
$uploadpath = $_FILES['h5p']['tmp_name'];
|
||||
$contentid = optional_param('contentId', 0, PARAM_INT);
|
||||
$editor->ajax->action(H5PEditorEndpoints::LIBRARY_UPLOAD, $token, $uploadpath, $contentid);
|
||||
break;
|
||||
|
||||
// Get the $language libraries translations.
|
||||
case 'translations':
|
||||
$language = required_param('language', PARAM_RAW);
|
||||
$editor->ajax->action(H5PEditorEndpoints::TRANSLATIONS, $language);
|
||||
break;
|
||||
|
||||
// Handle filtering of parameters through AJAX.
|
||||
case 'filter':
|
||||
$token = required_param('token', PARAM_RAW);
|
||||
$libraryparameters = required_param('libraryParameters', PARAM_RAW);
|
||||
|
||||
$editor->ajax->action(H5PEditorEndpoints::FILTER, $token, $libraryparameters);
|
||||
break;
|
||||
|
||||
// Throw error if AJAX action is not handled.
|
||||
default:
|
||||
throw new coding_exception('Unhandled AJAX');
|
||||
break;
|
||||
}
|
||||
@@ -105,4 +105,75 @@ class api {
|
||||
|
||||
return $DB->get_record('h5p_libraries', ['id' => $libraryid], '*', MUST_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a library as an object with properties that correspond to the fetched row's field names.
|
||||
*
|
||||
* @param array $params An associative array with the values of the machinename, majorversion and minorversion fields.
|
||||
* @param bool $configurable A library that has semantics so it can be configured in the editor.
|
||||
* @param string $fields Library attributes to retrieve.
|
||||
*
|
||||
* @return \stdClass|null An object with one attribute for each field name in $fields param.
|
||||
*/
|
||||
public static function get_library_details(array $params, bool $configurable, string $fields = ''): ?\stdClass {
|
||||
global $DB;
|
||||
|
||||
$select = "machinename = :machinename
|
||||
AND majorversion = :majorversion
|
||||
AND minorversion = :minorversion";
|
||||
|
||||
if ($configurable) {
|
||||
$select .= " AND semantics IS NOT NULL";
|
||||
}
|
||||
|
||||
$fields = $fields ?: '*';
|
||||
|
||||
$record = $DB->get_record_select('h5p_libraries', $select, $params, $fields);
|
||||
|
||||
return $record ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the H5P content type libraries versions.
|
||||
*
|
||||
* @param string|null $fields Library fields to return.
|
||||
*
|
||||
* @return array An array with an object for each content type library installed.
|
||||
*/
|
||||
public static function get_contenttype_libraries(?string $fields = ''): array {
|
||||
global $DB;
|
||||
|
||||
$libraries = [];
|
||||
$fields = $fields ?: '*';
|
||||
$select = "runnable = :runnable
|
||||
AND semantics IS NOT NULL";
|
||||
$params = ['runnable' => 1];
|
||||
$sort = 'title, majorversion DESC, minorversion DESC';
|
||||
|
||||
$records = $DB->get_records_select('h5p_libraries', $select, $params, $sort, $fields);
|
||||
|
||||
$added = [];
|
||||
foreach ($records as $library) {
|
||||
// Remove unique index.
|
||||
unset($library->id);
|
||||
|
||||
// Convert snakes to camels.
|
||||
$library->majorVersion = (int) $library->majorversion;
|
||||
unset($library->major_version);
|
||||
$library->minorVersion = (int) $library->minorversion;
|
||||
unset($library->minorversion);
|
||||
|
||||
// If we already add this library means that it is an old version,as the previous query was sorted by version.
|
||||
if (isset($added[$library->name])) {
|
||||
$library->isOld = true;
|
||||
} else {
|
||||
$added[$library->name] = true;
|
||||
}
|
||||
|
||||
// Add new library.
|
||||
$libraries[] = $library;
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,4 +356,25 @@ class core extends \H5PCore {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use sesskey instead of the H5P security token.
|
||||
*
|
||||
* @param string $action Not used.
|
||||
* @return string sesskey
|
||||
*/
|
||||
public static function createToken($action) {
|
||||
return sesskey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token matches the sesskey.
|
||||
*
|
||||
* @param string $action Not used.
|
||||
* @param string $token Token submitted.
|
||||
* @return boolean valid token
|
||||
*/
|
||||
public static function validToken($action, $token) {
|
||||
return confirm_sesskey($token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<?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 \core_h5p\editor_ajax
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>, base on code by Joubel AS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use H5PEditorAjaxInterface;
|
||||
|
||||
/**
|
||||
* Moodle's implementation of the H5P Editor Ajax interface.
|
||||
*
|
||||
* Makes it possible for the editor's core ajax functionality to communicate with the
|
||||
* database used by Moodle.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>, base on code by Joubel AS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class editor_ajax implements H5PEditorAjaxInterface {
|
||||
|
||||
/** The component for H5P. */
|
||||
public const EDITOR_AJAX_TOKEN = 'editorajax';
|
||||
|
||||
/**
|
||||
* Gets latest library versions that exists locally
|
||||
*
|
||||
* @return array Latest version of all local libraries
|
||||
*/
|
||||
public function getLatestLibraryVersions(): array {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT hl2.id, hl2.machinename as machine_name, hl2.title, hl2.majorversion as major_version,
|
||||
hl2.minorversion AS minor_version, hl2.patchversion as patch_version, '' as has_icon, 0 as restricted
|
||||
FROM {h5p_libraries} hl2
|
||||
LEFT JOIN {h5p_libraries} hl1
|
||||
ON hl1.machinename = hl2.machinename
|
||||
AND (hl2.majorversion < hl1.majorversion
|
||||
OR (hl2.majorversion = hl1.majorversion
|
||||
AND hl2.minorversion < hl1.minorversion)
|
||||
)
|
||||
WHERE hl2.runnable = 1
|
||||
AND hl1.majorversion is null
|
||||
ORDER BY hl2.machinename";
|
||||
|
||||
return $DB->get_records_sql($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locally stored Content Type Cache.
|
||||
*
|
||||
* If machine name is provided it will only get the given content type from the cache.
|
||||
*
|
||||
* @param null|string $machinename
|
||||
*
|
||||
* @return mixed|null Returns results from querying the database
|
||||
*/
|
||||
public function getContentTypeCache($machinename = null) {
|
||||
// This is to be implemented when the Hub client is used.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets recently used libraries for the current author
|
||||
*
|
||||
* @return array machine names. The first element in the array is the
|
||||
* most recently used.
|
||||
*/
|
||||
public function getAuthorsRecentlyUsedLibraries(): array {
|
||||
// This is to be implemented when the Hub client is used.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided token is valid for this endpoint.
|
||||
*
|
||||
* @param string $token The token that will be validated for.
|
||||
*
|
||||
* @return bool True if successful validation
|
||||
*/
|
||||
public function validateEditorToken($token): bool {
|
||||
return core::validToken(self::EDITOR_AJAX_TOKEN, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translations in one language for a list of libraries.
|
||||
*
|
||||
* @param array $libraries An array of libraries, in the form "<machineName> <majorVersion>.<minorVersion>
|
||||
* @param string $languagecode Language code
|
||||
*
|
||||
* @return array Translations in $languagecode available for libraries $libraries
|
||||
*/
|
||||
public function getTranslations($libraries, $languagecode): array {
|
||||
// To be implemented when translations are introduced.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?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 \core_h5p\editor_framework
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>, base on code by Joubel AS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use H5peditorStorage;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Moodle's implementation of the H5P Editor storage interface.
|
||||
*
|
||||
* Makes it possible for the editor's core library to communicate with the
|
||||
* database used by Moodle.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>, base on code by Joubel AS
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class editor_framework implements H5peditorStorage {
|
||||
|
||||
/**
|
||||
* Load language file(JSON).
|
||||
* Used to translate the editor fields(title, description etc.)
|
||||
*
|
||||
* @param string $name The machine readable name of the library(content type)
|
||||
* @param int $major Major part of version number
|
||||
* @param int $minor Minor part of version number
|
||||
* @param string $lang Language code
|
||||
*
|
||||
* @return string|boolean Translation in JSON format if available, false otherwise
|
||||
*/
|
||||
public function getLanguage($name, $major, $minor, $lang) {
|
||||
// To be implemented when translations are introduced.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of available language codes.
|
||||
*
|
||||
* Until translations is implemented, only returns the "en" language.
|
||||
*
|
||||
* @param string $machinename The machine readable name of the library(content type)
|
||||
* @param int $major Major part of version number
|
||||
* @param int $minor Minor part of version number
|
||||
*
|
||||
* @return array List of possible language codes
|
||||
*/
|
||||
public function getAvailableLanguages($machinename, $major, $minor): array {
|
||||
$defaultcode = 'en';
|
||||
$codes = [];
|
||||
|
||||
// Semantics is 'en' by default.
|
||||
array_unshift($codes, $defaultcode);
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Callback" for mark the given file as a permanent file.
|
||||
*
|
||||
* Used when saving content that has new uploaded files.
|
||||
*
|
||||
* @param int $fileid
|
||||
*/
|
||||
public function keepFile($fileid): void {
|
||||
// Temporal files will be removed on a task when they are in the "editor" file area and and are at least one day older.
|
||||
}
|
||||
|
||||
/**
|
||||
* Return libraries details.
|
||||
*
|
||||
* Two use cases:
|
||||
* 1. No input, will list all the available content types.
|
||||
* 2. Libraries supported are specified, load additional data and verify
|
||||
* that the content types are available. Used by e.g. the Presentation Tool
|
||||
* Editor that already knows which content types are supported in its
|
||||
* slides.
|
||||
*
|
||||
* @param array $libraries List of library names + version to load info for.
|
||||
*
|
||||
* @return array List of all libraries loaded.
|
||||
*/
|
||||
public function getLibraries($libraries = null): ?array {
|
||||
|
||||
if ($libraries !== null) {
|
||||
// Get details for the specified libraries.
|
||||
$librariesin = [];
|
||||
$fields = 'title, runnable';
|
||||
|
||||
foreach ($libraries as $library) {
|
||||
$params = [
|
||||
'machinename' => $library->name,
|
||||
'majorversion' => $library->majorVersion,
|
||||
'minorversion' => $library->minorVersion
|
||||
];
|
||||
|
||||
$details = api::get_library_details($params, true, $fields);
|
||||
|
||||
if ($details) {
|
||||
$library->title = $details->title;
|
||||
$library->runnable = $details->runnable;
|
||||
$librariesin[] = $library;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fields = 'id, machinename as name, title, majorversion, minorversion';
|
||||
$librariesin = api::get_contenttype_libraries($fields);
|
||||
}
|
||||
|
||||
return $librariesin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for other plugins to decide which styles and scripts are attached.
|
||||
*
|
||||
* This is useful for adding and/or modifying the functionality and look of
|
||||
* the content types.
|
||||
*
|
||||
* @param array $files List of files as objects with path and version as properties.
|
||||
* @param array $libraries List of libraries indexed by machineName with objects as values. The objects have majorVersion and
|
||||
* minorVersion as properties.
|
||||
*/
|
||||
public function alterLibraryFiles(&$files, $libraries): void {
|
||||
// This is to be implemented when the renderer is used.
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a file or moves it temporarily.
|
||||
*
|
||||
* This is often necessary in order to validate and store uploaded or fetched H5Ps.
|
||||
*
|
||||
* @param string $data Uri of data that should be saved as a temporary file.
|
||||
* @param bool $movefile Can be set to TRUE to move the data instead of saving it.
|
||||
*
|
||||
* @return bool|object Returns false if saving failed or an object with path
|
||||
* of the directory and file that is temporarily saved.
|
||||
*/
|
||||
public static function saveFileTemporarily($data, $movefile = false) {
|
||||
// This is to be implemented when the Hub client is used to upload libraries.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a file for later cleanup.
|
||||
*
|
||||
* Useful when files are not instantly cleaned up. E.g. for files that are uploaded through the editor.
|
||||
*
|
||||
* @param int $file Id of file that should be cleaned up
|
||||
* @param int|null $contentid Content id of file
|
||||
*/
|
||||
public static function markFileForCleanup($file, $contentid = null): ?int {
|
||||
// Temporal files will be removed on a task when they are in the "editor" file area and and are at least one day older.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up temporary files
|
||||
*
|
||||
* @param string $filepath Path to file or directory
|
||||
*/
|
||||
public static function removeTemporarilySavedFiles($filepath): void {
|
||||
// This is to be implemented when the Hub client is used to upload libraries.
|
||||
}
|
||||
}
|
||||
+34
-3
@@ -28,11 +28,10 @@ namespace core_h5p;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_h5p\local\library\autoloader;
|
||||
use core_h5p\framework;
|
||||
use core_h5p\core;
|
||||
use H5PContentValidator as content_validator;
|
||||
use H5peditor;
|
||||
use H5PStorage as storage;
|
||||
use H5PValidator as validator;
|
||||
use H5PContentValidator as content_validator;
|
||||
|
||||
/**
|
||||
* H5P factory class.
|
||||
@@ -62,6 +61,15 @@ class factory {
|
||||
/** @var content_validator The Moodle H5PContentValidator implementation */
|
||||
protected $content_validator;
|
||||
|
||||
/** @var editor_framework The Moodle H5peditorStorage implementation */
|
||||
protected $editorframework;
|
||||
|
||||
/** @var H5peditor */
|
||||
protected $editor;
|
||||
|
||||
/** @var editor_ajax The Moodle H5PEditorAjaxInterface implementation */
|
||||
protected $editorajaxinterface;
|
||||
|
||||
/**
|
||||
* factory constructor.
|
||||
*/
|
||||
@@ -151,4 +159,27 @@ class factory {
|
||||
|
||||
return $this->content_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of H5Peditor class.
|
||||
*
|
||||
* @return H5peditor
|
||||
*/
|
||||
public function get_editor(): H5peditor {
|
||||
if (null === $this->editor) {
|
||||
if (empty($this->editorframework)) {
|
||||
$this->editorframework = new editor_framework();
|
||||
}
|
||||
|
||||
if (empty($this->editorajaxinterface)) {
|
||||
$this->editorajaxinterface = new editor_ajax();
|
||||
}
|
||||
|
||||
if (empty($this->editor)) {
|
||||
$this->editor = new H5peditor($this->get_core(), $this->editorframework, $this->editorajaxinterface);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->editor;
|
||||
}
|
||||
}
|
||||
|
||||
+182
-24
@@ -24,7 +24,8 @@
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use H5peditorFile;
|
||||
use stored_file;
|
||||
|
||||
/**
|
||||
* Class to handle storage and export of H5P Content.
|
||||
@@ -47,6 +48,8 @@ class file_storage implements \H5PFileStorage {
|
||||
public const EXPORT_FILEAREA = 'export';
|
||||
/** The icon filename */
|
||||
public const ICON_FILENAME = 'icon.svg';
|
||||
/** The editor file area */
|
||||
public const EDITOR_FILEAREA = 'editor';
|
||||
|
||||
/**
|
||||
* @var \context $context Currently we use the system context everywhere.
|
||||
@@ -164,13 +167,19 @@ class file_storage implements \H5PFileStorage {
|
||||
* @param string $filename Name of export file.
|
||||
*/
|
||||
public function saveExport($source, $filename) {
|
||||
global $USER;
|
||||
|
||||
// Remove old export.
|
||||
$this->deleteExport($filename);
|
||||
|
||||
$filerecord = [
|
||||
'contextid' => $this->context->id,
|
||||
'component' => self::COMPONENT,
|
||||
'filearea' => self::EXPORT_FILEAREA,
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename
|
||||
'filename' => $filename,
|
||||
'userid' => $USER->id
|
||||
];
|
||||
$this->fs->create_file_from_pathname($filerecord, $source);
|
||||
}
|
||||
@@ -315,13 +324,25 @@ class file_storage implements \H5PFileStorage {
|
||||
|
||||
/**
|
||||
* Save files uploaded through the editor.
|
||||
* The files must be marked as temporary until the content form is saved.
|
||||
*
|
||||
* @param \H5peditorFile $file
|
||||
* @param H5peditorFile $file
|
||||
* @param int $contentid
|
||||
*
|
||||
* @return int The id of the saved file.
|
||||
*/
|
||||
public function saveFile($file, $contentid) {
|
||||
// This is to be implemented when the h5p editor is introduced / created.
|
||||
$record = array(
|
||||
'contextid' => $this->context->id,
|
||||
'component' => self::COMPONENT,
|
||||
'filearea' => $contentid === 0 ? self::EDITOR_FILEAREA : self::CONTENT_FILEAREA,
|
||||
'itemid' => $contentid,
|
||||
'filepath' => '/' . $file->getType() . 's/',
|
||||
'filename' => $file->getName()
|
||||
);
|
||||
|
||||
$storedfile = $this->fs->create_file_from_pathname($record, $_FILES['file']['tmp_name']);
|
||||
|
||||
return $storedfile->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,23 +351,81 @@ class file_storage implements \H5PFileStorage {
|
||||
*
|
||||
* @param string $file path + name
|
||||
* @param string|int $fromid Content ID or 'editor' string
|
||||
* @param int $toid Target Content ID
|
||||
* @param \stdClass $tocontent Target Content
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cloneContentFile($file, $fromid, $toid) {
|
||||
// This is to be implemented when the h5p editor is introduced / created.
|
||||
public function cloneContentFile($file, $fromid, $tocontent): void {
|
||||
// Determine source filearea and itemid.
|
||||
if ($fromid === self::EDITOR_FILEAREA) {
|
||||
$sourcefilearea = self::EDITOR_FILEAREA;
|
||||
$sourceitemid = 0;
|
||||
} else {
|
||||
$sourcefilearea = self::CONTENT_FILEAREA;
|
||||
$sourceitemid = (int)$fromid;
|
||||
}
|
||||
|
||||
$filepath = '/' . dirname($file) . '/';
|
||||
$filename = basename($file);
|
||||
|
||||
// Check to see if source exists.
|
||||
$sourcefile = $this->get_file($sourcefilearea, $sourceitemid, $file);
|
||||
if ($sourcefile === null) {
|
||||
return; // Nothing to copy from.
|
||||
}
|
||||
|
||||
// Check to make sure that file doesn't exist already in target.
|
||||
$targetfile = $this->get_file(self::CONTENT_FILEAREA, $tocontent->id, $file);
|
||||
if ( $targetfile !== null) {
|
||||
return; // File exists, no need to copy.
|
||||
}
|
||||
|
||||
// Create new file record.
|
||||
$record = [
|
||||
'contextid' => $this->context->id,
|
||||
'component' => self::COMPONENT,
|
||||
'filearea' => self::CONTENT_FILEAREA,
|
||||
'itemid' => $tocontent->id,
|
||||
'filepath' => $filepath,
|
||||
'filename' => $filename,
|
||||
];
|
||||
|
||||
$this->fs->create_file_from_storedfile($record, $sourcefile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy content from one directory to another. Defaults to cloning
|
||||
* content from the current temporary upload folder to the editor path.
|
||||
* Copy content from one directory to another.
|
||||
* Defaults to cloning content from the current temporary upload folder to the editor path.
|
||||
*
|
||||
* @param string $source path to source directory
|
||||
* @param string $contentid Id of content
|
||||
*
|
||||
* @return object Object containing h5p json and content json data
|
||||
*/
|
||||
public function moveContentDirectory($source, $contentid = null) {
|
||||
// This is to be implemented when the h5p editor is introduced / created.
|
||||
$contentidint = (int)$contentid;
|
||||
|
||||
if ($source === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get H5P and content json.
|
||||
$contentsource = $source . '/content';
|
||||
|
||||
// Move all temporary content files to editor.
|
||||
$it = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($contentsource,\RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
$it->rewind();
|
||||
while ($it->valid()) {
|
||||
$item = $it->current();
|
||||
$pathname = $it->getPathname();
|
||||
if (!$item->isDir() && !($item->getFilename() === 'content.json')) {
|
||||
$this->move_file($pathname, $contentidint);
|
||||
}
|
||||
$it->next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,26 +463,41 @@ class file_storage implements \H5PFileStorage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if content has the given file.
|
||||
* Used when saving content.
|
||||
* Checks to see if an H5P content has the given file.
|
||||
*
|
||||
* @param string $file path + name
|
||||
* @param int $contentid
|
||||
* @return string|int File ID or NULL if not found
|
||||
* @param string $file File path and name.
|
||||
* @param int $content Content id.
|
||||
*
|
||||
* @return int|null File ID or NULL if not found
|
||||
*/
|
||||
public function getContentFile($file, $contentid) {
|
||||
// This is to be implemented when the h5p editor is introduced / created.
|
||||
public function getContentFile($file, $content): ?int {
|
||||
if (is_object($content)) {
|
||||
$content = $content->id;
|
||||
}
|
||||
$contentfile = $this->get_file(self::CONTENT_FILEAREA, $content, $file);
|
||||
|
||||
return ($contentfile === null ? null : $contentfile->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove content files that are no longer used.
|
||||
*
|
||||
* Used when saving content.
|
||||
*
|
||||
* @param string $file path + name
|
||||
* @param int $contentid
|
||||
* @param string $file File path and name.
|
||||
* @param int $contentid Content id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeContentFile($file, $contentid) {
|
||||
// This is to be implemented when the h5p editor is introduced / created.
|
||||
public function removeContentFile($file, $contentid): void {
|
||||
// Although the interface defines $contentid as int, object given in \H5peditor::processParameters.
|
||||
if (is_object($contentid)) {
|
||||
$contentid = $contentid->id;
|
||||
}
|
||||
$existingfile = $this->get_file(self::CONTENT_FILEAREA, $contentid, $file);
|
||||
if ($existingfile !== null) {
|
||||
$existingfile->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -616,7 +710,7 @@ class file_storage implements \H5PFileStorage {
|
||||
* @param string $filename File name to retrieve.
|
||||
* @return bool|\stored_file Stored file instance if exists, false if not
|
||||
*/
|
||||
private function get_export_file(string $filename) {
|
||||
public function get_export_file(string $filename) {
|
||||
return $this->fs->get_file($this->context->id, self::COMPONENT, self::EXPORT_FILEAREA, 0, '/', $filename);
|
||||
}
|
||||
|
||||
@@ -657,4 +751,68 @@ class file_storage implements \H5PFileStorage {
|
||||
return $DB->get_field('files', 'itemid', ['component' => self::COMPONENT, 'filearea' => $filearea, 'filepath' => $filepath,
|
||||
'filename' => $filename]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to make it easy to load content files.
|
||||
*
|
||||
* @param string $filearea File area where the file is saved.
|
||||
* @param int $itemid Content instance or content id.
|
||||
* @param string $file File path and name.
|
||||
*
|
||||
* @return stored_file|null
|
||||
*/
|
||||
private function get_file(string $filearea, int $itemid, string $file): ?stored_file {
|
||||
if ($filearea === 'editor') {
|
||||
$itemid = 0;
|
||||
}
|
||||
|
||||
$filepath = '/'. dirname($file). '/';
|
||||
$filename = basename($file);
|
||||
|
||||
// Load file.
|
||||
$existingfile = $this->fs->get_file($this->context->id, self::COMPONENT, $filearea, $itemid, $filepath, $filename);
|
||||
if (!$existingfile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $existingfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a single file
|
||||
*
|
||||
* @param string $sourcefile Path to source file
|
||||
* @param int $contentid Content id or 0 if the file is in the editor file area
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function move_file(string $sourcefile, int $contentid): void {
|
||||
$pathparts = pathinfo($sourcefile);
|
||||
$filename = $pathparts['basename'];
|
||||
$filepath = $pathparts['dirname'];
|
||||
$foldername = basename($filepath);
|
||||
|
||||
// Create file record for content.
|
||||
$record = array(
|
||||
'contextid' => $this->context->id,
|
||||
'component' => self::COMPONENT,
|
||||
'filearea' => $contentid > 0 ? self::CONTENT_FILEAREA : self::EDITOR_FILEAREA,
|
||||
'itemid' => $contentid > 0 ? $contentid : 0,
|
||||
'filepath' => '/' . $foldername . '/',
|
||||
'filename' => $filename
|
||||
);
|
||||
|
||||
$file = $this->fs->get_file(
|
||||
$record['contextid'], $record['component'],
|
||||
$record['filearea'], $record['itemid'], $record['filepath'],
|
||||
$record['filename']
|
||||
);
|
||||
|
||||
if ($file) {
|
||||
// Delete it to make sure that it is replaced with correct content.
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
$this->fs->create_file_from_pathname($record, $sourcefile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,6 +795,8 @@ class framework implements \H5PFrameworkInterface {
|
||||
$content['library']['libraryId'] = $mainlibrary->id;
|
||||
}
|
||||
|
||||
$content['disable'] = $content['disable'] ?? null;
|
||||
|
||||
$data = [
|
||||
'jsoncontent' => $content['params'],
|
||||
'displayoptions' => $content['disable'],
|
||||
@@ -1162,7 +1164,7 @@ class framework implements \H5PFrameworkInterface {
|
||||
|
||||
$sql = "SELECT hc.id, hc.jsoncontent, hc.displayoptions, hl.id AS libraryid,
|
||||
hl.machinename, hl.title, hl.majorversion, hl.minorversion, hl.fullscreen,
|
||||
hl.embedtypes, hl.semantics, hc.filtered
|
||||
hl.embedtypes, hl.semantics, hc.filtered, hc.pathnamehash
|
||||
FROM {h5p} hc
|
||||
JOIN {h5p_libraries} hl ON hl.id = hc.mainlibraryid
|
||||
WHERE hc.id = :h5pid";
|
||||
@@ -1196,9 +1198,17 @@ class framework implements \H5PFrameworkInterface {
|
||||
'libraryMinorVersion' => $data->minorversion,
|
||||
'libraryEmbedTypes' => $data->embedtypes,
|
||||
'libraryFullscreen' => $data->fullscreen,
|
||||
'metadata' => ''
|
||||
'metadata' => '',
|
||||
'pathnamehash' => $data->pathnamehash
|
||||
);
|
||||
|
||||
$params = json_decode($data->jsoncontent);
|
||||
if (empty($params->metadata)) {
|
||||
$params->metadata = new \stdClass();
|
||||
}
|
||||
$content['metadata'] = $params->metadata;
|
||||
$content['params'] = json_encode($params->params ?? $params);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
namespace core_h5p\local\library;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* H5P autoloader management class.
|
||||
*
|
||||
@@ -41,13 +39,11 @@ class autoloader {
|
||||
*/
|
||||
public static function get_all_handlers(): array {
|
||||
$handlers = [];
|
||||
foreach (\core_component::get_plugin_types() as $ptype => $unused) {
|
||||
$plugins = \core_component::get_plugin_list_with_class($ptype, 'local\library\handler') +
|
||||
\core_component::get_plugin_list_with_class($ptype, 'local_library_handler');
|
||||
// Allow plugins to have the class either with namespace or without (useful for unittest).
|
||||
foreach ($plugins as $pname => $class) {
|
||||
$handlers[$pname] = $class;
|
||||
}
|
||||
$plugins = \core_component::get_plugin_list_with_class('h5plib', 'local\library\handler') +
|
||||
\core_component::get_plugin_list_with_class('h5plib', 'local_library_handler');
|
||||
// Allow plugins to have the class either with namespace or without (useful for unittest).
|
||||
foreach ($plugins as $pname => $class) {
|
||||
$handlers[$pname] = $class;
|
||||
}
|
||||
|
||||
return $handlers;
|
||||
@@ -115,6 +111,27 @@ class autoloader {
|
||||
return component_class_callback(self::get_handler_classname(), 'get_h5p_core_library_url', [$filepath, $params]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL for the current H5P Editor Library.
|
||||
*
|
||||
* @param string $filepath The path within the h5p root.
|
||||
* @param array $params These params override current params or add new.
|
||||
* @return null|\moodle_url The moodle_url instance to a file in the H5P Editor library.
|
||||
*/
|
||||
public static function get_h5p_editor_library_url(?string $filepath = null, ?array $params = null): ?\moodle_url {
|
||||
return component_class_callback(self::get_handler_classname(), 'get_h5p_editor_library_url', [$filepath, $params]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base path for the current H5P Editor Library.
|
||||
*
|
||||
* @param string $filepath The path within the h5p root.
|
||||
* @return string Path to a file in the H5P Editor library.
|
||||
*/
|
||||
public static function get_h5p_editor_library_base(?string $filepath = null): string {
|
||||
return component_class_callback(self::get_handler_classname(), 'get_h5p_editor_library_base', [$filepath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the H5P autoloader.
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,16 @@ abstract class handler {
|
||||
return static::get_h5p_library_base() . "/core/{$filepath}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base path for the current H5P Editor Library.
|
||||
*
|
||||
* @param null|string $filepath The path within the H5P root.
|
||||
* @return string Path to a file in the H5P Editor library.
|
||||
*/
|
||||
public static function get_h5p_editor_library_base(?string $filepath = null): string {
|
||||
return static::get_h5p_library_base() . "/editor/{$filepath}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the H5P autoloader.
|
||||
*/
|
||||
@@ -83,7 +93,11 @@ abstract class handler {
|
||||
$classes = static::get_class_list();
|
||||
|
||||
if (isset($classes[$classname])) {
|
||||
require_once($CFG->dirroot . static::get_h5p_core_library_base($classes[$classname]));
|
||||
if (file_exists($CFG->dirroot . static::get_h5p_core_library_base($classes[$classname]))) {
|
||||
require_once($CFG->dirroot . static::get_h5p_core_library_base($classes[$classname]));
|
||||
} else {
|
||||
require_once($CFG->dirroot . static::get_h5p_editor_library_base($classes[$classname]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +106,23 @@ abstract class handler {
|
||||
*
|
||||
* @param string $filepath The path within the h5p root
|
||||
* @param array $params these params override current params or add new
|
||||
* @return null|moodle_url
|
||||
* @return null|\moodle_url
|
||||
*/
|
||||
public static function get_h5p_core_library_url(?string $filepath = null, ?array $params = null): ?\moodle_url {
|
||||
return new \moodle_url(static::get_h5p_core_library_base($filepath), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL for the current H5P Editor Library.
|
||||
*
|
||||
* @param string $filepath The path within the h5p root.
|
||||
* @param array $params These params override current params or add new.
|
||||
* @return null|\moodle_url The moodle_url to a file in the H5P Editor library.
|
||||
*/
|
||||
public static function get_h5p_editor_library_url(?string $filepath = null, ?array $params = null): ?\moodle_url {
|
||||
return new \moodle_url(static::get_h5p_editor_library_base($filepath), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of classes with their location within the joubel directory.
|
||||
*
|
||||
@@ -113,6 +138,11 @@ abstract class handler {
|
||||
'H5PDevelopment' => 'h5p-development.class.php',
|
||||
'H5PFileStorage' => 'h5p-file-storage.interface.php',
|
||||
'H5PMetadata' => 'h5p-metadata.class.php',
|
||||
'H5peditor' => 'h5peditor.class.php',
|
||||
'H5peditorStorage' => 'h5peditor-storage.interface.php',
|
||||
'H5PEditorAjaxInterface' => 'h5peditor-ajax.interface.php',
|
||||
'H5PEditorAjax' => 'h5peditor-ajax.class.php',
|
||||
'H5peditorFile' => 'h5peditor-file.class.php',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ function core_h5p_pluginfile($course, $cm, $context, string $filearea, array $ar
|
||||
}
|
||||
$itemid = array_shift($args);
|
||||
break;
|
||||
case \core_h5p\file_storage::EDITOR_FILEAREA:
|
||||
case \core_h5p\file_storage::CACHED_ASSETS_FILEAREA:
|
||||
case \core_h5p\file_storage::EXPORT_FILEAREA:
|
||||
$itemid = 0;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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 H5PEditorAjaxInterface interface implementation.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @category test
|
||||
* @copyright 2020 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use core_h5p\local\library\autoloader;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
*
|
||||
* Test class covering the H5PEditorAjaxInterface interface implementation.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class editor_ajax_testcase extends \advanced_testcase {
|
||||
|
||||
/** @var editor_ajax H5P editor ajax instance */
|
||||
protected $editorajax;
|
||||
|
||||
/**
|
||||
* Set up function for tests.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
autoloader::register();
|
||||
|
||||
$this->editorajax = new editor_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that getLatestLibraryVersions method retrieves the latest installed library versions.
|
||||
*/
|
||||
public function test_getLatestLibraryVersions(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$h5pgenerator = $generator->get_plugin_generator('core_h5p');
|
||||
|
||||
// Create several libraries records.
|
||||
$h5pgenerator->create_library_record('Library1', 'Lib1', 2, 0);
|
||||
$lib2 = $h5pgenerator->create_library_record('Library2', 'Lib2', 2, 1);
|
||||
$expectedlibraries[] = $lib2->id;
|
||||
$lib3 = $h5pgenerator->create_library_record('Library3', 'Lib3', 1, 3);
|
||||
$expectedlibraries[] = $lib3->id;
|
||||
$h5pgenerator->create_library_record('Library1', 'Lib1', 2, 1);
|
||||
$lib12 = $h5pgenerator->create_library_record('Library1', 'Lib1', 3, 0);
|
||||
$expectedlibraries[] = $lib12->id;
|
||||
|
||||
$actuallibraries = $this->editorajax->getLatestLibraryVersions();
|
||||
ksort($actuallibraries);
|
||||
|
||||
$this->assertEquals($expectedlibraries, array_keys($actuallibraries));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the method validateEditorToken validates an existing token.
|
||||
*/
|
||||
public function test_validateEditorToken(): void {
|
||||
// The action param is not used at all.
|
||||
$token = core::createToken('editorajax');
|
||||
$wrongaction = core::createToken('wrongaction');
|
||||
$badtoken = 'xkadfpuealkdjsflkajsñf';
|
||||
|
||||
$validtoken = $this->editorajax->validateEditorToken($token);
|
||||
$invalidaction = $this->editorajax->validateEditorToken($wrongaction);
|
||||
$invalidtoken = $this->editorajax->validateEditorToken($badtoken);
|
||||
|
||||
$this->assertTrue($validtoken);
|
||||
$this->assertTrue($invalidaction);
|
||||
$this->assertFalse($invalidtoken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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 H5peditorStorage interface implementation.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @category test
|
||||
* @copyright 2020 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use core_h5p\local\library\autoloader;
|
||||
|
||||
/**
|
||||
*
|
||||
* Test class covering the H5peditorStorage interface implementation.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @copyright 2020 Victor Deniz <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class editor_framework_testcase extends \advanced_testcase {
|
||||
|
||||
/** @var editor_framework H5P editor_framework instance */
|
||||
protected $editorframework;
|
||||
|
||||
/**
|
||||
* Set up function for tests.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
autoloader::register();
|
||||
|
||||
$this->editorframework = new editor_framework();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the method getLibraries get the specified libraries or all the content types (runnable = 1).
|
||||
*/
|
||||
public function test_getLibraries(): void {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$h5pgenerator = $generator->get_plugin_generator('core_h5p');
|
||||
|
||||
// Generate some h5p related data.
|
||||
$data = $h5pgenerator->generate_h5p_data();
|
||||
|
||||
$expectedlibraries = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (isset($value->data)) {
|
||||
$value->data->name = $value->data->machinename;
|
||||
$value->data->majorVersion = $value->data->majorversion;
|
||||
$value->data->minorVersion = $value->data->minorversion;
|
||||
$expectedlibraries[$value->data->title] = $value->data;
|
||||
}
|
||||
}
|
||||
ksort($expectedlibraries);
|
||||
|
||||
// Get all libraries.
|
||||
$libraries = $this->editorframework->getLibraries();
|
||||
foreach ($libraries as $library) {
|
||||
$actuallibraries[] = $library->title;
|
||||
}
|
||||
sort($actuallibraries);
|
||||
|
||||
$this->assertEquals(array_keys($expectedlibraries), $actuallibraries);
|
||||
|
||||
// Get a subset of libraries.
|
||||
$librariessubset = array_slice($expectedlibraries, 0, 4);
|
||||
|
||||
$actuallibraries = [];
|
||||
$libraries = $this->editorframework->getLibraries($librariessubset);
|
||||
foreach ($libraries as $library) {
|
||||
$actuallibraries[] = $library->title;
|
||||
}
|
||||
|
||||
$this->assertEquals(array_keys($librariessubset), $actuallibraries);
|
||||
}
|
||||
}
|
||||
@@ -1609,9 +1609,17 @@ class framework_testcase extends \advanced_testcase {
|
||||
'libraryMinorVersion' => $mainlibrary->minorversion,
|
||||
'libraryEmbedTypes' => $mainlibrary->embedtypes,
|
||||
'libraryFullscreen' => $mainlibrary->fullscreen,
|
||||
'metadata' => ''
|
||||
'metadata' => '',
|
||||
'pathnamehash' => $h5p->pathnamehash
|
||||
);
|
||||
|
||||
$params = json_decode($h5p->jsoncontent);
|
||||
if (empty($params->metadata)) {
|
||||
$params->metadata = new \stdClass();
|
||||
}
|
||||
$expected['metadata'] = $params->metadata;
|
||||
$expected['params'] = json_encode($params->params ?? $params);
|
||||
|
||||
// The returned content should match the expected array.
|
||||
$this->assertEquals($expected, $content);
|
||||
}
|
||||
|
||||
@@ -381,4 +381,39 @@ class core_h5p_generator extends \component_generator_base {
|
||||
|
||||
return [$installedtypes, count($typestonotinstall)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a record on files table for a file that belongs to
|
||||
*
|
||||
* @param string $file File name and path inside the filearea.
|
||||
* @param string $filearea The filearea in which the file is ("editor" or "content").
|
||||
* @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor.
|
||||
*
|
||||
* @return stored_file;
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
|
||||
$filepath = '/'.dirname($file).'/';
|
||||
$filename = basename($file);
|
||||
|
||||
if (($filearea === 'content') && ($contentid == 0)) {
|
||||
throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
|
||||
}
|
||||
|
||||
$content = 'fake content';
|
||||
|
||||
$systemcontext = context_system::instance();
|
||||
|
||||
$filerecord = array(
|
||||
'contextid' => $systemcontext->id,
|
||||
'component' => \core_h5p\file_storage::COMPONENT,
|
||||
'filearea' => $filearea,
|
||||
'itemid' => ($filearea === 'editor') ? 0 : $contentid,
|
||||
'filepath' => $filepath,
|
||||
'filename' => $filename,
|
||||
);
|
||||
|
||||
$fs = new file_storage();
|
||||
return $fs->create_file_from_string($filerecord, $content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,4 +489,78 @@ class generator_testcase extends \advanced_testcase {
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of create_content_file(). Test whether a file belonging to a content is created.
|
||||
*
|
||||
* @dataProvider test_create_content_file_provider
|
||||
* @param array $filedata Data from the file to be created.
|
||||
* @param array $expecteddata Data expected.Data from the file to be created.
|
||||
*/
|
||||
public function test_create_content_file($filedata, $expecteddata): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = self::getDataGenerator()->get_plugin_generator('core_h5p');
|
||||
|
||||
if ($expecteddata[1] === 'exception') {
|
||||
$this->expectException('coding_exception');
|
||||
}
|
||||
call_user_func_array([$generator, 'create_content_file'], $filedata);
|
||||
|
||||
$systemcontext = \context_system::instance();
|
||||
$filearea = $filedata[1];
|
||||
$filepath = '/'. dirname($filedata[0]). '/';
|
||||
$filename = basename($filedata[0]);
|
||||
$itemid = $expecteddata[0];
|
||||
|
||||
$fs = new \file_storage();
|
||||
$exists = $fs->file_exists($systemcontext->id, file_storage::COMPONENT, $filearea, $itemid, $filepath,
|
||||
$filename);
|
||||
if ($expecteddata[1] === true) {
|
||||
$this->assertTrue($exists);
|
||||
} else if ($expecteddata[1] === false) {
|
||||
$this->assertFalse($exists);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_create_content_file(). Data from different files to be created.
|
||||
*
|
||||
* @return array
|
||||
**/
|
||||
public function test_create_content_file_provider(): array {
|
||||
return [
|
||||
'Create file in content with id 4' => [
|
||||
[
|
||||
'images/img1.png',
|
||||
'content',
|
||||
4
|
||||
],
|
||||
[
|
||||
4,
|
||||
true
|
||||
]
|
||||
],
|
||||
'Create file in the editor' => [
|
||||
[
|
||||
'images/img1.png',
|
||||
'editor'
|
||||
],
|
||||
[
|
||||
0,
|
||||
true
|
||||
]
|
||||
],
|
||||
'Create file in content without id' => [
|
||||
[
|
||||
'images/img1.png',
|
||||
'content'
|
||||
],
|
||||
[
|
||||
0,
|
||||
'exception'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ use core_h5p\file_storage;
|
||||
use core_h5p\local\library\autoloader;
|
||||
use core_h5p\helper;
|
||||
use file_archive;
|
||||
use moodle_exception;
|
||||
use ReflectionMethod;
|
||||
use zip_archive;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -617,4 +619,235 @@ class h5p_file_storage_testcase extends \advanced_testcase {
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the private method get_file, a wrapper for getting an H5P content file.
|
||||
*/
|
||||
public function test_get_file(): void {
|
||||
|
||||
$file = 'img/fake.png';
|
||||
$h5pcontentid = 3;
|
||||
|
||||
// Add a file to a H5P content.
|
||||
$this->h5p_generator->create_content_file($file, file_storage::CONTENT_FILEAREA, $h5pcontentid);
|
||||
|
||||
// Set get_file method accessibility.
|
||||
$method = new ReflectionMethod(file_storage::class, 'get_file');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$contentfile = $method->invoke(new file_storage(), file_storage::CONTENT_FILEAREA, $h5pcontentid, $file);
|
||||
|
||||
// Check that it returns an instance of store_file.
|
||||
$this->assertInstanceOf('stored_file', $contentfile);
|
||||
|
||||
// Add a file to editor.
|
||||
$this->h5p_generator->create_content_file($file, file_storage::EDITOR_FILEAREA, $h5pcontentid);
|
||||
|
||||
$editorfile = $method->invoke(new file_storage(), file_storage::EDITOR_FILEAREA, $h5pcontentid, $file);
|
||||
|
||||
// Check that it returns an instance of store_file.
|
||||
$this->assertInstanceOf('stored_file', $editorfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a single file is added to Moodle files.
|
||||
*/
|
||||
public function test_move_file(): void {
|
||||
|
||||
// Create temp folder.
|
||||
$tempfolder = make_request_directory(false);
|
||||
|
||||
// Create H5P content folder.
|
||||
$filepath = '/img/';
|
||||
$filename = 'fake.png';
|
||||
$h5pcontentfolder = $tempfolder . '/fakeH5Pcontent/content' . $filepath;
|
||||
if (!check_dir_exists($h5pcontentfolder, true, true)) {
|
||||
throw new moodle_exception('error_creating_temp_dir', 'error', $h5pcontentfolder);
|
||||
}
|
||||
|
||||
$file = $h5pcontentfolder . $filename;
|
||||
touch($file);
|
||||
|
||||
$h5pcontentid = 3;
|
||||
|
||||
// Check the file doesn't exist in Moodle files.
|
||||
$this->assertFalse($this->h5p_fs_fs->file_exists($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $h5pcontentid, $filepath, $filename));
|
||||
|
||||
// Set get_file method accessibility.
|
||||
$method = new ReflectionMethod(file_storage::class, 'move_file');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$method->invoke(new file_storage(), $file, $h5pcontentid);
|
||||
|
||||
// Check the file exist in Moodle files.
|
||||
$this->assertTrue($this->h5p_fs_fs->file_exists($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $h5pcontentid, $filepath, $filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a file is copied from another H5P content or the H5P editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_cloneContentFile(): void {
|
||||
|
||||
// Upload a file to the editor.
|
||||
$file = 'images/fake.jpg';
|
||||
$filepath = '/'.dirname($file).'/';
|
||||
$filename = basename($file);
|
||||
|
||||
$content = 'abcd';
|
||||
|
||||
$filerecord = array(
|
||||
'contextid' => $this->h5p_fs_context->id,
|
||||
'component' => file_storage::COMPONENT,
|
||||
'filearea' => file_storage::EDITOR_FILEAREA,
|
||||
'itemid' => 0,
|
||||
'filepath' => $filepath,
|
||||
'filename' => $filename,
|
||||
);
|
||||
|
||||
$this->h5p_fs_fs->create_file_from_string($filerecord, $content);
|
||||
|
||||
// Target H5P content, where the file will be cloned.
|
||||
$targetcontent = new \stdClass();
|
||||
$targetcontent->id = 999;
|
||||
|
||||
// Check the file doesn't exists before cloning.
|
||||
$this->assertFalse($this->h5p_fs_fs->get_file($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $targetcontent->id, $filepath, $filename));
|
||||
|
||||
// Copy file from the editor.
|
||||
$this->h5p_file_storage->cloneContentFile($file, 'editor', $targetcontent);
|
||||
|
||||
// Check the file exists after cloning.
|
||||
$this->assertInstanceOf(\stored_file::class, $this->h5p_fs_fs->get_file($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $targetcontent->id, $filepath, $filename));
|
||||
|
||||
// Simulate that an H5P content, with id $sourcecontentid, has a file.
|
||||
$file = 'images/fake2.jpg';
|
||||
$filepath = '/'.dirname($file).'/';
|
||||
$filename = basename($file);
|
||||
|
||||
$sourcecontentid = 111;
|
||||
$filerecord['filearea'] = 'content';
|
||||
$filerecord['itemid'] = $sourcecontentid;
|
||||
$filerecord['filepath'] = $filepath;
|
||||
$filerecord['filename'] = $filename;
|
||||
|
||||
$this->h5p_fs_fs->create_file_from_string($filerecord, $content);
|
||||
|
||||
// Check the file doesn't exists before cloning.
|
||||
$this->assertFalse($this->h5p_fs_fs->get_file($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $targetcontent->id, $filepath, $filename));
|
||||
|
||||
// Copy file from another H5P content.
|
||||
$this->h5p_file_storage->cloneContentFile($file, $sourcecontentid, $targetcontent);
|
||||
|
||||
// Check the file exists after cloning.
|
||||
$this->assertInstanceOf(\stored_file::class, $this->h5p_fs_fs->get_file($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $targetcontent->id, $filepath, $filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a given file exists in an H5P content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_getContentFile(): void {
|
||||
|
||||
$file = 'img/fake.png';
|
||||
$contentid = 3;
|
||||
|
||||
// Add a file to a H5P content.
|
||||
$this->h5p_generator->create_content_file($file, file_storage::CONTENT_FILEAREA, $contentid);
|
||||
|
||||
// Get an existing file id.
|
||||
$fileid = $this->h5p_file_storage->getContentFile($file, $contentid);
|
||||
$this->assertNotNull($fileid);
|
||||
|
||||
// Try to get a nonexistent file.
|
||||
$fileid = $this->h5p_file_storage->getContentFile($file, 5);
|
||||
$this->assertNull($fileid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the content folder of an H5P content is imported in the Moodle filesystem.
|
||||
*/
|
||||
public function test_moveContentDiretory(): void {
|
||||
global $DB;
|
||||
|
||||
// Create temp folder.
|
||||
$tempfolder = make_request_directory(false);
|
||||
|
||||
// Create H5P content folder.
|
||||
$h5pcontentfolder = $tempfolder . '/fakeH5Pcontent';
|
||||
$contentfolder = $h5pcontentfolder . '/content';
|
||||
if (!check_dir_exists($contentfolder, true, true)) {
|
||||
throw new moodle_exception('error_creating_temp_dir', 'error', $contentfolder);
|
||||
}
|
||||
|
||||
// Add content.json file.
|
||||
touch($contentfolder . 'content.json');
|
||||
|
||||
// Create several folders and files inside content folder.
|
||||
$filesexpected = array();
|
||||
$numfolders = random_int(2, 5);
|
||||
for ($numfolder = 1; $numfolder < $numfolders; $numfolder++) {
|
||||
$foldername = '/folder' . $numfolder;
|
||||
$newfolder = $contentfolder . $foldername;
|
||||
if (!check_dir_exists($newfolder, true, true)) {
|
||||
throw new moodle_exception('error_creating_temp_dir', 'error', $newfolder);
|
||||
}
|
||||
$numfiles = random_int(2, 5);
|
||||
for ($numfile = 1; $numfile < $numfiles; $numfile++) {
|
||||
$filename = '/file' . $numfile . '.ext';
|
||||
touch($newfolder . $filename);
|
||||
$filesexpected[] = $foldername . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
$targeth5pcontentid = 111;
|
||||
$this->h5p_file_storage->moveContentDirectory($h5pcontentfolder, $targeth5pcontentid);
|
||||
|
||||
// Get database records.
|
||||
$sql = "SELECT concat(filepath, filename)
|
||||
FROM {files}
|
||||
WHERE filearea = :filearea AND itemid = :itemid AND component = :component AND filename != '.'";
|
||||
$params = [
|
||||
'component' => file_storage::COMPONENT,
|
||||
'filearea' => file_storage::CONTENT_FILEAREA,
|
||||
'itemid' => $targeth5pcontentid
|
||||
];
|
||||
$filesdb = $DB->get_fieldset_sql($sql, $params);
|
||||
sort($filesdb);
|
||||
|
||||
// Check that created files match with database records.
|
||||
$this->assertEquals($filesexpected, $filesdb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an H5P content file is removed.
|
||||
*/
|
||||
public function test_removeContentFile(): void {
|
||||
|
||||
$file = 'img/fake.png';
|
||||
$filepath = '/' . dirname($file) . '/';
|
||||
$filename = basename($file);
|
||||
$h5pcontentid = 3;
|
||||
|
||||
// Add a file to a H5P content.
|
||||
$this->h5p_generator->create_content_file($file, file_storage::CONTENT_FILEAREA, $h5pcontentid);
|
||||
|
||||
// Check the file exists.
|
||||
$this->assertTrue($this->h5p_fs_fs->file_exists($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $h5pcontentid, $filepath, $filename));
|
||||
|
||||
$this->h5p_file_storage->removeContentFile($file, $h5pcontentid);
|
||||
|
||||
// Check the file doesn't exists.
|
||||
$this->assertFalse($this->h5p_fs_fs->file_exists($this->h5p_fs_context->id, file_storage::COMPONENT,
|
||||
file_storage::CONTENT_FILEAREA, $h5pcontentid, $filepath, $filename));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ $string['noextension'] = 'The file you uploaded is not a valid HTML5 Package. (I
|
||||
$string['noh5plibhandlerdefined'] = 'There isn\'t any H5P framework handler installed, so H5P content can\'t be displayed.';
|
||||
$string['nojson'] = 'The main h5p.json file is not valid';
|
||||
$string['nopermissiontodeploy'] = 'This file can\'t be displayed because it has been uploaded by a user without the required capability to deploy H5P content.';
|
||||
$string['nopermissiontoedit'] = 'You do not have permission to edit H5P content';
|
||||
$string['notrustablefile'] = 'This file can\'t be displayed because it has been uploaded by a user without the capability to update H5P content types. Please contact your administrator to ask for the content type to be installed.';
|
||||
$string['nounzip'] = 'The file you uploaded is not a valid HTML5 Package. (It is not possible to unzip it.)';
|
||||
$string['offlineDialogBody'] = 'We were unable to send information about your completion of this task. Please check your internet connection.';
|
||||
|
||||
Reference in New Issue
Block a user