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 |
}
|