Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 59 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ListUpdater | |
0.00% |
0 / 59 |
|
0.00% |
0 / 8 |
380 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
updateByRequest | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
setListElements | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
addOneElement | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getValueByUriKey | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getListSearchInput | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getElementURIFromKey | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getElementsFromPayload | |
0.00% |
0 / 7 |
|
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) 2022 (original work) Open Assessment Technologies SA. |
19 | */ |
20 | |
21 | declare(strict_types=1); |
22 | |
23 | namespace oat\taoBackOffice\model\lists\Service; |
24 | |
25 | use oat\tao\model\Lists\Business\Domain\Value; |
26 | use oat\tao\model\Lists\Business\Domain\ValueCollection; |
27 | use oat\tao\model\Lists\Business\Domain\ValueCollectionSearchRequest; |
28 | use oat\tao\model\Lists\Business\Input\ValueCollectionSearchInput; |
29 | use oat\tao\model\Lists\Business\Service\ValueCollectionService; |
30 | use oat\tao\model\Lists\DataAccess\Repository\ValueConflictException; |
31 | use oat\taoBackOffice\model\lists\Contract\ListUpdaterInterface; |
32 | use oat\taoBackOffice\model\lists\ListService; |
33 | use Psr\Http\Message\ServerRequestInterface; |
34 | use core_kernel_classes_Class; |
35 | use tao_helpers_Uri; |
36 | use BadFunctionCallException; |
37 | use OverflowException; |
38 | use RuntimeException; |
39 | |
40 | class ListUpdater implements ListUpdaterInterface |
41 | { |
42 | /** @var ValueCollectionService */ |
43 | private $valueCollectionService; |
44 | |
45 | /** @var ListService */ |
46 | private $listService; |
47 | |
48 | public function __construct( |
49 | ValueCollectionService $valueCollectionService, |
50 | ListService $listService |
51 | ) { |
52 | $this->valueCollectionService = $valueCollectionService; |
53 | $this->listService = $listService; |
54 | } |
55 | |
56 | /** |
57 | * @throws BadFunctionCallException if the payload contains too many items |
58 | * @throws OverflowException if the list exceeds the allowed number of items |
59 | * @throws ValueConflictException if element URIs are non-unique |
60 | * @throws RuntimeException if there is an unexpected persistence error |
61 | */ |
62 | public function updateByRequest(ServerRequestInterface $request): void |
63 | { |
64 | $post = (array) $request->getParsedBody(); |
65 | |
66 | if (!isset($post['uri'])) { |
67 | throw new BadFunctionCallException('Payload is missing the list URI'); |
68 | } |
69 | |
70 | $listClass = $this->listService->getList( |
71 | tao_helpers_Uri::decode($post['uri']) |
72 | ); |
73 | |
74 | if ($listClass === null) { |
75 | throw new BadFunctionCallException('Provided list class does not exist'); |
76 | } |
77 | |
78 | $payload = $post; |
79 | |
80 | if (isset($payload['label'])) { |
81 | $listClass->setLabel($payload['label']); |
82 | } |
83 | |
84 | unset($payload['uri'], $payload['label']); |
85 | |
86 | if (count($payload) > 500) { |
87 | throw new BadFunctionCallException('Payload contains too many items'); |
88 | } |
89 | |
90 | if (!$this->setListElements($listClass, $payload)) { |
91 | throw new RuntimeException('Error saving list items'); |
92 | } |
93 | } |
94 | |
95 | /** |
96 | * Updates the ValueCollection instance corresponding to a class. |
97 | * |
98 | * The payload is used to call ValueCollectionService::persist(): Depending |
99 | * on the particular underlying repository class used (instance of |
100 | * ValueCollectionRepositoryInterface), that may cause removing all existing |
101 | * items first (i.e. for remote lists) or merging the values provided with |
102 | * pre-existing values. |
103 | */ |
104 | private function setListElements( |
105 | core_kernel_classes_Class $listClass, |
106 | array $payload |
107 | ): bool { |
108 | // This method retrieves only elements corresponding to the URIs that |
109 | // are modified by the request (i.e. present in the POST data) instead |
110 | // of all list items. |
111 | // |
112 | // Retrieving existing elements is needed in order to return an accurate |
113 | // value for Value::hasModifiedUri() calls, as the repository might |
114 | // depend on that to check if an element needs to be created or updated. |
115 | // |
116 | // Note also we cannot POST two items with the same former URI, so there |
117 | // is no need to check for duplicates in the input data itself. |
118 | // |
119 | $elements = $this->getElementsFromPayload($payload); |
120 | $collection = $this->valueCollectionService->findAll( |
121 | $this->getListSearchInput($listClass, $elements) |
122 | ); |
123 | |
124 | foreach ($elements as $uriKey => $value) { |
125 | $newUri = trim($payload["uri_{$uriKey}"] ?? ''); |
126 | $this->addOneElement($collection, $uriKey, $value, $newUri); |
127 | } |
128 | |
129 | return $this->valueCollectionService->persist($collection); |
130 | } |
131 | |
132 | private function addOneElement( |
133 | ValueCollection $valueCollection, |
134 | string $uri, |
135 | string $value, |
136 | string $newUri |
137 | ): void { |
138 | $element = $this->getValueByUriKey($valueCollection, $uri); |
139 | |
140 | if ($element === null) { |
141 | $valueCollection->addValue(new Value(null, $newUri, $value)); |
142 | return; |
143 | } |
144 | |
145 | $element->setLabel($value); |
146 | |
147 | if ($newUri) { |
148 | $element->setUri($newUri); |
149 | } |
150 | } |
151 | |
152 | private function getValueByUriKey( |
153 | ValueCollection $valueCollection, |
154 | string $key |
155 | ): ?Value { |
156 | $uri = $this->getElementURIFromKey($key); |
157 | if (empty($uri)) { |
158 | return null; |
159 | } |
160 | |
161 | return $valueCollection->extractValueByUri($uri); |
162 | } |
163 | |
164 | private function getListSearchInput( |
165 | core_kernel_classes_Class $listClass, |
166 | array $elements |
167 | ): ValueCollectionSearchInput { |
168 | $uris = []; |
169 | |
170 | foreach ($elements as $key => $_value) { |
171 | $uri = $this->getElementURIFromKey($key); |
172 | if (!empty($uri)) { |
173 | $uris[] = $uri; |
174 | } |
175 | } |
176 | |
177 | $uris = array_unique($uris); |
178 | |
179 | return new ValueCollectionSearchInput( |
180 | (new ValueCollectionSearchRequest()) |
181 | ->setValueCollectionUri($listClass->getUri()) |
182 | ->setLimit(count($uris)) |
183 | ->setUris(...$uris) |
184 | ); |
185 | } |
186 | |
187 | private function getElementURIFromKey(string $key): ?string |
188 | { |
189 | return tao_helpers_Uri::decode( |
190 | preg_replace('/^list-element_[0-9]+_/', '', $key) |
191 | ); |
192 | } |
193 | |
194 | private function getElementsFromPayload(array $payload): array |
195 | { |
196 | return array_filter( |
197 | $payload, |
198 | function (string $key): bool { |
199 | return (bool)preg_match('/^list-element_/', $key); |
200 | }, |
201 | ARRAY_FILTER_USE_KEY |
202 | ); |
203 | } |
204 | } |