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