Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add container handling to ResourceRelationServiceProxy #4198

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@
[AccessRule::GRANT, TaoRoles::SYSTEM_ADMINISTRATOR, Users::class],
[AccessRule::GRANT, TaoRoles::GLOBAL_MANAGER, Users::class],
[AccessRule::GRANT, TaoRoles::GLOBAL_MANAGER, ['ext' => 'tao', 'mod' => 'MetadataImport']],
[AccessRule::GRANT, TaoRoles::BACK_OFFICE, ['ext' => 'tao', 'mod' => 'ResourceRelations']],
],
'routes' => [
'/tao/api' => ['class' => ApiRoute::class],
Expand Down
28 changes: 10 additions & 18 deletions models/classes/resources/ResourceWatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,19 @@ public function catchCreatedResourceEvent(ResourceCreated $event): void
public function catchUpdatedResourceEvent(ResourceUpdated $event): void
{
$resource = $event->getResource();
$updatedAt = $this->getUpdatedAt($resource);

if ($updatedAt instanceof core_kernel_classes_Literal) {
$updatedAt = (int) $updatedAt->literal;
}

$now = microtime(true);
$threshold = $this->getOption(self::OPTION_THRESHOLD);

if ($updatedAt === null || ($now - $updatedAt) > $threshold) {
$this->getLogger()->debug(
'triggering index update on resourceUpdated event'
);
$this->getLogger()->debug(
'triggering index update on resourceUpdated event'
);

$property = $this->getProperty(TaoOntology::PROPERTY_UPDATED_AT);
$this->updatedAtCache[$resource->getUri()] = $now;
$resource->editPropertyValues($property, $now);
$property = $this->getProperty(TaoOntology::PROPERTY_UPDATED_AT);
$this->updatedAtCache[$resource->getUri()] = $now;
$resource->editPropertyValues($property, $now);

$taskMessage = __('Adding/updating search index for updated resource');
$this->createResourceIndexingTask($resource, $taskMessage);
}
$taskMessage = __('Adding/updating search index for updated resource');
$this->createResourceIndexingTask($resource, $taskMessage);
}

public function catchDeletedResourceEvent(ResourceDeleted $event): void
Expand All @@ -117,7 +109,7 @@ public function catchDeletedResourceEvent(ResourceDeleted $event): void
}
}

/**
/**
* @return \core_kernel_classes_Container
* @throws \core_kernel_persistence_Exception
*/
Expand All @@ -129,7 +121,7 @@ public function getUpdatedAt(core_kernel_classes_Resource $resource)
$property = $this->getProperty(TaoOntology::PROPERTY_UPDATED_AT);
$updatedAt = $resource->getOnePropertyValue($property);
if ($updatedAt && $updatedAt instanceof core_kernel_classes_Literal) {
$updatedAt = (int) $updatedAt->literal;
$updatedAt = (int)$updatedAt->literal;
}
$this->updatedAtCache[$resource->getUri()] = $updatedAt;
}
Expand Down
2 changes: 2 additions & 0 deletions models/classes/resources/ResourcesServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
namespace oat\tao\model\resources;

use oat\generis\model\data\Ontology;
use oat\tao\model\resources\relation\service\ResourceRelationServiceProxy;
use oat\tao\model\resources\Service\ClassDeleter;
use oat\tao\model\accessControl\PermissionChecker;
use oat\generis\model\resource\Repository\ClassRepository;
Expand Down Expand Up @@ -66,6 +67,7 @@ public function __invoke(ContainerConfigurator $configurator): void
service(Ontology::SERVICE_ID),
service(ResourceRepository::class),
service(ClassRepository::class),
service(ResourceRelationServiceProxy::SERVICE_ID)
]
);
}
Expand Down
54 changes: 47 additions & 7 deletions models/classes/resources/Service/ClassDeleter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

namespace oat\tao\model\resources\Service;

