Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
QtiXmlDataManager
94.44% covered (success)
94.44%
34 / 36
60.00% covered (warning)
60.00%
3 / 5
12.02
0.00% covered (danger)
0.00%
0 / 1
 replaceItemIdentifier
95.00% covered (success)
95.00%
19 / 20
0.00% covered (danger)
0.00%
0 / 1
5
 replaceFileContent
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
4.01
 setAppNamespacePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileReferenceSerializer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIdentifierGenerator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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) 2020-2024 (original work) Open Assessment Technologies SA.
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoQtiItem\model\qti\copyist;
24
25use common_Exception;
26use core_kernel_classes_Resource;
27use core_kernel_persistence_Exception;
28use DOMDocument;
29use DOMElement;
30use oat\generis\model\fileReference\FileReferenceSerializer;
31use oat\generis\model\OntologyAwareTrait;
32use oat\oatbox\filesystem\Directory;
33use oat\oatbox\log\LoggerAwareTrait;
34use oat\oatbox\service\ConfigurableService;
35use oat\tao\model\IdentifierGenerator\Generator\IdentifierGeneratorInterface;
36use oat\tao\model\IdentifierGenerator\Generator\IdentifierGeneratorProxy;
37use oat\taoQtiItem\helpers\QtiFile;
38use oat\oatbox\filesystem\File;
39use tao_models_classes_FileNotFoundException;
40use taoItems_models_classes_ItemsService;
41
42class QtiXmlDataManager extends ConfigurableService
43{
44    use OntologyAwareTrait;
45    use LoggerAwareTrait;
46
47    /**
48     * @var string
49     */
50    private $prefix;
51
52    /**
53     * All files will be copied from the previous resource
54     * so our responsibility to fix everything inside these files
55     *
56     * @param string $sourceItemId
57     * @param string $destinationItemId
58     * @throws common_Exception
59     * @throws core_kernel_persistence_Exception
60     * @throws tao_models_classes_FileNotFoundException
61     */
62    public function replaceItemIdentifier(
63        string $sourceItemId,
64        string $destinationItemId
65    ): void {
66        $destinationItem = $this->getResource($destinationItemId);
67
68        /** @var taoItems_models_classes_ItemsService $itemService */
69        $itemService = $this->getServiceLocator()->get(taoItems_models_classes_ItemsService::class);
70        $serializer = $this->getFileReferenceSerializer();
71
72        foreach ($destinationItem->getUsedLanguages($itemService->getItemContentProperty()) as $lang) {
73            $destinationItemDirectory = $itemService->getItemDirectory($destinationItem, $lang);
74
75            foreach (
76                $destinationItem->getPropertyValuesCollection(
77                    $itemService->getItemContentProperty(),
78                    ['lg' => $lang]
79                )->getIterator() as $propertyValue
80            ) {
81                $id = $propertyValue instanceof core_kernel_classes_Resource
82                    ? $propertyValue->getUri()
83                    : (string) $propertyValue;
84                $destinationDirectory = $serializer->unserializeDirectory($id);
85                $iterator = $destinationDirectory->getFlyIterator(
86                    Directory::ITERATOR_FILE | Directory::ITERATOR_RECURSIVE
87                );
88
89                foreach ($iterator as $iteratorFile) {
90                    $filePath = $destinationItemDirectory->getRelPath($iteratorFile);
91                    /** @var File $newFile */
92                    $file = $destinationItemDirectory->getFile($filePath);
93
94                    // do replace if needed
95                    $this->replaceFileContent($file, $sourceItemId, $destinationItemId);
96                }
97            }
98        }
99    }
100
101    /**
102     * As I know that qti.xml consist of xml, and if structure is expected - just replace identifier
103     * @param File $file
104     * @param string $fromSourceId
105     * @param string $toSourceId
106     * @throws common_Exception
107     */
108    private function replaceFileContent(File $file, string $fromSourceId, string $toSourceId): void
109    {
110        if (preg_match('/' . QtiFile::FILE . '$/', $file->getBasename()) === 1) {
111            $xml = $file->read();
112            $dom = new DOMDocument('1.0', 'UTF-8');
113
114            if ($dom->loadXML($xml) === true) {
115                $assessmentItemNodes = $dom->getElementsByTagName('assessmentItem');
116                $identifierGenerator = $this->getIdentifierGenerator();
117
118                /** @var DOMElement $item */
119                foreach ($assessmentItemNodes as $item) {
120                    $identifier = $identifierGenerator->generate([
121                        IdentifierGeneratorInterface::OPTION_RESOURCE_ID => $toSourceId
122                    ]);
123
124                    $item->setAttribute('identifier', $identifier);
125                }
126            } else {
127                $this->logWarning('Qti.xml does not have a valid xml, identifier will not be replaced');
128            }
129
130            $file->put($dom->saveXML());
131        }
132    }
133
134    public function setAppNamespacePrefix(string $prefix): void
135    {
136        $this->prefix = $prefix;
137    }
138
139    /**
140     * Get serializer to persist filesystem object
141     *
142     * @return FileReferenceSerializer
143     */
144    protected function getFileReferenceSerializer(): FileReferenceSerializer
145    {
146        return $this->getServiceLocator()->get(FileReferenceSerializer::SERVICE_ID);
147    }
148
149    private function getIdentifierGenerator(): IdentifierGeneratorInterface
150    {
151        return $this->getServiceManager()->getContainer()->get(IdentifierGeneratorProxy::class);
152    }
153}