Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 247
0.00% covered (danger)
0.00%
0 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
Lists
0.00% covered (danger)
0.00%
0 / 247
0.00% covered (danger)
0.00%
0 / 26
4160
0.00% covered (danger)
0.00%
0 / 1
 index
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 remote
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
56
 reloadRemoteList
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 getListsData
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getListElements
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 saveLists
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
20
 create
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 rename
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 removeList
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 removeListElement
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 createList
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 getListData
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 getSortedElementsDependingOnListClass
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 createListElementsFinderContext
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 isListsDependencyEnabled
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 assertIsXmlHttpRequest
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getFeatureFlagChecker
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListCreator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListElementsFinder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListUpdater
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getListDeleter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguageClassSpecification
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguageListElementSortService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOntology
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRemoteListService
0.00% covered (danger)
0.00%
0 / 1
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) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung
21 *                         (under the project TAO-TRANSFER);
22 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
23 *                         (under the project TAO-SUSTAIN & TAO-DEV);
24 *               2013-2022 (update and modification) Open Assessment Technologies SA;
25 */
26
27declare(strict_types=1);
28
29namespace oat\taoBackOffice\controller;
30
31use oat\generis\model\data\Ontology;
32use oat\tao\helpers\Template;
33use oat\tao\model\featureFlag\FeatureFlagChecker;
34use oat\tao\model\featureFlag\FeatureFlagCheckerInterface;
35use oat\tao\model\http\HttpJsonResponseTrait;
36use oat\tao\model\Language\Business\Specification\LanguageClassSpecification;
37use oat\tao\model\Language\Service\LanguageListElementSortService;
38use oat\tao\model\Lists\Business\Contract\ListElementSorterInterface;
39use oat\tao\model\Lists\Business\Domain\CollectionType;
40use oat\tao\model\Lists\Business\Domain\ValueCollection;
41use oat\tao\model\Lists\Business\Domain\RemoteSourceContext;
42use oat\tao\model\Lists\Business\Service\RemoteSource;
43use oat\tao\model\Lists\Business\Service\RemoteSourcedListOntology;
44use oat\tao\model\Lists\Business\Service\ValueCollectionService;
45use oat\tao\model\Lists\DataAccess\Repository\ValueConflictException;
46use oat\tao\model\Specification\ClassSpecificationInterface;
47use oat\taoBackOffice\model\lists\Contract\ListDeleterInterface;
48use oat\taoBackOffice\model\lists\Contract\ListUpdaterInterface;
49use oat\taoBackOffice\model\lists\Exception\ListDeletionException;
50use oat\taoBackOffice\model\lists\RemoteListService;
51use oat\taoBackOffice\model\lists\Service\ListDeleter;
52use oat\taoBackOffice\model\lists\Service\ListUpdater;
53use oat\taoBackOffice\model\lists\ListCreatedResponse;
54use oat\taoBackOffice\model\lists\ListCreator;
55use oat\taoBackOffice\model\lists\ListService;
56use oat\taoBackOffice\model\ListElement\Context\ListElementsFinderContext;
57use oat\taoBackOffice\model\ListElement\Contract\ListElementsFinderInterface;
58use oat\taoBackOffice\model\ListElement\Service\ListElementsFinder;
59use BadFunctionCallException;
60use common_Exception;
61use common_exception_BadRequest;
62use common_exception_Error;
63use common_ext_ExtensionException;
64use core_kernel_classes_Class;
65use core_kernel_persistence_Exception;
66use tao_actions_CommonModule;
67use tao_actions_form_RemoteList;
68use tao_helpers_Scriptloader;
69use tao_helpers_Uri;
70use OverflowException;
71use RuntimeException;
72use Throwable;
73
74class Lists extends tao_actions_CommonModule
75{
76    use HttpJsonResponseTrait;
77
78    /** @var bool */
79    private $isListsDependencyEnabled;
80
81    /**
82     * This REST endpoint:
83     * - Returns the page with the lists for GET requests
84     * - Creates a new list and returns its name and URI for POST requests
85     *
86     * @throws common_Exception
87     * @throws common_ext_ExtensionException
88     * @throws core_kernel_persistence_Exception
89     */
90    public function index(): void
91    {
92        if ($this->getPsrRequest()->getMethod() === 'POST') {
93            $this->assertIsXmlHttpRequest();
94
95            $createdResponse = $this->getListCreator()->createEmptyList();
96            $this->setSuccessJsonResponse($createdResponse, 201);
97
98            return;
99        }
100
101        tao_helpers_Scriptloader::addCssFile(Template::css('lists.css', 'tao'));
102        $this->defaultData();
103
104        $this->setData('lists', $this->getListData());
105        $this->setData('maxItems', $this->getListService()->getMaxItems());
106        $this->setView('Lists/index.tpl');
107    }
108
109    /**
110     * @throws common_Exception
111     * @throws common_ext_ExtensionException
112     * @throws core_kernel_persistence_Exception
113     */
114    public function remote(): void
115    {
116        tao_helpers_Scriptloader::addCssFile(Template::css('lists.css', 'tao'));
117
118        $this->defaultData();
119
120        $remoteListFormFactory = new tao_actions_form_RemoteList(
121            [],
122            [
123                tao_actions_form_RemoteList::IS_LISTS_DEPENDENCY_ENABLED => $this->isListsDependencyEnabled(),
124            ]
125        );
126        $remoteListForm = $remoteListFormFactory->getForm();
127
128        if ($remoteListForm === null) {
129            throw new RuntimeException('Impossible to create remote sourced list form');
130        }
131
132        if ($remoteListForm->isSubmited()) {
133            if ($remoteListForm->isValid()) {
134                $values = $remoteListForm->getValues();
135                $newList = $this->createList($values);
136
137                try {
138                    $this->getRemoteListService()->sync($newList);
139                    $listElements = $this->getListElementsFinder()->find(
140                        $this->createListElementsFinderContext($newList)
141                    );
142
143                    $this->setSuccessJsonResponse(
144                        new ListCreatedResponse(
145                            $newList,
146                            $listElements->jsonSerialize(),
147                            $listElements->getTotalCount()
148                        ),
149                        201
150                    );
151
152                    return;
153                } catch (ValueConflictException $exception) {
154                    $this->setErrorJsonResponse($exception->getUserMessage());
155
156                    return;
157                } catch (RuntimeException $exception) {
158                    throw $exception;
159                } finally {
160                    if (isset($exception)) {
161                        $this->getListDeleter()->delete($newList);
162                    }
163                }
164            }
165        } else {
166            $newListLabel = __('List') . ' ' . (count($this->getListService()->getLists()) + 1);
167            $remoteListForm->getElement(tao_actions_form_RemoteList::FIELD_NAME)->setValue($newListLabel);
168        }
169
170        $this->setData('form', $remoteListForm->render());
171        $this->setData('lists', $this->getListData(true));
172        $this->setView('RemoteLists/index.tpl');
173    }
174
175    /**
176     * @throws common_Exception
177     */
178    public function reloadRemoteList(ValueCollectionService $valueCollectionService, RemoteSource $remoteSource): void
179    {
180        $this->assertIsXmlHttpRequest();
181
182        $saved = false;
183        $message = __('Attempt for reloading of remote list was not successful');
184
185        $uri = $_POST['uri'] ?? null;
186
187        if ($uri !== null) {
188            try {
189                $this->getRemoteListService()->sync(
190                    $this->getListService()->getList(tao_helpers_Uri::decode($uri)),
191                    true
192                );
193
194                $saved = true;
195                $message = __('Remote list was successfully reloaded');
196            } catch (Throwable $exception) {
197                if ($exception instanceof ValueConflictException) {
198                    $message = $exception->getUserMessage();
199                }
200            }
201        }
202
203        $this->returnJson([
204            'saved' => $saved,
205            'message' => $message,
206        ]);
207    }
208
209    /**
210     * @throws common_Exception
211     * @throws common_exception_Error
212     */
213    public function getListsData(): void
214    {
215        $this->assertIsXmlHttpRequest();
216
217        $data = [];
218
219        foreach ($this->getListService()->getLists() as $listClass) {
220            $data[] = $this->getListService()->toTree($listClass);
221        }
222
223        $this->returnJson(
224            [
225                'data' => __('Lists'),
226                'attributes' => ['class' => 'node-root'],
227                'children' => $data,
228                'state' => 'open',
229            ]
230        );
231    }
232
233    /**
234     * @throws common_Exception
235     */
236    public function getListElements(): void
237    {
238        $this->assertIsXmlHttpRequest();
239
240        $data = [
241            'elements' => [],
242            'totalCount' => 0,
243        ];
244
245        if ($this->hasGetParameter('listUri')) {
246            $listUri = tao_helpers_Uri::decode($this->getGetParameter('listUri'));
247            $list = $this->getListService()->getList($listUri);
248
249            if ($list !== null) {
250                $listElements = $this->getListElementsFinder()->find(
251                    $this->createListElementsFinderContext($list)
252                );
253
254                $data['elements'] = $this->getSortedElementsDependingOnListClass($list, $listElements);
255                $data['totalCount'] = $listElements->getTotalCount();
256            }
257        }
258
259        $this->setSuccessJsonResponse($data);
260    }
261
262    /**
263     * @throws common_exception_BadRequest
264     *
265     * @todo Use $this->setSuccessJsonResponse() & setErrorJsonResponse()
266     *       instead of returnJson(). For that, frontend should access
267     *       'success' attribute from the response instead of 'saved'
268     */
269    public function saveLists(ValueCollectionService $valueCollectionService): void
270    {
271        $this->assertIsXmlHttpRequest();
272
273        $valueCollectionService->setMaxItems(
274            $this->getListService()->getMaxItems()
275        );
276
277        try {
278            $this->getListUpdater()->updateByRequest($this->getPsrRequest());
279
280            $this->returnJson(
281                [
282                    'saved' => true
283                ]
284            );
285        } catch (BadFunctionCallException | RuntimeException $exception) {
286            $this->returnJson(
287                [
288                    'saved' => false,
289                    'errors' => [
290                        __($exception->getMessage()),
291                    ],
292                ]
293            );
294        } catch (OverflowException $exception) {
295            $this->returnJson(
296                [
297                    'saved' => false,
298                    'errors' => [
299                        __('The list exceeds the allowed number of items'),
300                    ],
301                ]
302            );
303        } catch (ValueConflictException $exception) {
304            $this->returnJson(
305                [
306                    'saved' => false,
307                    'errors' => [
308                        __('The list should contain unique URIs'),
309                    ],
310                ]
311            );
312        }
313    }
314
315    /**
316     * @throws common_Exception
317     * @throws core_kernel_persistence_Exception
318     */
319    public function create(): void
320    {
321        $this->assertIsXmlHttpRequest();
322
323        $response = [];
324
325        if ($this->hasRequestParameter('classUri')) {
326            $listService = $this->getListService();
327            $type = $this->getRequestParameter('type');
328
329            if ($type === 'class' && $this->getRequestParameter('classUri') === 'root') {
330                $createdResource = $listService->createList();
331            } elseif ($type === 'instance') {
332                $classUri = tao_helpers_Uri::decode($this->getRequestParameter('classUri'));
333                $listClass = $listService->getList($classUri);
334
335                if ($listClass !== null) {
336                    $listService->createListElement($listClass);
337                    $createdResource = iterator_to_array($listService->getListElements($listClass))[0] ?? null;
338                }
339            }
340
341            if (isset($createdResource)) {
342                $response['label'] = $createdResource->getLabel();
343                $response['uri'] = tao_helpers_Uri::encode($createdResource->getUri());
344            }
345        }
346
347        $this->returnJson($response);
348    }
349
350    /**
351     * @throws common_exception_BadRequest
352     */
353    public function rename(): void
354    {
355        $this->assertIsXmlHttpRequest();
356
357        $data = ['renamed' => false];
358
359        if ($this->hasRequestParameter('uri') && $this->hasRequestParameter('newName')) {
360            $listService = $this->getListService();
361            $newName = $this->getRequestParameter('newName');
362
363            if ($this->hasRequestParameter('classUri')) {
364                $classUri = tao_helpers_Uri::decode($this->getRequestParameter('classUri'));
365                $listClass = $listService->getList($classUri);
366                $resourceToRename = $listService->getListElement(
367                    $listClass,
368                    tao_helpers_Uri::decode($this->getRequestParameter('uri'))
369                );
370            } else {
371                $classUri = tao_helpers_Uri::decode($this->getRequestParameter('uri'));
372                $resourceToRename = $listService->getList($classUri);
373            }
374
375            if ($resourceToRename !== null) {
376                $resourceToRename->setLabel($newName);
377                $data['renamed'] = true;
378            }
379        }
380
381        $this->returnJson($data);
382    }
383
384    /**
385     * @throws common_exception_BadRequest
386     */
387    public function removeList(string $uri = null): void
388    {
389        $this->assertIsXmlHttpRequest();
390
391        $deleted = false;
392
393        if ($uri !== null) {
394            try {
395                $list = $this->getListService()->getList(tao_helpers_Uri::decode($uri));
396                $this->getListDeleter()->delete($list);
397
398                $deleted = true;
399            } catch (ListDeletionException $exception) {
400                $deleted = false;
401            }
402        }
403
404        $this->returnJson(['deleted' => $deleted]);
405    }
406
407    /**
408     * @throws common_exception_BadRequest
409     */
410    public function removeListElement(): void
411    {
412        $this->assertIsXmlHttpRequest();
413
414        $deleted = false;
415
416        if ($this->hasRequestParameter('uri')) {
417            $deleted = $this->getListService()->removeListElement(
418                $this->getOntology()->getResource(
419                    tao_helpers_Uri::decode($this->getRequestParameter('uri'))
420                )
421            );
422        }
423
424        $this->returnJson(['deleted' => $deleted]);
425    }
426
427    private function createList(array $values): core_kernel_classes_Class
428    {
429        $class = $this->getListService()->createList($values[tao_actions_form_RemoteList::FIELD_NAME]);
430
431        $propertyType = $class->getProperty(CollectionType::TYPE_PROPERTY);
432        $propertyRemote = $class->getProperty((string) CollectionType::remote());
433        $class->setPropertyValue($propertyType, $propertyRemote);
434
435        $propertySource = $class->getProperty(RemoteSourcedListOntology::PROPERTY_SOURCE_URI);
436        $class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_SOURCE_URL]);
437
438        $propertySource = $class->getProperty(RemoteSourcedListOntology::PROPERTY_ITEM_LABEL_PATH);
439        $class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_ITEM_LABEL_PATH]);
440
441        $propertySource = $class->getProperty(RemoteSourcedListOntology::PROPERTY_ITEM_URI_PATH);
442        $class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_ITEM_URI_PATH]);
443
444        if ($this->isListsDependencyEnabled()) {
445            $propertySource = $class->getProperty(RemoteSourcedListOntology::PROPERTY_DEPENDENCY_ITEM_URI_PATH);
446            $class->setPropertyValue(
447                $propertySource,
448                $values[tao_actions_form_RemoteList::FIELD_DEPENDENCY_ITEM_URI_PATH]
449            );
450        }
451
452        return $class;
453    }
454
455    /**
456     * @throws core_kernel_persistence_Exception
457     */
458    private function getListData(bool $showRemoteLists = false): array
459    {
460        $listService = $this->getListService();
461        $listElementsFinder = $this->getListElementsFinder();
462        $lists = [];
463
464        foreach ($listService->getLists() as $listClass) {
465            if ($listService->isRemote($listClass) !== $showRemoteLists) {
466                continue;
467            }
468
469            $listElements = $listElementsFinder->find($this->createListElementsFinderContext($listClass));
470
471            $lists[] = [
472                'uri' => tao_helpers_Uri::encode($listClass->getUri()),
473                'label' => $listClass->getLabel(),
474                'editable' => $listService->isEditable($listClass),
475                'elements' => $this->getSortedElementsDependingOnListClass($listClass, $listElements),
476                'totalCount' => $listElements->getTotalCount(),
477            ];
478        }
479
480        return $lists;
481    }
482
483    private function getSortedElementsDependingOnListClass(
484        core_kernel_classes_Class $listClass,
485        ValueCollection $listElements
486    ): array {
487        if ($this->getLanguageClassSpecification()->isSatisfiedBy($listClass)) {
488            return $this->getLanguageListElementSortService()->getSortedListCollectionValues($listElements);
489        }
490
491        return $listElements->jsonSerialize();
492    }
493
494    private function createListElementsFinderContext(core_kernel_classes_Class $listClass): ListElementsFinderContext
495    {
496        $parameters = [
497            ListElementsFinderContext::PARAMETER_LIST_CLASS => $listClass,
498        ];
499
500        if ($this->hasGetParameter('offset')) {
501            $parameters[ListElementsFinderContext::PARAMETER_OFFSET] = (int) $this->getGetParameter('offset');
502        }
503
504        if ($this->hasGetParameter('limit')) {
505            $parameters[ListElementsFinderContext::PARAMETER_LIMIT] = (int) $this->getGetParameter('limit');
506        }
507
508        // Todo to be able to sort limited selection we need to sort by RDBS now disabling limit for Language list
509        if ($this->getLanguageClassSpecification()->isSatisfiedBy($listClass)) {
510            $parameters[ListElementsFinderContext::PARAMETER_LIMIT] = 0;
511        }
512
513        return new ListElementsFinderContext($parameters);
514    }
515
516    private function isListsDependencyEnabled(): bool
517    {
518        if (!isset($this->isListsDependencyEnabled)) {
519            $this->isListsDependencyEnabled = $this->getFeatureFlagChecker()->isEnabled(
520                FeatureFlagCheckerInterface::FEATURE_FLAG_LISTS_DEPENDENCY_ENABLED
521            );
522        }
523
524        return $this->isListsDependencyEnabled;
525    }
526
527    private function assertIsXmlHttpRequest(): void
528    {
529        if (!$this->isXmlHttpRequest()) {
530            throw new common_exception_BadRequest('wrong request mode');
531        }
532    }
533
534    private function getFeatureFlagChecker(): FeatureFlagCheckerInterface
535    {
536        return $this->getPsrContainer()->get(FeatureFlagChecker::class);
537    }
538
539    private function getListService(): ListService
540    {
541        return $this->getPsrContainer()->get(ListService::class);
542    }
543
544    private function getListCreator(): ListCreator
545    {
546        return $this->getPsrContainer()->get(ListCreator::class);
547    }
548
549    private function getListElementsFinder(): ListElementsFinderInterface
550    {
551        return $this->getPsrContainer()->get(ListElementsFinder::class);
552    }
553
554    private function getListUpdater(): ListUpdaterInterface
555    {
556        return $this->getPsrContainer()->get(ListUpdater::class);
557    }
558
559    private function getListDeleter(): ListDeleterInterface
560    {
561        return $this->getPsrContainer()->get(ListDeleter::class);
562    }
563
564    private function getLanguageClassSpecification(): ClassSpecificationInterface
565    {
566        return $this->getPsrContainer()->get(LanguageClassSpecification::class);
567    }
568
569    private function getLanguageListElementSortService(): ListElementSorterInterface
570    {
571        return $this->getPsrContainer()->get(LanguageListElementSortService::class);
572    }
573
574    private function getOntology(): Ontology
575    {
576        return $this->getPsrContainer()->get(Ontology::SERVICE_ID);
577    }
578
579    private function getRemoteListService(): RemoteListService
580    {
581        return $this->getServiceManager()->getContainer()->get(RemoteListService::class);
582    }
583}