1<?php
2
3namespace Moodle;
4
5use stdClass;
6
7class 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;
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 = $machineName;
387    $libraryData->version = (object) array('major' => $majorVersion, 'minor' => $minorVersion);
388    $libraryData->title = $library['title'];
389
390    $libraryData->upgradesScript = $this->h5p->fs->getUpgradeScript($machineName, $majorVersion, $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($machineName, $majorVersion, $minorVersion);
397    $libraryData->semantics = $this->h5p->loadLibrarySemantics($machineName, $majorVersion, $minorVersion);
398    $libraryData->language  = $this->getLibraryLanguage($machineName, $majorVersion, $minorVersion, $languageCode);
399    $libraryData->defaultLanguage = empty($defaultLanguage) ? NULL : $this->getLibraryLanguage($machineName, $majorVersion, $minorVersion, $defaultLanguage);
400    $libraryData->languages = $this->storage->getAvailableLanguages($machineName, $majorVersion, $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::libraryToString(compact('machineName', 'majorVersion', 'minorVersion'), true);
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
604    return $lib;
605  }
606
607
608  /**
609   * Merge local libraries into cached libraries so that local libraries will
610   * get supplemented with the additional info from externally cached libraries.
611   *
612   * Also sets whether a given cached library is installed and up to date with
613   * the locally installed libraries
614   *
615   * @param array $local_libraries Locally installed libraries
616   * @param array $cached_libraries Cached libraries from the H5P hub
617   */
618  public function mergeLocalLibsIntoCachedLibs($local_libraries, &$cached_libraries) {
619    $can_create_restricted = $this->h5p->h5pF->hasPermission(H5PPermission::CREATE_RESTRICTED);
620
621    // Add local libraries to supplement content type cache
622    foreach ($local_libraries as $local_lib) {
623      $is_local_only = TRUE;
624      $icon_path = NULL;
625
626      // Check if icon is available locally:
627      if ($local_lib->has_icon) {
628        // Create path to icon:
629        $library_folder = H5PCore::libraryToString(array(
630          'machineName' => $local_lib->machine_name,
631          'majorVersion' => $local_lib->major_version,
632          'minorVersion' => $local_lib->minor_version
633        ), TRUE);
634        $icon_path = $this->h5p->h5pF->getLibraryFileUrl($library_folder, 'icon.svg');
635      }
636
637      foreach ($cached_libraries as &$cached_lib) {
638        // Determine if library is local
639        $is_matching_library = $cached_lib['machineName'] === $local_lib->machine_name;
640        if ($is_matching_library) {
641          $is_local_only = FALSE;
642
643          // Set icon if it exists locally
644          if (isset($icon_path)) {
645            $cached_lib['icon'] = $icon_path;
646          }
647
648          // Set local properties
649          $cached_lib['installed']  = TRUE;
650          $cached_lib['restricted'] = $can_create_restricted ? FALSE
651            : ($local_lib->restricted ? TRUE : FALSE);
652
653          // Set local version
654          $cached_lib['localMajorVersion'] = (int) $local_lib->major_version;
655          $cached_lib['localMinorVersion'] = (int) $local_lib->minor_version;
656          $cached_lib['localPatchVersion'] = (int) $local_lib->patch_version;
657
658          // Determine if library is newer or same as cache
659          $major_is_updated =
660            $cached_lib['majorVersion'] < $cached_lib['localMajorVersion'];
661
662          $minor_is_updated =
663            $cached_lib['majorVersion'] === $cached_lib['localMajorVersion'] &&
664            $cached_lib['minorVersion'] < $cached_lib['localMinorVersion'];
665
666          $patch_is_updated =
667            $cached_lib['majorVersion'] === $cached_lib['localMajorVersion'] &&
668            $cached_lib['minorVersion'] === $cached_lib['localMinorVersion'] &&
669            $cached_lib['patchVersion'] <= $cached_lib['localPatchVersion'];
670
671          $is_updated_library =
672            $major_is_updated ||
673            $minor_is_updated ||
674            $patch_is_updated;
675
676          if ($is_updated_library) {
677            $cached_lib['isUpToDate'] = TRUE;
678          }
679        }
680      }
681
682      // Add minimal data to display local only libraries
683      if ($is_local_only) {
684        $local_only_lib = array(
685          'id'                => (int) $local_lib->id,
686          'machineName'       => $local_lib->machine_name,
687          'title'             => $local_lib->title,
688          'description'       => '',
689          'majorVersion'      => (int) $local_lib->major_version,
690          'minorVersion'      => (int) $local_lib->minor_version,
691          'patchVersion'      => (int) $local_lib->patch_version,
692          'localMajorVersion' => (int) $local_lib->major_version,
693          'localMinorVersion' => (int) $local_lib->minor_version,
694          'localPatchVersion' => (int) $local_lib->patch_version,
695          'canInstall'        => FALSE,
696          'installed'         => TRUE,
697          'isUpToDate'        => TRUE,
698          'owner'             => '',
699          'restricted'        => $can_create_restricted ? FALSE :
700            ($local_lib->restricted ? TRUE : FALSE)
701        );
702
703        if (isset($icon_path)) {
704          $local_only_lib['icon'] = $icon_path;
705        }
706
707        $cached_libraries[] = $local_only_lib;
708      }
709    }
710
711    // Restrict LRS dependent content
712    if (!$this->h5p->h5pF->getOption('enable_lrs_content_types')) {
713      foreach ($cached_libraries as &$lib) {
714        if (in_array($lib['machineName'], array('H5P.Questionnaire', 'H5P.FreeTextQuestion'))) {
715          $lib['restricted'] = TRUE;
716        }
717      }
718    }
719  }
720
721  /**
722   * Determine if a library has a presave.js file in the root folder
723   *
724   * @param string $libraryName
725   * @return bool
726   */
727  public function hasPresave($libraryName){
728    if( isset($this->h5p->h5pD) ){
729      $parsedLibrary = H5PCore::libraryFromString($libraryName);
730      if($parsedLibrary !== false){
731        $machineName = $parsedLibrary['machineName'];
732        $majorVersion = $parsedLibrary['majorVersion'];
733        $minorVersion = $parsedLibrary['minorVersion'];
734        $library = $this->h5p->h5pD->getLibrary($machineName, $majorVersion, $minorVersion);
735        if( !is_null($library)){
736          return $this->h5p->fs->hasPresave($libraryName, $library['path']);
737        }
738      }
739    }
740    return $this->h5p->fs->hasPresave($libraryName);
741  }
742
743  /**
744   * Adds the path to the presave.js file to the list of dependency assets for the library
745   *
746   * @param array $assets
747   * @param array $library
748   * @param string $prefix
749   */
750  public function addPresaveFile(&$assets, $library, $prefix = ''){
751    $path = 'libraries' . '/' . H5PCore::libraryToString($library, true);
752    if( array_key_exists('path', $library)){
753      $path = $library['path'];
754    }
755    $version = "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}";
756    if( array_key_exists('version', $library) ){
757      $version = $library['version'];
758    }
759
760    $assets['scripts'][] = (object) array(
761      'path' => $prefix . '/' . $path . '/' . 'presave.js',
762      'version' => $version,
763    );
764  }
765}
766