Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 179 |
|
0.00% |
0 / 31 |
CRAP | |
0.00% |
0 / 1 |
PortableElementRegistry | |
0.00% |
0 / 179 |
|
0.00% |
0 / 31 |
4556 | |
0.00% |
0 / 1 |
getRegistry | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
fetch | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
getAllVersions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
get | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getAll | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
set | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getConfigFileSystem | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
remove | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
has | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
update | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
delete | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
removeAllVersions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
removeAll | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
unregister | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getLatestVersion | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getLatestCompatibleVersion | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
register | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
getFilesFromPortableElement | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRuntime | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getAliasVersion | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getLatest | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
getLatestRuntimes | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getLatestCreators | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
removeAssets | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
42 | |||
getZipLocation | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getManifest | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
export | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
getFileSystem | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getBaseUrl | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFileStream | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
krsortByVersion | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2016-2022 (original work) Open Assessment Technologies SA; |
19 | * |
20 | */ |
21 | |
22 | namespace oat\taoQtiItem\model\portableElement\storage; |
23 | |
24 | use oat\oatbox\filesystem\FileSystemService; |
25 | use oat\oatbox\log\LoggerAwareTrait; |
26 | use oat\taoQtiItem\model\portableElement\exception\PortableElementFileStorageException; |
27 | use oat\taoQtiItem\model\portableElement\exception\PortableElementInconsistencyModelException; |
28 | use oat\taoQtiItem\model\portableElement\exception\PortableElementNotFoundException; |
29 | use oat\taoQtiItem\model\portableElement\exception\PortableElementVersionIncompatibilityException; |
30 | use oat\taoQtiItem\model\portableElement\model\PortableElementModelTrait; |
31 | use oat\taoQtiItem\model\portableElement\element\PortableElementObject; |
32 | use Zend\ServiceManager\ServiceLocatorAwareInterface; |
33 | use Zend\ServiceManager\ServiceLocatorAwareTrait; |
34 | use Naneau\SemVer\Parser as SemVerParser; |
35 | |
36 | /** |
37 | * CreatorRegistry stores reference to |
38 | * |
39 | * @package taoQtiItem |
40 | */ |
41 | abstract class PortableElementRegistry implements ServiceLocatorAwareInterface |
42 | { |
43 | use ServiceLocatorAwareTrait; |
44 | use PortableElementModelTrait; |
45 | use LoggerAwareTrait; |
46 | |
47 | /** @var PortableElementFileStorage */ |
48 | protected $storage; |
49 | |
50 | protected $fileSystemId = 'taoQtiItem'; |
51 | |
52 | /** |
53 | * |
54 | * @var array |
55 | */ |
56 | private static $registries = []; |
57 | |
58 | /** |
59 | * |
60 | * @return PortableElementRegistry |
61 | * @author Lionel Lecaque, lionel@taotesting.com |
62 | */ |
63 | public static function getRegistry() |
64 | { |
65 | $class = get_called_class(); |
66 | if (!isset(self::$registries[$class])) { |
67 | self::$registries[$class] = new $class(); |
68 | } |
69 | |
70 | return self::$registries[$class]; |
71 | } |
72 | |
73 | /** |
74 | * Fetch a portable element with identifier & version |
75 | * |
76 | * @param $identifier |
77 | * @param null $version |
78 | * @return PortableElementObject |
79 | * @throws PortableElementNotFoundException |
80 | */ |
81 | public function fetch($identifier, $version = null) |
82 | { |
83 | $portableElements = $this->getAllVersions($identifier); |
84 | |
85 | // No version, return latest version |
86 | if (is_null($version)) { |
87 | $this->krsortByVersion($portableElements); |
88 | return $this->getModel()->createDataObject(reset($portableElements)); |
89 | } |
90 | |
91 | // Version is set, return associated record |
92 | if (isset($portableElements[$version])) { |
93 | return $this->getModel()->createDataObject($portableElements[$version]); |
94 | } |
95 | |
96 | // Version is set, no record found |
97 | throw new PortableElementNotFoundException( |
98 | $this->getModel()->getId() . ' with identifier ' . $identifier . ' found, ' |
99 | . 'but version "' . $version . '" does not exist.' |
100 | ); |
101 | } |
102 | |
103 | /** |
104 | * Get all record versions regarding $model->getTypeIdentifier() |
105 | * |
106 | * @param string $identifier |
107 | * @return array |
108 | * @throws PortableElementNotFoundException |
109 | * @throws PortableElementInconsistencyModelException |
110 | */ |
111 | protected function getAllVersions($identifier) |
112 | { |
113 | $portableElements = $this->get($identifier); |
114 | |
115 | // No portable element found |
116 | if ($portableElements == '') { |
117 | throw new PortableElementNotFoundException( |
118 | $this->getModel()->getId() . ' with identifier "' . $identifier . '" not found.' |
119 | ); |
120 | } |
121 | |
122 | return $portableElements; |
123 | } |
124 | |
125 | /** |
126 | * Retrieve the given element from list of portable element |
127 | * @param string $identifier |
128 | * @return string |
129 | */ |
130 | private function get($identifier) |
131 | { |
132 | $fileSystem = $this->getConfigFileSystem(); |
133 | |
134 | if (!empty($identifier) && $fileSystem->fileExists($identifier)) { |
135 | return json_decode($fileSystem->read($identifier), true); |
136 | } |
137 | |
138 | return false; |
139 | } |
140 | |
141 | private function getAll() |
142 | { |
143 | $elements = []; |
144 | $contents = $this->getConfigFileSystem()->listContents(); |
145 | |
146 | foreach ($contents as $file) { |
147 | if ($file['type'] === 'file') { |
148 | $identifier = basename($file['path']); |
149 | $elements[$identifier] = $this->get($identifier); |
150 | } |
151 | } |
152 | return $elements; |
153 | } |
154 | |
155 | |
156 | /** |
157 | * Add a value to the list with given id |
158 | * |
159 | * @param string $identifier |
160 | * @param string $value |
161 | */ |
162 | private function set($identifier, $value) |
163 | { |
164 | $this->getConfigFileSystem()->write($identifier, json_encode($value)); |
165 | } |
166 | |
167 | /** |
168 | * @return \oat\oatbox\filesystem\FileSystem |
169 | */ |
170 | private function getConfigFileSystem() |
171 | { |
172 | /** @var FileSystemService $fs */ |
173 | $fs = $this->getServiceLocator()->get(FileSystemService::SERVICE_ID); |
174 | return $fs->getFileSystem($this->fileSystemId); |
175 | } |
176 | |
177 | /** |
178 | * |
179 | * Remove a element from the array |
180 | * |
181 | * @param string $identifier |
182 | */ |
183 | private function remove(PortableElementObject $object) |
184 | { |
185 | $this->getConfigFileSystem()->delete($object->getTypeIdentifier()); |
186 | $this->getFileSystem()->unregisterAllFiles($object); |
187 | } |
188 | |
189 | /** |
190 | * @param $identifier |
191 | * @param null $version |
192 | * @return bool |
193 | */ |
194 | public function has($identifier, $version = null) |
195 | { |
196 | try { |
197 | return (bool)$this->fetch($identifier, $version); |
198 | } catch (PortableElementNotFoundException $e) { |
199 | return false; |
200 | } |
201 | } |
202 | |
203 | /** |
204 | * @param PortableElementObject $object |
205 | */ |
206 | public function update(PortableElementObject $object) |
207 | { |
208 | $mapByIdentifier = $this->get($object->getTypeIdentifier()); |
209 | if (!is_array($mapByIdentifier)) { |
210 | $mapByIdentifier = []; |
211 | } |
212 | $mapByIdentifier[$object->getVersion()] = $object->toArray(); |
213 | $this->set($object->getTypeIdentifier(), $mapByIdentifier); |
214 | } |
215 | |
216 | /** |
217 | * @param PortableElementObject $object |
218 | * @throws PortableElementNotFoundException |
219 | * @throws PortableElementVersionIncompatibilityException |
220 | * @throws PortableElementInconsistencyModelException |
221 | */ |
222 | public function delete(PortableElementObject $object) |
223 | { |
224 | $portableElements = $this->getAllVersions($object->getTypeIdentifier()); |
225 | |
226 | if (!isset($portableElements[$object->getVersion()])) { |
227 | throw new PortableElementVersionIncompatibilityException( |
228 | $this->getModel()->getId() . ' with identifier ' . $object->getTypeIdentifier() . ' found, ' |
229 | . 'but version ' . $object->getVersion() . ' does not exist. Deletion impossible.' |
230 | ); |
231 | } |
232 | |
233 | unset($portableElements[$object->getVersion()]); |
234 | if (empty($portableElements)) { |
235 | $this->remove($object); |
236 | } else { |
237 | $this->set($object->getTypeIdentifier(), $portableElements); |
238 | } |
239 | } |
240 | |
241 | /** |
242 | * @param string $identifier |
243 | * @throws PortableElementNotFoundException |
244 | */ |
245 | public function removeAllVersions($identifier) |
246 | { |
247 | if (!$this->has($identifier)) { |
248 | throw new PortableElementNotFoundException( |
249 | 'Unable to find portable element (' . $identifier . ') into registry. Deletion impossible.' |
250 | ); |
251 | } |
252 | |
253 | foreach ($this->getAllVersions($identifier) as $version) { |
254 | $this->unregister($this->getModel()->createDataObject($version)); |
255 | } |
256 | } |
257 | |
258 | /** |
259 | * Unregister all previously registered pci, in all version |
260 | * Remove all assets |
261 | */ |
262 | public function removeAll() |
263 | { |
264 | $portableElements = $this->getAll(); |
265 | foreach ($portableElements as $identifier => $versions) { |
266 | $this->removeAllVersions($identifier); |
267 | } |
268 | } |
269 | |
270 | /** |
271 | * Unregister portable element by removing the given version data & asset files |
272 | * If $model doesn't have version, all versions will be removed |
273 | * |
274 | * @param PortableElementObject $object |
275 | * @throws PortableElementNotFoundException |
276 | * @throws PortableElementVersionIncompatibilityException |
277 | * @throws \common_Exception |
278 | */ |
279 | public function unregister(PortableElementObject $object) |
280 | { |
281 | $object = $this->fetch($object->getTypeIdentifier(), $object->getVersion()); |
282 | |
283 | if (!$object->hasVersion()) { |
284 | $this->removeAllVersions($object); |
285 | } else { |
286 | $this->removeAssets($object); |
287 | $this->delete($object); |
288 | } |
289 | } |
290 | |
291 | /** |
292 | * @param string $identifier |
293 | * @return PortableElementObject |
294 | * @throws PortableElementNotFoundException |
295 | */ |
296 | public function getLatestVersion($identifier) |
297 | { |
298 | $portableElements = $this->getAllVersions($identifier); |
299 | |
300 | if (empty($portableElements)) { |
301 | throw new PortableElementNotFoundException( |
302 | 'Unable to find any version of protable element "' . $identifier . '"' |
303 | ); |
304 | } |
305 | |
306 | $this->krsortByVersion($portableElements); |
307 | return $this->getModel()->createDataObject(reset($portableElements)); |
308 | } |
309 | |
310 | public function getLatestCompatibleVersion(string $identifier, string $targetVersion): ?PortableElementObject |
311 | { |
312 | try { |
313 | $registered = $this->getAllVersions($identifier); |
314 | } catch (PortableElementNotFoundException $e) { |
315 | $this->logDebug($e->getMessage()); |
316 | return null; |
317 | } |
318 | $this->krsortByVersion($registered); |
319 | |
320 | foreach ($registered as $registeredVersion => $model) { |
321 | if (intval($targetVersion) === intval($registeredVersion)) { |
322 | return $this->getModel()->createDataObject($model); |
323 | } |
324 | } |
325 | |
326 | return null; |
327 | } |
328 | |
329 | /** |
330 | * @param PortableElementObject $object |
331 | * @param string $source Temporary directory path |
332 | * @throws PortableElementFileStorageException |
333 | * @throws PortableElementVersionIncompatibilityException |
334 | */ |
335 | public function register(PortableElementObject $object, $source) |
336 | { |
337 | try { |
338 | $latestVersion = $this->getLatestVersion($object->getTypeIdentifier()); |
339 | if (version_compare($object->getVersion(), $latestVersion->getVersion(), '<')) { |
340 | throw new PortableElementVersionIncompatibilityException( |
341 | 'A newer version of the code already exists ' . $latestVersion->getVersion( |
342 | ) . ' > ' . $object->getVersion() |
343 | ); |
344 | } |
345 | } catch (PortableElementNotFoundException $e) { |
346 | if (!$object->hasVersion()) { |
347 | $object->setVersion('0.0.0'); |
348 | } |
349 | // The portable element to register does not exist, continue |
350 | } |
351 | |
352 | $files = $this->getFilesFromPortableElement($object); |
353 | $this->getFileSystem()->registerFiles($object, $files, $source); |
354 | |
355 | $this->update($object); |
356 | |
357 | //register alias with the exact same files |
358 | $aliasObject = clone $object; |
359 | $aliasObject->setVersion($this->getAliasVersion($object->getVersion())); |
360 | $this->getFileSystem()->registerFiles($aliasObject, $files, $source); |
361 | $this->update($aliasObject); |
362 | } |
363 | |
364 | /** |
365 | * Get list of files following Pci Model |
366 | * |
367 | * @param PortableElementObject $object |
368 | * @return array |
369 | * @throws \common_Exception |
370 | */ |
371 | protected function getFilesFromPortableElement(PortableElementObject $object) |
372 | { |
373 | $validator = $object->getModel()->getValidator(); |
374 | return $validator->getAssets($object); |
375 | } |
376 | |
377 | /** |
378 | * Return the runtime of a portable element |
379 | * |
380 | * @param PortableElementObject $object |
381 | * @return PortableElementObject |
382 | * @throws PortableElementNotFoundException |
383 | */ |
384 | protected function getRuntime(PortableElementObject $object) |
385 | { |
386 | $runtime = $object->toArray(); |
387 | $runtime['model'] = $object->getModelId(); |
388 | $runtime['xmlns'] = $object->getNamespace(); |
389 | $runtime['runtime'] = $object->getRuntimeAliases(); |
390 | $runtime['creator'] = $object->getCreatorAliases(); |
391 | $runtime['baseUrl'] = $this->getBaseUrl($object); |
392 | return $runtime; |
393 | } |
394 | |
395 | /** |
396 | * Get the alias version for a given version number, e.g. 2.1.5 becomes 2.1.* |
397 | * @param $versionString |
398 | * @return mixed |
399 | */ |
400 | private function getAliasVersion($versionString) |
401 | { |
402 | if (preg_match('/^[0-9]+\.[0-9]+\.\*$/', $versionString)) { |
403 | //already an alias version string |
404 | return $versionString; |
405 | } else { |
406 | $version = SemVerParser::parse($versionString); |
407 | return $version->getMajor() . '.' . $version->getMinor() . '.*'; |
408 | } |
409 | } |
410 | |
411 | /** |
412 | * Get the latest registered portable element data object |
413 | * @param bool $useVersionAlias |
414 | * @return PortableElementObject[] |
415 | */ |
416 | public function getLatest($useVersionAlias = false) |
417 | { |
418 | $all = []; |
419 | foreach ($this->getAll() as $typeIdentifier => $versions) { |
420 | if (empty($versions)) { |
421 | continue; |
422 | } |
423 | |
424 | $this->krsortByVersion($versions); |
425 | $object = $this->getModel()->createDataObject(reset($versions)); |
426 | if ($useVersionAlias) { |
427 | $object->setVersion($this->getAliasVersion($object->getVersion())); |
428 | } |
429 | $all[$typeIdentifier] = $object; |
430 | } |
431 | return $all; |
432 | } |
433 | |
434 | /** |
435 | * Get the last version of portable element runtimes |
436 | * |
437 | * @return array |
438 | * @throws PortableElementInconsistencyModelException |
439 | */ |
440 | public function getLatestRuntimes($useVersionAlias = false) |
441 | { |
442 | return array_map(function ($portableElementDataObject) { |
443 | return [$this->getRuntime($portableElementDataObject)]; |
444 | }, $this->getLatest($useVersionAlias)); |
445 | } |
446 | |
447 | |
448 | /** |
449 | * Get the last version of portable element creators |
450 | * |
451 | * @return PortableElementObject[] |
452 | * @throws PortableElementInconsistencyModelException |
453 | */ |
454 | public function getLatestCreators($useVersionAlias = false) |
455 | { |
456 | return array_filter($this->getLatest($useVersionAlias), function ($portableElementDataObject) { |
457 | return !empty($portableElementDataObject->getCreator()); |
458 | }); |
459 | } |
460 | |
461 | /** |
462 | * Remove all registered files for a PCI identifier from FileSystem |
463 | * If $targetedVersion is given, remove only assets for this version |
464 | * |
465 | * @param PortableElementObject $object |
466 | * @return bool |
467 | * @throws \common_Exception |
468 | */ |
469 | protected function removeAssets(PortableElementObject $object) |
470 | { |
471 | if (!$object->hasVersion()) { |
472 | throw new PortableElementVersionIncompatibilityException( |
473 | 'Unable to delete asset files whitout model version.' |
474 | ); |
475 | } |
476 | |
477 | $object = $this->fetch($object->getTypeIdentifier(), $object->getVersion()); |
478 | |
479 | $files[] = array_merge($object->getRuntime(), $object->getCreator()); |
480 | $filesToRemove = []; |
481 | foreach ($files as $key => $file) { |
482 | if (is_array($file)) { |
483 | array_merge($filesToRemove, $file); |
484 | } else { |
485 | $filesToRemove[] = $file; |
486 | } |
487 | } |
488 | |
489 | if (empty($filesToRemove)) { |
490 | return true; |
491 | } |
492 | |
493 | if (!$this->getFileSystem()->unregisterFiles($object, $filesToRemove)) { |
494 | throw new PortableElementFileStorageException( |
495 | 'Unable to delete asset files for PCI "' . $object->getTypeIdentifier() |
496 | . '" at version "' . $object->getVersion() . '"' |
497 | ); |
498 | } |
499 | return true; |
500 | } |
501 | |
502 | /** |
503 | * Create an temp export tree and return path |
504 | * |
505 | * @param PortableElementObject $object |
506 | * @return string |
507 | */ |
508 | protected function getZipLocation(PortableElementObject $object) |
509 | { |
510 | return \tao_helpers_Export::getExportPath() |
511 | . DIRECTORY_SEPARATOR |
512 | . 'pciPackage_' |
513 | . $object->getTypeIdentifier() |
514 | . '.zip'; |
515 | } |
516 | |
517 | /** |
518 | * Get manifest representation of Pci Model |
519 | * |
520 | * @param PortableElementObject $object |
521 | * @return string |
522 | */ |
523 | public function getManifest(PortableElementObject $object) |
524 | { |
525 | return json_encode($object->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
526 | } |
527 | |
528 | /** |
529 | * Export a portable element to a zip package |
530 | * |
531 | * @throws \common_Exception |
532 | */ |
533 | public function export(PortableElementObject $object): string |
534 | { |
535 | $zip = new \ZipArchive(); |
536 | $path = $this->getZipLocation($object); |
537 | |
538 | if ($zip->open($path, \ZipArchive::CREATE) !== true) { |
539 | throw new \common_Exception('Unable to create zip file ' . $path); |
540 | } |
541 | |
542 | $manifest = $this->getManifest($object); |
543 | $zip->addFromString($this->getModel()->getManifestName(), $manifest); |
544 | |
545 | $files = $this->getFilesFromPortableElement($object); |
546 | |
547 | $filesystem = $this->getFileSystem(); |
548 | foreach ($files as $file) { |
549 | try { |
550 | $zip->addFromString($file, $filesystem->getFileContentFromModelStorage($object, $file)); |
551 | } catch (PortableElementFileStorageException $e) { |
552 | // do not include missing/sharedClientLib files |
553 | continue; |
554 | } |
555 | } |
556 | |
557 | $zip->close(); |
558 | |
559 | return $path; |
560 | } |
561 | |
562 | /** |
563 | * Get the fly filesystem based on OPTION_FS configuration |
564 | * |
565 | * @return PortableElementFileStorage |
566 | */ |
567 | public function getFileSystem() |
568 | { |
569 | if (!$this->storage) { |
570 | $this->storage = $this->getServiceLocator()->get(PortableElementFileStorage::SERVICE_ID); |
571 | $this->storage->setServiceLocator($this->getServiceLocator()); |
572 | $this->storage->setModel($this->getModel()); |
573 | } |
574 | return $this->storage; |
575 | } |
576 | |
577 | /** |
578 | * Return the absolute url of PCI storage |
579 | * |
580 | * @param PortableElementObject $object |
581 | * @return string |
582 | * @throws PortableElementNotFoundException |
583 | */ |
584 | public function getBaseUrl(PortableElementObject $object) |
585 | { |
586 | $object = $this->fetch($object->getTypeIdentifier(), $object->getVersion()); |
587 | return $this->getFileSystem()->getFileUrl($object); |
588 | } |
589 | |
590 | /** |
591 | * @param PortableElementObject $object |
592 | * @param $file |
593 | * @return bool|false|resource |
594 | * @throws \common_Exception |
595 | */ |
596 | public function getFileStream(PortableElementObject $object, $file) |
597 | { |
598 | return $this->getFileSystem()->getFileStream($object, $file); |
599 | } |
600 | |
601 | /** |
602 | * Sort array keys by version (DESC) |
603 | * |
604 | * @param array $array |
605 | */ |
606 | protected function krsortByVersion(array &$array) |
607 | { |
608 | uksort($array, function ($a, $b) { |
609 | return -version_compare($a, $b); |
610 | }); |
611 | } |
612 | } |