Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}