Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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