Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.52% covered (warning)
65.52%
38 / 58
27.27% covered (danger)
27.27%
3 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaResourcePreparer
65.52% covered (warning)
65.52%
38 / 58
27.27% covered (danger)
27.27%
3 / 11
53.72
0.00% covered (danger)
0.00%
0 / 1
 prepare
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
7.08
 replaceComponentPath
25.00% covered (danger)
25.00%
2 / 8
0.00% covered (danger)
0.00%
0 / 1
3.69
 withMediaResolver
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMediaResolver
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getComponents
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 getComponentSource
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
 setComponentSource
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getMediaAsset
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 getEncodedSource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFileManagement
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSharedStimulusResourceSpecification
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 (original work) Open Assessment Technologies SA;
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoMediaManager\model\export\service;
24
25use core_kernel_classes_Resource;
26use Exception;
27use LogicException;
28use oat\generis\model\OntologyAwareTrait;
29use oat\oatbox\service\ConfigurableService;
30use oat\tao\model\media\MediaAsset;
31use oat\tao\model\media\TaoMediaException;
32use oat\tao\model\media\TaoMediaResolver;
33use oat\taoMediaManager\model\fileManagement\FileManagement;
34use oat\taoMediaManager\model\MediaSource;
35use oat\taoMediaManager\model\sharedStimulus\specification\SharedStimulusResourceSpecification;
36use Psr\Http\Message\StreamInterface;
37use qtism\data\content\BodyElement;
38use qtism\data\content\xhtml\Img;
39use qtism\data\content\xhtml\QtiObject;
40use qtism\data\storage\xml\XmlDocument;
41use tao_models_classes_FileNotFoundException;
42
43class MediaResourcePreparer extends ConfigurableService implements MediaResourcePreparerInterface
44{
45    use OntologyAwareTrait;
46
47    private const PROCESS_XML_ELEMENTS = [
48        'img',
49        'object',
50    ];
51
52    /** @var TaoMediaResolver */
53    private $mediaResolver;
54
55    public function prepare(core_kernel_classes_Resource $mediaResource, StreamInterface $contents): string
56    {
57        if (!$this->getSharedStimulusResourceSpecification()->isSatisfiedBy($mediaResource)) {
58            return (string)$contents;
59        }
60
61        $xmlDocument = new XmlDocument();
62        $xmlDocument->loadFromString((string)$contents);
63
64        $missingMediaAssets = [];
65        foreach ($this->getComponents($xmlDocument) as $component) {
66            $mediaAsset = $this->getMediaAsset($component);
67
68            if ($mediaAsset) {
69                try {
70                    $this->replaceComponentPath($component, $mediaAsset);
71                } catch (tao_models_classes_FileNotFoundException $fileNotFoundException) {
72                    $missingAsset = 'FilePath: ' . $fileNotFoundException->getFilePath();
73                    if ($component instanceof Img) {
74                        $missingAsset = 'Image: ' . $component->getAlt() . ' ' . $missingAsset;
75                    }
76
77                    $missingMediaAssets[] = $missingAsset;
78                }
79            }
80        }
81
82        if (!empty($missingMediaAssets)) {
83            // Report missing file errors to upper levels in aggregated form.
84            throw new MediaReferencesNotFoundException($missingMediaAssets);
85        }
86
87        return $xmlDocument->saveToString();
88    }
89
90    private function replaceComponentPath(BodyElement $component, MediaAsset $mediaAsset): void
91    {
92        $mediaSource = $mediaAsset->getMediaSource();
93
94        $fileInfo = $mediaSource->getFileInfo($mediaAsset->getMediaIdentifier());
95
96        $stream = $this->getFileManagement()->getFileStream($fileInfo['link']);
97
98        $contents = $stream->getContents();
99
100        if (!$contents) {
101            throw new tao_models_classes_FileNotFoundException($fileInfo['link']);
102        }
103
104        $base64Content = $this->getEncodedSource($fileInfo['mime'], $contents);
105
106        $this->setComponentSource($component, $base64Content);
107    }
108
109    public function withMediaResolver(TaoMediaResolver $mediaResolver): MediaResourcePreparerInterface
110    {
111        $this->mediaResolver = $mediaResolver;
112
113        return $this;
114    }
115
116    private function getMediaResolver(): TaoMediaResolver
117    {
118        if (!$this->mediaResolver) {
119            return $this->mediaResolver = new TaoMediaResolver();
120        }
121
122        return $this->mediaResolver;
123    }
124
125    /**
126     * @return BodyElement[]
127     */
128    private function getComponents(XmlDocument $xmlDocument): array
129    {
130        $components = [];
131
132        foreach (self::PROCESS_XML_ELEMENTS as $element) {
133            $components = array_merge(
134                $components,
135                $xmlDocument->getDocumentComponent()
136                    ->getComponentsByClassName($element)
137                    ->getArrayCopy()
138            );
139        }
140
141        return $components;
142    }
143
144    private function getComponentSource(BodyElement $element): string
145    {
146        if ($element instanceof Img) {
147            return $element->getSrc();
148        }
149
150        if ($element instanceof QtiObject) {
151            return $element->getData();
152        }
153
154        throw new LogicException(sprintf('Body element [%s] not supported', get_class($element)));
155    }
156
157    private function setComponentSource(BodyElement $element, string $source): void
158    {
159        if ($element instanceof Img) {
160            $element->setSrc($source);
161        }
162
163        if ($element instanceof QtiObject) {
164            $element->setData($source);
165        }
166    }
167
168    /**
169     * @throws Exception
170     */
171    private function getMediaAsset(BodyElement $component): ?MediaAsset
172    {
173        try {
174            $source = $this->getComponentSource($component);
175
176            $mediaAsset = $this->getMediaResolver()
177                ->resolve($source);
178
179            $mediaSource = $mediaAsset->getMediaSource();
180
181            return $mediaSource instanceof MediaSource ? $mediaAsset : null;
182        } catch (TaoMediaException $exception) {
183            return null;
184        }
185    }
186
187    private function getEncodedSource(string $mimeType, string $content): string
188    {
189        return 'data:' . $mimeType . ';base64,' . base64_encode($content);
190    }
191
192    private function getFileManagement(): FileManagement
193    {
194        return $this->getServiceLocator()->get(FileManagement::SERVICE_ID);
195    }
196
197    private function getSharedStimulusResourceSpecification(): SharedStimulusResourceSpecification
198    {
199        return $this->getServiceLocator()->get(SharedStimulusResourceSpecification::class);
200    }
201}