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
use stdClass;
6
 
7
class H5peditor {
8
 
9
  private static $hasWYSIWYGEditor = array(
10
    'H5P.CoursePresentation',
11
    'H5P.InteractiveVideo',
12
    'H5P.DragQuestion'
13
  );
14
 
15
  public static $styles = array(
16
    'libs/darkroom.css',
17
    'styles/css/h5p-hub-client.css',
18
    'styles/css/fonts.css',
19
    'styles/css/application.css',
20
    'styles/css/libs/zebra_datepicker.min.css'
21
  );
22
  public static $scripts = array(
23
    'scripts/h5p-hub-client.js',
24
    'scripts/h5peditor.js',
25
    'scripts/h5peditor-semantic-structure.js',
26
    'scripts/h5peditor-editor.js',
27
    'scripts/h5peditor-library-selector.js',
28
    'scripts/h5peditor-fullscreen-bar.js',
29
    'scripts/h5peditor-form.js',
30
    'scripts/h5peditor-text.js',
31
    'scripts/h5peditor-html.js',
32
    'scripts/h5peditor-number.js',
33
    'scripts/h5peditor-textarea.js',
34
    'scripts/h5peditor-file-uploader.js',
35
    'scripts/h5peditor-file.js',
36
    'scripts/h5peditor-image.js',
37
    'scripts/h5peditor-image-popup.js',
38
    'scripts/h5peditor-av.js',
39
    'scripts/h5peditor-group.js',
40
    'scripts/h5peditor-boolean.js',
41
    'scripts/h5peditor-list.js',
42
    'scripts/h5peditor-list-editor.js',
43
    'scripts/h5peditor-library.js',
44
    'scripts/h5peditor-library-list-cache.js',
45
    'scripts/h5peditor-select.js',
46
    'scripts/h5peditor-selector-hub.js',
47
    'scripts/h5peditor-selector-legacy.js',
48
    'scripts/h5peditor-dimensions.js',
49
    'scripts/h5peditor-coordinates.js',
50
    'scripts/h5peditor-none.js',
51
    'scripts/h5peditor-metadata.js',
52
    'scripts/h5peditor-metadata-author-widget.js',
53
    'scripts/h5peditor-metadata-changelog-widget.js',
54
    'scripts/h5peditor-pre-save.js',
55
    'ckeditor/ckeditor.js',
56
  );
57
  private $h5p, $storage;
58
  public $ajax, $ajaxInterface, $content;
59
 
60
  /**
61
   * Constructor for the core editor library.
62
   *
63
   * @param H5PCore $h5p Instance of core
64
   * @param H5peditorStorage $storage Instance of h5peditor storage interface
65
   * @param H5PEditorAjaxInterface $ajaxInterface Instance of h5peditor ajax
66
   * interface
67
   */
68
  function __construct($h5p, $storage, $ajaxInterface) {
69
    $this->h5p = $h5p;
70
    $this->storage = $storage;
71
    $this->ajaxInterface = $ajaxInterface;
72
    $this->ajax = new H5PEditorAjax($h5p, $this, $storage);
73
  }
74
 
75
  /**
76
   * Get list of libraries.
77
   *
78
   * @return array
79
   */
80
  public function getLibraries() {
81
    if (isset($_POST['libraries'])) {
82
      // Get details for the specified libraries.
83
      $libraries = array();
84
      foreach ($_POST['libraries'] as $libraryName) {
85
        $matches = array();
86
        preg_match_all('/(.+)\s(\d+)\.(\d+)$/', $libraryName, $matches);
87
        if ($matches && $matches[1] && $matches[2] && $matches[3]) {
88
          $libraries[] = (object) array(
89
            'uberName' => $libraryName,
90
            'name' => $matches[1][0],
91
            'majorVersion' => $matches[2][0],
92
            'minorVersion' => $matches[3][0]
93
          );
94
        }
95
      }
96
    }
97
 
98
    $libraries = $this->storage->getLibraries(!isset($libraries) ? NULL : $libraries);
99
 
100
    if ($this->h5p->development_mode & H5PDevelopment::MODE_LIBRARY) {
101
      $devLibs = $this->h5p->h5pD->getLibraries();
102
    }
103
 
104
    for ($i = 0, $s = count($libraries); $i < $s; $i++) {
105
      if (!empty($devLibs)) {
106
        $lid = $libraries[$i]->name . ' ' . $libraries[$i]->majorVersion . '.' . $libraries[$i]->minorVersion;
107
        if (isset($devLibs[$lid])) {
108
          // Replace library with devlib
109
          $isOld = !empty($libraries[$i]->isOld) && $libraries[$i]->isOld === TRUE;
110
          $libraries[$i] = (object) array(
111
            'uberName' => $lid,
112
            'name' => $devLibs[$lid]['machineName'],
113
            'title' => $devLibs[$lid]['title'],
114
            'majorVersion' => $devLibs[$lid]['majorVersion'],
115
            'minorVersion' => $devLibs[$lid]['minorVersion'],
116
            'runnable' => $devLibs[$lid]['runnable'],
117
            'restricted' => $libraries[$i]->restricted,
118
            'tutorialUrl' => $libraries[$i]->tutorialUrl,
119
            'metadataSettings' => $devLibs[$lid]['metadataSettings'],
120
          );
121
          if ($isOld) {
122
            $libraries[$i]->isOld = TRUE;
123
          }
124
        }
125
      }
126
 
127
      // Some libraries rely on an LRS to work and must be enabled manually
128
      if (in_array($libraries[$i]->name, array('H5P.Questionnaire', 'H5P.FreeTextQuestion')) &&
129
          !$this->h5p->h5pF->getOption('enable_lrs_content_types')) {
130
        $libraries[$i]->restricted = TRUE;
131
      }
132
    }
133
 
134
    return $libraries;
135
  }
136
 
137
  /**
138
   * Get translations for a language for a list of libraries
139
   *
140
   * @param array $libraries An array of libraries, in the form "<machineName> <majorVersion>.<minorVersion>
141
   * @param string $language_code
142
   * @return array
143
   */
144
  public function getTranslations($libraries, $language_code) {
145
    return $this->ajaxInterface->getTranslations($libraries, $language_code);
146
  }
147
 
148
  /**
149
   * Move uploaded files, remove old files and update library usage.
150
   *
151
   * @param stdClass $content
152
   * @param array $newLibrary
153
   * @param array $newParameters
154
   * @param array $oldLibrary
155
   * @param array $oldParameters
156
   */
157
  public function processParameters($content, $newLibrary, $newParameters, $oldLibrary = NULL, $oldParameters = NULL) {
158
    $newFiles = array();
159
    $oldFiles = array();
160
 
161
    // Keep track of current content ID (used when processing files)
162
    $this->content = $content;
163
 
164
    // Find new libraries/content dependencies and files.
165
    // Start by creating a fake library field to process. This way we get all the dependencies of the main library as well.
166
    $field = (object) array(
167
      'type' => 'library'
168
    );
169
    $libraryParams = (object) array(
170
      'library' => H5PCore::libraryToString($newLibrary),
171
      'params' => $newParameters
172
    );
173
    $this->processField($field, $libraryParams, $newFiles);
174
 
175
    if ($oldLibrary !== NULL) {
176
      // Find old files and libraries.
177
      $this->processSemantics($oldFiles, $this->h5p->loadLibrarySemantics($oldLibrary['name'], $oldLibrary['majorVersion'], $oldLibrary['minorVersion']), $oldParameters);
178
 
179
      // Remove old files.
180
      for ($i = 0, $s = count($oldFiles); $i < $s; $i++) {
181
        if (!in_array($oldFiles[$i], $newFiles) &&
182
            preg_match('/^(\w+:\/\/|\.\.\/)/i', $oldFiles[$i]) === 0) {
183
          $this->h5p->fs->removeContentFile($oldFiles[$i], $content);
184
          // (optionally we could just have marked them as tmp files)
185
        }
186
      }
187
    }
188
  }
189
 
190
  /**
191
   * Recursive function that moves the new files in to the h5p content folder and generates a list over the old files.
192
   * Also locates all the librares.
193
   *
194
   * @param array $files
195
   * @param array $libraries
196
   * @param array $semantics
197
   * @param array $params
198
   */
199
  private function processSemantics(&$files, $semantics, &$params) {
200
    for ($i = 0, $s = count($semantics); $i < $s; $i++) {
201
      $field = $semantics[$i];
202
      if (!isset($params->{$field->name})) {
203
        continue;
204
      }
205
      $this->processField($field, $params->{$field->name}, $files);
206
    }
207
  }
208
 
209
  /**
210
   * Process a single field.
211
   *
212
   * @staticvar string $h5peditor_path
213
   * @param object $field
214
   * @param mixed $params
215
   * @param array $files
216
   */
217
  private function processField(&$field, &$params, &$files) {
218
    switch ($field->type) {
219
      case 'file':
220
      case 'image':
221
        if (isset($params->path)) {
222
          $this->processFile($params, $files);
223
 
224
          // Process original image
225
          if (isset($params->originalImage) && isset($params->originalImage->path)) {
226
            $this->processFile($params->originalImage, $files);
227
          }
228
        }
229
        break;
230
 
231
      case 'video':
232
      case 'audio':
233
        if (is_array($params)) {
234
          for ($i = 0, $s = count($params); $i < $s; $i++) {
235
            $this->processFile($params[$i], $files);
236
          }
237
        }
238
        break;
239
 
240
      case 'library':
241
        if (isset($params->library) && isset($params->params)) {
242
          $library = H5PCore::libraryFromString($params->library);
243
          $semantics = $this->h5p->loadLibrarySemantics($library['machineName'], $library['majorVersion'], $library['minorVersion']);
244
 
245
          // Process parameters for the library.
246
          $this->processSemantics($files, $semantics, $params->params);
247
        }
248
        break;
249
 
250
      case 'group':
251
        if (isset($params)) {
252
          $isSubContent = isset($field->isSubContent) && $field->isSubContent == TRUE;
253
 
254
          if (count($field->fields) == 1 && !$isSubContent) {
255
            $params = (object) array($field->fields[0]->name => $params);
256
          }
257
          $this->processSemantics($files, $field->fields, $params);
258
        }
259
        break;
260
 
261
      case 'list':
262
        if (is_array($params)) {
263
          for ($j = 0, $t = count($params); $j < $t; $j++) {
264
            $this->processField($field->field, $params[$j], $files);
265
          }
266
        }
267
        break;
268
    }
269
  }
270
 
271
  /**
272
   * @param mixed $params
273
   * @param array $files
274
   */
275
  private function processFile(&$params, &$files) {
276
    if (preg_match('/^https?:\/\//', $params->path)) {
277
      return; // Skip external files
278
    }
279
 
280
    // Remove temporary files suffix
281
    if (substr($params->path, -4, 4) === '#tmp') {
282
      $params->path = substr($params->path, 0, strlen($params->path) - 4);
283
    }
284
 
285
    // File could be copied from another content folder.
286
    $matches = array();
287
    if (preg_match($this->h5p->relativePathRegExp, $params->path, $matches)) {
288
 
289
      // Create a copy of the file
290
      $this->h5p->fs->cloneContentFile($matches[5], $matches[4], $this->content);
291
 
292
      // Update Params with correct filename
293
      $params->path = $matches[5];
294
    }
295
    else {
296
      // Check if file exists in content folder
297
      $fileId = $this->h5p->fs->getContentFile($params->path, $this->content);
298
      if ($fileId) {
299
        // Mark the file as a keeper
300
        $this->storage->keepFile($fileId);
301
      }
302
      else {
303
        // File is not in content folder, try to copy it from the editor tmp dir
304
        // to content folder.
305
        $this->h5p->fs->cloneContentFile($params->path, 'editor', $this->content);
306
        // (not removed in case someone has copied it)
307
        // (will automatically be removed after 24 hours)
308
      }
309
    }
310
 
311
    $files[] = $params->path;
312
  }
313
 
314
  /**
315
   * TODO: Consider moving to core.
316
   */
317
  public function getLibraryLanguage($machineName, $majorVersion, $minorVersion, $languageCode) {
318
    if ($this->h5p->development_mode & H5PDevelopment::MODE_LIBRARY) {
319
      // Try to get language development library first.
320
      $language = $this->h5p->h5pD->getLanguage($machineName, $majorVersion, $minorVersion, $languageCode);
321
    }
322
 
323
    if (isset($language) === FALSE) {
324
      $language = $this->storage->getLanguage($machineName, $majorVersion, $minorVersion, $languageCode);
325
    }
326
 
327
    return ($language === FALSE ? NULL : $language);
328
  }
329
 
330
  /**
331
   * Return all libraries used by the given editor library.
332
   *
333
   * @param string $machineName Library identfier part 1
334
   * @param int $majorVersion Library identfier part 2
335
   * @param int $minorVersion Library identfier part 3
336
   */
337
  public function findEditorLibraries($machineName, $majorVersion, $minorVersion) {
338
    $library = $this->h5p->loadLibrary($machineName, $majorVersion, $minorVersion);
339
    $dependencies = array();
340
    $this->h5p->findLibraryDependencies($dependencies, $library);
341
 
342
    // Load addons for wysiwyg editors
343
    if (in_array($machineName, self::$hasWYSIWYGEditor)) {
344
      $addons = $this->h5p->h5pF->loadAddons();
345
      foreach ($addons as $addon) {
346
        $key = 'editor-' . $addon['machineName'];
347
        $dependencies[$key]['weight'] = sizeof($dependencies)+1;
348
        $dependencies[$key]['type'] = 'editor';
349
        $dependencies[$key]['library'] = $addon;
350
      }
351
    }
352
 
353
    // Order dependencies by weight
354
    $orderedDependencies = array();
355
    for ($i = 1, $s = count($dependencies); $i <= $s; $i++) {
356
      foreach ($dependencies as $dependency) {
357
        if ($dependency['weight'] === $i && $dependency['type'] === 'editor') {
358
          // Only load editor libraries.
359
          $dependency['library']['id'] = $dependency['library']['libraryId'];
360
          $orderedDependencies[$dependency['library']['libraryId']] = $dependency['library'];
361
          break;
362
        }
363
      }
364
    }
365
 
366
    return $orderedDependencies;
367
  }
368
 
369
  /**
370
   * Get all scripts, css and semantics data for a library
371
   *
372
   * @param string $machineName Library name
373
   * @param int $majorVersion
374
   * @param int $minorVersion
375
   * @param string $prefix Optional part to add between URL and asset path
376
   * @param string $fileDir Optional file dir to read files from
377
   *
378
   * @return array Libraries that was requested
379
   */
380
  public function getLibraryData($machineName, $majorVersion, $minorVersion, $languageCode, $prefix = '', $fileDir = '', $defaultLanguage = '') {
381
    $libraryData = new stdClass();
382
 
383
    $library = $this->h5p->loadLibrary($machineName, $majorVersion, $minorVersion);
384
 
385
    // Include name and version in data object for convenience
386
    $libraryData->name = $library['machineName'];
387
    $libraryData->version = (object) array('major' => $library['majorVersion'], 'minor' => $library['minorVersion']);
388
    $libraryData->title = $library['title'];
389
 
390
    $libraryData->upgradesScript = $this->h5p->fs->getUpgradeScript($library['machineName'], $library['majorVersion'], $library['minorVersion']);
391
    if ($libraryData->upgradesScript !== NULL) {
392
      // If valid add URL prefix
393
      $libraryData->upgradesScript = $this->h5p->url . $prefix . $libraryData->upgradesScript;
394
    }
395
 
396
    $libraries              = $this->findEditorLibraries($library['machineName'], $library['majorVersion'], $library['minorVersion']);
397
    $libraryData->semantics = $this->h5p->loadLibrarySemantics($library['machineName'], $library['majorVersion'], $library['minorVersion']);
398
    $libraryData->language  = $this->getLibraryLanguage($library['machineName'], $library['majorVersion'], $library['minorVersion'], $languageCode);
399
    $libraryData->defaultLanguage = empty($defaultLanguage) ? NULL : $this->getLibraryLanguage($library['machineName'], $library['majorVersion'], $library['minorVersion'], $defaultLanguage);
400
    $libraryData->languages = $this->storage->getAvailableLanguages($library['machineName'], $library['majorVersion'], $library['minorVersion']);
401
 
402
    // Temporarily disable asset aggregation
403
    $aggregateAssets            = $this->h5p->aggregateAssets;
404
    $this->h5p->aggregateAssets = FALSE;
405
    // This is done to prevent files being loaded multiple times due to how
406
    // the editor works.
407
 
408
    // Get list of JS and CSS files that belongs to the dependencies
409
    $files = $this->h5p->getDependenciesFiles($libraries, $prefix);
410
    $libraryName = H5PCore::libraryToFolderName($library);
411
    if ($this->hasPresave($libraryName) === true) {
412
      $this->addPresaveFile($files, $library, $prefix);
413
    }
414
    $this->storage->alterLibraryFiles($files, $libraries);
415
 
416
    // Restore asset aggregation setting
417
    $this->h5p->aggregateAssets = $aggregateAssets;
418
 
419
    // Create base URL
420
    $url = $this->h5p->url;
421
 
422
    // Javascripts
423
    if (!empty($files['scripts'])) {
424
      foreach ($files['scripts'] as $script) {
425
        if (preg_match('/:\/\//', $script->path) === 1) {
426
          // External file
427
          $libraryData->javascript[] = $script->path . $script->version;
428
        }
429
        else {
430
          // Local file
431
          $path = $url . $script->path;
432
          if (!isset($this->h5p->h5pD)) {
433
            $path .= $script->version;
434
          }
435
          $libraryData->javascript[] = $path;
436
        }
437
      }
438
    }
439
 
440
    // Stylesheets
441
    if (!empty($files['styles'])) {
442
      foreach ($files['styles'] as $css) {
443
        if (preg_match('/:\/\//', $css->path) === 1) {
444
          // External file
445
          $libraryData->css[] = $css->path . $css->version;
446
        }
447
        else {
448
          // Local file
449
          $path = $url . $css->path;
450
          if (!isset($this->h5p->h5pD)) {
451
            $path .= $css->version;
452
          }
453
          $libraryData->css[] = $path;
454
        }
455
      }
456
    }
457
 
458
    $translations = array();
459
    // Add translations for libraries.
460
    foreach ($libraries as $library) {
461
      if (empty($library['semantics'])) {
462
        $translation = $this->getLibraryLanguage($library['machineName'], $library['majorVersion'], $library['minorVersion'], $languageCode);
463
 
464
        // If translation was not found, and this is not the English one, try to load
465
        // the English translation
466
        if ($translation === NULL && $languageCode !== 'en') {
467
          $translation = $this->getLibraryLanguage($library['machineName'], $library['majorVersion'], $library['minorVersion'], 'en');
468
        }
469
 
470
        if ($translation !== NULL) {
471
          $translations[$library['machineName']] = json_decode($translation);
472
        }
473
      }
474
    }
475
 
476
    $libraryData->translations = $translations;
477
 
478
    return $libraryData;
479
  }
480
 
481
  /**
482
   * This function will prefix all paths within a CSS file.
483
   * Copied from Drupal 6.
484
   *
485
   * @staticvar type $_base
486
   * @param type $matches
487
   * @param type $base
488
   * @return type
489
   */
490
  public static function buildCssPath($matches, $base = NULL) {
491
    static $_base;
492
    // Store base path for preg_replace_callback.
493
    if (isset($base)) {
494
      $_base = $base;
495
    }
496
 
497
    // Prefix with base and remove '../' segments where possible.
498
    $path = $_base . $matches[1];
499
    $last = '';
500
    while ($path != $last) {
501
      $last = $path;
502
      $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
503
    }
504
    return 'url('. $path .')';
505
  }
506
 
507
  /**
508
   * Gets content type cache, applies user specific properties and formats
509
   * as camelCase.
510
   *
511
   * @return array $libraries Cached libraries from the H5P Hub with user specific
512
   * permission properties
513
   */
514
  public function getUserSpecificContentTypeCache() {
515
    $cached_libraries = $this->ajaxInterface->getContentTypeCache();
516
 
517
    // Check if user has access to install libraries
518
    $libraries = array();
519
    foreach ($cached_libraries as &$result) {
520
      // Check if user can install content type
521
      $result->restricted = !$this->canInstallContentType($result);
522
 
523
      // Formats json
524
      $libraries[] = $this->getCachedLibsMap($result);
525
    }
526
 
527
    return $libraries;
528
  }
529
 
530
  public function canInstallContentType($contentType) {
531
    $canInstallAll         = $this->h5p->h5pF->hasPermission(H5PPermission::UPDATE_LIBRARIES);
532
    $canInstallRecommended = $this->h5p->h5pF->hasPermission(H5PPermission::INSTALL_RECOMMENDED);
533
 
534
    return $canInstallAll || $contentType->is_recommended && $canInstallRecommended;
535
  }
536
 
537
  /**
538
   * Gets local and external libraries data with metadata to display
539
   * all libraries that are currently available for the user.
540
   *
541
   * @return array $libraries Latest local and external libraries data with
542
   * user specific permissions
543
   */
544
  public function getLatestGlobalLibrariesData() {
545
    $latest_local_libraries = $this->ajaxInterface->getLatestLibraryVersions();
546
    $cached_libraries       = $this->getUserSpecificContentTypeCache();
547
    $this->mergeLocalLibsIntoCachedLibs($latest_local_libraries, $cached_libraries);
548
    return $cached_libraries;
549
  }
550
 
551
 
552
  /**
553
   * Extract library properties from cached library so they are ready to be
554
   * returned as JSON
555
   *
556
   * @param object $cached_library A single library from the content type cache
557
   *
558
   * @return array A map containing the necessary properties for a cached
559
   * library to send to the front-end
560
   */
561
  public function getCachedLibsMap($cached_library) {
562
    $restricted = isset($cached_library->restricted) ? $cached_library->restricted : FALSE;
563
 
564
    // Add mandatory fields
565
    $lib = array(
566
      'id'              => intval($cached_library->id),
567
      'machineName'     => $cached_library->machine_name,
568
      'majorVersion'    => intval( $cached_library->major_version),
569
      'minorVersion'    => intval($cached_library->minor_version),
570
      'patchVersion'    => intval($cached_library->patch_version),
571
      'h5pMajorVersion' => intval($cached_library->h5p_major_version),
572
      'h5pMinorVersion' => intval($cached_library->h5p_minor_version),
573
      'title'           => $cached_library->title,
574
      'summary'         => $cached_library->summary,
575
      'description'     => $cached_library->description,
576
      'icon'            => $cached_library->icon,
577
      'createdAt'       => intval($cached_library->created_at),
578
      'updatedAt'       => intval($cached_library->updated_at),
579
      'isRecommended'   => $cached_library->is_recommended != 0,
580
      'popularity'      => intval($cached_library->popularity),
581
      'screenshots'     => json_decode($cached_library->screenshots),
582
      'license'         => json_decode($cached_library->license),
583
      'owner'           => $cached_library->owner,
584
      'installed'       => FALSE,
585
      'isUpToDate'      => FALSE,
586
      'restricted'      => $restricted,
587
      'canInstall'      => !$restricted
588
    );
589
 
590
    // Add optional fields
591
    if (!empty($cached_library->categories)) {
592
      $lib['categories'] = json_decode($cached_library->categories);
593
    }
594
    if (!empty($cached_library->keywords)) {
595
      $lib['keywords'] = json_decode($cached_library->keywords);
596
    }
597
    if (!empty($cached_library->tutorial)) {
598
      $lib['tutorial'] = $cached_library->tutorial;
599
    }
600
    if (!empty($cached_library->example)) {
601
      $lib['example'] = $cached_library->example;
602
    }
603
    if (!empty($cached_library->icons)) {
604
      $lib['icons'] = json_decode($cached_library->icons);
605
    }
606
 
607
    return $lib;
608
  }
609
 
610
 
611
  /**
612
   * Merge local libraries into cached libraries so that local libraries will
613
   * get supplemented with the additional info from externally cached libraries.
614
   *
615
   * Also sets whether a given cached library is installed and up to date with
616
   * the locally installed libraries
617
   *
618
   * @param array $local_libraries Locally installed libraries
619
   * @param array $cached_libraries Cached libraries from the H5P hub
620
   */
621
  public function mergeLocalLibsIntoCachedLibs($local_libraries, &$cached_libraries) {
622
    $can_create_restricted = $this->h5p->h5pF->hasPermission(H5PPermission::CREATE_RESTRICTED);
623
 
624
    // Add local libraries to supplement content type cache
625
    foreach ($local_libraries as $local_lib) {
626
      $is_local_only = TRUE;
627
      $icon_path = NULL;
628
 
629
      // Check if icon is available locally:
630
      if ($local_lib->has_icon) {
631
        // Create path to icon:
632
        $library_folder = H5PCore::libraryToFolderName([
633
          'machineName' => $local_lib->machine_name,
634
          'majorVersion' => $local_lib->major_version,
635
          'minorVersion' => $local_lib->minor_version,
636
          'patchVersion' => $local_lib->patch_version,
637
          'patchVersionInFolderName' => $local_lib->patch_version_in_folder_name
638
        ]);
639
        $icon_path = $this->h5p->h5pF->getLibraryFileUrl($library_folder, 'icon.svg');
640
      }
641
 
642
      foreach ($cached_libraries as &$cached_lib) {
643
        // Determine if library is local
644
        $is_matching_library = $cached_lib['machineName'] === $local_lib->machine_name;
645
        if ($is_matching_library) {
646
          $is_local_only = FALSE;
647
 
648
          // Set icon if it exists locally
649
          if (isset($icon_path)) {
650
            $cached_lib['icon'] = $icon_path;
651
          }
652
 
653
          // Set local properties
654
          $cached_lib['installed']  = TRUE;
655
          $cached_lib['restricted'] = $can_create_restricted ? FALSE
656
            : ($local_lib->restricted ? TRUE : FALSE);
657
 
658
          // Set local version
659
          $cached_lib['localMajorVersion'] = (int) $local_lib->major_version;
660
          $cached_lib['localMinorVersion'] = (int) $local_lib->minor_version;
661
          $cached_lib['localPatchVersion'] = (int) $local_lib->patch_version;
662
 
663
          // Determine if library is newer or same as cache
664
          $major_is_updated =
665
            $cached_lib['majorVersion'] < $cached_lib['localMajorVersion'];
666
 
667
          $minor_is_updated =
668
            $cached_lib['majorVersion'] === $cached_lib['localMajorVersion'] &&
669
            $cached_lib['minorVersion'] < $cached_lib['localMinorVersion'];
670
 
671
          $patch_is_updated =
672
            $cached_lib['majorVersion'] === $cached_lib['localMajorVersion'] &&
673
            $cached_lib['minorVersion'] === $cached_lib['localMinorVersion'] &&
674
            $cached_lib['patchVersion'] <= $cached_lib['localPatchVersion'];
675
 
676
          $is_updated_library =
677
            $major_is_updated ||
678
            $minor_is_updated ||
679
            $patch_is_updated;
680
 
681
          if ($is_updated_library) {
682
            $cached_lib['isUpToDate'] = TRUE;
683
          }
684
        }
685
      }
686
 
687
      // Add minimal data to display local only libraries
688
      if ($is_local_only) {
689
        $local_only_lib = array(
690
          'id'                => (int) $local_lib->id,
691
          'machineName'       => $local_lib->machine_name,
692
          'title'             => $local_lib->title,
693
          'description'       => '',
694
          'majorVersion'      => (int) $local_lib->major_version,
695
          'minorVersion'      => (int) $local_lib->minor_version,
696
          'patchVersion'      => (int) $local_lib->patch_version,
697
          'localMajorVersion' => (int) $local_lib->major_version,
698
          'localMinorVersion' => (int) $local_lib->minor_version,
699
          'localPatchVersion' => (int) $local_lib->patch_version,
700
          'canInstall'        => FALSE,
701
          'installed'         => TRUE,
702
          'isUpToDate'        => TRUE,
703
          'owner'             => '',
704
          'restricted'        => $can_create_restricted ? FALSE :
705
            ($local_lib->restricted ? TRUE : FALSE)
706
        );
707
 
708
        if (isset($icon_path)) {
709
          $local_only_lib['icon'] = $icon_path;
710
        }
711
 
712
        $cached_libraries[] = $local_only_lib;
713
      }
714
    }
715
 
716
    // Restrict LRS dependent content
717
    if (!$this->h5p->h5pF->getOption('enable_lrs_content_types')) {
718
      foreach ($cached_libraries as &$lib) {
719
        if (in_array($lib['machineName'], array('H5P.Questionnaire', 'H5P.FreeTextQuestion'))) {
720
          $lib['restricted'] = TRUE;
721
        }
722
      }
723
    }
724
  }
725
 
726
  /**
727
   * Determine if a library has a presave.js file in the root folder
728
   *
729
   * @param string $libraryName
730
   * @return bool
731
   */
732
  public function hasPresave($libraryName){
733
    if( isset($this->h5p->h5pD) ){
734
      $parsedLibrary = H5PCore::libraryFromString($libraryName);
735
      if($parsedLibrary !== false){
736
        $machineName = $parsedLibrary['machineName'];
737
        $majorVersion = $parsedLibrary['majorVersion'];
738
        $minorVersion = $parsedLibrary['minorVersion'];
739
        $library = $this->h5p->h5pD->getLibrary($machineName, $majorVersion, $minorVersion);
740
        if( !is_null($library)){
741
          return $this->h5p->fs->hasPresave($libraryName, $library['path']);
742
        }
743
      }
744
    }
745
    return $this->h5p->fs->hasPresave($libraryName);
746
  }
747
 
748
  /**
749
   * Adds the path to the presave.js file to the list of dependency assets for the library
750
   *
751
   * @param array $assets
752
   * @param array $library
753
   * @param string $prefix
754
   */
755
  public function addPresaveFile(&$assets, $library, $prefix = ''){
756
    $path = 'libraries' . '/' . H5PCore::libraryToFolderName($library);
757
    if( array_key_exists('path', $library)){
758
      $path = $library['path'];
759
    }
760
    $version = "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}";
761
    if( array_key_exists('version', $library) ){
762
      $version = $library['version'];
763
    }
764
 
765
    $assets['scripts'][] = (object) array(
766
      'path' => $prefix . '/' . $path . '/' . 'presave.js',
767
      'version' => $version,
768
    );
769
  }
770
}