use oat\tao\model\resources\relation\FindAllQuery;
use oat\tao\model\resources\relation\ResourceRelationCollection;
use oat\tao\model\resources\relation\service\ResourceRelationServiceProxy;
use oat\tao\model\TaoOntology;
use Throwable;
use core_kernel_classes_Class;
use core_kernel_classes_Property;
Expand All @@ -37,6 +41,9 @@

class ClassDeleter implements ClassDeleterInterface
{
private const RELATION_RESOURCE_MAP = [
TaoOntology::CLASS_URI_ITEM => 'itemClass'
];
private const PROPERTY_INDEX = OntologyIndex::PROPERTY_INDEX;

/** @var ClassSpecificationInterface */
Expand All @@ -59,19 +66,22 @@ class ClassDeleter implements ClassDeleterInterface

/** @var core_kernel_classes_Class|null */
private $selectedClass;
private ResourceRelationServiceProxy $resourceRelationServiceProxy;

public function __construct(
ClassSpecificationInterface $rootClassSpecification,
PermissionCheckerInterface $permissionChecker,
Ontology $ontology,
ResourceRepositoryInterface $resourceRepository,
ResourceRepositoryInterface $classRepository
ResourceRepositoryInterface $classRepository,
ResourceRelationServiceProxy $resourceRelationServiceProxy
) {
$this->rootClassSpecification = $rootClassSpecification;
$this->permissionChecker = $permissionChecker;
$this->ontology = $ontology;
$this->resourceRepository = $resourceRepository;
$this->classRepository = $classRepository;
$this->resourceRelationServiceProxy = $resourceRelationServiceProxy;

$this->propertyIndex = $ontology->getProperty(self::PROPERTY_INDEX);
}
Expand Down Expand Up @@ -102,11 +112,8 @@ public function delete(core_kernel_classes_Class $class): void

if ($class->exists()) {
throw new PartialClassDeletionException(
'Unable to delete the selected resource because you do not have the required rights to delete '
. 'part of its content.',
// phpcs:disable Generic.Files.LineLength
__('Unable to delete the selected resource because you do not have the required rights to delete part of its content.')
// phpcs:enable Generic.Files.LineLength
'Some of resources has not be deleted',
__('Some of resources has not be deleted')
);
}
}
Expand Down Expand Up @@ -159,8 +166,14 @@ private function deleteClassContent(core_kernel_classes_Class $class, bool $isCl
private function deleteInstances(core_kernel_classes_Class $class): bool
{
$status = true;
$resources = $class->getInstances();
if ($query = $this->createQuery($class)) {
$itemsInUse = $this->resourceRelationServiceProxy->findRelations($query);
$resources = $this->filterInstances($resources, $itemsInUse);
$status = false;
}

foreach ($class->getInstances() as $instance) {
foreach ($resources as $instance) {
if (!$instance->exists()) {
continue;
}
Expand Down Expand Up @@ -224,4 +237,31 @@ private function deleteProperty(core_kernel_classes_Property $property): bool

return true;
}

private function defineResourceType(core_kernel_classes_Class $class): ?string
{
if (isset(self::RELATION_RESOURCE_MAP[$class->getRootId()])) {
return self::RELATION_RESOURCE_MAP[$class->getRootId()];
}

return null;
}

private function createQuery($class): ?FindAllQuery
{
if ($this->defineResourceType($class)) {
return new FindAllQuery(null, $class->getUri(), $this->defineResourceType($class));
}

return null;
}

private function filterInstances(array $resourceCollection, ResourceRelationCollection $itemsInUse): iterable
{
foreach ($itemsInUse->getIterator() as $item) {
unset($resourceCollection[$item->getId()]);
}

return $resourceCollection;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* 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) 2020 (original work) Open Assessment Technologies SA;
* Copyright (c) 2020-2025 (original work) Open Assessment Technologies SA;
*/

declare(strict_types=1);
Expand Down Expand Up @@ -64,10 +64,12 @@ public function findRelations(FindAllQuery $query): ResourceRelationCollection
continue;
}

$container = $this->getServiceManager()->getContainer();

foreach ($services as $serviceId) {
$relations = array_merge(
$relations,
$this->getResourceRelationService($serviceId)
$container->get($serviceId)
->findRelations($query)
->getIterator()
->getArrayCopy()
Expand All @@ -78,11 +80,6 @@ public function findRelations(FindAllQuery $query): ResourceRelationCollection
return new ResourceRelationCollection(...$relations);
}

private function getResourceRelationService(string $serviceId): ResourceRelationServiceInterface
{
return $this->getServiceLocator()->get($serviceId);
}

private function getServices(string $type): array
{
$services = (array)$this->getOption(self::OPTION_SERVICES, []);
Expand Down
83 changes: 82 additions & 1 deletion test/unit/models/classes/resources/Service/ClassDeleterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@

namespace oat\tao\test\unit\model\resources\Service;

use ArrayIterator;
use core_kernel_classes_Class;
use oat\generis\test\TestCase;
use core_kernel_classes_Property;
use core_kernel_classes_Resource;
use oat\generis\model\data\Ontology;
use oat\tao\model\resources\relation\ResourceRelation;
use oat\tao\model\resources\relation\ResourceRelationCollection;
use oat\tao\model\resources\relation\service\ResourceRelationServiceProxy;
use oat\tao\model\TaoOntology;
use PHPUnit\Framework\MockObject\MockObject;
use oat\tao\model\resources\Service\ClassDeleter;
use oat\tao\model\accessControl\PermissionCheckerInterface;
Expand Down Expand Up @@ -71,13 +76,17 @@ protected function setUp(): void

$this->resourceRepository = $this->createMock(ResourceRepositoryInterface::class);
$this->classRepository = $this->createMock(ResourceRepositoryInterface::class);
$this->resourceRelationServiceProxyMock = $this->createMock(
ResourceRelationServiceProxy::class
);

$this->sut = new ClassDeleter(
$this->rootClassSpecification,
$this->permissionChecker,
$this->ontology,
$this->resourceRepository,
$this->classRepository
$this->classRepository,
$this->resourceRelationServiceProxyMock
);
}

Expand Down Expand Up @@ -834,4 +843,76 @@ static function (string $uri): bool {

$this->sut->delete($class);
}

public function testDeleteClassWithResourceWithRelations(): void
{
$class = $this->createMock(core_kernel_classes_Class::class);

$this->rootClassSpecification
->method('isSatisfiedBy')
->willReturn(false);

$this->permissionChecker
->method('hasReadAccess')
->willReturn(true);

$class->expects($this->once())
->method('getSubClasses')
->willReturn([]);

$classResource = $this->createMock(core_kernel_classes_Resource::class);
$class->expects($this->once())
->method('getInstances')
->willReturn([
'resourceRelationId' => $classResource,
'not-used-id' => $classResource
]);

$class->method('getRootId')
->willReturn(TaoOntology::CLASS_URI_ITEM);

$resourceRelationCollectionMock = $this->createMock(ResourceRelationCollection::class);

$this->resourceRelationServiceProxyMock->expects($this->once())
->method('findRelations')
->willReturn($resourceRelationCollectionMock);

$resourceRelationMock = $this->createMock(ResourceRelation::class);

$resourceRelationCollectionMock->expects($this->once())
->method('getIterator')
->willReturn(new ArrayIterator([
$resourceRelationMock,
$resourceRelationMock
]));

$resourceRelationMock->expects($this->exactly(2))
->method('getId')
->willReturnOnConsecutiveCalls(
'resourceRelationId',
'some-mistake'
);

$classResource
->method('exists')
->willReturn(true);

$this->permissionChecker->expects($this->once())
->method('hasWriteAccess')
->willReturn(true);

$this->resourceRepository
->expects($this->once())
->method('delete');

$this->classRepository->expects($this->never())
->method('delete');

$class->expects($this->once())
->method('exists')
->willReturn(true);

$this->expectException(PartialClassDeletionException::class);
$this->sut->delete($class);
}
}
Loading