Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
50 / 60
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClassCopier
83.33% covered (warning)
83.33%
50 / 60
62.50% covered (warning)
62.50%
5 / 8
25.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 withPermissionCopier
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 withPermissionCopiers
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 transfer
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doCopy
76.67% covered (warning)
76.67%
23 / 30
0.00% covered (danger)
0.00%
0 / 1
8.81
 assertInSameRootClass
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 isTranslationInstance
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 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) 2022-2023 (original work) Open Assessment Technologies SA.
19 *
20 * @author Andrei Shapiro <andrei.shapiro@taotesting.com>
21 */
22
23declare(strict_types=1);
24
25namespace oat\tao\model\resources\Service;
26
27use core_kernel_classes_Resource;
28use InvalidArgumentException;
29use core_kernel_classes_Class;
30use oat\generis\model\data\Ontology;
31use oat\tao\model\resources\Command\ResourceTransferCommand;
32use oat\tao\model\resources\Contract\ClassCopierInterface;
33use oat\tao\model\resources\Contract\PermissionCopierInterface;
34use oat\tao\model\resources\Contract\ClassMetadataCopierInterface;
35use oat\tao\model\resources\Contract\ClassMetadataMapperInterface;
36use oat\tao\model\resources\Contract\ResourceTransferInterface;
37use oat\tao\model\resources\Contract\RootClassesListServiceInterface;
38use oat\tao\model\resources\ResourceTransferResult;
39use oat\tao\model\TaoOntology;
40
41class ClassCopier implements ClassCopierInterface, ResourceTransferInterface
42{
43    private RootClassesListServiceInterface $rootClassesListService;
44    private ClassMetadataCopierInterface $classMetadataCopier;
45    private ResourceTransferInterface $instanceCopier;
46    private ClassMetadataMapperInterface $classMetadataMapper;
47    private PermissionCopierInterface $permissionCopier;
48    private Ontology $ontology;
49    private array $copiedClasses = [];
50    private bool $assertionCompleted = false;
51
52    public function __construct(
53        RootClassesListServiceInterface $rootClassesListService,
54        ClassMetadataCopierInterface $classMetadataCopier,
55        ResourceTransferInterface $instanceCopier,
56        ClassMetadataMapperInterface $classMetadataMapper,
57        Ontology $ontology
58    ) {
59        $this->rootClassesListService = $rootClassesListService;
60        $this->classMetadataCopier = $classMetadataCopier;
61        $this->instanceCopier = $instanceCopier;
62        $this->classMetadataMapper = $classMetadataMapper;
63        $this->ontology = $ontology;
64    }
65
66    public function withPermissionCopier(PermissionCopierInterface $permissionCopier): void
67    {
68        $this->permissionCopier = $permissionCopier;
69    }
70
71    /**
72     * This method is to be used with tagged_iterator() from service providers
73     * (but only the last copier from the iterable is effectively applied).
74     */
75    public function withPermissionCopiers(iterable $copiers): void
76    {
77        foreach ($copiers as $copier) {
78            $this->withPermissionCopier($copier);
79        }
80    }
81
82    public function transfer(ResourceTransferCommand $command): ResourceTransferResult
83    {
84        $class = $this->ontology->getClass($command->getFrom());
85        $destinationClass = $this->ontology->getClass($command->getTo());
86        $newClass = $this->doCopy($class, $destinationClass, $command->keepOriginalAcl());
87
88        return new ResourceTransferResult($newClass->getUri());
89    }
90
91    public function copy(
92        core_kernel_classes_Class $class,
93        core_kernel_classes_Class $destinationClass
94    ): core_kernel_classes_Class {
95        return $this->doCopy($class, $destinationClass);
96    }
97
98    private function doCopy(
99        core_kernel_classes_Class $class,
100        core_kernel_classes_Class $destinationClass,
101        bool $keepOriginalPermission = true
102    ): core_kernel_classes_Class {
103        if (in_array($class->getUri(), $this->copiedClasses, true)) {
104            return $class;
105        }
106
107        $this->assertInSameRootClass($class, $destinationClass);
108
109        $newClass = $destinationClass->createSubClass($class->getLabel());
110        $newClassUri = $newClass->getUri();
111
112        $this->copiedClasses[] = $newClassUri;
113
114        $this->classMetadataCopier->copy($class, $newClass);
115
116        if (isset($this->permissionCopier)) {
117            $this->permissionCopier->copy(
118                $keepOriginalPermission ? $class : $destinationClass,
119                $newClass
120            );
121        }
122
123        foreach ($class->getInstances() as $instance) {
124            if ($this->isTranslationInstance($instance)) {
125                continue;
126            }
127
128            $aclMode = $keepOriginalPermission ?
129                ResourceTransferCommand::ACL_KEEP_ORIGINAL :
130                ResourceTransferCommand::ACL_USE_DESTINATION;
131
132            $this->instanceCopier->transfer(
133                new ResourceTransferCommand(
134                    $instance->getUri(),
135                    $newClassUri,
136                    $aclMode,
137                    ResourceTransferCommand::TRANSFER_MODE_COPY
138                )
139            );
140        }
141
142        foreach ($class->getSubClasses() as $subClass) {
143            $this->doCopy($subClass, $newClass, $keepOriginalPermission);
144        }
145
146        $this->classMetadataMapper->remove($newClass->getProperties());
147
148        return $newClass;
149    }
150
151    private function assertInSameRootClass(
152        core_kernel_classes_Class $class,
153        core_kernel_classes_Class $destinationClass
154    ): void {
155        if ($this->assertionCompleted) {
156            return;
157        }
158
159        foreach ($this->rootClassesListService->list() as $rootClass) {
160            if (
161                ($class->equals($rootClass) || $class->isSubClassOf($rootClass))
162                && !$destinationClass->equals($rootClass)
163                && !$destinationClass->isSubClassOf($rootClass)
164            ) {
165                throw new InvalidArgumentException(
166                    sprintf(
167                        'Selected class (%s) and destination class (%s) must be in the same root class (%s).',
168                        $class->getUri(),
169                        $destinationClass->getUri(),
170                        $rootClass->getUri()
171                    )
172                );
173            }
174        }
175
176        $this->assertionCompleted = true;
177    }
178
179    private function isTranslationInstance(core_kernel_classes_Resource $instance): bool
180    {
181        $originalProperty = $instance->getProperty(TaoOntology::PROPERTY_TRANSLATION_ORIGINAL_RESOURCE_URI);
182
183        return $originalProperty && !empty($instance->getOnePropertyValue($originalProperty));
184    }
185}