Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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