tao-test/app/taoBackOffice/controller/Lists.php

600 lines
21 KiB
PHP

<?php
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2);
* 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
* 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
* 2013-2018 (update and modification) Open Assessment Technologies SA;
*
*/
namespace oat\taoBackOffice\controller;
use common_exception_BadRequest;
use oat\tao\model\featureFlag\FeatureFlagChecker;
use common_ext_ExtensionException as ExtensionException;
use core_kernel_classes_Class as RdfClass;
use core_kernel_classes_Property as RdfProperty;
use core_kernel_persistence_Exception;
use oat\generis\model\OntologyAwareTrait;
use oat\tao\helpers\Template;
use oat\tao\model\Lists\Business\Domain\CollectionType;
use oat\tao\model\Lists\Business\Domain\Value;
use oat\tao\model\Lists\Business\Domain\ValueCollection;
use oat\tao\model\featureFlag\FeatureFlagCheckerInterface;
use oat\tao\model\Lists\Business\Domain\RemoteSourceContext;
use oat\tao\model\Lists\Business\Domain\ValueCollectionSearchRequest;
use oat\tao\model\Lists\Business\Input\ValueCollectionSearchInput;
use oat\tao\model\Lists\Business\Service\RemoteSource;
use oat\tao\model\Lists\Business\Service\RemoteSourcedListOntology;
use oat\tao\model\Lists\Business\Service\ValueCollectionService;
use oat\tao\model\Lists\DataAccess\Repository\ValueConflictException;
use oat\tao\model\TaoOntology;
use oat\taoBackOffice\model\lists\ListService;
use RuntimeException;
use tao_actions_CommonModule;
use tao_actions_form_List;
use tao_actions_form_RemoteList;
use tao_helpers_Scriptloader;
use tao_helpers_Uri;
use tao_models_classes_LanguageService;
/**
* This controller provide the actions to manage the lists of data
*
* @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu}
* @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php
* @package taoBackOffice
*
*/
class Lists extends tao_actions_CommonModule
{
use OntologyAwareTrait;
private const REMOTE_LIST_PREVIEW_LIMIT = 20;
/** @var bool */
private $isListsDependencyEnabled;
/**
* Show the list of users
* @return void
*/
public function index()
{
tao_helpers_Scriptloader::addCssFile(Template::css('lists.css', 'tao'));
$this->defaultData();
$myAdderFormContainer = new tao_actions_form_List();
$myForm = $myAdderFormContainer->getForm();
if ($myForm->isSubmited()) {
if ($myForm->isValid()) {
$values = $myForm->getValues();
$newList = $this->getListService()->createList($values['label']);
$i = 0;
while ($i < $values['size']) {
$this->getListService()->createListElement($newList, __('element') . ' ' . ($i + 1));
$i++;
}
}
} else {
$myForm->getElement('label')->setValue(__('List') . ' ' . (count($this->getListService()->getLists()) + 1));
}
$this->setData('form', $myForm->render());
$this->setData('lists', $this->getListData());
$this->setView('Lists/index.tpl');
}
/**
* @param ValueCollectionService $valueCollectionService
* @param RemoteSource $remoteSource
*
* @throws ExtensionException
* @throws core_kernel_persistence_Exception
*/
public function remote(
ValueCollectionService $valueCollectionService,
RemoteSource $remoteSource
) {
tao_helpers_Scriptloader::addCssFile(Template::css('lists.css', 'tao'));
$this->defaultData();
$remoteListFormFactory = new tao_actions_form_RemoteList(
[],
[
tao_actions_form_RemoteList::IS_LISTS_DEPENDENCY_ENABLED => $this->isListsDependencyEnabled(),
]
);
$remoteListForm = $remoteListFormFactory->getForm();
if ($remoteListForm === null) {
throw new RuntimeException('Impossible to create remote sourced list form');
}
if ($remoteListForm->isSubmited()) {
if ($remoteListForm->isValid()) {
$values = $remoteListForm->getValues();
$newList = $this->createList($values);
try {
$this->sync($valueCollectionService, $remoteSource, $newList);
}
catch (ValueConflictException $exception) {
$this->returnError(
$exception->getMessage() . __(' Probably given list was already imported.')
);
return;
}
catch (RuntimeException $exception) {
throw $exception;
} finally {
if (isset($exception)) {
$this->removeList(tao_helpers_Uri::encode($newList->getUri()));
}
}
}
} else {
$newListLabel = __('List') . ' ' . (count($this->getListService()->getLists()) + 1);
$remoteListForm->getElement(tao_actions_form_RemoteList::FIELD_NAME)->setValue($newListLabel);
}
$this->setData('form', $remoteListForm->render());
$this->setData('lists', $this->getListData(true));
$this->setView('RemoteLists/index.tpl');
}
/**
* @param ValueCollectionService $valueCollectionService
* @param RemoteSource $remoteSource
*
* @throws common_exception_BadRequest
*/
public function reloadRemoteList(ValueCollectionService $valueCollectionService, RemoteSource $remoteSource): void
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$saved = false;
$uri = $_POST['uri'] ?? null;
if (null !== $uri) {
$decodedUri = tao_helpers_Uri::decode($uri);
$this->sync(
$valueCollectionService,
$remoteSource,
$this->getListService()->getList($decodedUri)
);
$saved = true;
}
$this->returnJson(['saved' => $saved]);
}
private function createList(array $values): RdfClass
{
$class = $this->getListService()->createList($values[tao_actions_form_RemoteList::FIELD_NAME]);
$propertyType = new RdfProperty(CollectionType::TYPE_PROPERTY);
$propertyRemote = new RdfProperty((string) CollectionType::remote());
$class->setPropertyValue($propertyType, $propertyRemote);
$propertySource = new RdfProperty(RemoteSourcedListOntology::PROPERTY_SOURCE_URI);
$class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_SOURCE_URL]);
$propertySource = new RdfProperty(RemoteSourcedListOntology::PROPERTY_ITEM_LABEL_PATH);
$class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_ITEM_LABEL_PATH]);
$propertySource = new RdfProperty(RemoteSourcedListOntology::PROPERTY_ITEM_URI_PATH);
$class->setPropertyValue($propertySource, $values[tao_actions_form_RemoteList::FIELD_ITEM_URI_PATH]);
if ($this->isListsDependencyEnabled()) {
$propertySource = new RdfProperty(RemoteSourcedListOntology::PROPERTY_DEPENDENCY_ITEM_URI_PATH);
$class->setPropertyValue(
$propertySource,
$values[tao_actions_form_RemoteList::FIELD_DEPENDENCY_ITEM_URI_PATH]
);
}
return $class;
}
/**
* @param ValueCollectionService $valueCollectionService
* @param RemoteSource $remoteSource
* @param RdfClass $collectionClass
*
* @throws core_kernel_persistence_Exception
*/
public function sync(
ValueCollectionService $valueCollectionService,
RemoteSource $remoteSource,
RdfClass $collectionClass
): void {
$context = $this->createRemoteSourceContext($collectionClass);
$collection = new ValueCollection(
$collectionClass->getUri(),
...iterator_to_array($remoteSource->fetchByContext($context))
);
$result = $valueCollectionService->persist($collection);
if (!$result) {
throw new RuntimeException('Sync was not successful');
}
}
/**
* Returns all lists with all values for all lists
*
* @param bool $showRemoteLists
*
* @return array
* @throws core_kernel_persistence_Exception
*/
private function getListData(bool $showRemoteLists = false): array
{
$listService = $this->getListService();
$lists = [];
foreach ($listService->getLists() as $listClass) {
if ($listService->isRemote($listClass) !== $showRemoteLists) {
continue;
}
$elements = [];
foreach (
$listService->getListElements(
$listClass,
true,
$showRemoteLists ? self::REMOTE_LIST_PREVIEW_LIMIT : 0
) as $index => $listElement
) {
$elements[$index] = [
'uri' => tao_helpers_Uri::encode($listElement->getUri()),
'label' => $listElement->getLabel()
];
ksort($elements);
}
if ($showRemoteLists && count($elements) === self::REMOTE_LIST_PREVIEW_LIMIT) {
$elements[] = [
'uri' => '',
'label' => '...',
];
}
$lists[] = [
'uri' => tao_helpers_Uri::encode($listClass->getUri()),
'label' => $listClass->getLabel(),
// The Language list should not be editable.
// @todo Make two different kind of lists: system list that are not editable and usual list.
'editable' => $listClass->isSubClassOf($this->getClass(TaoOntology::CLASS_URI_LIST)) && $listClass->getUri() !== tao_models_classes_LanguageService::CLASS_URI_LANGUAGES,
'elements' => $elements
];
}
return $lists;
}
/**
* get the JSON data to populate the tree widget
*
* @throws \common_exception_Error
* @throws common_exception_BadRequest
*/
public function getListsData()
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$data = [];
foreach ($this->getListService()->getLists() as $listClass) {
$data[] = $this->getListService()->toTree($listClass);
}
$this->returnJson([
'data' => __('Lists'),
'attributes' => ['class' => 'node-root'],
'children' => $data,
'state' => 'open'
]);
}
/**
* get the elements in JSON of the list in parameter
* @throws common_exception_BadRequest
* @return void
*/
public function getListElements()
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$data = [];
if ($this->hasRequestParameter('listUri')) {
$list = $this->getListService()->getList(tao_helpers_Uri::decode($this->getRequestParameter('listUri')));
if (!is_null($list)) {
$isRemote = $this->getListService()->isRemote($list);
$limit = $isRemote
? self::REMOTE_LIST_PREVIEW_LIMIT
: 0;
foreach ($this->getListService()->getListELements($list, true, $limit) as $listElement) {
$data[tao_helpers_Uri::encode($listElement->getUri())] = $listElement->getLabel();
}
if ($isRemote && count($data) === self::REMOTE_LIST_PREVIEW_LIMIT) {
$data[''] = '...';
}
}
}
$this->returnJson($data);
}
/**
* Save a list and it's elements
*
* @param ValueCollectionService $valueCollectionService
*
* @return void
*
* @throws common_exception_BadRequest
*/
public function saveLists(ValueCollectionService $valueCollectionService): void
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
if (!$this->hasRequestParameter('uri')) {
$this->returnJson(['saved' => false]);
return;
}
$listClass = $this->getListService()->getList(tao_helpers_Uri::decode($this->getRequestParameter('uri')));
if (null === $listClass) {
$this->returnJson(['saved' => false]);
return;
}
// use $_POST instead of getRequestParameters to prevent html encoding
$payload = $_POST;
unset($payload['uri']);
if (isset($payload['label'])) {
$listClass->setLabel($payload['label']);
unset($payload['label']);
}
$elements = $valueCollectionService->findAll(
new ValueCollectionSearchInput(
(new ValueCollectionSearchRequest())
->setValueCollectionUri($listClass->getUri())
)
);
foreach ($payload as $key => $value) {
if (preg_match('/^list-element_/', $key)) {
$encodedUri = preg_replace('/^list-element_[0-9]+_/', '', $key);
$uri = tao_helpers_Uri::decode($encodedUri);
$newUriValue = trim($payload["uri_$key"] ?? '');
$element = $elements->extractValueByUri($uri);
if (null === $element) {
$elements->addValue(new Value(null, $newUriValue, $value));
} else {
$element->setLabel($value);
if ($newUriValue) {
$element->setUri($newUriValue);
}
}
}
}
try {
$this->returnJson(['saved' => $valueCollectionService->persist($elements)]);
} catch (ValueConflictException $exception) {
$this->returnJson(['saved' => false, 'errors' => [__('The list should contain unique URIs')]]);
}
}
/**
* Create a list or a list element
* @throws common_exception_BadRequest
* @return void
*/
public function create()
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$response = [];
if ($this->getRequestParameter('classUri')) {
if ($this->getRequestParameter('type') === 'class' && $this->getRequestParameter('classUri') ==='root') {
$listClass = $this->getListService()->createList();
if (!is_null($listClass)) {
$response['label'] = $listClass->getLabel();
$response['uri'] = tao_helpers_Uri::encode($listClass->getUri());
}
}
if ($this->getRequestParameter('type') === 'instance') {
$listClass = $this->getListService()->getList(tao_helpers_Uri::decode($this->getRequestParameter('classUri')));
if (!is_null($listClass)) {
$listElt = $this->getListService()->createListElement($listClass);
if (!is_null($listElt)) {
$response['label'] = $listElt->getLabel();
$response['uri'] = tao_helpers_Uri::encode($listElt->getUri());
}
}
}
}
$this->returnJson($response);
}
/**
* Rename a list node: change the label of a resource
* Render the json response with the renamed status
* @throws common_exception_BadRequest
* @return void
*/
public function rename()
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$data = ['renamed' => false];
if ($this->hasRequestParameter('uri') && $this->hasRequestParameter('newName')) {
if ($this->hasRequestParameter('classUri')) {
$listClass = $this->getListService()->getList(tao_helpers_Uri::decode($this->getRequestParameter('classUri')));
$listElt = $this->getListService()->getListElement($listClass, tao_helpers_Uri::decode($this->getRequestParameter('uri')));
if (!is_null($listElt)) {
$listElt->setLabel($this->getRequestParameter('newName'));
if ($listElt->getLabel() == $this->getRequestParameter('newName')) {
$data['renamed'] = true;
}
}
} else {
$listClass = $this->getListService()->getList(tao_helpers_Uri::decode($this->getRequestParameter('uri')));
if (!is_null($listClass)) {
$listClass->setLabel($this->getRequestParameter('newName'));
if ($listClass->getLabel() == $this->getRequestParameter('newName')) {
$data['renamed'] = true;
}
}
}
}
$this->returnJson($data);
}
/**
* Remove the list in parameter
*
* @param string|null $uri
*
* @return void
* @throws common_exception_BadRequest
*/
public function removeList(?string $uri = null): void
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$deleted = false;
if (null !== $uri) {
$decodedUri = tao_helpers_Uri::decode($uri);
$deleted = $this->getListService()->removeList(
$this->getListService()->getList($decodedUri)
);
}
$this->returnJson(['deleted' => $deleted]);
}
/**
* Remove the list element in parameter
* @throws common_exception_BadRequest
* @return void
*/
public function removeListElement()
{
if (!$this->isXmlHttpRequest()) {
throw new common_exception_BadRequest('wrong request mode');
}
$deleted = false;
if ($this->hasRequestParameter('uri')) {
$deleted = $this->getListService()->removeListElement(
$this->getResource(tao_helpers_Uri::decode($this->getRequestParameter('uri')))
);
}
$this->returnJson(['deleted' => $deleted]);
}
/**
* @return ListService
*/
protected function getListService()
{
return ListService::singleton();
}
private function createRemoteSourceContext(RdfClass $collectionClass): RemoteSourceContext
{
$sourceUrl = (string) $collectionClass->getOnePropertyValue(
$collectionClass->getProperty(RemoteSourcedListOntology::PROPERTY_SOURCE_URI)
);
$uriPath = (string) $collectionClass->getOnePropertyValue(
$collectionClass->getProperty(RemoteSourcedListOntology::PROPERTY_ITEM_URI_PATH)
);
$labelPath = (string) $collectionClass->getOnePropertyValue(
$collectionClass->getProperty(RemoteSourcedListOntology::PROPERTY_ITEM_LABEL_PATH)
);
$parameters = [
RemoteSourceContext::PARAM_SOURCE_URL => $sourceUrl,
RemoteSourceContext::PARAM_URI_PATH => $uriPath,
RemoteSourceContext::PARAM_LABEL_PATH => $labelPath,
RemoteSourceContext::PARAM_PARSER => 'jsonpath',
];
if ($this->isListsDependencyEnabled()) {
$dependencyUriPath = (string) $collectionClass->getOnePropertyValue(
$collectionClass->getProperty(RemoteSourcedListOntology::PROPERTY_DEPENDENCY_ITEM_URI_PATH)
);
$parameters[RemoteSourceContext::PARAM_DEPENDENCY_URI_PATH] = $dependencyUriPath;
}
return new RemoteSourceContext($parameters);
}
private function isListsDependencyEnabled(): bool
{
if (!isset($this->isListsDependencyEnabled)) {
$this->isListsDependencyEnabled = $this->getFeatureFlagChecker()->isEnabled(
FeatureFlagCheckerInterface::FEATURE_FLAG_LISTS_DEPENDENCY_ENABLED
);
}
return $this->isListsDependencyEnabled;
}
private function getFeatureFlagChecker(): FeatureFlagCheckerInterface
{
return $this->getServiceLocator()->get(FeatureFlagChecker::class);
}
}