Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/dev/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
CI bot committed Jan 6, 2025
2 parents f0ed629 + 26ac517 commit 8960cdc
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace Oro\Bundle\ApiBundle\Processor\CustomizeLoadedData;

use Oro\Bundle\ApiBundle\Config\EntityDefinitionConfig;
use Oro\Bundle\ApiBundle\Config\Extra\ConfigExtraInterface;
use Oro\Bundle\ApiBundle\Config\Extra\HateoasConfigExtra;
use Oro\Bundle\ApiBundle\Processor\CustomizeDataContext;
use Oro\Bundle\ApiBundle\Util\ConfigUtil;

/**
* The execution context for processors for "customize_loaded_data" action.
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class CustomizeLoadedDataContext extends CustomizeDataContext
{
Expand Down Expand Up @@ -63,9 +65,11 @@ public function setIdentifierOnly(bool $identifierOnly): void
/**
* Gets the name under which the given field should be represented in response data.
*/
public function getResultFieldName(string $propertyPath): ?string
public function getResultFieldName(string $propertyPath, ?EntityDefinitionConfig $config = null): ?string
{
$config = $this->getConfig();
if (null === $config) {
$config = $this->getConfig();
}
if (null === $config) {
return $propertyPath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ private function mergeDefinition(EntityDefinitionConfig $config, EntityDefinitio
$config->setKey($configToMerge->getKey());
$this->mergeUpsertConfig($config->getUpsertConfig(), $configToMerge->getUpsertConfig());
$this->mergeEntityConfigAttributes($config, $configToMerge);
if ($configToMerge->hasAclResource() && null === $configToMerge->getAclResource()) {
$config->setAclResource(null);
}
if ($configToMerge->getIdentifierFieldNames()) {
$config->setIdentifierFieldNames($configToMerge->getIdentifierFieldNames());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
*/
class NormalizeIncludedData implements ProcessorInterface
{
private const string CALLBACKS = '_normalize_included_data_callbacks';

private DoctrineHelper $doctrineHelper;
private EntityInstantiator $entityInstantiator;
private AclProtectedEntityLoader $entityLoader;
Expand Down Expand Up @@ -73,6 +75,25 @@ public function __construct(
$this->upsertCriteriaBuilder = $upsertCriteriaBuilder;
}

/**
* Registers a callback function that should be used to normalize the given entity type.
* The callback function should have the following signature:
* function (
* mixed $entityIdOrCriteria,
* EntityDefinitionConfig $config,
* EntityIdMetadataInterface $metadata
* ): ?object
*/
public static function registerNormalizeIncludedDataCallback(
FormContext $context,
string $entityClass,
callable $callback
): void {
$callbacks = $context->get(self::CALLBACKS) ?? [];
$callbacks[$entityClass] = $callback;
$context->set(self::CALLBACKS, $callbacks);
}

#[\Override]
public function process(ContextInterface $context): void
{
Expand Down Expand Up @@ -122,6 +143,7 @@ private function normalizeIncludedData(array $requestData): array

private function loadIncludedEntities(array $includedData): ?IncludedEntityCollection
{
$callbacks = $this->context->get(self::CALLBACKS) ?? [];
$includedEntities = new IncludedEntityCollection();
$includedPointer = $this->buildPointer('', JsonApiDoc::INCLUDED);
foreach ($includedData as $index => $data) {
Expand All @@ -145,7 +167,8 @@ private function loadIncludedEntities(array $includedData): ?IncludedEntityColle
$upsertFlag,
$data,
$index,
$pointer
$pointer,
$callbacks[$entityClass] ?? null
);
}

Expand All @@ -167,7 +190,8 @@ private function loadIncludedEntity(
bool|array|null $upsertFlag,
array $data,
int $index,
string $pointer
string $pointer,
?callable $callback
): void {
$entityId = $entityIncludeId;
if (null !== $entityId) {
Expand All @@ -185,21 +209,23 @@ private function loadIncludedEntity(
} else {
$hasErrors = false;
$resolvedEntityClass = $this->doctrineHelper->resolveManageableEntityClass($entityClass);
if ($resolvedEntityClass) {
if ($resolvedEntityClass || null !== $callback) {
if ($upsertConfig->isAllowedFields($upsertFlag)) {
$metadata = $this->getEntityMetadata($entityClass, true);
$criteria = $this->getUpsertFindEntityCriteria($metadata, $upsertFlag, $data, $pointer);
if (null === $criteria) {
$hasErrors = true;
} else {
try {
$entity = $this->entityLoader->findEntityBy(
$resolvedEntityClass,
$criteria,
$config,
$metadata,
$this->context->getRequestType()
);
$entity = null !== $callback
? $callback($criteria, $config, $metadata)
: $this->entityLoader->findEntityBy(
$resolvedEntityClass,
$criteria,
$config,
$metadata,
$this->context->getRequestType()
);
} catch (AccessDeniedException $e) {
$hasErrors = true;
$this->addAccessDeniedValidationError($e, $pointer);
Expand Down Expand Up @@ -245,15 +271,19 @@ private function loadIncludedEntity(
}
if (!$hasErrors) {
$resolvedEntityClass = $this->doctrineHelper->resolveManageableEntityClass($entityClass);
if ($resolvedEntityClass) {
if ($resolvedEntityClass || null !== $callback) {
$entityConfig = $this->getEntityConfig($entityClass);
$entityMetadata = $this->getEntityMetadata($resolvedEntityClass ?? $entityClass);
try {
$entity = $this->entityLoader->findEntity(
$resolvedEntityClass,
$entityId,
$this->getEntityConfig($entityClass),
$this->getEntityMetadata($resolvedEntityClass),
$this->context->getRequestType()
);
$entity = null !== $callback
? $callback($entityId, $entityConfig, $entityMetadata)
: $this->entityLoader->findEntity(
$resolvedEntityClass,
$entityId,
$entityConfig,
$entityMetadata,
$this->context->getRequestType()
);
} catch (AccessDeniedException $e) {
$hasErrors = true;
$this->addAccessDeniedValidationError($e, $pointer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public function process(ContextInterface $context): void
$context->getRequestType()
);
if (\in_array($context->getAction(), $excludeActions, true)) {
throw new ActionNotAllowedException();
throw new ActionNotAllowedException(
$context->isMainRequest()
? 'The action is not allowed.'
: \sprintf('The "%s" action is not allowed.', $context->getAction())
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ public function testTryToCreateIncludedEntityWhenCreateActionForItIsDisabled()
$this->assertResponseValidationError(
[
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "create" action is not allowed.',
'source' => ['pointer' => '/included/0']
],
$response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public function testTryToCreateEntityWhenCreateActionDisabled()
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "create" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down Expand Up @@ -349,7 +349,7 @@ public function testTryToCreateEntityWhenCreateActionDisabledAfterUpdateListRequ
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "create" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down Expand Up @@ -392,7 +392,7 @@ public function testTryToCreateEntityWhenGetActionDisabledAfterUpdateListRequest
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "create" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down Expand Up @@ -635,7 +635,7 @@ public function testTryToUpdateEntityWhenUpdateActionDisabled()
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "update" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down Expand Up @@ -677,7 +677,7 @@ public function testTryToUpdateEntityWhenUpdateActionDisabledAfterUpdateListRequ
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "update" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down Expand Up @@ -721,7 +721,7 @@ public function testTryToUpdateEntityWhenGetActionDisabledAfterUpdateListRequest
'id' => $operationId . '-1-1',
'status' => 405,
'title' => 'action not allowed exception',
'detail' => 'The action is not allowed.',
'detail' => 'The "update" action is not allowed.',
'source' => ['pointer' => '/data/0']
],
$operationId
Expand Down
29 changes: 29 additions & 0 deletions src/Oro/Bundle/ApiBundle/Tests/Unit/Fixtures/Model/SomeModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Oro\Bundle\ApiBundle\Tests\Unit\Fixtures\Model;

class SomeModel
{
private ?int $id;
private ?string $name = null;

public function __construct(?int $id)
{
$this->id = $id;
}

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name): void
{
$this->name = $name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,38 @@ public function testGetResultFieldValueWhenDataDoesNotHaveFieldValue()
self::assertNull($this->context->getResultFieldValue($propertyName, $data));
}

public function testGetResultFieldNameForSpecifiedConfigWhenFieldDoesNotExist()
{
$propertyPath = 'test';
$config = new EntityDefinitionConfig();
self::assertEquals($propertyPath, $this->context->getResultFieldName($propertyPath, $config));
}

public function testGetResultFieldNameForSpecifiedConfigForNotRenamedField()
{
$propertyPath = 'test';
$config = new EntityDefinitionConfig();
$config->addField($propertyPath);
self::assertEquals($propertyPath, $this->context->getResultFieldName($propertyPath, $config));
}

public function testGetResultFieldNameForSpecifiedConfigForRenamedField()
{
$fieldName = 'renamedTest';
$propertyPath = 'test';
$config = new EntityDefinitionConfig();
$config->addField($fieldName)->setPropertyPath($propertyPath);
self::assertEquals($fieldName, $this->context->getResultFieldName($propertyPath, $config));
}

public function testGetResultFieldNameForSpecifiedConfigForComputedField()
{
$fieldName = 'test';
$config = new EntityDefinitionConfig();
$config->addField($fieldName)->setPropertyPath(ConfigUtil::IGNORE_PROPERTY_PATH);
self::assertEquals($fieldName, $this->context->getResultFieldName($fieldName, $config));
}

public function testGetResultFieldValueByPropertyPathWithoutConfig()
{
$data = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ public function testMergeParentDefinition(): void
);
}

public function testMergeParentDefinitionWithNullAclResource(): void
{
$parentResourceClass = 'Test\ParentEntity';

$parentDefinition = new EntityDefinitionConfig();
$parentDefinition->setExcludeAll();
$parentDefinition->setKey('parent key');
$parentDefinition->setAclResource('parent_entity_acl');

$definition = new EntityDefinitionConfig();
$definition->setKey('key');
$definition->setAclResource(null);

$this->context->setResult($definition);
$this->loadParentConfig($parentResourceClass, $this->getConfig($parentDefinition));
$this->mergeParentResourceHelper->mergeParentResourceConfig($this->context, $parentResourceClass);

self::assertSame($parentDefinition, $this->context->getResult());
self::assertFalse($parentDefinition->getUpsertConfig()->hasEnabled());
self::assertEquals('key', $parentDefinition->getKey());
self::assertEquals(
[
'parent_resource_class' => 'Test\ParentEntity',
'acl_resource' => null
],
$parentDefinition->toArray()
);
}

public function testMergeParentDefinitionWhenEntityHasAnotherIdentifierField(): void
{
$parentResourceClass = 'Test\ParentEntity';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,35 @@ protected function setUp(): void
$this->processor = new ValidateActionAvailability($this->resourcesProvider);
}

public function testProcessWhenActionIsExcluded()
public function testProcessForPrimaryEntityWhenActionIsExcluded()
{
$this->expectException(ActionNotAllowedException::class);
$this->expectExceptionMessage('The action is not allowed.');
$entityClass = 'Test\Class';

$this->resourcesProvider->expects(self::once())
->method('getResourceExcludeActions')
->with($entityClass, $this->context->getVersion(), $this->context->getRequestType())
->willReturn(['action1', 'action2']);

$this->context->setMasterRequest(true);
$this->context->setClassName($entityClass);
$this->context->setAction('action1');
$this->processor->process($this->context);
}

public function testProcessForIncludedEntityWhenActionIsExcluded()
{
$this->expectException(ActionNotAllowedException::class);
$this->expectExceptionMessage('The "action1" action is not allowed.');
$entityClass = 'Test\Class';

$this->resourcesProvider->expects(self::once())
->method('getResourceExcludeActions')
->with($entityClass, $this->context->getVersion(), $this->context->getRequestType())
->willReturn(['action1', 'action2']);

$this->context->setMasterRequest(false);
$this->context->setClassName($entityClass);
$this->context->setAction('action1');
$this->processor->process($this->context);
Expand Down
Loading

0 comments on commit 8960cdc

Please sign in to comment.