Differences Between: [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 installing libraries from the Content Type Hub 26 */ 27 const LIBRARY_INSTALL = 'library-install'; 28 29 /** 30 * Endpoint for uploading libraries used by the editor through the Content 31 * Type Hub. 32 */ 33 const LIBRARY_UPLOAD = 'library-upload'; 34 35 /** 36 * Endpoint for uploading files used by the editor. 37 */ 38 const FILES = 'files'; 39 40 /** 41 * Endpoint for retrieveing translation files 42 */ 43 const TRANSLATIONS = 'translations'; 44 45 /** 46 * Endpoint for filtering parameters. 47 */ 48 const FILTER = 'filter'; 49 } 50 51 52 /** 53 * Class H5PEditorAjax 54 * @package modules\h5peditor\h5peditor 55 */ 56 class H5PEditorAjax { 57 58 /** 59 * @var H5PCore 60 */ 61 public $core; 62 63 /** 64 * @var \H5peditor 65 */ 66 public $editor; 67 68 /** 69 * @var \H5peditorStorage 70 */ 71 public $storage; 72 73 /** 74 * H5PEditorAjax constructor requires core, editor and storage as building 75 * blocks. 76 * 77 * @param H5PCore $H5PCore 78 * @param H5peditor $H5PEditor 79 * @param H5peditorStorage $H5PEditorStorage 80 */ 81 public function __construct(H5PCore $H5PCore, H5peditor $H5PEditor, H5peditorStorage $H5PEditorStorage) { 82 $this->core = $H5PCore; 83 $this->editor = $H5PEditor; 84 $this->storage = $H5PEditorStorage; 85 } 86 87 /** 88 * @param $endpoint 89 */ 90 public function action($endpoint) { 91 switch ($endpoint) { 92 case H5PEditorEndpoints::LIBRARIES: 93 H5PCore::ajaxSuccess($this->editor->getLibraries(), TRUE); 94 break; 95 96 case H5PEditorEndpoints::SINGLE_LIBRARY: 97 // pass on arguments 98 $args = func_get_args(); 99 array_shift($args); 100 $library = call_user_func_array( 101 array($this->editor, 'getLibraryData'), $args 102 ); 103 H5PCore::ajaxSuccess($library, TRUE); 104 break; 105 106 case H5PEditorEndpoints::CONTENT_TYPE_CACHE: 107 if (!$this->isHubOn()) return; 108 H5PCore::ajaxSuccess($this->getContentTypeCache(!$this->isContentTypeCacheUpdated()), TRUE); 109 break; 110 111 case H5PEditorEndpoints::LIBRARY_INSTALL: 112 if (!$this->isPostRequest()) return; 113 114 $token = func_get_arg(1); 115 if (!$this->isValidEditorToken($token)) return; 116 117 $machineName = func_get_arg(2); 118 $this->libraryInstall($machineName); 119 break; 120 121 case H5PEditorEndpoints::LIBRARY_UPLOAD: 122 if (!$this->isPostRequest()) return; 123 124 $token = func_get_arg(1); 125 if (!$this->isValidEditorToken($token)) return; 126 127 $uploadPath = func_get_arg(2); 128 $contentId = func_get_arg(3); 129 $this->libraryUpload($uploadPath, $contentId); 130 break; 131 132 case H5PEditorEndpoints::FILES: 133 $token = func_get_arg(1); 134 $contentId = func_get_arg(2); 135 if (!$this->isValidEditorToken($token)) return; 136 $this->fileUpload($contentId); 137 break; 138 139 case H5PEditorEndpoints::TRANSLATIONS: 140 $language = func_get_arg(1); 141 H5PCore::ajaxSuccess($this->editor->getTranslations($_POST['libraries'], $language)); 142 break; 143 144 case H5PEditorEndpoints::FILTER: 145 $token = func_get_arg(1); 146 if (!$this->isValidEditorToken($token)) return; 147 $this->filter(func_get_arg(2)); 148 break; 149 } 150 } 151 152 /** 153 * Handles uploaded files from the editor, making sure they are validated 154 * and ready to be permanently stored if saved. 155 * 156 * Marks all uploaded files as 157 * temporary so they can be cleaned up when we have finished using them. 158 * 159 * @param int $contentId Id of content if already existing content 160 */ 161 private function fileUpload($contentId = NULL) { 162 $file = new H5peditorFile($this->core->h5pF); 163 if (!$file->isLoaded()) { 164 H5PCore::ajaxError($this->core->h5pF->t('File not found on server. Check file upload settings.')); 165 return; 166 } 167 168 // Make sure file is valid and mark it for cleanup at a later time 169 if ($file->validate()) { 170 $file_id = $this->core->fs->saveFile($file, 0); 171 $this->storage->markFileForCleanup($file_id, 0); 172 } 173 $file->printResult(); 174 } 175 176 /** 177 * Handles uploading libraries so they are ready to be modified or directly saved. 178 * 179 * Validates and saves any dependencies, then exposes content to the editor. 180 * 181 * @param {string} $uploadFilePath Path to the file that should be uploaded 182 * @param {int} $contentId Content id of library 183 */ 184 private function libraryUpload($uploadFilePath, $contentId) { 185 // Verify h5p upload 186 if (!$uploadFilePath) { 187 H5PCore::ajaxError($this->core->h5pF->t('Could not get posted H5P.'), 'NO_CONTENT_TYPE'); 188 exit; 189 } 190 191 $file = $this->saveFileTemporarily($uploadFilePath, TRUE); 192 if (!$file) return; 193 194 // These has to be set instead of sending parameteres to the validation function. 195 if (!$this->isValidPackage()) return; 196 197 // Install any required dependencies 198 $storage = new H5PStorage($this->core->h5pF, $this->core); 199 $storage->savePackage(NULL, NULL, TRUE); 200 201 // Make content available to editor 202 $files = $this->core->fs->moveContentDirectory($this->core->h5pF->getUploadedH5pFolderPath(), $contentId); 203 204 // Clean up 205 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath()); 206 207 // Mark all files as temporary 208 // TODO: Uncomment once moveContentDirectory() is fixed. JI-366 209 /*foreach ($files as $file) { 210 $this->storage->markFileForCleanup($file, 0); 211 }*/ 212 213 H5PCore::ajaxSuccess(array( 214 'h5p' => $this->core->mainJsonData, 215 'content' => $this->core->contentJsonData, 216 'contentTypes' => $this->getContentTypeCache() 217 )); 218 } 219 220 /** 221 * Validates security tokens used for the editor 222 * 223 * @param string $token 224 * 225 * @return bool 226 */ 227 private function isValidEditorToken($token) { 228 $isValidToken = $this->editor->ajaxInterface->validateEditorToken($token); 229 if (!$isValidToken) { 230 H5PCore::ajaxError( 231 $this->core->h5pF->t('Invalid security token.'), 232 'INVALID_TOKEN' 233 ); 234 return FALSE; 235 } 236 return TRUE; 237 } 238 239 /** 240 * Handles installation of libraries from the Content Type Hub. 241 * 242 * Accepts a machine name and attempts to fetch and install it from the Hub if 243 * it is valid. Will also install any dependencies to the requested library. 244 * 245 * @param string $machineName Name of library that should be installed 246 */ 247 private function libraryInstall($machineName) { 248 249 // Determine which content type to install from post data 250 if (!$machineName) { 251 H5PCore::ajaxError($this->core->h5pF->t('No content type was specified.'), 'NO_CONTENT_TYPE'); 252 return; 253 } 254 255 // Look up content type to ensure it's valid(and to check permissions) 256 $contentType = $this->editor->ajaxInterface->getContentTypeCache($machineName); 257 if (!$contentType) { 258 H5PCore::ajaxError($this->core->h5pF->t('The chosen content type is invalid.'), 'INVALID_CONTENT_TYPE'); 259 return; 260 } 261 262 // Check install permissions 263 if (!$this->editor->canInstallContentType($contentType)) { 264 H5PCore::ajaxError($this->core->h5pF->t('You do not have permission to install content types. Contact the administrator of your site.'), 'INSTALL_DENIED'); 265 return; 266 } 267 else { 268 // Override core permission check 269 $this->core->mayUpdateLibraries(TRUE); 270 } 271 272 // Retrieve content type from hub endpoint 273 $response = $this->callHubEndpoint(H5PHubEndpoints::CONTENT_TYPES . $machineName); 274 if (!$response) return; 275 276 // Session parameters has to be set for validation and saving of packages 277 if (!$this->isValidPackage(TRUE)) return; 278 279 // Save H5P 280 $storage = new H5PStorage($this->core->h5pF, $this->core); 281 $storage->savePackage(NULL, NULL, TRUE); 282 283 // Clean up 284 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pFolderPath()); 285 286 // Successfully installed. Refresh content types 287 H5PCore::ajaxSuccess($this->getContentTypeCache()); 288 } 289 290 /** 291 * End-point for filter parameter values according to semantics. 292 * 293 * @param {string} $libraryParameters 294 */ 295 private function filter($libraryParameters) { 296 $libraryParameters = json_decode($libraryParameters); 297 if (!$libraryParameters) { 298 H5PCore::ajaxError($this->core->h5pF->t('Could not parse post data.'), 'NO_LIBRARY_PARAMETERS'); 299 exit; 300 } 301 302 // Filter parameters and send back to client 303 $validator = new H5PContentValidator($this->core->h5pF, $this->core); 304 $validator->validateLibrary($libraryParameters, (object) array('options' => array($libraryParameters->library))); 305 H5PCore::ajaxSuccess($libraryParameters); 306 } 307 308 /** 309 * Validates the package. Sets error messages if validation fails. 310 * 311 * @param bool $skipContent Will not validate cotent if set to TRUE 312 * 313 * @return bool 314 */ 315 private function isValidPackage($skipContent = FALSE) { 316 $validator = new H5PValidator($this->core->h5pF, $this->core); 317 if (!$validator->isValidPackage($skipContent, FALSE)) { 318 $this->storage->removeTemporarilySavedFiles($this->core->h5pF->getUploadedH5pPath()); 319 320 H5PCore::ajaxError( 321 $this->core->h5pF->t('Validating h5p package failed.'), 322 'VALIDATION_FAILED', 323 NULL, 324 $this->core->h5pF->getMessages('error') 325 ); 326 return FALSE; 327 } 328 329 return TRUE; 330 } 331 332 /** 333 * Saves a file or moves it temporarily. This is often necessary in order to 334 * validate and store uploaded or fetched H5Ps. 335 * 336 * Sets error messages if saving fails. 337 * 338 * @param string $data Uri of data that should be saved as a temporary file 339 * @param boolean $move_file Can be set to TRUE to move the data instead of saving it 340 * 341 * @return bool|object Returns false if saving failed or the path to the file 342 * if saving succeeded 343 */ 344 private function saveFileTemporarily($data, $move_file = FALSE) { 345 $file = $this->storage->saveFileTemporarily($data, $move_file); 346 if (!$file) { 347 H5PCore::ajaxError( 348 $this->core->h5pF->t('Failed to download the requested H5P.'), 349 'DOWNLOAD_FAILED' 350 ); 351 return FALSE; 352 } 353 354 return $file; 355 } 356 357 /** 358 * Calls provided hub endpoint and downloads the response to a .h5p file. 359 * 360 * @param string $endpoint Endpoint without protocol 361 * 362 * @return bool 363 */ 364 private function callHubEndpoint($endpoint) { 365 $path = $this->core->h5pF->getUploadedH5pPath(); 366 $response = $this->core->h5pF->fetchExternalData(H5PHubEndpoints::createURL($endpoint), NULL, TRUE, empty($path) ? TRUE : $path); 367 if (!$response) { 368 H5PCore::ajaxError( 369 $this->core->h5pF->t('Failed to download the requested H5P.'), 370 'DOWNLOAD_FAILED', 371 NULL, 372 $this->core->h5pF->getMessages('error') 373 ); 374 return FALSE; 375 } 376 377 return TRUE; 378 } 379 380 /** 381 * Checks if request is a POST. Sets error message on fail. 382 * 383 * @return bool 384 */ 385 private function isPostRequest() { 386 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 387 H5PCore::ajaxError( 388 $this->core->h5pF->t('A post message is required to access the given endpoint'), 389 'REQUIRES_POST', 390 405 391 ); 392 return FALSE; 393 } 394 return TRUE; 395 } 396 397 /** 398 * Checks if H5P Hub is enabled. Sets error message on fail. 399 * 400 * @return bool 401 */ 402 private function isHubOn() { 403 if (!$this->core->h5pF->getOption('hub_is_enabled', TRUE)) { 404 H5PCore::ajaxError( 405 $this->core->h5pF->t('The hub is disabled. You can enable it in the H5P settings.'), 406 'HUB_DISABLED', 407 403 408 ); 409 return false; 410 } 411 return true; 412 } 413 414 /** 415 * Checks if Content Type Cache is up to date. Immediately tries to fetch 416 * a new Content Type Cache if it is outdated. 417 * Sets error message if fetching new Content Type Cache fails. 418 * 419 * @return bool 420 */ 421 private function isContentTypeCacheUpdated() { 422 423 // Update content type cache if enabled and too old 424 $ct_cache_last_update = $this->core->h5pF->getOption('content_type_cache_updated_at', 0); 425 $outdated_cache = $ct_cache_last_update + (60 * 60 * 24 * 7); // 1 week 426 if (time() > $outdated_cache) { 427 $success = $this->core->updateContentTypeCache(); 428 if (!$success) { 429 return false; 430 } 431 } 432 return true; 433 } 434 435 /** 436 * Gets content type cache for globally available libraries and the order 437 * in which they have been used by the author 438 * 439 * @param bool $cacheOutdated The cache is outdated and not able to update 440 */ 441 private function getContentTypeCache($cacheOutdated = FALSE) { 442 $canUpdateOrInstall = ($this->core->h5pF->hasPermission(H5PPermission::INSTALL_RECOMMENDED) || 443 $this->core->h5pF->hasPermission(H5PPermission::UPDATE_LIBRARIES)); 444 return array( 445 'outdated' => $cacheOutdated && $canUpdateOrInstall, 446 'libraries' => $this->editor->getLatestGlobalLibrariesData(), 447 'recentlyUsed' => $this->editor->ajaxInterface->getAuthorsRecentlyUsedLibraries(), 448 'apiVersion' => array( 449 'major' => H5PCore::$coreApi['majorVersion'], 450 'minor' => H5PCore::$coreApi['minorVersion'] 451 ), 452 'details' => $this->core->h5pF->getMessages('info') 453 ); 454 } 455 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body