NOBUG: Fixed SVG browser compatibility
[moodle.git] / h5p / h5plib / v124 / joubel / editor / h5peditor-ajax.class.php
blob354014f4cb2b8edec678a98f0d9f27cde15fcb35
1 <?php
3 namespace Moodle;
5 abstract class H5PEditorEndpoints {
7 /**
8 * Endpoint for retrieving library data necessary for displaying
9 * content types in the editor.
11 const LIBRARIES = 'libraries';
13 /**
14 * Endpoint for retrieving a singe library's data necessary for displaying
15 * main libraries
17 const SINGLE_LIBRARY = 'single-library';
19 /**
20 * Endpoint for retrieving the currently stored content type cache
22 const CONTENT_TYPE_CACHE = 'content-type-cache';
24 /**
25 * Endpoint for retrieving the currently stored content hub metadata cache
27 const CONTENT_HUB_METADATA_CACHE = 'content-hub-metadata-cache';
29 /**
30 * Endpoint for installing libraries from the Content Type Hub
32 const LIBRARY_INSTALL = 'library-install';
34 /**
35 * Endpoint for uploading libraries used by the editor through the Content
36 * Type Hub.
38 const LIBRARY_UPLOAD = 'library-upload';
40 /**
41 * Endpoint for uploading files used by the editor.
43 const FILES = 'files';
45 /**
46 * Endpoint for retrieveing translation files
48 const TRANSLATIONS = 'translations';
50 /**
51 * Endpoint for filtering parameters.
53 const FILTER = 'filter';
55 /**
56 * Endpoint for installing libraries from the Content Type Hub
58 const GET_HUB_CONTENT = 'get-hub-content';
62 /**
63 * Class H5PEditorAjax
64 * @package modules\h5peditor\h5peditor
66 class H5PEditorAjax {
68 /**
69 * @var H5PCore
71 public $core;
73 /**
74 * @var H5peditor
76 public $editor;
78 /**
79 * @var H5peditorStorage
81 public $storage;
83 /**
84 * H5PEditorAjax constructor requires core, editor and storage as building
85 * blocks.
87 * @param H5PCore $H5PCore
88 * @param H5peditor $H5PEditor
89 * @param H5peditorStorage $H5PEditorStorage
91 public function __construct(H5PCore $H5PCore, H5peditor $H5PEditor, H5peditorStorage $H5PEditorStorage) {
92 $this->core = $H5PCore;
93 $this->editor = $H5PEditor;
94 $this->storage = $H5PEditorStorage;
97 /**
98 * @param $endpoint
100 public function action($endpoint) {
101 switch ($endpoint) {
102 case H5PEditorEndpoints::LIBRARIES:
103 H5PCore::ajaxSuccess($this->editor->getLibraries(), TRUE);
104 break;
106 case H5PEditorEndpoints::SINGLE_LIBRARY:
107 // pass on arguments
108 $args = func_get_args();
109 array_shift($args);
110 $library = call_user_func_array(
111 array($this->editor, 'getLibraryData'), $args
113 H5PCore::ajaxSuccess($library, TRUE);
114 break;
116 case H5PEditorEndpoints::CONTENT_TYPE_CACHE:
117 if (!$this->isHubOn()) return;
118 H5PCore::ajaxSuccess($this->getContentTypeCache(!$this->isContentTypeCacheUpdated()), TRUE);
119 break;
121 case H5PEditorEndpoints::CONTENT_HUB_METADATA_CACHE:
122 if (!$this->isHubOn()) return;
123 header('Cache-Control: no-cache');
124 header('Content-Type: application/json; charset=utf-8');
125 print '{"success":true,"data":' . $this->core->getUpdatedContentHubMetadataCache(func_get_arg(1)) . '}';
126 break;
128 case H5PEditorEndpoints::LIBRARY_INSTALL:
129 if (!$this->isPostRequest()) return;
131 $token = func_get_arg(1);
132 if (!$this->isValidEditorToken($token)) return;
134 $machineName = func_get_arg(2);
135 $this->libraryInstall($machineName);
136 break;
138 case H5PEditorEndpoints::LIBRARY_UPLOAD:
139 if (!$this->isPostRequest()) return;
141 $token = func_get_arg(1);
142 if (!$this->isValidEditorToken($token)) return;
144 $uploadPath = func_get_arg(2);
145 $contentId = func_get_arg(3);
146 $this->libraryUpload($uploadPath, $contentId);
147 break;
149 case H5PEditorEndpoints::FILES:
150 $token = func_get_arg(1);
151 $contentId = func_get_arg(2);
152 if (!$this->isValidEditorToken($token)) return;
153 $this->fileUpload($contentId);
154 break;
156 case H5PEditorEndpoints::TRANSLATIONS:
157 $language = func_get_arg(1);
158 H5PCore::ajaxSuccess($this->editor->getTranslations($_POST['libraries'], $language));
159 break;
161 case H5PEditorEndpoints::FILTER:
162 $token = func_get_arg(1);
163 if (!$this->isValidEditorToken($token)) return;
164 $this->filter(func_get_arg(2));
165 break;
167 case H5PEditorEndpoints::GET_HUB_CONTENT:
168 if (!$this->isPostRequest() || !$this->isValidEditorToken(func_get_arg(1))) {
169 return;
171 $this->getHubContent(func_get_arg(2), func_get_arg(3));
172 break;
177 * Handles uploaded files from the editor, making sure they are validated
178 * and ready to be permanently stored if saved.
180 * Marks all uploaded files as
181 * temporary so they can be cleaned up when we have finished using them.
183 * @param int $contentId Id of content if already existing content
185 private function fileUpload($contentId = NULL) {
186 $file = new H5peditorFile($this->core->h5pF);
187 if (!$file->isLoaded()) {
188 H5PCore::ajaxError($this->core->h5pF->t('File not found on server. Check file upload settings.'));
189 return;
192 // Make sure file is valid and mark it for cleanup at a later time
193 if ($file->validate()) {
194 $file_id = $this->core->fs->saveFile($file, 0);
195 $this->storage->markFileForCleanup($file_id, 0);
197 $file->printResult();
201 * Handles uploading libraries so they are ready to be modified or directly saved.
203 * Validates and saves any dependencies, then exposes content to the editor.
205 * @param {string} $uploadFilePath Path to the file that should be uploaded
206 * @param {int} $contentId Content id of library
208 private function libraryUpload($uploadFilePath, $contentId) {
209 // Verify h5p upload
210 if (!$uploadFilePath) {
211 H5PCore::ajaxError($this->core->h5pF->t('Could not get posted H5P.'), 'NO_CONTENT_TYPE');
212 exit;
215 $file = $this->saveFileTemporarily($uploadFilePath, TRUE);
216 if (!$file) return;
218 $this->processContent($contentId);
222 * Process H5P content from local H5P package.
224 * @param integer $contentId The Local Content ID / vid. TODO Remove when JI-366 is fixed
226 private function processContent($contentId) {
227 // Check if the downloaded package is valid
228 if (!$this->isValidPackage()) {
229 return; // Validation errors
232 // Install any required dependencies (libraries) from the package
233 // (if permission allows it, of course)
234 $storage = new H5PStorage($this->core->h5pF, $this->core);
235 $storage->savePackage(NULL, NULL, TRUE);
237 // Make content available to editor
238 $files = $this->core->fs->moveContentDirectory($this->core->h5pF->getUploadedH5pFolderPath(), $contentId);
240 // Clean up
241 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath());
243 // Mark all files as temporary
244 // TODO: Uncomment once moveContentDirectory() is fixed. JI-366
245 /*foreach ($files as $file) {
246 $this->storage->markFileForCleanup($file, 0);
249 H5PCore::ajaxSuccess(array(
250 'h5p' => $this->core->mainJsonData,
251 'content' => $this->core->contentJsonData,
252 'contentTypes' => $this->getContentTypeCache()
257 * Validates security tokens used for the editor
259 * @param string $token
261 * @return bool
263 private function isValidEditorToken($token) {
264 $isValidToken = $this->editor->ajaxInterface->validateEditorToken($token);
265 if (!$isValidToken) {
266 H5PCore::ajaxError(
267 $this->core->h5pF->t('Invalid security token.'),
268 'INVALID_TOKEN'
270 return FALSE;
272 return TRUE;
276 * Handles installation of libraries from the Content Type Hub.
278 * Accepts a machine name and attempts to fetch and install it from the Hub if
279 * it is valid. Will also install any dependencies to the requested library.
281 * @param string $machineName Name of library that should be installed
283 private function libraryInstall($machineName) {
285 // Determine which content type to install from post data
286 if (!$machineName) {
287 H5PCore::ajaxError($this->core->h5pF->t('No content type was specified.'), 'NO_CONTENT_TYPE');
288 return;
291 // Look up content type to ensure it's valid(and to check permissions)
292 $contentType = $this->editor->ajaxInterface->getContentTypeCache($machineName);
293 if (!$contentType) {
294 H5PCore::ajaxError($this->core->h5pF->t('The chosen content type is invalid.'), 'INVALID_CONTENT_TYPE');
295 return;
298 // Check install permissions
299 if (!$this->editor->canInstallContentType($contentType)) {
300 H5PCore::ajaxError($this->core->h5pF->t('You do not have permission to install content types. Contact the administrator of your site.'), 'INSTALL_DENIED');
301 return;
303 else {
304 // Override core permission check
305 $this->core->mayUpdateLibraries(TRUE);
308 // Retrieve content type from hub endpoint
309 $response = $this->callHubEndpoint(H5PHubEndpoints::CONTENT_TYPES . $machineName);
310 if (!$response) return;
312 // Session parameters has to be set for validation and saving of packages
313 if (!$this->isValidPackage(TRUE)) return;
315 // Save H5P
316 $storage = new H5PStorage($this->core->h5pF, $this->core);
317 $storage->savePackage(NULL, NULL, TRUE);
319 // Clean up
320 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath());
322 // Successfully installed. Refresh content types
323 H5PCore::ajaxSuccess($this->getContentTypeCache());
327 * End-point for filter parameter values according to semantics.
329 * @param {string} $libraryParameters
331 private function filter($libraryParameters) {
332 $libraryParameters = json_decode($libraryParameters);
333 if (!$libraryParameters) {
334 H5PCore::ajaxError($this->core->h5pF->t('Could not parse post data.'), 'NO_LIBRARY_PARAMETERS');
335 exit;
338 // Filter parameters and send back to client
339 $validator = new H5PContentValidator($this->core->h5pF, $this->core);
340 $validator->validateLibrary($libraryParameters, (object) array('options' => array($libraryParameters->library)));
341 H5PCore::ajaxSuccess($libraryParameters);
345 * Download and use content from the HUB
347 * @param integer $hubId The Hub Content ID
348 * @param integer $localContentId The Local Content ID
350 private function getHubContent($hubId, $localContentId) {
351 // Download H5P file
352 if (!$this->callHubEndpoint(H5PHubEndpoints::CONTENT . '/' . $hubId . '/export')) {
353 return; // Download failed
356 $this->processContent($localContentId);
360 * Validates the package. Sets error messages if validation fails.
362 * @param bool $skipContent Will not validate cotent if set to TRUE
364 * @return bool
366 private function isValidPackage($skipContent = FALSE) {
367 $validator = new H5PValidator($this->core->h5pF, $this->core);
368 if (!$validator->isValidPackage($skipContent, FALSE)) {
369 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pPath());
371 H5PCore::ajaxError(
372 $this->core->h5pF->t('Validating h5p package failed.'),
373 'VALIDATION_FAILED',
374 NULL,
375 $this->core->h5pF->getMessages('error')
377 return FALSE;
380 return TRUE;
384 * Saves a file or moves it temporarily. This is often necessary in order to
385 * validate and store uploaded or fetched H5Ps.
387 * Sets error messages if saving fails.
389 * @param string $data Uri of data that should be saved as a temporary file
390 * @param boolean $move_file Can be set to TRUE to move the data instead of saving it
392 * @return bool|object Returns false if saving failed or the path to the file
393 * if saving succeeded
395 private function saveFileTemporarily($data, $move_file = FALSE) {
396 $file = $this->storage->saveFileTemporarily($data, $move_file);
397 if (!$file) {
398 H5PCore::ajaxError(
399 $this->core->h5pF->t('Failed to download the requested H5P.'),
400 'DOWNLOAD_FAILED'
402 return FALSE;
405 return $file;
409 * Calls provided hub endpoint and downloads the response to a .h5p file.
411 * @param string $endpoint Endpoint without protocol
413 * @return bool
415 private function callHubEndpoint($endpoint) {
416 $path = $this->core->h5pF->getUploadedH5pPath();
417 $response = $this->core->h5pF->fetchExternalData(H5PHubEndpoints::createURL($endpoint), NULL, TRUE, empty($path) ? TRUE : $path);
418 if (!$response) {
419 H5PCore::ajaxError(
420 $this->core->h5pF->t('Failed to download the requested H5P.'),
421 'DOWNLOAD_FAILED',
422 NULL,
423 $this->core->h5pF->getMessages('error')
425 return FALSE;
428 return TRUE;
432 * Checks if request is a POST. Sets error message on fail.
434 * @return bool
436 private function isPostRequest() {
437 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
438 H5PCore::ajaxError(
439 $this->core->h5pF->t('A post message is required to access the given endpoint'),
440 'REQUIRES_POST',
443 return FALSE;
445 return TRUE;
449 * Checks if H5P Hub is enabled. Sets error message on fail.
451 * @return bool
453 private function isHubOn() {
454 if (!$this->core->h5pF->getOption('hub_is_enabled', TRUE)) {
455 H5PCore::ajaxError(
456 $this->core->h5pF->t('The hub is disabled. You can enable it in the H5P settings.'),
457 'HUB_DISABLED',
460 return false;
462 return true;
466 * Checks if Content Type Cache is up to date. Immediately tries to fetch
467 * a new Content Type Cache if it is outdated.
468 * Sets error message if fetching new Content Type Cache fails.
470 * @return bool
472 private function isContentTypeCacheUpdated() {
474 // Update content type cache if enabled and too old
475 $ct_cache_last_update = $this->core->h5pF->getOption('content_type_cache_updated_at', 0);
476 $outdated_cache = $ct_cache_last_update + (60 * 60 * 24 * 7); // 1 week
477 if (time() > $outdated_cache) {
478 $success = $this->core->updateContentTypeCache();
479 if (!$success) {
480 return false;
483 return true;
487 * Gets content type cache for globally available libraries and the order
488 * in which they have been used by the author
490 * @param bool $cacheOutdated The cache is outdated and not able to update
492 private function getContentTypeCache($cacheOutdated = FALSE) {
493 $canUpdateOrInstall = ($this->core->h5pF->hasPermission(H5PPermission::INSTALL_RECOMMENDED) ||
494 $this->core->h5pF->hasPermission(H5PPermission::UPDATE_LIBRARIES));
495 return array(
496 'outdated' => $cacheOutdated && $canUpdateOrInstall,
497 'libraries' => $this->editor->getLatestGlobalLibrariesData(),
498 'recentlyUsed' => $this->editor->ajaxInterface->getAuthorsRecentlyUsedLibraries(),
499 'apiVersion' => array(
500 'major' => H5PCore::$coreApi['majorVersion'],
501 'minor' => H5PCore::$coreApi['minorVersion']
503 'details' => $this->core->h5pF->getMessages('info')