See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 3 namespace Moodle; 4 5 abstract class H5PEditorEndpoints { 6 7 /** 8 * Endpoint for retrieving library data necessary for displaying 9 * content types in the editor. 10 */ 11 const LIBRARIES = 'libraries'; 12 13 /** 14 * Endpoint for retrieving a singe library's data necessary for displaying 15 * main libraries 16 */ 17 const SINGLE_LIBRARY = 'single-library'; 18 19 /** 20 * Endpoint for retrieving the currently stored content type cache 21 */ 22 const CONTENT_TYPE_CACHE = 'content-type-cache'; 23 24 /** 25 * Endpoint for retrieving the currently stored content hub metadata cache 26 */ 27 const CONTENT_HUB_METADATA_CACHE = 'content-hub-metadata-cache'; 28 29 /** 30 * Endpoint for installing libraries from the Content Type Hub 31 */ 32 const LIBRARY_INSTALL = 'library-install'; 33 34 /** 35 * Endpoint for uploading libraries used by the editor through the Content 36 * Type Hub. 37 */ 38 const LIBRARY_UPLOAD = 'library-upload'; 39 40 /** 41 * Endpoint for uploading files used by the editor. 42 */ 43 const FILES = 'files'; 44 45 /** 46 * Endpoint for retrieveing translation files 47 */ 48 const TRANSLATIONS = 'translations'; 49 50 /** 51 * Endpoint for filtering parameters. 52 */ 53 const FILTER = 'filter'; 54 55 /** 56 * Endpoint for installing libraries from the Content Type Hub 57 */ 58 const GET_HUB_CONTENT = 'get-hub-content'; 59 } 60 61 62 /** 63 * Class H5PEditorAjax 64 * @package modules\h5peditor\h5peditor 65 */ 66 class H5PEditorAjax { 67 68 /** 69 * @var H5PCore 70 */ 71 public $core; 72 73 /** 74 * @var H5peditor 75 */ 76 public $editor; 77 78 /** 79 * @var H5peditorStorage 80 */ 81 public $storage; 82 83 /** 84 * H5PEditorAjax constructor requires core, editor and storage as building 85 * blocks. 86 * 87 * @param H5PCore $H5PCore 88 * @param H5peditor $H5PEditor 89 * @param H5peditorStorage $H5PEditorStorage 90 */ 91 public function __construct(H5PCore $H5PCore, H5peditor $H5PEditor, H5peditorStorage $H5PEditorStorage) { 92 $this->core = $H5PCore; 93 $this->editor = $H5PEditor; 94 $this->storage = $H5PEditorStorage; 95 } 96 97 /** 98 * @param $endpoint 99 */ 100 public function action($endpoint) { 101 switch ($endpoint) { 102 case H5PEditorEndpoints::LIBRARIES: 103 H5PCore::ajaxSuccess($this->editor->getLibraries(), TRUE); 104 break; 105 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 112 ); 113 H5PCore::ajaxSuccess($library, TRUE); 114 break; 115 116 case H5PEditorEndpoints::CONTENT_TYPE_CACHE: 117 if (!$this->isHubOn()) return; 118 H5PCore::ajaxSuccess($this->getContentTypeCache(!$this->isContentTypeCacheUpdated()), TRUE); 119 break; 120 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; 127 128 case H5PEditorEndpoints::LIBRARY_INSTALL: 129 if (!$this->isPostRequest()) return; 130 131 $token = func_get_arg(1); 132 if (!$this->isValidEditorToken($token)) return; 133 134 $machineName = func_get_arg(2); 135 $this->libraryInstall($machineName); 136 break; 137 138 case H5PEditorEndpoints::LIBRARY_UPLOAD: 139 if (!$this->isPostRequest()) return; 140 141 $token = func_get_arg(1); 142 if (!$this->isValidEditorToken($token)) return; 143 144 $uploadPath = func_get_arg(2); 145 $contentId = func_get_arg(3); 146 $this->libraryUpload($uploadPath, $contentId); 147 break; 148 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; 155 156 case H5PEditorEndpoints::TRANSLATIONS: 157 $language = func_get_arg(1); 158 H5PCore::ajaxSuccess($this->editor->getTranslations($_POST['libraries'], $language)); 159 break; 160 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; 166 167 case H5PEditorEndpoints::GET_HUB_CONTENT: 168 if (!$this->isPostRequest() || !$this->isValidEditorToken(func_get_arg(1))) { 169 return; 170 } 171 $this->getHubContent(func_get_arg(2), func_get_arg(3)); 172 break; 173 } 174 } 175 176 /** 177 * Handles uploaded files from the editor, making sure they are validated 178 * and ready to be permanently stored if saved. 179 * 180 * Marks all uploaded files as 181 * temporary so they can be cleaned up when we have finished using them. 182 * 183 * @param int $contentId Id of content if already existing content 184 */ 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; 190 } 191 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); 196 } 197 $file->printResult(); 198 } 199 200 /** 201 * Handles uploading libraries so they are ready to be modified or directly saved. 202 * 203 * Validates and saves any dependencies, then exposes content to the editor. 204 * 205 * @param {string} $uploadFilePath Path to the file that should be uploaded 206 * @param {int} $contentId Content id of library 207 */ 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; 213 } 214 215 $file = $this->saveFileTemporarily($uploadFilePath, TRUE); 216 if (!$file) return; 217 218 $this->processContent($contentId); 219 } 220 221 /** 222 * Process H5P content from local H5P package. 223 * 224 * @param integer $contentId The Local Content ID / vid. TODO Remove when JI-366 is fixed 225 */ 226 private function processContent($contentId) { 227 // Check if the downloaded package is valid 228 if (!$this->isValidPackage()) { 229 return; // Validation errors 230 } 231 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); 236 237 // Make content available to editor 238 $files = $this->core->fs->moveContentDirectory($this->core->h5pF->getUploadedH5pFolderPath(), $contentId); 239 240 // Clean up 241 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath()); 242 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); 247 }*/ 248 249 H5PCore::ajaxSuccess(array( 250 'h5p' => $this->core->mainJsonData, 251 'content' => $this->core->contentJsonData, 252 'contentTypes' => $this->getContentTypeCache() 253 )); 254 } 255 256 /** 257 * Validates security tokens used for the editor 258 * 259 * @param string $token 260 * 261 * @return bool 262 */ 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' 269 ); 270 return FALSE; 271 } 272 return TRUE; 273 } 274 275 /** 276 * Handles installation of libraries from the Content Type Hub. 277 * 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. 280 * 281 * @param string $machineName Name of library that should be installed 282 */ 283 private function libraryInstall($machineName) { 284 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; 289 } 290 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; 296 } 297 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; 302 } 303 else { 304 // Override core permission check 305 $this->core->mayUpdateLibraries(TRUE); 306 } 307 308 // Retrieve content type from hub endpoint 309 $response = $this->callHubEndpoint(H5PHubEndpoints::CONTENT_TYPES . $machineName); 310 if (!$response) return; 311 312 // Session parameters has to be set for validation and saving of packages 313 if (!$this->isValidPackage(TRUE)) return; 314 315 // Save H5P 316 $storage = new H5PStorage($this->core->h5pF, $this->core); 317 $storage->savePackage(NULL, NULL, TRUE); 318 319 // Clean up 320 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath()); 321 322 // Successfully installed. Refresh content types 323 H5PCore::ajaxSuccess($this->getContentTypeCache()); 324 } 325 326 /** 327 * End-point for filter parameter values according to semantics. 328 * 329 * @param {string} $libraryParameters 330 */ 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; 336 } 337 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); 342 } 343 344 /** 345 * Download and use content from the HUB 346 * 347 * @param integer $hubId The Hub Content ID 348 * @param integer $localContentId The Local Content ID 349 */ 350 private function getHubContent($hubId, $localContentId) { 351 // Download H5P file 352 if (!$this->callHubEndpoint(H5PHubEndpoints::CONTENT . '/' . $hubId . '/export')) { 353 return; // Download failed 354 } 355 356 $this->processContent($localContentId); 357 } 358 359 /** 360 * Validates the package. Sets error messages if validation fails. 361 * 362 * @param bool $skipContent Will not validate cotent if set to TRUE 363 * 364 * @return bool 365 */ 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()); 370 371 H5PCore::ajaxError( 372 $this->core->h5pF->t('Validating h5p package failed.'), 373 'VALIDATION_FAILED', 374 NULL, 375 $this->core->h5pF->getMessages('error') 376 ); 377 return FALSE; 378 } 379 380 return TRUE; 381 } 382 383 /** 384 * Saves a file or moves it temporarily. This is often necessary in order to 385 * validate and store uploaded or fetched H5Ps. 386 * 387 * Sets error messages if saving fails. 388 * 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 391 * 392 * @return bool|object Returns false if saving failed or the path to the file 393 * if saving succeeded 394 */ 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' 401 ); 402 return FALSE; 403 } 404 405 return $file; 406 } 407 408 /** 409 * Calls provided hub endpoint and downloads the response to a .h5p file. 410 * 411 * @param string $endpoint Endpoint without protocol 412 * 413 * @return bool 414 */ 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') 424 ); 425 return FALSE; 426 } 427 428 return TRUE; 429 } 430 431 /** 432 * Checks if request is a POST. Sets error message on fail. 433 * 434 * @return bool 435 */ 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', 441 405 442 ); 443 return FALSE; 444 } 445 return TRUE; 446 } 447 448 /** 449 * Checks if H5P Hub is enabled. Sets error message on fail. 450 * 451 * @return bool 452 */ 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', 458 403 459 ); 460 return false; 461 } 462 return true; 463 } 464 465 /** 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. 469 * 470 * @return bool 471 */ 472 private function isContentTypeCacheUpdated() { 473 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; 481 } 482 } 483 return true; 484 } 485 486 /** 487 * Gets content type cache for globally available libraries and the order 488 * in which they have been used by the author 489 * 490 * @param bool $cacheOutdated The cache is outdated and not able to update 491 */ 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'] 502 ), 503 'details' => $this->core->h5pF->getMessages('info') 504 ); 505 } 506 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body