From 6281b7536dbbea449ecb70719b93e515c0637aa4 Mon Sep 17 00:00:00 2001 From: Christian Einvik <84850107+chrieinv@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:07:02 +0200 Subject: [PATCH] Update h5p packages (#2291) * Update h5p/h5p-core and h5p/h5p-editor Composer packages, use patch version in library folder name --- sourcecode/apis/contentauthor/.gitignore | 2 + .../app/Console/Commands/PublishPresave.php | 2 +- .../apis/contentauthor/app/H5PContent.php | 9 +- .../app/H5PLibrariesHubCache.php | 16 +- .../apis/contentauthor/app/H5PLibrary.php | 77 +- .../Admin/AdminH5PDetailsController.php | 2 +- .../app/Http/Controllers/H5PController.php | 2 +- .../app/Libraries/H5P/AjaxRequest.php | 14 +- .../app/Libraries/H5P/EditorAjax.php | 2 +- .../app/Libraries/H5P/EditorStorage.php | 4 +- .../app/Libraries/H5P/Framework.php | 60 +- .../app/Libraries/H5P/H5PExport.php | 4 +- .../app/Libraries/H5P/H5PViewConfig.php | 2 +- .../H5P/Interfaces/CerpusStorageInterface.php | 4 - .../H5P/Storage/H5PCerpusStorage.php | 23 +- sourcecode/apis/contentauthor/composer.json | 4 +- sourcecode/apis/contentauthor/composer.lock | 40 +- ..._in_folder_name_to_h5p_libraries_table.php | 31 + .../js/h5p/core-override/h5p-content-type.js | 41 + .../h5p/{ => core-override}/request-queue.js | 0 .../assets/entrypoints/h5p-core-bundle.js | 5 +- .../assets/entrypoints/h5p-core.scss | 1 + .../resources/views/h5p/show.blade.php | 4 +- .../Integration/Article/ArticleLockTest.php | 1 + .../tests/Integration/Article/ArticleTest.php | 8 +- .../Article/ArticleVersioningTest.php | 1 + .../tests/Integration/H5PContentTest.php | 64 ++ .../Integration/H5PLibrariesHubCacheTest.php | 32 + .../Http/Controllers/H5PControllerTest.php | 29 +- .../H5P/API/H5PImportControllerTest.php | 6 +- .../Libraries/H5P/AjaxRequestTest.php | 64 ++ .../Integration/Libraries/H5P/CRUTest.php | 5 +- .../Libraries/H5P/EditorAjaxTest.php | 74 ++ .../Libraries/H5P/EditorStorageTest.php | 58 + .../Libraries/H5P/FrameworkTest.php | 206 ++++ .../Libraries/H5P/H5PExportTest.php | 35 +- .../H5P/Storage/H5pCerpusStorageTest.php | 64 +- .../Integration/Models/H5PLibraryTest.php | 100 ++ .../AdminH5PDetailsControllerTest.php | 11 +- .../Unit/Libraries/H5P/FrameworkTest.php | 26 + .../H5P.Blanks-1.14.6/css/blanks.css | 113 ++ .../libraries/H5P.Blanks-1.14.6/icon.svg | 68 ++ .../libraries/H5P.Blanks-1.14.6/js/blanks.js | 1004 +++++++++++++++++ .../libraries/H5P.Blanks-1.14.6/js/cloze.js | 238 ++++ .../H5P.Blanks-1.14.6/language/nb.json | 214 ++++ .../libraries/H5P.Blanks-1.14.6/library.json | 65 ++ .../libraries/H5P.Blanks-1.14.6/presave.js | 40 + .../H5P.Blanks-1.14.6/semantics.json | 489 ++++++++ .../libraries/H5P.Blanks-1.14.6/upgrades.js | 124 ++ 49 files changed, 3372 insertions(+), 116 deletions(-) create mode 100644 sourcecode/apis/contentauthor/database/migrations/2023_04_25_065450_add_patch_version_in_folder_name_to_h5p_libraries_table.php create mode 100755 sourcecode/apis/contentauthor/public/js/h5p/core-override/h5p-content-type.js rename sourcecode/apis/contentauthor/public/js/h5p/{ => core-override}/request-queue.js (100%) mode change 100644 => 100755 create mode 100644 sourcecode/apis/contentauthor/tests/Integration/H5PContentTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/H5PLibrariesHubCacheTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/AjaxRequestTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorAjaxTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorStorageTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/FrameworkTest.php create mode 100644 sourcecode/apis/contentauthor/tests/Integration/Models/H5PLibraryTest.php create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/css/blanks.css create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/icon.svg create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/js/blanks.js create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/js/cloze.js create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/language/nb.json create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/library.json create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/presave.js create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/semantics.json create mode 100644 sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/upgrades.js diff --git a/sourcecode/apis/contentauthor/.gitignore b/sourcecode/apis/contentauthor/.gitignore index a6ae563e73..b9730013e9 100644 --- a/sourcecode/apis/contentauthor/.gitignore +++ b/sourcecode/apis/contentauthor/.gitignore @@ -31,10 +31,12 @@ Homestead.json !/public/js/videos/brightcove.js !/public/js/videos/streamps.js !/public/js/h5p/ndlah5p-youtube.js +!public/js/h5p/core-override !/public/js/h5p/h5peditor-pre-save.js !public/css /public/css/* !/public/css/ndlah5p-youtube.css + mix-manifest.json app/Libraries/oauth-php/example/server/cache/ ca1.sql diff --git a/sourcecode/apis/contentauthor/app/Console/Commands/PublishPresave.php b/sourcecode/apis/contentauthor/app/Console/Commands/PublishPresave.php index d54850953c..bc2383837e 100644 --- a/sourcecode/apis/contentauthor/app/Console/Commands/PublishPresave.php +++ b/sourcecode/apis/contentauthor/app/Console/Commands/PublishPresave.php @@ -57,7 +57,7 @@ public function handle(): void private static function getDestination(H5PLibrary $library): string { - $directory = $library->getLibraryString(true); + $directory = $library->getFolderName(); return "libraries/$directory/presave.js"; } diff --git a/sourcecode/apis/contentauthor/app/H5PContent.php b/sourcecode/apis/contentauthor/app/H5PContent.php index a9e4838e64..bb89121d20 100644 --- a/sourcecode/apis/contentauthor/app/H5PContent.php +++ b/sourcecode/apis/contentauthor/app/H5PContent.php @@ -344,14 +344,7 @@ public static function getContentTypeInfo(string $contentType): ?ContentTypeData if ($library->has_icon) { $h5pFramework = app(H5PFrameworkInterface::class); - - $library_folder = H5PCore::libraryToString([ - 'machineName' => $library->machine_name, - 'majorVersion' => $library->major_version, - 'minorVersion' => $library->minor_version - ], true); - - + $library_folder = $library->getFolderName(); $icon_path = $h5pFramework->getLibraryFileUrl($library_folder, 'icon.svg'); if (!empty($icon_path)) { diff --git a/sourcecode/apis/contentauthor/app/H5PLibrariesHubCache.php b/sourcecode/apis/contentauthor/app/H5PLibrariesHubCache.php index 895e297860..722ed01c64 100644 --- a/sourcecode/apis/contentauthor/app/H5PLibrariesHubCache.php +++ b/sourcecode/apis/contentauthor/app/H5PLibrariesHubCache.php @@ -45,10 +45,16 @@ public function libraries(): HasMany public function getLibraryString($folderName = false) { - return \H5PCore::libraryToString([ - 'machineName' => $this->name, - 'majorVersion' => $this->major_version, - 'minorVersion' => $this->minor_version, - ], $folderName); + return $folderName ? + \H5PCore::libraryToFolderName([ + 'machineName' => $this->name, + 'majorVersion' => $this->major_version, + 'minorVersion' => $this->minor_version, + ]) : + \H5PCore::libraryToString([ + 'machineName' => $this->name, + 'majorVersion' => $this->major_version, + 'minorVersion' => $this->minor_version, + ]); } } diff --git a/sourcecode/apis/contentauthor/app/H5PLibrary.php b/sourcecode/apis/contentauthor/app/H5PLibrary.php index b0577e85e7..6316f5ecb2 100644 --- a/sourcecode/apis/contentauthor/app/H5PLibrary.php +++ b/sourcecode/apis/contentauthor/app/H5PLibrary.php @@ -36,6 +36,10 @@ class H5PLibrary extends Model protected $guarded = ['id']; + protected $casts = [ + 'patch_version_in_folder_name' => 'bool', + ]; + protected static function boot(): void { parent::boot(); @@ -144,23 +148,86 @@ public function getVersions($asModels = false) return $asModels !== true ? $versions : $this->hydrate($versions->toArray()); } - public function getLibraryString($folderName = false) + /** + * @param bool|null $withPatchVersion Null to use patchVersionInFolderName value to decide or true/false to force + */ + public function getLibraryString(?bool $withPatchVersion = null): string + { + return self::getLibraryName($this->getLibraryH5PFriendly(), false, $withPatchVersion); + } + + /** + * @param bool|null $withPatchVersion Null to use patchVersionInFolderName value to decide or true/false to force + */ + public function getFolderName(?bool $withPatchVersion = null): string { - return \H5PCore::libraryToString($this->getLibraryH5PFriendly(), $folderName); + return self::getLibraryName($this->getLibraryH5PFriendly(), true, $withPatchVersion); } - public function getLibraryH5PFriendly($machineName = 'name') + /** + * @param array{machineName?: string, name?: string, majorVersion: int, minorVersion: int, patchVersion: int, patchVersionInFolderName: bool} $libraryData + * @param bool|null $withPatchVersion Null to use patchVersionInFolderName value to decide or true/false to force + * @throws \InvalidArgumentException If requesting full version without patchVersion present in data + */ + public static function libraryToFolderName(array $libraryData, ?bool $withPatchVersion = null): string + { + return self::getLibraryName($libraryData, true, $withPatchVersion); + } + + /** + * @param array{machineName?: string, name?: string, majorVersion: int, minorVersion: int, patchVersion: int, patchVersionInFolderName: bool} $libraryData + * @param bool|null $withPatchVersion Null to use patchVersionInFolderName value to decide or true/false to force + * @throws \InvalidArgumentException If requesting full version without patchVersion present in data + */ + public static function libraryToString(array $libraryData, ?bool $withPatchVersion = null): string + { + return self::getLibraryName($libraryData, false, $withPatchVersion); + } + + /** + * @throws \InvalidArgumentException If requesting full version without patchVersion present in data + */ + private static function getLibraryName(array $libraryData, bool $asFolder, ?bool $withPatchVersion): string + { + $usePatch = $withPatchVersion === true || ($withPatchVersion === null && array_key_exists('patchVersionInFolderName', $libraryData) && $libraryData['patchVersionInFolderName']); + if ($usePatch && !isset($libraryData['patchVersion'])) { + throw new \InvalidArgumentException('Full version name requested but patch version missing'); + } + + if ($usePatch) { + $format = $asFolder ? '%s-%d.%d.%d' : '%s %d.%d.%d'; + } else { + $format = $asFolder ? '%s-%d.%d' : '%s %d.%d'; + } + + return sprintf( + $format, + $libraryData['machineName'] ?? $libraryData['name'], + $libraryData['majorVersion'], + $libraryData['minorVersion'], + $libraryData['patchVersion'] ?? '' + ); + } + + public function getLibraryH5PFriendly($machineName = 'name'): array { return [ 'machineName' => $this->$machineName, 'majorVersion' => $this->major_version, 'minorVersion' => $this->minor_version, + 'patchVersion' => $this->patch_version, + 'patchVersionInFolderName' => $this->patch_version_in_folder_name, ]; } public function getTitleAndVersionString() { - return \H5PCore::libraryToString($this->getLibraryH5PFriendly('title')); + return self::getLibraryName([ + 'machineName' => $this->title, + 'majorVersion' => $this->major_version, + 'minorVersion' => $this->minor_version, + 'patchVersion' => $this->patch_version, + ], false, true); } /** @@ -249,7 +316,7 @@ public function getAddons() public function supportsMaxScore(): bool { - $libraryLocation = sprintf('libraries/%s/presave.js', self::getLibraryString(true)); + $libraryLocation = sprintf('libraries/%s/presave.js', self::getFolderName()); if (Storage::disk()->exists($libraryLocation)) { return true; } diff --git a/sourcecode/apis/contentauthor/app/Http/Controllers/Admin/AdminH5PDetailsController.php b/sourcecode/apis/contentauthor/app/Http/Controllers/Admin/AdminH5PDetailsController.php index 174de114b1..c7bbe3cdb3 100644 --- a/sourcecode/apis/contentauthor/app/Http/Controllers/Admin/AdminH5PDetailsController.php +++ b/sourcecode/apis/contentauthor/app/Http/Controllers/Admin/AdminH5PDetailsController.php @@ -31,7 +31,7 @@ public function __construct( public function checkLibrary(H5PLibrary $library): View { - $h5pDataFolderName = $library->getLibraryString(true); + $h5pDataFolderName = $library->getFolderName(); $tmpLibrariesRelative = 'libraries'; $tmpLibraryRelative = 'libraries/' . $h5pDataFolderName; // Download files from bucket to tmp folder diff --git a/sourcecode/apis/contentauthor/app/Http/Controllers/H5PController.php b/sourcecode/apis/contentauthor/app/Http/Controllers/H5PController.php index 983ffd4762..381168c953 100644 --- a/sourcecode/apis/contentauthor/app/Http/Controllers/H5PController.php +++ b/sourcecode/apis/contentauthor/app/Http/Controllers/H5PController.php @@ -332,7 +332,7 @@ public function edit(Request $request, int $id): View $state = H5PStateDataObject::create($displayOptions + [ 'id' => $h5pContent->id, - 'library' => $library->getLibraryString(), + 'library' => $library->getLibraryString(false), 'libraryid' => $h5pContent->library_id, 'parameters' => $params, 'language_iso_639_3' => $contentLanguage, diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/AjaxRequest.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/AjaxRequest.php index 58aebcaad6..a53987606e 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/AjaxRequest.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/AjaxRequest.php @@ -259,8 +259,8 @@ private function libraryRebuild(Request $request): array $libraries = collect(); $this->getLibraryDetails($H5PLibrary, $libraries); - if ($libraries->has($H5PLibrary->getLibraryString())) { - $libraryData = $libraries->get($H5PLibrary->getLibraryString()); + if ($libraries->has($H5PLibrary->getLibraryString(false))) { + $libraryData = $libraries->get($H5PLibrary->getLibraryString(false)); if (array_key_exists('semantics', $libraryData)) { $H5PLibrary->semantics = $libraryData['semantics']; $H5PLibrary->save(); @@ -292,7 +292,7 @@ private function getLibraryDetails(H5PLibrary $H5PLibrary, Collection $affectedL { /** @var H5PValidator $validator */ $validator = resolve(H5PValidator::class); - $h5pDataFolderName = $H5PLibrary->getLibraryString(true); + $h5pDataFolderName = $H5PLibrary->getFolderName(); $tmpLibrariesRelative = 'libraries'; $tmpLibraryRelative = 'libraries/' . $h5pDataFolderName; // Download files from bucket to tmp folder @@ -304,18 +304,18 @@ private function getLibraryDetails(H5PLibrary $H5PLibrary, Collection $affectedL ); $tmpLibraries = $this->core->h5pF->getH5pPath($tmpLibrariesRelative); $tmpLibraryFolder = $this->core->h5pF->getH5pPath($tmpLibraryRelative); - $libraryData = $validator->getLibraryData($H5PLibrary->getLibraryString(true), $tmpLibraryFolder, $tmpLibraries); + $libraryData = $validator->getLibraryData($H5PLibrary->getFolderName(), $tmpLibraryFolder, $tmpLibraries); $libraryData['libraryId'] = $H5PLibrary->id; - if (!$affectedLibraries->has($H5PLibrary->getLibraryString())) { - $affectedLibraries->put($H5PLibrary->getLibraryString(), $libraryData); + if (!$affectedLibraries->has($H5PLibrary->getLibraryString(false))) { + $affectedLibraries->put($H5PLibrary->getLibraryString(false), $libraryData); } foreach (['preloadedDependencies', 'dynamicDependencies', 'editorDependencies'] as $value) { if (!empty($libraryData[$value])) { foreach ($libraryData[$value] as $library) { /** @var H5PLibrary $dependentLibrary */ $dependentLibrary = H5PLibrary::fromLibrary($library)->first(); - if (!$affectedLibraries->has($dependentLibrary->getLibraryString())) { + if (!$affectedLibraries->has($dependentLibrary->getLibraryString(false))) { $affectedLibraries = $this->getLibraryDetails($dependentLibrary, $affectedLibraries); } } diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorAjax.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorAjax.php index b461aa8801..0e1a5e2731 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorAjax.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorAjax.php @@ -102,7 +102,7 @@ public function getTranslations($libraries, $language_code) }) ->get() ->mapWithKeys(function ($library) { - return [$library->library->getLibraryString() => $library->translation]; + return [$library->library->getLibraryString(false) => $library->translation]; }) ->toArray(); } diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorStorage.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorStorage.php index e35612a558..7754d1ea4c 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorStorage.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/EditorStorage.php @@ -65,7 +65,7 @@ public function getLibraries($libraries = null) ->map(function ($h5pLibrary) { /** @var H5PLibrary $h5pLibrary */ $library = [ - 'uberName' => $h5pLibrary->getLibraryString(), + 'uberName' => $h5pLibrary->getLibraryString(false), 'name' => $h5pLibrary->name, 'majorVersion' => $h5pLibrary->major_version, 'minorVersion' => $h5pLibrary->minor_version, @@ -101,7 +101,7 @@ public function getLibraries($libraries = null) $library->minorVersion = $library->minor_version; // Add new library - $library->uberName = $library->getLibraryString(); + $library->uberName = $library->getLibraryString(false); if ($index > 0) { $library->isOld = true; } diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/Framework.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/Framework.php index 0776f5d758..09805149d0 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/Framework.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/Framework.php @@ -21,6 +21,7 @@ use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Request; +use Illuminate\Support\Stringable; use InvalidArgumentException; use PDO; use Psr\Http\Message\ResponseInterface; @@ -132,19 +133,35 @@ public function getPlatformInfo() ]; } - public function fetchExternalData($url, $data = null, $blocking = true, $stream = null): string|null - { + public function fetchExternalData( + $url, + $data = null, + $blocking = true, + $stream = null, + $fullData = false, + $headers = [], + $files = [], + $method = 'POST' + ): string|array|null { $method = $data ? 'POST' : 'GET'; $options = [RequestOptions::FORM_PARAMS => $data]; if ($stream !== null) { $options[RequestOptions::SINK] = $stream; } + $options[RequestOptions::HEADERS] = $headers; return $this->httpClient->requestAsync($method, $url, $options) - ->then(static function (ResponseInterface $response) use ($blocking) { + ->then(static function (ResponseInterface $response) use ($blocking, $fullData) { if (!$blocking) { return null; } + if ($fullData) { + return [ + 'status' => $response->getStatusCode(), + 'headers' => $response->getHeaders(), + 'data' => $response->getBody()->getContents(), + ]; + } return $response->getBody()->getContents(); }) @@ -490,7 +507,8 @@ public function saveLibraryData(&$libraryData, $new = true) 'metadata_settings' => $libraryData['metadataSettings'], 'add_to' => $libraryData['addTo'], 'has_icon' => $libraryData['hasIcon'] ?? 0, - 'tutorial_url' => '' + 'tutorial_url' => '', + 'patch_version_in_folder_name' => true, ]); $libraryData['libraryId'] = $h5pLibrary->id; @@ -809,6 +827,7 @@ public function loadLibrary($machineName, $majorVersion, $minorVersion): array|f 'preloadedCss' => $h5pLibrary->preloaded_css, 'dropLibraryCss' => $h5pLibrary->drop_library_css, 'semantics' => $h5pLibrary->semantics, + 'patchVersionInFolderName' => $h5pLibrary->patch_version_in_folder_name, ]; foreach ($h5pLibrary->libraries as $dependency) { @@ -816,6 +835,8 @@ public function loadLibrary($machineName, $majorVersion, $minorVersion): array|f 'machineName' => $dependency->requiredLibrary->name, 'majorVersion' => $dependency->requiredLibrary->major_version, 'minorVersion' => $dependency->requiredLibrary->minor_version, + 'patchVersion' => $dependency->requiredLibrary->patch_version, + 'patchVersionInFolderName' => $dependency->requiredLibrary->patch_version_in_folder_name, ]; } @@ -897,10 +918,11 @@ public function deleteLibrary($library): void throw new TypeError(sprintf('Expected object, %s given', get_debug_type($library))); } + /** @var H5PLibrary $libraryModel */ $libraryModel = H5PLibrary::findOrFail($library->id); $libraryModel->deleteOrFail(); - app(CerpusStorageInterface::class)->deleteLibrary($libraryModel); + app(\H5PFileStorage::class)->deleteLibrary($libraryModel->getLibraryH5PFriendly()); } /** @@ -943,6 +965,8 @@ public function loadContent($id) 'libraryName' => $h5pcontent->library->name, 'libraryMajorVersion' => $h5pcontent->library->major_version, 'libraryMinorVersion' => $h5pcontent->library->minor_version, + 'libraryPatchVersion' => $h5pcontent->library->patch_version, + 'libraryFullVersionName' => $h5pcontent->library->getLibraryString(), 'libraryEmbedTypes' => $h5pcontent->library->embed_types, 'libraryFullscreen' => $h5pcontent->library->fullscreen, 'language' => $h5pcontent->metadata->default_language ?? null, @@ -976,6 +1000,8 @@ public function loadContent($id) * - preloadedJs(optional): comma separated string with js file paths * - preloadedCss(optional): comma separated sting with css file paths * - dropCss(optional): csv of machine names + * - dependencyType: editor or preloaded + * - patchVersionInFolderName: Is patch version a part of the folder name */ public function loadContentDependencies($id, $type = null) { @@ -993,6 +1019,7 @@ public function loadContentDependencies($id, $type = null) , hl.preloaded_js AS preloadedJs , hcl.drop_css AS dropCss , hcl.dependency_type AS dependencyType + , hl.patch_version_in_folder_name AS patchVersionInFolderName FROM h5p_contents_libraries hcl JOIN h5p_libraries hl ON hcl.library_id = hl.id WHERE hcl.content_id = ?"; @@ -1324,4 +1351,27 @@ public function libraryHasUpgrade($library) $h5pLibrary = H5PLibrary::fromLibrary($library)->first(); return !is_null($h5pLibrary) && $h5pLibrary->isUpgradable(); } + + public function replaceContentHubMetadataCache($metadata, $lang) + { + // H5P Content Hub is not in use + } + + public function getContentHubMetadataCache($lang = 'en') + { + // H5P Content Hub is not in use + return new Stringable(); + } + + public function getContentHubMetadataChecked($lang = 'en') + { + // H5P Content Hub is not in use + return now()->toRfc7231String(); + } + + public function setContentHubMetadataChecked($time, $lang = 'en') + { + // H5P Content Hub is not in use + return true; + } } diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PExport.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PExport.php index ef6b211808..e212c435d9 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PExport.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PExport.php @@ -44,7 +44,7 @@ public function generateExport(H5PContent $content): bool $parameters = $content->parameters; } - $library = H5PCore::libraryFromString($content->library->getLibraryString()) + $library = H5PCore::libraryFromString($content->library->getLibraryString(false)) ?: throw new UnexpectedValueException('Bad library string'); $library['libraryId'] = $content->library->id; $library['name'] = $content->library->name; @@ -60,7 +60,7 @@ public function generateExport(H5PContent $content): bool // resolving dependencies, as it likes to corrupt the data $validatorParams = (object)[ - 'library' => $content->library->getLibraryString(), + 'library' => $content->library->getLibraryString(false), 'params' => json_decode($content->parameters) ]; $this->validator->validateLibrary($validatorParams, (object) [ diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PViewConfig.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PViewConfig.php index 0aaee9ae4a..581d16c676 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PViewConfig.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/H5PViewConfig.php @@ -76,7 +76,7 @@ public function loadContent(int $id): static ); $this->contentConfig['exportUrl'] = route('content-download', ['h5p' => $this->content['id']]); - $this->contentConfig['library'] = $this->h5pCore->libraryToString($this->content['library']); + $this->contentConfig['library'] = $this->content['libraryFullVersionName']; $this->contentConfig['fullScreen'] = $this->content['library']['fullscreen']; $this->contentConfig['title'] = $this->content['title']; $this->contentConfig['metadata'] = $this->content['metadata']; diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/Interfaces/CerpusStorageInterface.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/Interfaces/CerpusStorageInterface.php index 546efea1f4..643768c6fd 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/Interfaces/CerpusStorageInterface.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/Interfaces/CerpusStorageInterface.php @@ -2,8 +2,6 @@ namespace App\Libraries\H5P\Interfaces; -use App\H5PLibrary; - interface CerpusStorageInterface { public function getDisplayPath(bool $fullUrl = true); @@ -18,8 +16,6 @@ public function getAjaxPath(); public function alterLibraryFiles($files); - public function deleteLibrary(H5PLibrary $library); - public function getFileUrl(string $path); /** diff --git a/sourcecode/apis/contentauthor/app/Libraries/H5P/Storage/H5PCerpusStorage.php b/sourcecode/apis/contentauthor/app/Libraries/H5P/Storage/H5PCerpusStorage.php index eb3429071f..5e8c02192c 100644 --- a/sourcecode/apis/contentauthor/app/Libraries/H5P/Storage/H5PCerpusStorage.php +++ b/sourcecode/apis/contentauthor/app/Libraries/H5P/Storage/H5PCerpusStorage.php @@ -107,7 +107,8 @@ public function cloneContentFile($file, $fromId, $toId) */ public function saveLibrary($library) { - $path = sprintf(ContentStorageSettings::LIBRARY_PATH, \H5PCore::libraryToString($library, true)); + $library['patchVersionInFolderName'] = true; + $path = sprintf(ContentStorageSettings::LIBRARY_PATH, \H5PCore::libraryToFolderName($library)); $libraryPath = Str::after($library['uploadDirectory'], $this->uploadDisk->path("")); $this->deleteLibraryFromPath($path); @@ -236,9 +237,11 @@ public function exportContent($id, $target) */ public function exportLibrary($library, $target) { - $folder = \H5PCore::libraryToString($library, true); + $folder = H5PLibrary::libraryToFolderName($library); + // To make the exported file backward compatible, we don't use patch in target folder name + $targetFolder = H5PLibrary::libraryToFolderName($library, false); $srcPath = sprintf(ContentStorageSettings::LIBRARY_PATH, $folder); - $finalTarget = Str::after($target, $this->uploadDisk->path("")) . "/$folder"; + $finalTarget = Str::after($target, $this->uploadDisk->path("")) . "/$targetFolder"; if ($this->hasLibraryVersion($folder, sprintf(ContentStorageSettings::LIBRARY_VERSION_PREFIX, $library['majorVersion'], $library['minorVersion'], $library['patchVersion']))) { $this->exportLocalDirectory($srcPath, $finalTarget, $folder); } else { @@ -494,11 +497,10 @@ public function hasPresave($libraryName, $developmentPath = null) */ public function getUpgradeScript($machineName, $majorVersion, $minorVersion) { - $path = sprintf(ContentStorageSettings::UPGRADE_SCRIPT_PATH, \H5PCore::libraryToString([ - 'machineName' => $machineName, - 'majorVersion' => $majorVersion, - 'minorVersion' => $minorVersion, - ], true)); + /** @var H5PLibrary $library */ + $library = H5PLibrary::fromLibrary([$machineName, $majorVersion, $minorVersion])->latestVersion()->first(); + $path = sprintf(ContentStorageSettings::UPGRADE_SCRIPT_PATH, $library->getFolderName()); + return $this->filesystem->exists($path) ? "/$path" : null; } @@ -601,11 +603,12 @@ private function hasLibraryVersion($path, $versionString): bool ->isNotEmpty(); } - public function deleteLibrary(H5PLibrary $library) + public function deleteLibrary($library) { - $libraryPath = sprintf(ContentStorageSettings::LIBRARY_PATH, $library->getLibraryString(true)); + $libraryPath = sprintf(ContentStorageSettings::LIBRARY_PATH, \H5PCore::libraryToFolderName($library)); $deleteRemote = $this->deleteLibraryFromPath($libraryPath); $deleteLocal = $this->uploadDisk->exists($libraryPath) ? $this->uploadDisk->deleteDirectory($libraryPath) : true; + return $deleteRemote && $deleteLocal; } diff --git a/sourcecode/apis/contentauthor/composer.json b/sourcecode/apis/contentauthor/composer.json index be9b5acc0e..d68e52ef1d 100644 --- a/sourcecode/apis/contentauthor/composer.json +++ b/sourcecode/apis/contentauthor/composer.json @@ -27,8 +27,8 @@ "embed/embed": "^3.2", "firebase/php-jwt": "^6.3", "guzzlehttp/guzzle": "^7.4", - "h5p/h5p-core": "^1.24", - "h5p/h5p-editor": "dev-master#1ae19fdb80839b32dad3846d6b0a5c745f8f6187", + "h5p/h5p-core": "dev-master#0a82667e00175dea55e37ad8bbebedbfed25b5b6", + "h5p/h5p-editor": "dev-master#0365b081efa8b55ab9fd58594aa599f9630268f6", "laravel/framework": "^9.17", "laravel/horizon": "^5.7", "laravel/tinker": "^2.6", diff --git a/sourcecode/apis/contentauthor/composer.lock b/sourcecode/apis/contentauthor/composer.lock index b4762e51d0..caad9f3bc7 100644 --- a/sourcecode/apis/contentauthor/composer.lock +++ b/sourcecode/apis/contentauthor/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8f90b383667fa459cb93c1d1c6f8360", + "content-hash": "43a247439a9ee7a8b7897e4ede814bcd", "packages": [ { "name": "auth0/auth0-php", @@ -2463,21 +2463,22 @@ }, { "name": "h5p/h5p-core", - "version": "1.24.3", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/h5p/h5p-php-library.git", - "reference": "db3da7a1441ae6c9ffacbb8110f41758c85beaa0" + "reference": "0a82667e00175dea55e37ad8bbebedbfed25b5b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/h5p/h5p-php-library/zipball/db3da7a1441ae6c9ffacbb8110f41758c85beaa0", - "reference": "db3da7a1441ae6c9ffacbb8110f41758c85beaa0", + "url": "https://api.github.com/repos/h5p/h5p-php-library/zipball/0a82667e00175dea55e37ad8bbebedbfed25b5b6", + "reference": "0a82667e00175dea55e37ad8bbebedbfed25b5b6", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.0.0" }, + "default-branch": true, "type": "library", "autoload": { "files": [ @@ -2518,9 +2519,9 @@ ], "support": { "issues": "https://github.com/h5p/h5p-php-library/issues", - "source": "https://github.com/h5p/h5p-php-library/tree/wp-1.15.3" + "source": "https://github.com/h5p/h5p-php-library/tree/master" }, - "time": "2021-04-22T09:30:45+00:00" + "time": "2023-06-27T13:26:01+00:00" }, { "name": "h5p/h5p-editor", @@ -2528,12 +2529,12 @@ "source": { "type": "git", "url": "https://github.com/h5p/h5p-editor-php-library.git", - "reference": "1ae19fdb80839b32dad3846d6b0a5c745f8f6187" + "reference": "0365b081efa8b55ab9fd58594aa599f9630268f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/h5p/h5p-editor-php-library/zipball/1ae19fdb80839b32dad3846d6b0a5c745f8f6187", - "reference": "1ae19fdb80839b32dad3846d6b0a5c745f8f6187", + "url": "https://api.github.com/repos/h5p/h5p-editor-php-library/zipball/0365b081efa8b55ab9fd58594aa599f9630268f6", + "reference": "0365b081efa8b55ab9fd58594aa599f9630268f6", "shasum": "" }, "require": { @@ -2582,7 +2583,7 @@ "issues": "https://github.com/h5p/h5p-editor-php-library/issues", "source": "https://github.com/h5p/h5p-editor-php-library/tree/master" }, - "time": "2022-06-02T06:11:10+00:00" + "time": "2023-03-09T14:54:57+00:00" }, { "name": "kamermans/guzzle-oauth2-subscriber", @@ -5992,20 +5993,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.3", + "version": "4.7.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2" + "reference": "60a4c63ab724854332900504274f6150ff26d286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", + "reference": "60a4c63ab724854332900504274f6150ff26d286", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -6068,7 +6069,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.3" + "source": "https://github.com/ramsey/uuid/tree/4.7.4" }, "funding": [ { @@ -6080,7 +6081,7 @@ "type": "tidelift" } ], - "time": "2023-01-12T18:13:24+00:00" + "time": "2023-04-15T23:01:58+00:00" }, { "name": "spatie/backtrace", @@ -11724,6 +11725,7 @@ "stability-flags": { "cerpus/edlib-resource-kit": 20, "cerpus/edlib-resource-kit-laravel": 20, + "h5p/h5p-core": 20, "h5p/h5p-editor": 20 }, "prefer-stable": false, diff --git a/sourcecode/apis/contentauthor/database/migrations/2023_04_25_065450_add_patch_version_in_folder_name_to_h5p_libraries_table.php b/sourcecode/apis/contentauthor/database/migrations/2023_04_25_065450_add_patch_version_in_folder_name_to_h5p_libraries_table.php new file mode 100644 index 0000000000..51b607d866 --- /dev/null +++ b/sourcecode/apis/contentauthor/database/migrations/2023_04_25_065450_add_patch_version_in_folder_name_to_h5p_libraries_table.php @@ -0,0 +1,31 @@ +boolean('patch_version_in_folder_name')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('h5p_libraries', function (Blueprint $table) { + $table->dropColumn('patch_version_in_folder_name'); + }); + } +}; diff --git a/sourcecode/apis/contentauthor/public/js/h5p/core-override/h5p-content-type.js b/sourcecode/apis/contentauthor/public/js/h5p/core-override/h5p-content-type.js new file mode 100755 index 0000000000..1ce6e1dbc5 --- /dev/null +++ b/sourcecode/apis/contentauthor/public/js/h5p/core-override/h5p-content-type.js @@ -0,0 +1,41 @@ +/** + * H5P.ContentType is a base class for all content types. Used by newRunnable() + * + * Functions here may be overridable by the libraries. In special cases, + * it is also possible to override H5P.ContentType on a global level. + * + * NOTE that this doesn't actually 'extend' the event dispatcher but instead + * it creates a single instance which all content types shares as their base + * prototype. (in some cases this may be the root of strange event behavior) + * + * @class + * @augments H5P.EventDispatcher + */ +H5P.ContentType = function (isRootLibrary) { + + function ContentType() {} + + // Inherit from EventDispatcher. + ContentType.prototype = new H5P.EventDispatcher(); + + /** + * Is library standalone or not? Not beeing standalone, means it is + * included in another library + * + * @return {Boolean} + */ + ContentType.prototype.isRoot = function () { + return isRootLibrary; + }; + + /** + * Returns the file path of a file in the current library + * @param {string} filePath The path to the file relative to the library folder + * @return {string} The full path to the file + */ + ContentType.prototype.getLibraryFilePath = function (filePath) { + return H5P.getLibraryPath(this.libraryInfo.versionedName.replace(' ', '-')) + '/' + filePath; + }; + + return ContentType; +}; diff --git a/sourcecode/apis/contentauthor/public/js/h5p/request-queue.js b/sourcecode/apis/contentauthor/public/js/h5p/core-override/request-queue.js old mode 100644 new mode 100755 similarity index 100% rename from sourcecode/apis/contentauthor/public/js/h5p/request-queue.js rename to sourcecode/apis/contentauthor/public/js/h5p/core-override/request-queue.js diff --git a/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core-bundle.js b/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core-bundle.js index 04daeb7a66..00b4d45b95 100644 --- a/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core-bundle.js +++ b/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core-bundle.js @@ -3,7 +3,8 @@ import '../../../vendor/h5p/h5p-core/js/h5p.js'; import '../../../vendor/h5p/h5p-core/js/h5p-event-dispatcher.js'; import '../../../vendor/h5p/h5p-core/js/h5p-x-api-event.js'; import '../../../vendor/h5p/h5p-core/js/h5p-x-api.js'; -import '../../../vendor/h5p/h5p-core/js/h5p-content-type.js'; +import '../../../public/js/h5p/core-override/h5p-content-type.js'; //TODO Replaced to support patch-version in library folder name. Used by libraries to loads assets that is not js or css import '../../../vendor/h5p/h5p-core/js/h5p-confirmation-dialog.js'; -import '../../../public/js/h5p/request-queue.js'; //TODO Change to vanilla H5P when they fix import '../../../vendor/h5p/h5p-core/js/h5p-action-bar.js'; +import '../../../public/js/h5p/core-override/request-queue.js'; //TODO Change to vanilla H5P when they fix https://github.com/h5p/h5p-php-library/pull/66 +import '../../../vendor/h5p/h5p-core/js/h5p-tooltip.js'; diff --git a/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core.scss b/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core.scss index f05a95dcfa..10a43c61bf 100644 --- a/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core.scss +++ b/sourcecode/apis/contentauthor/resources/assets/entrypoints/h5p-core.scss @@ -1,3 +1,4 @@ @import '../../../vendor/h5p/h5p-core/styles/h5p.css'; @import '../../../vendor/h5p/h5p-core/styles/h5p-confirmation-dialog.css'; @import '../../../vendor/h5p/h5p-core/styles/h5p-core-button.css'; +@import '../../../vendor/h5p/h5p-core/styles/h5p-tooltip.css'; diff --git a/sourcecode/apis/contentauthor/resources/views/h5p/show.blade.php b/sourcecode/apis/contentauthor/resources/views/h5p/show.blade.php index a485e9580c..e1629114fb 100644 --- a/sourcecode/apis/contentauthor/resources/views/h5p/show.blade.php +++ b/sourcecode/apis/contentauthor/resources/views/h5p/show.blade.php @@ -14,7 +14,7 @@ @foreach( $styles as $css) {!! HTML::style($css) !!} @endforeach - {!! HTML::script('https://code.jquery.com/jquery-1.11.3.min.js') !!} + {!! HTML::script('https://code.jquery.com/jquery-1.12.4.min.js') !!} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleLockTest.php b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleLockTest.php index eaf17ec16a..f2be17c291 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleLockTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleLockTest.php @@ -33,6 +33,7 @@ public function setUp(): void $versionData = new VersionData(); $this->setupVersion([ 'createVersion' => $versionData->populate((object) ['id' => $this->faker->uuid]), + 'getVersion' => $versionData->populate((object) ['id' => $this->faker->uuid]), ]); } diff --git a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleTest.php b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleTest.php index 33af3dcb4c..a20251e6ec 100755 --- a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleTest.php @@ -132,7 +132,7 @@ public function testCreateArticleWithMathContent() public function testCreateAndEditArticleWithIframeContent() { - $this->setupVersion(); + $this->setupVersion(['getVersion' => false]); Event::fake(); $authId = Str::uuid(); @@ -175,7 +175,7 @@ public function testCreateAndEditArticleWithIframeContent() public function testEditArticle() { - $this->setupVersion(); + $this->setupVersion(['getVersion' => false]); $this->setupAuthApi([ 'getUser' => new User("1", "this", "that", "this@that.com") ]); @@ -218,7 +218,7 @@ public function testEditArticle() public function testEditArticleWithDraftEnabled() { - $this->setupVersion(); + $this->setupVersion(['getVersion' => false]); $this->setupAuthApi([ 'getUser' => new User("1", "this", "that", "this@that.com") ]); @@ -285,7 +285,7 @@ public function testEditArticleWithDraftEnabled() public function testViewArticle() { - $this->setupVersion(); + $this->setupVersion(['getVersion' => false]); /** @var Article $article */ $article = Article::factory()->create([ diff --git a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleVersioningTest.php b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleVersioningTest.php index d63fa64eac..f31c0c9a01 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleVersioningTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Article/ArticleVersioningTest.php @@ -33,6 +33,7 @@ public function setUp(): void $versionData = new VersionData(); $this->setupVersion([ 'createVersion' => $versionData->populate((object) ['id' => $this->faker->uuid]), + 'getVersion' => $versionData->populate((object) ['id' => $this->faker->uuid]), ]); } diff --git a/sourcecode/apis/contentauthor/tests/Integration/H5PContentTest.php b/sourcecode/apis/contentauthor/tests/Integration/H5PContentTest.php new file mode 100644 index 0000000000..2ae26ac0b9 --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/H5PContentTest.php @@ -0,0 +1,64 @@ +create([ + 'has_icon' => 1, + 'patch_version_in_folder_name' => $usePatch, + ]); + + $frameWork = $this->createMock(\H5PFrameworkInterface::class); + $this->instance(\H5PFrameworkInterface::class, $frameWork); + + $frameWork + ->expects($this->once()) + ->method('getLibraryFileUrl') + ->with($expectedPath, 'icon.svg') + ->willReturn("assets/$expectedPath/icon.svg"); + + $result = H5PContent::getContentTypeInfo('H5P.Foobar'); + $this->assertInstanceOf(ContentTypeDataObject::class, $result); + $this->assertSame('H5P.Foobar', $result->contentType); + $this->assertSame($library->title, $result->title); + $this->assertSame("assets/$expectedPath/icon.svg", $result->icon); + } + + public function provider_getContentTypeInfo(): \Generator + { + yield [false, 'H5P.Foobar-1.2']; + yield [true, 'H5P.Foobar-1.2.3']; + } + + public function test_getContentTypeInfo_NoIcon(): void + { + /** @var H5PLibrary $library */ + $library = H5PLibrary::factory()->create(); + + $frameWork = $this->createMock(\H5PFrameworkInterface::class); + $this->instance(\H5PFrameworkInterface::class, $frameWork); + + $frameWork + ->expects($this->never()) + ->method('getLibraryFileUrl'); + + $result = H5PContent::getContentTypeInfo('H5P.Foobar'); + $this->assertInstanceOf(ContentTypeDataObject::class, $result); + $this->assertSame('H5P.Foobar', $result->contentType); + $this->assertSame($library->title, $result->title); + $this->assertSame('http://localhost/graphical/h5p_logo.svg', $result->icon); + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/H5PLibrariesHubCacheTest.php b/sourcecode/apis/contentauthor/tests/Integration/H5PLibrariesHubCacheTest.php new file mode 100644 index 0000000000..d72ec0cc4d --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/H5PLibrariesHubCacheTest.php @@ -0,0 +1,32 @@ + 'H5P.Foobar', + 'major_version' => 1, + 'minor_version' => 2, + 'patch_version_in_folder_name' => $usePatch, + ]); + + $this->assertSame($expected, $lib->getLibraryString($isFolder)); + } + + public function provider_LibraryString(): \Generator + { + yield [true, false, 'H5P.Foobar-1.2']; + yield [true, true, 'H5P.Foobar-1.2']; + yield [false, false, 'H5P.Foobar 1.2']; + yield [false, true, 'H5P.Foobar 1.2']; + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Http/Controllers/H5PControllerTest.php b/sourcecode/apis/contentauthor/tests/Integration/Http/Controllers/H5PControllerTest.php index 48878700cc..3d80f89e3e 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Http/Controllers/H5PControllerTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Http/Controllers/H5PControllerTest.php @@ -30,8 +30,8 @@ class H5PControllerTest extends TestCase use RefreshDatabase; use MockAuthApi; - /** @dataProvider provider_adapterMode */ - public function testCreate(string $adapterMode): void + /** @dataProvider provider_testCreate */ + public function testCreate(string $adapterMode, ?string $contentType): void { $faker = Factory::create(); $this->session([ @@ -49,11 +49,15 @@ public function testCreate(string $adapterMode): void 'redirectToken' => $faker->uuid, ]); + H5PLibrary::factory()->create(); + /** @var H5PCore $h5pCore */ $h5pCore = app(H5pCore::class); + /** @var H5PController $articleController */ $articleController = app(H5PController::class); - $result = $articleController->create($request, $h5pCore); + $result = $articleController->create($request, $h5pCore, $contentType); + $this->assertInstanceOf(View::class, $result); $data = $result->getData(); @@ -67,9 +71,14 @@ public function testCreate(string $adapterMode): void $this->assertNotEmpty($data['editorSetup']); $this->assertNotEmpty($data['state']); $this->assertArrayHasKey('configJs', $data); + $this->assertSame($contentType, $data['libName']); $config = json_decode(substr($result['config'], 25, -9), true, flags: JSON_THROW_ON_ERROR); - $this->assertTrue($config['hubIsEnabled']); + if ($contentType === null) { + $this->assertTrue($config['hubIsEnabled']); + } else { + $this->assertFalse($config['hubIsEnabled']); + } $this->assertEmpty($config['contents']); $this->assertSame('nb-no', $config['locale']); $this->assertSame('nb', $config['localeConverted']); @@ -90,10 +99,11 @@ public function testCreate(string $adapterMode): void $this->assertEquals('Emily Quackfaster', $editorSetup['creatorName']); $state = json_decode($data['state'], true, flags: JSON_THROW_ON_ERROR); + $this->assertNull($state['id']); $this->assertNull($state['title']); $this->assertFalse($state['isPublished']); - $this->assertNull($state['library']); + $this->assertSame($contentType, $state['library']); $this->assertNull($state['libraryid']); $this->assertSame('nob', $state['language_iso_639_3']); $this->assertEquals(config('license.default-license'), $state['license']); @@ -106,6 +116,14 @@ public function testCreate(string $adapterMode): void } } + public function provider_testCreate(): \Generator + { + yield 'cerpus-withoutContentType' => ['cerpus', null]; + yield 'ndla-withoutContentType' => ['ndla', null]; + yield 'cerpus-withContentType' => ['cerpus', 'H5P.Toolbar 1.2']; + yield 'ndla-withContentType' => ['ndla', 'H5P.Toolbar 1.2']; + } + /** @dataProvider provider_adapterMode */ public function testEdit(string $adapterMode): void { @@ -201,6 +219,7 @@ public function testEdit(string $adapterMode): void $this->assertNotEmpty($config['editor']['ajaxPath']); $editorSetup = json_decode($data['editorSetup'], true, flags: JSON_THROW_ON_ERROR); + $this->assertEquals($lib->title . ' 1.6.3', $editorSetup['contentProperties']['type']); $this->assertEquals('Emily Quackfaster', $editorSetup['contentProperties']['ownerName']); $this->assertSame($upgradeLib->id, $editorSetup['libraryUpgradeList'][0]['id']); $this->assertSame('nb', $editorSetup['h5pLanguage']); diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/API/H5PImportControllerTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/API/H5PImportControllerTest.php index fd942a2cbf..f746f51fe7 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/API/H5PImportControllerTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/API/H5PImportControllerTest.php @@ -121,7 +121,7 @@ public function importH5P() 'title' => $title, 'library_id' => $library->id, ]); - $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getLibraryString(true)))); + $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getFolderName()))); /** @var H5PContent $h5pContent */ $h5pContent = H5PContent::with('metadata') @@ -174,7 +174,7 @@ public function importH5PWithImage() 'title' => $title, 'library_id' => $library->id, ]); - $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getLibraryString(true)))); + $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getFolderName()))); $h5pContent = H5PContent::with('metadata') ->where('title', $title) @@ -230,7 +230,7 @@ public function importH5PWithMetadata() 'title' => $title, 'library_id' => $library->id, ]); - $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getLibraryString(true)))); + $this->assertFileExists($this->fakeDisk->path(sprintf("libraries/%s/semantics.json", $library->getFolderName()))); $h5pContent = H5PContent::with('metadata') ->where('title', $title) diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/AjaxRequestTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/AjaxRequestTest.php new file mode 100644 index 0000000000..ba807412a1 --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/AjaxRequestTest.php @@ -0,0 +1,64 @@ +create(); + /** @var H5PLibrary $preLib */ + $preLib = H5PLibrary::factory()->create(['name' => 'player', 'major_version' => 3, 'minor_version' => 14]); + /** @var H5PLibrary $dynLib */ + $dynLib = H5PLibrary::factory()->create(['name' => 'H5P.Dynamic', 'major_version' => 2, 'minor_version' => 42, 'patch_version' => 3, 'patch_version_in_folder_name' => true]); + /** @var H5PLibrary $edLib */ + $edLib = H5PLibrary::factory()->create(['name' => 'FontOk', 'major_version' => 1, 'minor_version' => 3]); + + $this->assertDatabaseEmpty('h5p_libraries_libraries'); + + $validator = $this->createMock(\H5PValidator::class); + $this->instance(\H5PValidator::class, $validator); + $validator + ->expects($this->exactly(4)) + ->method('getLibraryData') + ->withConsecutive(['H5P.Foobar-1.2'], ['player-3.14'], ['H5P.Dynamic-2.42.3'], ['FontOk-1.3']) + ->willReturnOnConsecutiveCalls([ + 'preloadedDependencies' => [$preLib->getLibraryH5PFriendly()], + 'dynamicDependencies' => [$dynLib->getLibraryH5PFriendly()], + 'editorDependencies' => [$edLib->getLibraryH5PFriendly()], + ], [], [], []); + + $this + ->withSession(['isAdmin' => true]) + ->post('/ajax', ['action' => AjaxRequest::LIBRARY_REBUILD, 'libraryId' => $library->id]) + ->assertOk() + ->assertJson([ + 'success' => true, + 'message' => 'Library rebuild', + ]); + + $this->assertDatabaseHas('h5p_libraries_libraries', [ + 'library_id' => $library->id, + 'required_library_id' => $preLib->id, + 'dependency_type' => 'preloaded', + ]); + $this->assertDatabaseHas('h5p_libraries_libraries', [ + 'library_id' => $library->id, + 'required_library_id' => $dynLib->id, + 'dependency_type' => 'dynamic', + ]); + $this->assertDatabaseHas('h5p_libraries_libraries', [ + 'library_id' => $library->id, + 'required_library_id' => $edLib->id, + 'dependency_type' => 'editor', + ]); + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/CRUTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/CRUTest.php index 35ac4e648f..b6e94c1ec1 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/CRUTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/CRUTest.php @@ -496,6 +496,7 @@ public function enabledUserPublish_NotOwner() /** @var H5PContent $newContent */ $newContent = $contents->first(); + /** @var H5PLibrary $library */ $library = $newContent->library()->first(); $this->setupH5PAdapter([ @@ -513,7 +514,7 @@ public function enabledUserPublish_NotOwner() '_token' => csrf_token(), 'title' => 'New resource', 'action' => 'create', - 'library' => $library->getLibraryString(), + 'library' => $library->getLibraryString(false), 'parameters' => '{"params":{"simpleTest":"SimpleTest"},"metadata":{}}', 'license' => "PRIVATE", 'lti_message_type' => $this->faker->word, @@ -536,7 +537,7 @@ public function enabledUserPublish_NotOwner() ->put(route('h5p.update', $newContent->id), [ '_token' => csrf_token(), 'title' => $newContent->title, - 'library' => $library->getLibraryString(), + 'library' => $library->getLibraryString(false), 'parameters' => '{"params":{"simpleTest":"SimpleTest"},"metadata":{}}', 'license' => "PRIVATE", 'lti_message_type' => $this->faker->word, diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorAjaxTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorAjaxTest.php new file mode 100644 index 0000000000..4be0882b9f --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorAjaxTest.php @@ -0,0 +1,74 @@ +create(); + /** @var H5PLibrary $libTest */ + $libTest = H5PLibrary::factory()->create([ + 'name' => 'H5P.UnitTest', + 'major_version' => 3, + 'minor_version' => 14, + 'patch_version' => 42, + 'patch_version_in_folder_name' => 1, + ]); + + H5PLibraryLanguage::create([ + 'library_id' => $libFoo->id, + 'language_code' => 'nb', + 'translation' => json_encode(['lib' => $libFoo->getLibraryString(false), 'lang' => 'nb']), + ]); + + H5PLibraryLanguage::create([ + 'library_id' => $libTest->id, + 'language_code' => 'nb', + 'translation' => json_encode(['lib' => $libTest->getLibraryString(false), 'lang' => 'nb']), + ]); + + H5PLibraryLanguage::create([ + 'library_id' => $libFoo->id, + 'language_code' => 'nn', + 'translation' => json_encode(['lib' => $libFoo->getLibraryString(false), 'lang' => 'nn']), + ]); + + H5PLibraryLanguage::create([ + 'library_id' => $libTest->id, + 'language_code' => 'en', + 'translation' => json_encode(['lib' => $libTest->getLibraryString(false), 'lang' => 'en']), + ]); + + $translations = (new EditorAjax())->getTranslations( + [ + $libFoo->getLibraryString(false), + $libTest->getLibraryString(false), + ], + 'nb' + ); + + $this->assertIsArray($translations); + + $this->assertArrayHasKey($libFoo->getLibraryString(false), $translations); + $data = json_decode($translations[$libFoo->getLibraryString(false)], true, JSON_THROW_ON_ERROR); + $this->assertSame($libFoo->getLibraryString(false), $data['lib']); + $this->assertSame('nb', $data['lang']); + + $this->assertArrayHasKey($libFoo->getLibraryString(false), $translations); + $data = json_decode($translations[$libTest->getLibraryString(false)], true, JSON_THROW_ON_ERROR); + $this->assertSame($libTest->getLibraryString(false), $data['lib']); + $this->assertSame('nb', $data['lang']); + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorStorageTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorStorageTest.php new file mode 100644 index 0000000000..3f5bf69cc4 --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/EditorStorageTest.php @@ -0,0 +1,58 @@ +create(['semantics' => 'something']); + $libraries = (object) [ + 'uberName' => $library->getLibraryString(false), + 'name' => $library->name, + 'majorVersion' => $library->major_version, + 'minorVersion' => $library->minor_version, + ]; + + $core = $this->createMock(\H5PCore::class); + $this->instance(\H5PCore::class, $core); + + /** @var EditorStorage $editorStorage */ + $editorStorage = app(EditorStorage::class); + $ret = $editorStorage->getLibraries([$libraries]); + + $this->assertCount(1, $ret); + $this->assertSame($library->id, $ret[0]->id); + $this->assertSame('H5P.Foobar 1.2', $ret[0]->uberName); + } + + public function test_getLibrary_allLibraries(): void + { + /** @var H5PLibrary $lib1 */ + $lib1 = H5PLibrary::factory()->create(['semantics' => 'something']); + /** @var H5PLibrary $lib2 */ + $lib2 = H5PLibrary::factory()->create(['name' => 'H5P.Headphones', 'semantics' => 'something']); + + $core = $this->createMock(\H5PCore::class); + $this->instance(\H5PCore::class, $core); + + /** @var EditorStorage $editorStorage */ + $editorStorage = app(EditorStorage::class); + $ret = $editorStorage->getLibraries(); + + $this->assertCount(2, $ret); + $this->assertSame($lib1->id, $ret[0]->id); + $this->assertSame($lib2->id, $ret[1]->id); + + $this->assertSame('H5P.Foobar 1.2', $ret[0]->uberName); + $this->assertSame('H5P.Headphones 1.2', $ret[1]->uberName); + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/FrameworkTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/FrameworkTest.php new file mode 100644 index 0000000000..2c35da2c6d --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/FrameworkTest.php @@ -0,0 +1,206 @@ + */ + private ArrayObject $history; + + private Framework $framework; + + private MockHandler $mockedResponses; + + protected function setUp(): void + { + parent::setUp(); + + $this->history = new ArrayObject(); + $this->mockedResponses = new MockHandler(); + + $handler = HandlerStack::create($this->mockedResponses); + $handler->push(Middleware::history($this->history)); + + $client = new Client(['handler' => $handler]); + + $this->framework = new Framework( + $client, + $this->createMock(PDO::class), + $this->createMock(Filesystem::class), + ); + } + + public function testSaveLibrary(): void + { + $input = [ + 'machineName' => 'H5P.UnitTest', + 'title' => 'Unit Test', + 'majorVersion' => 2, + 'minorVersion' => 4, + 'patchVersion' => 6, + 'runnable' => 1, + 'metadataSettings' => 'Yupp', + 'addTo' => ['machineName' => 'Something'], + 'hasIcon' => 1, + 'embedTypes' => ['E1', 'E2'], + 'preloadedJs' => [ + ['path' => 'PJ1', 'name' => 'PJ1 name', 'machineName' => 'H5P.Pj1'], + ['path' => 'PJ2', 'name' => 'PJ2 name', 'machineName' => 'H5P.Pj2'], + ], + 'preloadedCss' => [ + ['path' => 'PC1', 'name' => 'PC1 name', 'machineName' => 'H5P.Pc1'], + ['path' => 'PC2', 'name' => 'PC2 name', 'machineName' => 'H5P.Pc1'], + ], + 'dropLibraryCss' => [ + ['path' => 'DC1', 'name' => 'DC1 name', 'machineName' => 'H5P.Dc1'], + ['path' => 'DC2', 'name' => 'DC2 name', 'machineName' => 'H5P.Dc2'], + ], + 'language' => [ + 'nb' => 'Norsk Bokmål', + 'nn' => 'Norsk Nynorsk', + ], + ]; + $this->framework->saveLibraryData($input); + + $this->assertDatabaseHas('h5p_libraries', ['id' => $input['libraryId']]); + $this->assertDatabaseHas('h5p_libraries_languages', [ + 'library_id' => $input['libraryId'], + 'language_code' => 'nb', + 'translation' => 'Norsk Bokmål', + ]); + $this->assertDatabaseHas('h5p_libraries_languages', [ + 'library_id' => $input['libraryId'], + 'language_code' => 'nn', + 'translation' => 'Norsk Nynorsk', + ]); + + /** @var H5PLibrary $library */ + $library = H5PLibrary::find($input['libraryId']); + + $this->assertSame('H5P.UnitTest', $library->name); + $this->assertSame('Unit Test', $library->title); + $this->assertSame(2, $library->major_version); + $this->assertSame(4, $library->minor_version); + $this->assertSame(6, $library->patch_version); + $this->assertSame(1, $library->runnable); + $this->assertSame(0, $library->fullscreen); + $this->assertSame('E1, E2', $library->embed_types); + $this->assertSame('PJ1, PJ2', $library->preloaded_js); + $this->assertSame('PC1, PC2', $library->preloaded_css); + $this->assertSame('H5P.Dc1, H5P.Dc2', $library->drop_library_css); + $this->assertSame('', $library->semantics); + $this->assertSame(1, $library->has_icon); + $this->assertSame(true, $library->patch_version_in_folder_name); + } + + public function testLoadLibrary(): void + { + H5PLibrary::factory()->create([ + 'major_version' => 1, + 'minor_version' => 1, + 'patch_version' => 9, + ]); + H5PLibrary::factory()->create([ + 'major_version' => 1, + 'minor_version' => 2, + 'patch_version' => 2, + ]); + /** @var H5PLibrary $editDep */ + $editDep = H5PLibrary::factory()->create([ + 'name' => 'H5PEditor.Foobar', + 'patch_version_in_folder_name' => true, + ]); + /** @var H5PLibrary $saved */ + $saved = H5PLibrary::factory()->create([ + 'patch_version_in_folder_name' => true, + ]); + H5PLibraryLibrary::create([ + 'library_id' => $saved->id, + 'required_library_id' => $editDep->id, + 'dependency_type' => 'editor', + ]); + + $library = $this->framework->loadLibrary('H5P.Foobar', 1, 2); + $this->assertSame($saved->id, $library['libraryId']); + $this->assertSame($saved->name, $library['machineName']); + $this->assertSame($saved->major_version, $library['majorVersion']); + $this->assertSame($saved->minor_version, $library['minorVersion']); + $this->assertSame($saved->patch_version, $library['patchVersion']); + $this->assertSame($saved->patch_version_in_folder_name, $library['patchVersionInFolderName']); + + $this->assertSame($editDep->name, $library['editorDependencies'][0]['machineName']); + $this->assertSame($editDep->patch_version_in_folder_name, $library['editorDependencies'][0]['patchVersionInFolderName']); + } + + /** @dataProvider provider_usePatch */ + public function test_deleteLibrary($usePatch): void + { + $disk = Storage::fake(); + $caStorage = App(ContentAuthorStorage::class); + $tmpDisk = Storage::fake($caStorage->getH5pTmpDiskName()); + + /** @var H5PLibrary $library */ + $library = H5PLibrary::factory()->create(['patch_version_in_folder_name' => $usePatch]); + $path = 'libraries/' . $library->getFolderName(); + + $this->assertFalse($disk->exists($path)); + $disk->put($path . '/library.json', 'just testing'); + $this->assertTrue($disk->exists($path . '/library.json')); + + $this->assertFalse($tmpDisk->exists($path)); + $tmpDisk->put($path . '/library.json', 'just testing'); + $this->assertTrue($tmpDisk->exists($path . '/library.json')); + + $lib = ['id' => $library->id]; + $this->assertDatabaseHas('h5p_libraries', $lib); + $this->framework->deleteLibrary((object) $lib); + $this->assertDatabaseMissing('h5p_libraries', $lib); + + $this->assertFalse($disk->exists($path)); + $this->assertFalse($tmpDisk->exists($path)); + } + + /** @dataProvider provider_usePatch */ + public function test_loadContent($usePatch): void + { + /** @var H5PLibrary $h5pLibrary */ + $h5pLibrary = H5PLibrary::factory()->create(['patch_version_in_folder_name' => $usePatch]); + /** @var H5PContent $h5pContent */ + $h5pContent = H5PContent::factory()->create(['library_id' => $h5pLibrary->id]); + + $content = $this->framework->loadContent($h5pContent->id); + + $this->assertSame($h5pContent->id, $content['id']); + $this->assertSame($h5pContent->id, $content['contentId']); + $this->assertSame($h5pLibrary->id, $content['libraryId']); + $this->assertSame($h5pLibrary->getLibraryString(), $content['libraryFullVersionName']); + } + + public function provider_usePatch(): \Generator + { + yield [false]; + yield [true]; + } +} diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/H5PExportTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/H5PExportTest.php index 7a58ed6518..27380ecd7d 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/H5PExportTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/H5PExportTest.php @@ -3,6 +3,7 @@ namespace Tests\Integration\Libraries\H5P; use App\H5PContent; +use App\H5PLibrary; use App\Libraries\DataObjects\ContentStorageSettings; use App\Libraries\H5P\H5PExport; use App\Libraries\H5P\Interfaces\H5PExternalProviderInterface; @@ -33,26 +34,38 @@ protected function setUp(): void $this->testDisk = Storage::disk('testDisk'); $this->exportDisk = Storage::fake(); config(['h5p.storage.path' => $this->exportDisk->path("")]); + symlink($this->testDisk->path('files/libraries'), $this->exportDisk->path('libraries')); } - private function linkLibrariesFolder() + protected function tearDown(): void { - symlink($this->testDisk->path('files/libraries'), $this->exportDisk->path('libraries')); + parent::tearDown(); + + unlink($this->exportDisk->path('libraries')); } /** * @test * @throws Exception + * @dataProvider provider_noMultimedia */ - public function noMultimedia() + public function noMultimedia(bool $usePatchFolder) { $this->setupH5PAdapter([ 'alterLibrarySemantics' => null, 'getExternalProviders' => [], ]); - $this->linkLibrariesFolder(); $this->seed(TestH5PSeeder::class); + + if ($usePatchFolder) { + $lib = H5PLibrary::find(284); + $lib->minor_version = 14; + $lib->patch_version = 6; + $lib->patch_version_in_folder_name = true; + $lib->save(); + } + $params = '{"text":"
Fill in the missing words<\/p>\n","overallFeedback":[{"from":0,"to":100}],"showSolutions":"Show solution","tryAgain":"Retry","checkAnswer":"Check","notFilledOut":"Please fill in all blanks to view solution","answerIsCorrect":"\':ans\' is correct","answerIsWrong":"\':ans\' is wrong","answeredCorrectly":"Answered correctly","answeredIncorrectly":"Answered incorrectly","solutionLabel":"Correct answer:","inputLabel":"Blank input @num of @total","inputHasTipLabel":"Tip available","tipLabel":"Tip","behaviour":{"enableRetry":true,"enableSolutionsButton":true,"enableCheckButton":true,"autoCheck":false,"caseSensitive":true,"showSolutionsRequiresInput":true,"separateLines":false,"disableImageZooming":false,"confirmCheckDialog":false,"confirmRetryDialog":false,"acceptSpellingErrors":false},"scoreBarLabel":"You got :num out of :total points","confirmCheck":{"header":"Finish ?","body":"Are you sure you wish to finish ?","cancelLabel":"Cancel","confirmLabel":"Finish"},"confirmRetry":{"header":"Retry ?","body":"Are you sure you wish to retry ?","cancelLabel":"Cancel","confirmLabel":"Confirm"},"questions":["
*Fishing* is a fun *activity*.<\/p>\n"]}'; $h5p = H5PContent::factory()->create([ 'parameters' => $params, @@ -74,6 +87,12 @@ public function noMultimedia() $zipArchive->close(); } + public function provider_noMultimedia(): \Generator + { + yield 'minorFolder' => [false]; + yield 'patchFolder' => [true]; + } + /** * @test * @throws Exception @@ -85,7 +104,6 @@ public function withLocalImage() 'getExternalProviders' => [], ]); - $this->linkLibrariesFolder(); $this->seed(TestH5PSeeder::class); $params = '{"media":{"params":{"contentName":"Image","file":{"path":"images\/file-5f6ca98160e6c.jpg","mime":"image\/jpeg","copyright":{"license":"U"},"width":196,"height":358}},"library":"H5P.Image 1.1","metadata":{"contentType":"Image","license":"U","title":"Untitled Image","authors":[],"changes":[],"extraTitle":"Untitled Image"},"subContentId":"ca86c100-d25c-4e19-ac6b-f50f843da292"},"text":"Fill in the missing words","overallFeedback":[{"from":0,"to":100}],"showSolutions":"Show solution","tryAgain":"Retry","checkAnswer":"Check","notFilledOut":"Please fill in all blanks to view solution","answerIsCorrect":"':ans' is correct","answerIsWrong":"':ans' is wrong","answeredCorrectly":"Answered correctly","answeredIncorrectly":"Answered incorrectly","solutionLabel":"Correct answer:","inputLabel":"Blank input @num of @total","inputHasTipLabel":"Tip available","tipLabel":"Tip","behaviour":{"enableRetry":true,"enableSolutionsButton":true,"enableCheckButton":true,"autoCheck":false,"caseSensitive":true,"showSolutionsRequiresInput":true,"separateLines":false,"disableImageZooming":false,"confirmCheckDialog":false,"confirmRetryDialog":false,"acceptSpellingErrors":false},"scoreBarLabel":"You got :num out of :total points","confirmCheck":{"header":"Finish ?","body":"Are you sure you wish to finish ?","cancelLabel":"Cancel","confirmLabel":"Finish"},"confirmRetry":{"header":"Retry ?","body":"Are you sure you wish to retry ?","cancelLabel":"Cancel","confirmLabel":"Confirm"},"questions":["
Not all *superheros* wear capes!<\/p>\n"]}'; $h5p = H5PContent::factory()->create([ @@ -125,7 +143,6 @@ public function withRemoteImage_noLocalConvert() $imageUrl = $this->faker->imageUrl(); - $this->linkLibrariesFolder(); $this->seed(TestH5PSeeder::class); $params = '{"media":{"params":{"contentName":"Image","file":{"path":"' . $imageUrl . '","mime":"image\/jpeg","copyright":{"license":"U"},"width":196,"height":358}},"library":"H5P.Image 1.1","metadata":{"contentType":"Image","license":"U","title":"Untitled Image","authors":[],"changes":[],"extraTitle":"Untitled Image"},"subContentId":"ca86c100-d25c-4e19-ac6b-f50f843da292"},"text":"Fill in the missing words","overallFeedback":[{"from":0,"to":100}],"showSolutions":"Show solution","tryAgain":"Retry","checkAnswer":"Check","notFilledOut":"Please fill in all blanks to view solution","answerIsCorrect":"':ans' is correct","answerIsWrong":"':ans' is wrong","answeredCorrectly":"Answered correctly","answeredIncorrectly":"Answered incorrectly","solutionLabel":"Correct answer:","inputLabel":"Blank input @num of @total","inputHasTipLabel":"Tip available","tipLabel":"Tip","behaviour":{"enableRetry":true,"enableSolutionsButton":true,"enableCheckButton":true,"autoCheck":false,"caseSensitive":true,"showSolutionsRequiresInput":true,"separateLines":false,"disableImageZooming":false,"confirmCheckDialog":false,"confirmRetryDialog":false,"acceptSpellingErrors":false},"scoreBarLabel":"You got :num out of :total points","confirmCheck":{"header":"Finish ?","body":"Are you sure you wish to finish ?","cancelLabel":"Cancel","confirmLabel":"Finish"},"confirmRetry":{"header":"Retry ?","body":"Are you sure you wish to retry ?","cancelLabel":"Cancel","confirmLabel":"Confirm"},"questions":["
Not all *superheros* wear capes!<\/p>\n"]}'; $h5p = H5PContent::factory()->create([ @@ -159,7 +176,7 @@ public function withRemoteImage_noLocalConvert() public function withRemoteImage_storeLocally() { $imageUrl = $this->faker->imageUrl(); - $this->linkLibrariesFolder(); + $this->seed(TestH5PSeeder::class); $params = '{"media":{"params":{"contentName":"Image","file":{"path":"' . $imageUrl . '","mime":"image\/jpeg","copyright":{"license":"U"},"width":196,"height":358}},"library":"H5P.Image 1.1","metadata":{"contentType":"Image","license":"U","title":"Untitled Image","authors":[],"changes":[],"extraTitle":"Untitled Image"},"subContentId":"ca86c100-d25c-4e19-ac6b-f50f843da292"},"text":"Fill in the missing words","overallFeedback":[{"from":0,"to":100}],"showSolutions":"Show solution","tryAgain":"Retry","checkAnswer":"Check","notFilledOut":"Please fill in all blanks to view solution","answerIsCorrect":"':ans' is correct","answerIsWrong":"':ans' is wrong","answeredCorrectly":"Answered correctly","answeredIncorrectly":"Answered incorrectly","solutionLabel":"Correct answer:","inputLabel":"Blank input @num of @total","inputHasTipLabel":"Tip available","tipLabel":"Tip","behaviour":{"enableRetry":true,"enableSolutionsButton":true,"enableCheckButton":true,"autoCheck":false,"caseSensitive":true,"showSolutionsRequiresInput":true,"separateLines":false,"disableImageZooming":false,"confirmCheckDialog":false,"confirmRetryDialog":false,"acceptSpellingErrors":false},"scoreBarLabel":"You got :num out of :total points","confirmCheck":{"header":"Finish ?","body":"Are you sure you wish to finish ?","cancelLabel":"Cancel","confirmLabel":"Finish"},"confirmRetry":{"header":"Retry ?","body":"Are you sure you wish to retry ?","cancelLabel":"Cancel","confirmLabel":"Confirm"},"questions":["
Not all *superheros* wear capes!<\/p>\n"]}'; $h5p = H5PContent::factory()->create([ @@ -219,7 +236,6 @@ public function withRemoteImage_storeLocally() */ public function withRemoteVideo_storeLocally() { - $this->linkLibrariesFolder(); $this->seed(TestH5PSeeder::class); $params = '{"interactiveVideo":{"video":{"startScreenOptions":{"title":"Interactive Video","hideStartTitle":false,"copyright":""},"textTracks":[{"label":"Subtitles","kind":"subtitles","srcLang":"en"}],"files":[{"path":"https://bc/12456","mime":"video/BrightCove","copyright":{"license":"U"}}]},"assets":{"interactions":[]},"summary":{"task":{"library":"H5P.Summary 1.8","params":{"intro":"Choose the correct statement.","summaries":[{"subContentId":"2363644d-ab92-4f93-8d11-0d6e916bd83d","tip":""}],"overallFeedback":[{"from":0,"to":100}],"solvedLabel":"Progress:","scoreLabel":"Wrong answers:","resultLabel":"Your result","labelCorrect":"Correct.","labelIncorrect":"Incorrect! Please try again.","labelCorrectAnswers":"Correct answers.","tipButtonLabel":"Show tip","scoreBarLabel":"You got :num out of :total points","progressText":"Progress :num of :total"},"subContentId":"211f821f-67f9-4717-a7a4-362c4d507545","metadata":{"contentType":"Summary","license":"U","title":"Untitled Summary"}},"displayAt":3}},"override":{"autoplay":false,"loop":false,"showBookmarksmenuOnLoad":false,"showRewind10":false,"preventSkipping":false,"deactivateSound":false},"l10n":{"interaction":"Interaction","play":"Play","pause":"Pause","mute":"Mute","unmute":"Unmute","quality":"Video Quality","captions":"Captions","close":"Close","fullscreen":"Fullscreen","exitFullscreen":"Exit Fullscreen","summary":"Summary","bookmarks":"Bookmarks","defaultAdaptivitySeekLabel":"Continue","continueWithVideo":"Continue with video","playbackRate":"Playback Rate","rewind10":"Rewind 10 Seconds","navDisabled":"Navigation is disabled","sndDisabled":"Sound is disabled","requiresCompletionWarning":"You need to answer all the questions correctly before continuing.","back":"Back","hours":"Hours","minutes":"Minutes","seconds":"Seconds","currentTime":"Current time:","totalTime":"Total time:","navigationHotkeyInstructions":"Use key k for starting and stopping video at any time","singleInteractionAnnouncement":"Interaction appeared:","multipleInteractionsAnnouncement":"Multiple interactions appeared.","videoPausedAnnouncement":"Video is paused","content":"Content"}}'; $h5p = H5PContent::factory()->create([ @@ -278,7 +294,7 @@ public function withRemoteVideo_storeLocally() public function withRemoteVideoAndImage_storeLocally() { $imageUrl = $this->faker->imageUrl(); - $this->linkLibrariesFolder(); + $this->seed(TestH5PSeeder::class); $params = '{"interactiveVideo":{"video":{"startScreenOptions":{"title":"Interactive Video","hideStartTitle":false,"copyright":""},"textTracks":[{"label":"Subtitles","kind":"subtitles","srcLang":"en"}],"files":[{"path":"https://bc/ref:12456","mime":"video/BrightCove","copyright":{"license":"U"}}]},"assets":{"interactions":[{"x":5.230125523012552,"y":13.011152416356877,"width":14.986376021798364,"height":10,"duration":{"from":0,"to":10},"libraryTitle":"Image","action":{"library":"H5P.Image 1.0","params":{"contentName":"Image","file":{"path":"' . $imageUrl . '","mime":"image/jpeg","copyright":{"license":"U"},"width":1100,"height":734},"alt":"test"},"subContentId":"b0bd1110-ada3-4afe-9be9-1a822ea9643d","metadata":{"contentType":"Image","license":"U"}},"visuals":{"backgroundColor":"rgba(0,0,0,0)","boxShadow":true},"pause":false,"displayType":"poster","buttonOnMobile":false,"goto":{"url":{"protocol":"http://"},"visualize":false,"type":""},"label":""}]},"summary":{"task":{"library":"H5P.Summary 1.8","params":{"intro":"Choose the correct statement.","summaries":[{"subContentId":"2363644d-ab92-4f93-8d11-0d6e916bd83d","tip":""}],"overallFeedback":[{"from":0,"to":100}],"solvedLabel":"Progress:","scoreLabel":"Wrong answers:","resultLabel":"Your result","labelCorrect":"Correct.","labelIncorrect":"Incorrect! Please try again.","labelCorrectAnswers":"Correct answers.","tipButtonLabel":"Show tip","scoreBarLabel":"You got :num out of :total points","progressText":"Progress :num of :total"},"subContentId":"211f821f-67f9-4717-a7a4-362c4d507545","metadata":{"contentType":"Summary","license":"U","title":"Untitled Summary"}},"displayAt":3}},"override":{"autoplay":false,"loop":false,"showBookmarksmenuOnLoad":false,"showRewind10":false,"preventSkipping":false,"deactivateSound":false},"l10n":{"interaction":"Interaction","play":"Play","pause":"Pause","mute":"Mute","unmute":"Unmute","quality":"Video Quality","captions":"Captions","close":"Close","fullscreen":"Fullscreen","exitFullscreen":"Exit Fullscreen","summary":"Summary","bookmarks":"Bookmarks","defaultAdaptivitySeekLabel":"Continue","continueWithVideo":"Continue with video","playbackRate":"Playback Rate","rewind10":"Rewind 10 Seconds","navDisabled":"Navigation is disabled","sndDisabled":"Sound is disabled","requiresCompletionWarning":"You need to answer all the questions correctly before continuing.","back":"Back","hours":"Hours","minutes":"Minutes","seconds":"Seconds","currentTime":"Current time:","totalTime":"Total time:","navigationHotkeyInstructions":"Use key k for starting and stopping video at any time","singleInteractionAnnouncement":"Interaction appeared:","multipleInteractionsAnnouncement":"Multiple interactions appeared.","videoPausedAnnouncement":"Video is paused","content":"Content"}}'; $h5p = H5PContent::factory()->create([ @@ -352,7 +368,6 @@ public function withRemoteVideoAndImage_storeLocally() */ public function withRemoteCaptions_storeLocally() { - $this->linkLibrariesFolder(); $this->seed(TestH5PSeeder::class); $params = '{"interactiveVideo":{"video":{"startScreenOptions":{"title":"Interactive Video","hideStartTitle":false,"copyright":""},"textTracks":[{"kind":"captions","label":"Nynorsk","srcLang":"nn","track":{"externalId":"87ada56a-5670-4b43-96e3-4aef54284197","path":"https:\/\/urltocaptions/text.vtt","mime":"text\/webvtt","copyright":{"license":"U"}}}],"files":[{"path":"https://bc/12456","mime":"video/BrightCove","copyright":{"license":"U"}}]},"assets":{"interactions":[]},"summary":{"task":{"library":"H5P.Summary 1.8","params":{"intro":"Choose the correct statement.","summaries":[{"subContentId":"2363644d-ab92-4f93-8d11-0d6e916bd83d","tip":""}],"overallFeedback":[{"from":0,"to":100}],"solvedLabel":"Progress:","scoreLabel":"Wrong answers:","resultLabel":"Your result","labelCorrect":"Correct.","labelIncorrect":"Incorrect! Please try again.","labelCorrectAnswers":"Correct answers.","tipButtonLabel":"Show tip","scoreBarLabel":"You got :num out of :total points","progressText":"Progress :num of :total"},"subContentId":"211f821f-67f9-4717-a7a4-362c4d507545","metadata":{"contentType":"Summary","license":"U","title":"Untitled Summary"}},"displayAt":3}},"override":{"autoplay":false,"loop":false,"showBookmarksmenuOnLoad":false,"showRewind10":false,"preventSkipping":false,"deactivateSound":false},"l10n":{"interaction":"Interaction","play":"Play","pause":"Pause","mute":"Mute","unmute":"Unmute","quality":"Video Quality","captions":"Captions","close":"Close","fullscreen":"Fullscreen","exitFullscreen":"Exit Fullscreen","summary":"Summary","bookmarks":"Bookmarks","defaultAdaptivitySeekLabel":"Continue","continueWithVideo":"Continue with video","playbackRate":"Playback Rate","rewind10":"Rewind 10 Seconds","navDisabled":"Navigation is disabled","sndDisabled":"Sound is disabled","requiresCompletionWarning":"You need to answer all the questions correctly before continuing.","back":"Back","hours":"Hours","minutes":"Minutes","seconds":"Seconds","currentTime":"Current time:","totalTime":"Total time:","navigationHotkeyInstructions":"Use key k for starting and stopping video at any time","singleInteractionAnnouncement":"Interaction appeared:","multipleInteractionsAnnouncement":"Multiple interactions appeared.","videoPausedAnnouncement":"Video is paused","content":"Content"}}'; $h5p = H5PContent::factory()->create([ diff --git a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/Storage/H5pCerpusStorageTest.php b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/Storage/H5pCerpusStorageTest.php index 8a631e8f4e..b40e010a0c 100644 --- a/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/Storage/H5pCerpusStorageTest.php +++ b/sourcecode/apis/contentauthor/tests/Integration/Libraries/H5P/Storage/H5pCerpusStorageTest.php @@ -2,24 +2,23 @@ namespace Tests\Integration\Libraries\H5P\Storage; +use App\H5PLibrary; use App\Libraries\ContentAuthorStorage; +use App\Libraries\DataObjects\ContentStorageSettings; use App\Libraries\H5P\Storage\H5PCerpusStorage; use App\Libraries\H5P\Video\NullVideoAdapter; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Storage; use Psr\Log\NullLogger; use Tests\TestCase; class H5pCerpusStorageTest extends TestCase { - protected function setUp(): void - { - $this->markTestIncomplete('Fix these later'); - } + use RefreshDatabase; public function test_correct_url_without_cdn_prefix() { - $disk = Storage::fake('fake'); - + $disk = Storage::fake('test'); $disk->put('test.txt', 'some content'); $cerpusStorage = new H5pCerpusStorage( @@ -28,27 +27,26 @@ public function test_correct_url_without_cdn_prefix() new NullVideoAdapter(), ); - $this->assertEquals("/test.txt", $cerpusStorage->getFileUrl('test.txt')); + $this->assertEquals("http://localhost/content/assets/test.txt", $cerpusStorage->getFileUrl('test.txt')); } public function test_correct_url_with_cdn_prefix() { - $disk = Storage::fake('fake'); - + $disk = Storage::fake('test'); $disk->put('test.txt', 'some content'); $cerpusStorage = new H5pCerpusStorage( - new ContentAuthorStorage(''), + new ContentAuthorStorage('https://not.localhost.test/prefix/'), new NullLogger(), new NullVideoAdapter(), ); - $this->assertEquals("http://test/aaa/test.txt", $cerpusStorage->getFileUrl('test.txt')); + $this->assertEquals("https://not.localhost.test/prefix/test.txt", $cerpusStorage->getFileUrl('test.txt')); } public function test_correct_url_when_file_not_found() { - $disk = Storage::fake('fake'); + Storage::fake('test'); $cerpusStorage = new H5pCerpusStorage( new ContentAuthorStorage(''), @@ -56,7 +54,47 @@ public function test_correct_url_when_file_not_found() new NullVideoAdapter(), ); - $this->assertEquals("", $cerpusStorage->getFileUrl('test.txt')); } + + /** @dataProvider provide_test_getUpdateScript */ + public function test_getUpgradeScript(array $libConfig): void + { + $disk = Storage::fake(); + + /** @var H5PLibrary $library */ + $library = H5PLibrary::factory()->create($libConfig); + $file = sprintf(ContentStorageSettings::UPGRADE_SCRIPT_PATH, $library->getFolderName()); + + $this->assertFalse($disk->exists($file)); + $disk->put($file, 'just testing'); + $this->assertTrue($disk->exists($file)); + + $cerpusStorage = new H5pCerpusStorage( + new ContentAuthorStorage(''), + new NullLogger(), + new NullVideoAdapter(), + ); + + $path = $cerpusStorage->getUpgradeScript($library->name, $library->major_version, $library->minor_version); + + $this->assertStringContainsString($file, $path); + } + + public function provide_test_getUpdateScript(): \Generator + { + yield 'withoutPatch' => [[ + 'name' => 'H5P.Blanks', + 'major_version' => 1, + 'minor_version' => 11, + ]]; + + yield 'withPatch' => [[ + 'name' => 'H5P.Blanks', + 'major_version' => 1, + 'minor_version' => 14, + 'patch_version' => 6, + 'patch_version_in_folder_name' => 1, + ]]; + } } diff --git a/sourcecode/apis/contentauthor/tests/Integration/Models/H5PLibraryTest.php b/sourcecode/apis/contentauthor/tests/Integration/Models/H5PLibraryTest.php new file mode 100644 index 0000000000..5484e64fef --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/Integration/Models/H5PLibraryTest.php @@ -0,0 +1,100 @@ +make([ + 'patch_version_in_folder_name' => $hasPatch, + ]); + + $this->assertSame($expected, $lib->getLibraryString($usePatch)); + } + + public function provider_getLibraryString(): \Generator + { + yield 0 => [null, false, 'H5P.Foobar 1.2']; + yield 1 => [null, true, 'H5P.Foobar 1.2.3']; + yield 2 => [true, false, 'H5P.Foobar 1.2.3']; + yield 3 => [true, true, 'H5P.Foobar 1.2.3']; + yield 4 => [false, false, 'H5P.Foobar 1.2']; + yield 5 => [false, true, 'H5P.Foobar 1.2']; + } + + /** + * @dataProvider provider_getFolderName + */ + public function test_getFolderName($usePatch, $hasPatch, $expected): void + { + /** @var H5PLibrary $lib */ + $lib = H5PLibrary::factory()->make([ + 'patch_version_in_folder_name' => $hasPatch, + ]); + + $this->assertSame($expected, $lib->getFolderName($usePatch)); + } + + public function provider_getFolderName(): \Generator + { + yield 0 => [null, false, 'H5P.Foobar-1.2']; + yield 1 => [null, true, 'H5P.Foobar-1.2.3']; + yield 2 => [true, false, 'H5P.Foobar-1.2.3']; + yield 3 => [true, true, 'H5P.Foobar-1.2.3']; + yield 4 => [false, false, 'H5P.Foobar-1.2']; + yield 5 => [false, true, 'H5P.Foobar-1.2']; + } + + /** @dataProvider provider_libraryToFolderName */ + public function test_libraryToFolderName($data, $usePatch, $expected): void + { + $this->assertSame($expected, H5PLibrary::libraryToFolderName($data, $usePatch)); + } + + public function provider_libraryToFolderName(): \Generator + { + yield 0 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => true], false, 'H5P.Foobar-2.1']; + yield 1 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => false], true, 'H5P.Foobar-2.1.4']; + yield 2 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => 0], null, 'H5P.Foobar-2.1']; + yield 3 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => 1], null, 'H5P.Foobar-2.1.4']; + yield 4 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4], true, 'H5P.Foobar-2.1.4']; + yield 5 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4], null, 'H5P.Foobar-2.1']; + yield 6 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersionInFolderName' => true], false, 'H5P.Foobar-2.1']; + } + + /** @dataProvider provider_libraryToFolderNameExceptions */ + public function test_libraryToFolderNameExceptions($data, $usePatch): void + { + $this->expectException(\InvalidArgumentException::class); + H5PLibrary::libraryToFolderName($data, $usePatch); + } + + public function provider_libraryToFolderNameExceptions(): \Generator + { + yield 0 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersionInFolderName' => true], true]; + yield 1 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersionInFolderName' => false], true]; + yield 2 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersionInFolderName' => true], null]; + } + + /** @dataProvider provider_libraryToString */ + public function test_libraryToString($data, $usePatch, $expected): void + { + $this->assertSame($expected, H5PLibrary::libraryToString($data, $usePatch)); + } + + public function provider_libraryToString(): \Generator + { + yield 0 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => true], null, 'H5P.Foobar 2.1.4']; + yield 1 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => false], null, 'H5P.Foobar 2.1']; + yield 2 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => true], false, 'H5P.Foobar 2.1']; + yield 3 => [['name' => 'H5P.Foobar', 'majorVersion' => 2, 'minorVersion' => 1, 'patchVersion' => 4, 'patchVersionInFolderName' => false], true, 'H5P.Foobar 2.1.4']; + } +} diff --git a/sourcecode/apis/contentauthor/tests/Unit/Http/Controllers/AdminH5PDetailsControllerTest.php b/sourcecode/apis/contentauthor/tests/Unit/Http/Controllers/AdminH5PDetailsControllerTest.php index 7f953b9f00..855444842e 100644 --- a/sourcecode/apis/contentauthor/tests/Unit/Http/Controllers/AdminH5PDetailsControllerTest.php +++ b/sourcecode/apis/contentauthor/tests/Unit/Http/Controllers/AdminH5PDetailsControllerTest.php @@ -10,6 +10,7 @@ use App\Libraries\ContentAuthorStorage; use App\Libraries\H5P\Framework; use Cerpus\VersionClient\VersionData; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Http\Request; @@ -81,7 +82,14 @@ public function test_checkLibrary(): void $this->instance(ContentAuthorStorage::class, $storage); $storage ->expects($this->once()) - ->method('copyFolder'); + ->method('copyFolder') + ->with( + $this->isInstanceOf(FilesystemAdapter::class), + $this->isInstanceOf(FilesystemAdapter::class), + $this->equalTo('libraries/H5P.Foobar-1.2'), + $this->equalTo('libraries/H5P.Foobar-1.2'), + $this->equalTo([]), + ); $framework = $this->createMock(Framework::class); $this->instance(Framework::class, $framework); @@ -97,6 +105,7 @@ public function test_checkLibrary(): void $validator ->expects($this->once()) ->method('getLibraryData') + ->with($this->equalTo('H5P.Foobar-1.2'), $this->isNull(), $this->isNull()) ->willReturn([ 'editorDependencies' => [ [ diff --git a/sourcecode/apis/contentauthor/tests/Unit/Libraries/H5P/FrameworkTest.php b/sourcecode/apis/contentauthor/tests/Unit/Libraries/H5P/FrameworkTest.php index 6e65d103ee..29deee12d3 100644 --- a/sourcecode/apis/contentauthor/tests/Unit/Libraries/H5P/FrameworkTest.php +++ b/sourcecode/apis/contentauthor/tests/Unit/Libraries/H5P/FrameworkTest.php @@ -31,6 +31,8 @@ final class FrameworkTest extends TestCase protected function setUp(): void { + parent::setUp(); + $this->history = new ArrayObject(); $this->mockedResponses = new MockHandler(); @@ -87,6 +89,30 @@ public function testFetchExternalDataWithData(): void ); } + public function testFetchExternalDataWithFullData(): void + { + $this->mockedResponses->append(new Response(200, [], 'Some body')); + + $response = $this->framework->fetchExternalData( + 'http://www.example.com', + [ + 'foo' => 'bar', + ], + fullData: true, + ); + + $this->assertSame( + 'foo=bar', + $this->history[0]['request']->getBody()->getContents(), + ); + $this->assertIsArray($response); + $this->assertArrayHasKey('status', $response); + $this->assertArrayHasKey('headers', $response); + $this->assertArrayHasKey('data', $response); + $this->assertSame(200, $response['status']); + $this->assertSame('Some body', $response['data']); + } + public function testFetchExternalDataWithGuzzleError(): void { $this->mockedResponses->append(new TransferException()); diff --git a/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/css/blanks.css b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/css/blanks.css new file mode 100644 index 0000000000..b5e99c0986 --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/css/blanks.css @@ -0,0 +1,113 @@ +.h5p-blanks { + position: relative; +} +.h5p-blanks .h5p-input-wrapper { + display: inline-block; + position: relative; +} +.h5p-blanks .h5p-text-input { + font-family: H5PDroidSans, sans-serif; + font-size: 1em; + border-radius: 0.25em; + border: 1px solid #a0a0a0; + padding: 0.1875em 1em 0.1875em 0.5em; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 6em; +} +.h5p-blanks .h5p-text-input:focus { + outline: none; + box-shadow: 0 0 0.5em 0 #6391CA; + border-color: #6391CA; +} +.h5p-blanks .h5p-text-input:disabled { + opacity: 1; +} +.h5p-blanks .h5p-text-input.h5p-not-filled-out { + background: #fff0f0; +} +.h5p-blanks .h5p-separate-lines .h5p-input-wrapper { + display: block; +} +.h5p-blanks .h5p-separate-lines .h5p-text-input { + width: 100%; + box-sizing: border-box; + -moz-box-sizing: border-box; +} + +/* Correctly answered input */ +.h5p-blanks .h5p-correct .h5p-text-input { + background: #9dd8bb; + border: 1px solid #9dd8bb; + color: #255c41; +} +/* Showing solution */ +.h5p-blanks .h5p-correct-answer { + color: #255c41; + font-weight: bold; + border: 1px #255c41 dashed; + background-color: #d4f6e6; + padding: 0.15em; + border-radius: 0.25em; + margin-left: .5em; +} +.h5p-blanks .h5p-correct:after { + position: absolute; + right: 0.5em; + top: 0; + text-decoration: none; + content: "\f00c"; + font-family: 'H5PFontAwesome4'; + color: #255c41; +} + +/* Wrongly answered input */ +.h5p-blanks .h5p-wrong .h5p-text-input { + background-color: #f7d0d0; + border: 1px solid #f7d0d0; + color: #b71c1c; + text-decoration: line-through; +} +.h5p-blanks .h5p-wrong:after { + position: absolute; + right: 0.5em; + top: 0; + font-family: 'H5PFontAwesome4'; + text-decoration: none; + content: "\f00d"; + color: #b71c1c; +} + +/* Actual text paragraphs */ +.h5p-blanks .h5p-question-content p { + line-height: 1.75em; + margin: 0 0 1em; +} + +/* Header and footer blocks (title + evaluation, buttons) */ +.h5p-blanks .joubel-tip-container { + position: absolute; + right: 0.4em; + font-size: 1em; +} +.h5p-blanks .joubel-tip-container .joubel-icon-tip-normal { + line-height: 1em; +} +.h5p-blanks .has-tip .h5p-text-input { + padding-right: 2.25em; +} +.h5p-blanks .has-tip.h5p-correct:after, +.h5p-blanks .has-tip.h5p-wrong:after { + right: 2.25em; +} +.h5p-blanks .has-tip.h5p-correct .h5p-text-input, +.h5p-blanks .has-tip.h5p-wrong .h5p-text-input { + padding-right: 3.5em; +} +.h5p-blanks .hidden-but-read { + position: absolute; + height: 0; + width: 0; + overflow: hidden; +} diff --git a/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/icon.svg b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/icon.svg new file mode 100644 index 0000000000..ef1db071e0 --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/icon.svg @@ -0,0 +1,68 @@ + diff --git a/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/js/blanks.js b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/js/blanks.js new file mode 100644 index 0000000000..30194deb3b --- /dev/null +++ b/sourcecode/apis/contentauthor/tests/files/libraries/H5P.Blanks-1.14.6/js/blanks.js @@ -0,0 +1,1004 @@ +/*global H5P*/ +H5P.Blanks = (function ($, Question) { + /** + * @constant + * @default + */ + var STATE_ONGOING = 'ongoing'; + var STATE_CHECKING = 'checking'; + var STATE_SHOWING_SOLUTION = 'showing-solution'; + var STATE_FINISHED = 'finished'; + + const XAPI_ALTERNATIVE_EXTENSION = 'https://h5p.org/x-api/alternatives'; + const XAPI_CASE_SENSITIVITY = 'https://h5p.org/x-api/case-sensitivity'; + const XAPI_REPORTING_VERSION_EXTENSION = 'https://h5p.org/x-api/h5p-reporting-version'; + + /** + * @typedef {Object} Params + * Parameters/configuration object for Blanks + * + * @property {Object} Params.behaviour + * @property {string} Params.behaviour.confirmRetryDialog + * @property {string} Params.behaviour.confirmCheckDialog + * + * @property {Object} Params.confirmRetry + * @property {string} Params.confirmRetry.header + * @property {string} Params.confirmRetry.body + * @property {string} Params.confirmRetry.cancelLabel + * @property {string} Params.confirmRetry.confirmLabel + * + * @property {Object} Params.confirmCheck + * @property {string} Params.confirmCheck.header + * @property {string} Params.confirmCheck.body + * @property {string} Params.confirmCheck.cancelLabel + * @property {string} Params.confirmCheck.confirmLabel + */ + + /** + * Initialize module. + * + * @class H5P.Blanks + * @extends H5P.Question + * @param {Params} params + * @param {number} id Content identification + * @param {Object} contentData Task specific content data + */ + function Blanks(params, id, contentData) { + var self = this; + + // Inheritance + Question.call(self, 'blanks'); + + // IDs + this.contentId = id; + this.contentData = contentData; + + this.params = $.extend(true, {}, { + text: "Fill in", + questions: [ + "
Oslo is the capital of *Norway*.
" + ], + overallFeedback: [], + userAnswers: [], // TODO This isn't in semantics? + showSolutions: "Show solution", + tryAgain: "Try again", + checkAnswer: "Check", + changeAnswer: "Change answer", + notFilledOut: "Please fill in all blanks to view solution", + answerIsCorrect: "':ans' is correct", + answerIsWrong: "':ans' is wrong", + answeredCorrectly: "Answered correctly", + answeredIncorrectly: "Answered incorrectly", + solutionLabel: "Correct answer:", + inputLabel: "Blank input @num of @total", + inputHasTipLabel: "Tip available", + tipLabel: "Tip", + scoreBarLabel: 'You got :num out of :total points', + behaviour: { + enableRetry: true, + enableSolutionsButton: true, + enableCheckButton: true, + caseSensitive: true, + showSolutionsRequiresInput: true, + autoCheck: false, + separateLines: false + }, + a11yCheck: 'Check the answers. The responses will be marked as correct, incorrect, or unanswered.', + a11yShowSolution: 'Show the solution. The task will be marked with its correct solution.', + a11yRetry: 'Retry the task. Reset all responses and start the task over again.', + a11yHeader: 'Checking mode', + submitAnswer: 'Submit', + }, params); + + // Delete empty questions + for (var i = this.params.questions.length - 1; i >= 0; i--) { + if (!this.params.questions[i]) { + this.params.questions.splice(i, 1); + } + } + + // Previous state + this.contentData = contentData; + if (this.contentData !== undefined && this.contentData.previousState !== undefined) { + this.previousState = this.contentData.previousState; + } + + // Clozes + this.clozes = []; + + // Keep track tabbing forward or backwards + this.shiftPressed = false; + + H5P.$body.keydown(function (event) { + if (event.keyCode === 16) { + self.shiftPressed = true; + } + }).keyup(function (event) { + if (event.keyCode === 16) { + self.shiftPressed = false; + } + }); + + // Using instructions as label for our text groups + this.labelId = 'h5p-blanks-instructions-' + Blanks.idCounter; + this.content = self.createQuestions(); + + // Check for task media + var media = self.params.media; + if (media && media.type && media.type.library) { + media = media.type; + var type = media.library.split(' ')[0]; + if (type === 'H5P.Image') { + if (media.params.file) { + // Register task image + self.setImage(media.params.file.path, { + disableImageZooming: self.params.media.disableImageZooming || false, + alt: media.params.alt, + title: media.params.title + }); + } + } + else if (type === 'H5P.Video') { + if (media.params.sources) { + // Register task video + self.setVideo(media); + } + } + else if (type === 'H5P.Audio') { + if (media.params.files) { + // Register task audio + self.setAudio(media); + } + } + } + + // Register task introduction text + self.setIntroduction('