Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.94% covered (warning)
87.94%
124 / 141
75.00% covered (warning)
75.00%
12 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
RdfMediaRelationRepository
87.94% covered (warning)
87.94%
124 / 141
75.00% covered (warning)
75.00%
12 / 16
30.47
0.00% covered (danger)
0.00%
0 / 1
 findAll
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 findMediaWithRelations
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
5
 findAllByTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 remove
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 getPropertyByRelation
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 findAllByMedia
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 findAllMediaByTarget
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 getItemAssetUris
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getRelatedItemUrisByAssetUri
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 applyQueryTargetType
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
5.50
 getComplexSearchService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mapTargetRelations
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 mapSourceRelations
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 createMediaRelation
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPersistence
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
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) 2020 (original work) Open Assessment Technologies SA;
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoMediaManager\model\relation\repository\rdf;
24
25use common_exception_Error;
26use common_persistence_SqlPersistence;
27use core_kernel_classes_Class as ClassResource;
28use core_kernel_classes_Property;
29use LogicException;
30use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService;
31use oat\generis\model\OntologyAwareTrait;
32use oat\generis\persistence\PersistenceManager;
33use oat\oatbox\service\ConfigurableService;
34use oat\search\base\exception\SearchGateWayExeption;
35use oat\search\base\QueryInterface;
36use oat\search\helper\SupportedOperatorHelper;
37use oat\taoMediaManager\model\exception\ComplexSearchLimitException;
38use oat\taoMediaManager\model\relation\MediaRelation;
39use oat\taoMediaManager\model\relation\MediaRelationCollection;
40use oat\taoMediaManager\model\relation\repository\MediaRelationRepositoryInterface;
41use oat\taoMediaManager\model\relation\repository\query\FindAllByTargetQuery;
42use oat\taoMediaManager\model\relation\repository\query\FindAllQuery;
43use oat\taoMediaManager\model\TaoMediaOntology;
44use PDO;
45
46class RdfMediaRelationRepository extends ConfigurableService implements MediaRelationRepositoryInterface
47{
48    use OntologyAwareTrait;
49
50    private const CLASS_MEDIA_LIMIT = 20;
51    private const NESTED_CLASS_LIMIT = 10;
52
53    private const ITEM_RELATION_PROPERTY = 'http://www.tao.lu/Ontologies/TAOMedia.rdf#RelatedItem';
54    private const MEDIA_RELATION_PROPERTY = 'http://www.tao.lu/Ontologies/TAOMedia.rdf#RelatedMedia';
55
56    /**
57     * @throws common_exception_Error
58     * @throws SearchGateWayExeption
59     * @throws ComplexSearchLimitException
60     */
61    public function findAll(FindAllQuery $findAllQuery): MediaRelationCollection
62    {
63        if ($findAllQuery->getClassId()) {
64            return $this->findMediaWithRelations($this->getClass($findAllQuery->getClassId()));
65        }
66
67        return $this->findAllByMedia($findAllQuery->getMediaId());
68    }
69
70    /**
71     * @throws common_exception_Error
72     * @throws SearchGateWayExeption
73     * @throws ComplexSearchLimitException
74     */
75    private function findMediaWithRelations(ClassResource $class): MediaRelationCollection
76    {
77        if (count($class->getSubClasses(true)) > self::NESTED_CLASS_LIMIT) {
78            throw new ComplexSearchLimitException(self::NESTED_CLASS_LIMIT);
79        }
80
81        $mediaRelationCollection = new MediaRelationCollection();
82
83        $includedMedia = [];
84        $includedMediaQueryBuilder = $this->getComplexSearchService()->query();
85        $includedMediaQuery = $this->getComplexSearchService()->searchType(
86            $includedMediaQueryBuilder,
87            $class->getUri(),
88            true
89        );
90        $includedMediaQueryBuilder->setCriteria($includedMediaQuery);
91        $includedMediaResult = $this->getComplexSearchService()->getGateway()->search($includedMediaQueryBuilder);
92
93        if (count($includedMediaResult) < 1) {
94            return $mediaRelationCollection;
95        }
96
97        foreach ($includedMediaResult as $media) {
98            $includedMedia[] = $media->getUri();
99        }
100
101        $queryBuilder = $this->getComplexSearchService()->query();
102        $includedMediaQuery->addCriterion(self::ITEM_RELATION_PROPERTY, SupportedOperatorHelper::IS_NOT_NULL, '');
103        $queryBuilder->setCriteria($includedMediaQuery);
104
105        $orQuery = $this->getComplexSearchService()->searchType($queryBuilder, $class->getUri(), true);
106        $orQuery->addCriterion(self::MEDIA_RELATION_PROPERTY, SupportedOperatorHelper::IS_NOT_NULL, '');
107        $orQuery->addCriterion(self::MEDIA_RELATION_PROPERTY, SupportedOperatorHelper::NOT_IN, $includedMedia);
108        $queryBuilder->setOr($orQuery);
109
110        $queryBuilder->setLimit(self::CLASS_MEDIA_LIMIT);
111        $mediaResult = $this->getComplexSearchService()->getGateway()->search($queryBuilder);
112
113
114        /** @var Resource $media */
115        foreach ($mediaResult as $media) {
116            $mediaRelationCollection->add(
117                new MediaRelation(MediaRelation::MEDIA_TYPE, $media->getUri(), $media->getLabel())
118            );
119        }
120
121        return $mediaRelationCollection;
122    }
123
124    public function findAllByTarget(FindAllByTargetQuery $findAllQuery): MediaRelationCollection
125    {
126        return $this->findAllMediaByTarget($findAllQuery->getTargetId(), $findAllQuery->getType());
127    }
128
129    public function save(MediaRelation $relation): void
130    {
131        $mediaResource = $this->getResource($relation->getSourceId());
132
133        if (!$mediaResource->setPropertyValue($this->getPropertyByRelation($relation), $relation->getId())) {
134            throw new LogicException(
135                sprintf(
136                    'Error saving media relation %s [%s:%s]',
137                    $relation->getType(),
138                    $relation->getSourceId(),
139                    $relation->getId()
140                )
141            );
142        }
143
144        $this->getLogger()->info(
145            sprintf(
146                'Media relation saved, media "%s" is now part of %s "%s"',
147                $relation->getSourceId(),
148                $relation->getType(),
149                $relation->getId()
150            )
151        );
152    }
153
154    public function remove(MediaRelation $relation): void
155    {
156        $mediaResource = $this->getResource($relation->getSourceId());
157
158        if (!$mediaResource->removePropertyValue($this->getPropertyByRelation($relation), $relation->getId())) {
159            throw new LogicException(
160                sprintf(
161                    'Error removing media relation %s [%s:%s]',
162                    $relation->getType(),
163                    $relation->getSourceId(),
164                    $relation->getId()
165                )
166            );
167        }
168
169        $this->getLogger()->info(
170            sprintf(
171                'Media relation removed, media "%s" is not linked to %s "%s" anymore',
172                $relation->getSourceId(),
173                $relation->getType(),
174                $relation->getId()
175            )
176        );
177    }
178
179    private function getPropertyByRelation(MediaRelation $mediaRelation): core_kernel_classes_Property
180    {
181        $uri = $mediaRelation->isMedia()
182            ? self::MEDIA_RELATION_PROPERTY
183            : self::ITEM_RELATION_PROPERTY;
184
185        return $this->getProperty($uri);
186    }
187
188    private function findAllByMedia(string $mediaId): MediaRelationCollection
189    {
190        $mediaResource = $this->getResource($mediaId);
191
192        $rdfMediaRelations = $mediaResource->getPropertiesValues([
193            $this->getProperty(self::ITEM_RELATION_PROPERTY),
194            $this->getProperty(self::MEDIA_RELATION_PROPERTY),
195        ]);
196
197        return new MediaRelationCollection(
198            ... $this->mapTargetRelations(
199                MediaRelation::ITEM_TYPE,
200                $rdfMediaRelations[self::ITEM_RELATION_PROPERTY],
201                $mediaId
202            )->getIterator(),
203            ... $this->mapTargetRelations(
204                MediaRelation::MEDIA_TYPE,
205                $rdfMediaRelations[self::MEDIA_RELATION_PROPERTY],
206                $mediaId
207            )->getIterator()
208        );
209    }
210
211    private function findAllMediaByTarget(string $targetId, string $type): MediaRelationCollection
212    {
213        $search = $this->getComplexSearchService();
214
215        $queryBuilder = $search->query();
216
217        $query = $search->searchType(
218            $queryBuilder,
219            TaoMediaOntology::CLASS_URI_MEDIA_ROOT,
220            true
221        );
222
223        $this->applyQueryTargetType($query, $targetId, $type);
224
225        $queryBuilder->setCriteria($query);
226
227        $result = $search->getGateway()
228            ->search($queryBuilder);
229
230        return $this->mapSourceRelations($type, (array)$result, $targetId);
231    }
232
233    public function getItemAssetUris(string $itemUri): array
234    {
235        $statement = $this->getPersistence()->query(
236            'SELECT DISTINCT subject FROM statements WHERE predicate = ? AND object = ?',
237            [self::ITEM_RELATION_PROPERTY, $itemUri]
238        );
239
240        return $statement->fetchAll(PDO::FETCH_COLUMN);
241    }
242
243    public function getRelatedItemUrisByAssetUri(string $assetUri): array
244    {
245        $statement = $this->getPersistence()->query(
246            'SELECT DISTINCT object FROM statements WHERE predicate = ? AND subject = ?',
247            [self::ITEM_RELATION_PROPERTY, $assetUri]
248        );
249
250        return $statement->fetchAll(PDO::FETCH_COLUMN);
251    }
252
253    private function applyQueryTargetType(QueryInterface $query, $targetId, $type)
254    {
255        switch ($type) {
256            case MediaRelation::ITEM_TYPE:
257                $query
258                    ->add(self::ITEM_RELATION_PROPERTY)
259                    ->equals($targetId);
260                break;
261
262            case MediaRelation::MEDIA_TYPE:
263                $query
264                    ->add(self::MEDIA_RELATION_PROPERTY)
265                    ->equals($targetId);
266                break;
267
268            default:
269                throw new LogicException('MediaRelation query type is unknown.');
270        }
271    }
272
273    private function getComplexSearchService(): ComplexSearchService
274    {
275        return $this->getServiceLocator()->get(ComplexSearchService::SERVICE_ID);
276    }
277
278    /**
279     * @param string $type
280     * @param Resource[]
281     * @param string $sourceId
282     */
283    private function mapTargetRelations(
284        string $type,
285        array $rdfMediaRelations,
286        string $sourceId
287    ): MediaRelationCollection {
288        $collection = new MediaRelationCollection();
289
290        foreach ($rdfMediaRelations as $target) {
291            $collection->add(
292                $this->createMediaRelation($type, $target->getUri(), $sourceId, $target->getLabel())
293            );
294        }
295
296        return $collection;
297    }
298
299    private function mapSourceRelations(
300        string $type,
301        array $rdfMediaRelations,
302        string $targetId,
303        string $targetLabel = ''
304    ): MediaRelationCollection {
305        $collection = new MediaRelationCollection();
306
307        foreach ($rdfMediaRelations as $source) {
308            $collection->add(
309                $this->createMediaRelation($type, $targetId, $source->subject, $targetLabel)
310            );
311        }
312
313        return $collection;
314    }
315
316    private function createMediaRelation(
317        string $type,
318        string $targetId,
319        string $mediaId,
320        string $targetLabel = ''
321    ): MediaRelation {
322        return (new MediaRelation($type, $targetId, $targetLabel))
323            ->withSourceId($mediaId);
324    }
325
326    private function getPersistence(): common_persistence_SqlPersistence
327    {
328        /** @var PersistenceManager $persistenceManager */
329        $persistenceManager = $this->getServiceManager()->get(PersistenceManager::SERVICE_ID);
330
331        return $persistenceManager->getPersistenceById('default');
332    }
333}