From 5b2575c8f28530db221d718386235d4e05dbcaf2 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 10:40:17 +0200 Subject: [PATCH 01/10] Handling own/other permissions for API v2 --- Config/config.php | 3 + Entity/CustomField.php | 8 +- Entity/CustomFieldOption.php | 8 +- Entity/CustomItem.php | 8 +- Entity/CustomObject.php | 6 +- ...ApiPlatformPermissionContextSubscriber.php | 99 +++++++++++++++ .../ApiPlatform/CustomItemFunctionalTest.php | 113 +++++++++++++++++- 7 files changed, 228 insertions(+), 17 deletions(-) create mode 100644 EventListener/ApiPlatformPermissionContextSubscriber.php diff --git a/Config/config.php b/Config/config.php index fea80efa2..add337b95 100644 --- a/Config/config.php +++ b/Config/config.php @@ -637,6 +637,9 @@ ], ], 'events' => [ + 'custom_objects.api_platform.permission_context.subscriber' => [ + 'class' => \MauticPlugin\CustomObjectsBundle\EventListener\ApiPlatformPermissionContextSubscriber::class, + ], 'custom_object.api.subscriber' => [ 'class' => \MauticPlugin\CustomObjectsBundle\EventListener\ApiSubscriber::class, 'arguments' => [ diff --git a/Entity/CustomField.php b/Entity/CustomField.php index d49bbef0f..94e6062fb 100644 --- a/Entity/CustomField.php +++ b/Entity/CustomField.php @@ -42,10 +42,10 @@ * "post"={"security"="'custom_objects:custom_fields:create'"} * }, * itemOperations={ - * "get"={"security"="'custom_objects:custom_fields:view'"}, - * "put"={"security"="'custom_objects:custom_fields:edit'"}, - * "patch"={"security"="'custom_objects:custom_fields:edit'"}, - * "delete"={"security"="'custom_objects:custom_fields:delete'"} + * "get"={"security"="is_granted('custom_objects:custom_fields:viewown', object)"}, + * "put"={"security"="is_granted('custom_objects:custom_fields:editown', object)"}, + * "patch"={"security"="is_granted('custom_objects:custom_fields:editown', object)"}, + * "delete"={"security"="is_granted('custom_objects:custom_fields:deleteown', object)"} * }, * shortName="custom_fields", * normalizationContext={"groups"={"custom_field:read"}, "swagger_definition_name"="Read"}, diff --git a/Entity/CustomFieldOption.php b/Entity/CustomFieldOption.php index f1073d4ad..d2eb4fb4f 100644 --- a/Entity/CustomFieldOption.php +++ b/Entity/CustomFieldOption.php @@ -23,10 +23,10 @@ * "post"={"security"="'custom_objects:custom_fields:create'"} * }, * itemOperations={ - * "get"={"security"="'custom_objects:custom_fields:view(getCustomField)'"}, - * "put"={"security"="'custom_objects:custom_fields:edit(getCustomField)'"}, - * "patch"={"security"="'custom_objects:custom_fields:edit(getCustomField)'"}, - * "delete"={"security"="'custom_objects:custom_fields:delete(getCustomField)'"} + * "get"={"security"="is_granted('custom_objects:custom_fields:viewown(getCustomField)', object)"}, + * "put"={"security"="is_granted('custom_objects:custom_fields:editown(getCustomField)', object)"}, + * "patch"={"security"="is_granted('custom_objects:custom_fields:editown(getCustomField)', object)"}, + * "delete"={"security"="is_granted('custom_objects:custom_fields:deleteown(getCustomField)', object)"} * }, * shortName="custom_field_options" * ) diff --git a/Entity/CustomItem.php b/Entity/CustomItem.php index 549117951..22d6b4c82 100644 --- a/Entity/CustomItem.php +++ b/Entity/CustomItem.php @@ -32,10 +32,10 @@ * "post"={"security"="'custom_objects:[customObject]:create'"} * }, * itemOperations={ - * "get"={"security"="'custom_objects:[customObject]:view'"}, - * "put"={"security"="'custom_objects:[customObject]:edit'"}, - * "patch"={"security"="'custom_objects:[customObject]:edit'"}, - * "delete"={"security"="'custom_objects:[customObject]:delete'"} + * "get"={"security"="is_granted('custom_objects:[customObject]:viewown', object)"}, + * "put"={"security"="is_granted('custom_objects:[customObject]:editown', object)"}, + * "patch"={"security"="is_granted('custom_objects:[customObject]:editown', object)"}, + * "delete"={"security"="is_granted('custom_objects:[customObject]:deleteown', object)"} * }, * shortName="custom_items", * normalizationContext={"groups"={"custom_item:read"}, "swagger_definition_name"="Read"}, diff --git a/Entity/CustomObject.php b/Entity/CustomObject.php index f98e5f5b5..51b013fcb 100644 --- a/Entity/CustomObject.php +++ b/Entity/CustomObject.php @@ -30,9 +30,9 @@ * "post"={"security"="'custom_objects:custom_objects:create'"} * }, * itemOperations={ - * "get"={"security"="'custom_objects:custom_objects:view'"}, - * "patch"={"security"="'custom_objects:custom_objects:edit'"}, - * "delete"={"security"="'custom_objects:custom_objects:delete'"} + * "get"={"security"="is_granted('custom_objects:custom_objects:viewown', object)"}, + * "patch"={"security"="is_granted('custom_objects:custom_objects:editown', object)"}, + * "delete"={"security"="is_granted('custom_objects:custom_objects:deleteown', object)"} * }, * shortName="custom_objects", * normalizationContext={"groups"={"custom_object:read"}, "swagger_definition_name"="Read"}, diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php new file mode 100644 index 000000000..08ef48842 --- /dev/null +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -0,0 +1,99 @@ + ['onApiPlatformPermissionContext', 0], + ]; + } + + public function onApiPlatformPermissionContext(\Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent $event): void + { + $permission = $event->getPermission(); + + if (0 !== strpos($permission, 'custom_objects:')) { + return; + } + + if (false === strpos($permission, '[') && false === strpos($permission, '(')) { + return; + } + + $requestObject = $event->getRequestObject(); + $objectPath = $this->extractObjectPath($permission); + + if (null !== $objectPath) { + $requestObject = $this->resolveRequestObject($requestObject, $objectPath); + $permission = substr($permission, 0, strpos($permission, '(')); + } + + $event->setRequestObject($requestObject); + $event->setPermission($this->resolvePermissionPlaceholder($event, $requestObject, $permission)); + } + + private function extractObjectPath(string $permission): ?string + { + if (preg_match('#\((.*?)\)#', $permission, $match) && !empty($match[1])) { + return $match[1]; + } + + return null; + } + + private function resolveRequestObject(mixed $requestObject, string $objectPath): mixed + { + foreach (explode('.', $objectPath) as $property) { + if (!is_object($requestObject) || !method_exists($requestObject, $property)) { + return $requestObject; + } + + $requestObject = $requestObject->$property(); + } + + return $requestObject; + } + + private function resolvePermissionPlaceholder(\Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent $event, mixed $requestObject, string $permission): string + { + if (!preg_match('#\[(.*?)\]#', $permission, $match) || empty($match[1])) { + return $permission; + } + + $property = $match[1]; + $objectId = null; + $request = $event->getRequest(); + $content = $request instanceof Request ? json_decode($request->getContent(), true) : null; + + if (is_array($content) && array_key_exists($property, $content)) { + $objectId = $content[$property]; + } elseif (is_object($requestObject)) { + $getter = 'get'.ucfirst($property); + if (method_exists($requestObject, $getter)) { + $objectId = $requestObject->$getter(); + } + } + + if (is_object($objectId) && method_exists($objectId, 'getId')) { + $objectId = $objectId->getId(); + } + + if (is_string($objectId) && false !== strrpos($objectId, '/')) { + $objectId = substr($objectId, strrpos($objectId, '/') + 1); + } + + if (null === $objectId || '' === $objectId) { + return $permission; + } + + return preg_replace('#\[(.*?)\]#', (string) $objectId, $permission, 1) ?? $permission; + } +} \ No newline at end of file diff --git a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php index 0a7594a93..47604d312 100644 --- a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php @@ -70,6 +70,108 @@ public function getCustomItemsDataProvider(): iterable yield [['viewown', 'editown', 'create', 'deleteown', 'publishown'], Response::HTTP_FORBIDDEN]; } + public function testGetCustomItemDeniesOwnPermissionForOtherUsersItem(): void + { + $user = $this->getUser(); + + self::assertNotNull($user); + + $customItem = $this->createCustomItem(['viewown'], false, $user->getId() + 9999); + $response = $this->retrieveEntity('/api/v2/custom_items/'.$customItem->getId()); + $json = json_decode($response->getContent(), true); + + self::assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertAccessForbiddenContent($json); + } + + /** + * @dataProvider ownedCustomItemOperationsDataProvider + * + * @param array $permissions + */ + public function testOwnedCustomItemAllowsOwnScopedOperations(callable $operation, callable $assertion, array $permissions, int $expectedResponse): void + { + $customItem = $this->createCustomItem($permissions, true); + + $response = $operation($this, $customItem); + + self::assertSame($expectedResponse, $response->getStatusCode()); + + $assertion($this, $response, $customItem); + } + + /** + * @return iterable, 3: int}> + */ + public function ownedCustomItemOperationsDataProvider(): iterable + { + $getOperation = static fn (self $test, CustomItem $customItem): Response => $test->retrieveEntity('/api/v2/custom_items/'.$customItem->getId()); + $putOperation = static fn (self $test, CustomItem $customItem): Response => $test->updateEntity('/api/v2/custom_items/'.$customItem->getId(), [ + 'name' => 'Custom Item Edited', + 'fieldValues' => [ + [ + 'id' => '/api/v2/custom_fields/'.$customItem->getCustomObject()->getCustomFields()->first()->getId(), + 'value' => 'test3', + ], + ], + ]); + $patchOperation = static fn (self $test, CustomItem $customItem): Response => $test->patchEntity('/api/v2/custom_items/'.$customItem->getId(), [ + 'fieldValues' => [ + [ + 'id' => '/api/v2/custom_fields/'.$customItem->getCustomObject()->getCustomFields()->first()->getId(), + 'value' => 'test2', + ], + ], + ]); + $deleteOperation = static fn (self $test, CustomItem $customItem): Response => $test->deleteEntity('/api/v2/custom_items/'.$customItem->getId()); + + $forbiddenAssertion = static function (self $test, Response $response): void { + $json = json_decode($response->getContent(), true); + $test->assertAccessForbiddenContent($json); + }; + $getSuccessAssertion = static function (self $test, Response $response, CustomItem $customItem): void { + $json = json_decode($response->getContent(), true); + $test->assertSuccessContent($json, $customItem); + }; + $writeSuccessAssertion = static function (self $test, Response $response, CustomItem $customItem): void { + $json = json_decode($response->getContent(), true); + $test->em->clear(); + $customItem = $test->em->getRepository(CustomItem::class)->find($customItem->getId()); + $test->customItemModel->populateCustomFields($customItem); + $test->assertSuccessContent($json, $customItem); + }; + $deleteSuccessAssertion = static function (self $test, Response $response, CustomItem $customItem): void { + $json = json_decode($response->getContent(), true); + $test->em->clear(); + self::assertNull($json); + self::assertNull($test->em->getRepository(CustomItem::class)->find($customItem->getId())); + }; + + yield [$getOperation, $getSuccessAssertion, ['viewown'], Response::HTTP_OK]; + + yield [$getOperation, $forbiddenAssertion, [], Response::HTTP_FORBIDDEN]; + + yield [$getOperation, $getSuccessAssertion, ['viewown', 'viewother'], Response::HTTP_OK]; + + yield [$putOperation, $writeSuccessAssertion, ['editown'], Response::HTTP_OK]; + + yield [$putOperation, $forbiddenAssertion, ['editother'], Response::HTTP_FORBIDDEN]; + + yield [$putOperation, $writeSuccessAssertion, ['editown', 'editother'], Response::HTTP_OK]; + + yield [$patchOperation, $writeSuccessAssertion, ['editown'], Response::HTTP_OK]; + + yield [$patchOperation, $forbiddenAssertion, ['editother'], Response::HTTP_FORBIDDEN]; + + yield [$patchOperation, $writeSuccessAssertion, ['editown', 'editother'], Response::HTTP_OK]; + + yield [$deleteOperation, $deleteSuccessAssertion, ['deleteown'], Response::HTTP_NO_CONTENT]; + + yield [$deleteOperation, $forbiddenAssertion, ['deleteother'], Response::HTTP_FORBIDDEN]; + + yield [$deleteOperation, $deleteSuccessAssertion, ['deleteown', 'deleteother'], Response::HTTP_NO_CONTENT]; + } + /** * @dataProvider postCustomItemsDataProvider * @@ -257,22 +359,29 @@ private function createCustomObject(): CustomObject /** * @param array $permissions */ - private function createCustomItem(array $permissions): CustomItem + private function createCustomItem(array $permissions, bool $ownedByCurrentUser = false, ?int $ownerId = null): CustomItem { $customObject = $this->createCustomObject(); $category = $this->createCategory(); $customField = $this->createCustomField($customObject); + $user = $this->getUser(); $customItem = new CustomItem($customObject); $customItem->setName('Custom Item'); $customItem->setLanguage('en'); $customItem->setCategory($category); + + if ($ownedByCurrentUser && null !== $user) { + $customItem->setCreatedBy($user); + } elseif (null !== $ownerId) { + $customItem->setCreatedBy($ownerId); + } + $customFieldValue = new CustomFieldValueText($customField, $customItem, 'value'); $customItem->addCustomFieldValue($customFieldValue); $this->em->persist($customItem); $this->em->flush(); - $user = $this->getUser(); $this->setPermission($user, 'custom_objects:'.$customObject->getId(), $permissions); return $customItem; From e28c846da7f330da6574641cb606403aba252499 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 11:58:10 +0200 Subject: [PATCH 02/10] STAN fiixes --- .../ApiPlatformPermissionContextSubscriber.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php index 08ef48842..8ee8bbba3 100644 --- a/EventListener/ApiPlatformPermissionContextSubscriber.php +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -42,11 +42,15 @@ public function onApiPlatformPermissionContext(\Mautic\ApiBundle\Event\ApiPlatfo private function extractObjectPath(string $permission): ?string { - if (preg_match('#\((.*?)\)#', $permission, $match) && !empty($match[1])) { - return $match[1]; + if (1 !== preg_match('#\((.*?)\)#', $permission, $match)) { + return null; } - return null; + if (!isset($match[1]) || '' === $match[1]) { + return null; + } + + return $match[1]; } private function resolveRequestObject(mixed $requestObject, string $objectPath): mixed @@ -64,7 +68,11 @@ private function resolveRequestObject(mixed $requestObject, string $objectPath): private function resolvePermissionPlaceholder(\Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent $event, mixed $requestObject, string $permission): string { - if (!preg_match('#\[(.*?)\]#', $permission, $match) || empty($match[1])) { + if (1 !== preg_match('#\[(.*?)\]#', $permission, $match)) { + return $permission; + } + + if (!isset($match[1]) || '' === $match[1]) { return $permission; } From 58439f3f97e611e6cfc340dd59c58c6f588b1071 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 12:56:11 +0200 Subject: [PATCH 03/10] CS fixes --- EventListener/ApiPlatformPermissionContextSubscriber.php | 2 +- Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php index 8ee8bbba3..a8f145be6 100644 --- a/EventListener/ApiPlatformPermissionContextSubscriber.php +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -104,4 +104,4 @@ private function resolvePermissionPlaceholder(\Mautic\ApiBundle\Event\ApiPlatfor return preg_replace('#\[(.*?)\]#', (string) $objectId, $permission, 1) ?? $permission; } -} \ No newline at end of file +} diff --git a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php index 47604d312..fc40d0287 100644 --- a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php @@ -73,7 +73,7 @@ public function getCustomItemsDataProvider(): iterable public function testGetCustomItemDeniesOwnPermissionForOtherUsersItem(): void { $user = $this->getUser(); - + self::assertNotNull($user); $customItem = $this->createCustomItem(['viewown'], false, $user->getId() + 9999); From 609d5baaf06ae8039b85d244a1001d8f4e6ec693 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 14:23:50 +0200 Subject: [PATCH 04/10] Reverting some of the ApiPlatform permission syntax changes so this plugin would still work with stock M4 --- Entity/CustomField.php | 8 ++++---- Entity/CustomFieldOption.php | 8 ++++---- Entity/CustomItem.php | 8 ++++---- Entity/CustomObject.php | 6 +++--- .../ApiPlatformPermissionContextSubscriber.php | 17 ++++++++++++----- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Entity/CustomField.php b/Entity/CustomField.php index 94e6062fb..d49bbef0f 100644 --- a/Entity/CustomField.php +++ b/Entity/CustomField.php @@ -42,10 +42,10 @@ * "post"={"security"="'custom_objects:custom_fields:create'"} * }, * itemOperations={ - * "get"={"security"="is_granted('custom_objects:custom_fields:viewown', object)"}, - * "put"={"security"="is_granted('custom_objects:custom_fields:editown', object)"}, - * "patch"={"security"="is_granted('custom_objects:custom_fields:editown', object)"}, - * "delete"={"security"="is_granted('custom_objects:custom_fields:deleteown', object)"} + * "get"={"security"="'custom_objects:custom_fields:view'"}, + * "put"={"security"="'custom_objects:custom_fields:edit'"}, + * "patch"={"security"="'custom_objects:custom_fields:edit'"}, + * "delete"={"security"="'custom_objects:custom_fields:delete'"} * }, * shortName="custom_fields", * normalizationContext={"groups"={"custom_field:read"}, "swagger_definition_name"="Read"}, diff --git a/Entity/CustomFieldOption.php b/Entity/CustomFieldOption.php index d2eb4fb4f..f1073d4ad 100644 --- a/Entity/CustomFieldOption.php +++ b/Entity/CustomFieldOption.php @@ -23,10 +23,10 @@ * "post"={"security"="'custom_objects:custom_fields:create'"} * }, * itemOperations={ - * "get"={"security"="is_granted('custom_objects:custom_fields:viewown(getCustomField)', object)"}, - * "put"={"security"="is_granted('custom_objects:custom_fields:editown(getCustomField)', object)"}, - * "patch"={"security"="is_granted('custom_objects:custom_fields:editown(getCustomField)', object)"}, - * "delete"={"security"="is_granted('custom_objects:custom_fields:deleteown(getCustomField)', object)"} + * "get"={"security"="'custom_objects:custom_fields:view(getCustomField)'"}, + * "put"={"security"="'custom_objects:custom_fields:edit(getCustomField)'"}, + * "patch"={"security"="'custom_objects:custom_fields:edit(getCustomField)'"}, + * "delete"={"security"="'custom_objects:custom_fields:delete(getCustomField)'"} * }, * shortName="custom_field_options" * ) diff --git a/Entity/CustomItem.php b/Entity/CustomItem.php index 22d6b4c82..549117951 100644 --- a/Entity/CustomItem.php +++ b/Entity/CustomItem.php @@ -32,10 +32,10 @@ * "post"={"security"="'custom_objects:[customObject]:create'"} * }, * itemOperations={ - * "get"={"security"="is_granted('custom_objects:[customObject]:viewown', object)"}, - * "put"={"security"="is_granted('custom_objects:[customObject]:editown', object)"}, - * "patch"={"security"="is_granted('custom_objects:[customObject]:editown', object)"}, - * "delete"={"security"="is_granted('custom_objects:[customObject]:deleteown', object)"} + * "get"={"security"="'custom_objects:[customObject]:view'"}, + * "put"={"security"="'custom_objects:[customObject]:edit'"}, + * "patch"={"security"="'custom_objects:[customObject]:edit'"}, + * "delete"={"security"="'custom_objects:[customObject]:delete'"} * }, * shortName="custom_items", * normalizationContext={"groups"={"custom_item:read"}, "swagger_definition_name"="Read"}, diff --git a/Entity/CustomObject.php b/Entity/CustomObject.php index 51b013fcb..f98e5f5b5 100644 --- a/Entity/CustomObject.php +++ b/Entity/CustomObject.php @@ -30,9 +30,9 @@ * "post"={"security"="'custom_objects:custom_objects:create'"} * }, * itemOperations={ - * "get"={"security"="is_granted('custom_objects:custom_objects:viewown', object)"}, - * "patch"={"security"="is_granted('custom_objects:custom_objects:editown', object)"}, - * "delete"={"security"="is_granted('custom_objects:custom_objects:deleteown', object)"} + * "get"={"security"="'custom_objects:custom_objects:view'"}, + * "patch"={"security"="'custom_objects:custom_objects:edit'"}, + * "delete"={"security"="'custom_objects:custom_objects:delete'"} * }, * shortName="custom_objects", * normalizationContext={"groups"={"custom_object:read"}, "swagger_definition_name"="Read"}, diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php index a8f145be6..4f903e5c4 100644 --- a/EventListener/ApiPlatformPermissionContextSubscriber.php +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -16,11 +16,18 @@ public static function getSubscribedEvents(): array ]; } - public function onApiPlatformPermissionContext(\Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent $event): void + public function onApiPlatformPermissionContext(object $event): void { - $permission = $event->getPermission(); + if (!method_exists($event, 'getPermission') + || !method_exists($event, 'setPermission') + || !method_exists($event, 'getRequestObject') + || !method_exists($event, 'setRequestObject') + ) { + return; + } - if (0 !== strpos($permission, 'custom_objects:')) { + $permission = $event->getPermission(); + if (!is_string($permission) || 0 !== strpos($permission, 'custom_objects:')) { return; } @@ -66,7 +73,7 @@ private function resolveRequestObject(mixed $requestObject, string $objectPath): return $requestObject; } - private function resolvePermissionPlaceholder(\Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent $event, mixed $requestObject, string $permission): string + private function resolvePermissionPlaceholder(object $event, mixed $requestObject, string $permission): string { if (1 !== preg_match('#\[(.*?)\]#', $permission, $match)) { return $permission; @@ -78,7 +85,7 @@ private function resolvePermissionPlaceholder(\Mautic\ApiBundle\Event\ApiPlatfor $property = $match[1]; $objectId = null; - $request = $event->getRequest(); + $request = method_exists($event, 'getRequest') ? $event->getRequest() : null; $content = $request instanceof Request ? json_decode($request->getContent(), true) : null; if (is_array($content) && array_key_exists($property, $content)) { From b8157efafdddaaf2eac4f4a6f3cdefb10d7476f9 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 16:22:13 +0200 Subject: [PATCH 05/10] Enabling the CO plugin for a test, correcting a response --- .../ApiPlatform/CustomFieldOptionFunctionalTest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Tests/Functional/ApiPlatform/CustomFieldOptionFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomFieldOptionFunctionalTest.php index 81d331717..e182f70ba 100644 --- a/Tests/Functional/ApiPlatform/CustomFieldOptionFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomFieldOptionFunctionalTest.php @@ -13,6 +13,13 @@ final class CustomFieldOptionFunctionalTest extends AbstractApiPlatformFunctionalTest { + public function setUp(): void + { + $this->configParams['custom_objects_enabled'] = true; + + parent::setUp(); + } + public function testCustomFieldOptionCRUD(): void { foreach ($this->getCRUDProvider() as $parameters) { @@ -148,8 +155,8 @@ private function getCRUDProvider(): array Response::HTTP_CREATED, Response::HTTP_OK, 'New Custom Field Option', - Response::HTTP_FORBIDDEN, - null, + Response::HTTP_OK, + 'Edited Custom Field Option', Response::HTTP_NO_CONTENT, ], 'no_create' => [ From d57e4c5ccfe5f3bd56be4607cc4b7f8c2b60998b Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 16:26:44 +0200 Subject: [PATCH 06/10] CS fix --- Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php index fc40d0287..81d278b91 100644 --- a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php @@ -101,7 +101,7 @@ public function testOwnedCustomItemAllowsOwnScopedOperations(callable $operation } /** - * @return iterable, 3: int}> + * @return iterable|, 3: int}> */ public function ownedCustomItemOperationsDataProvider(): iterable { From e254a4349b26b8d13650f27df38e12d5ffd832e5 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 8 Jun 2026 17:04:44 +0200 Subject: [PATCH 07/10] CS fix --- Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php index 81d278b91..fc40d0287 100644 --- a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php @@ -101,7 +101,7 @@ public function testOwnedCustomItemAllowsOwnScopedOperations(callable $operation } /** - * @return iterable|, 3: int}> + * @return iterable, 3: int}> */ public function ownedCustomItemOperationsDataProvider(): iterable { From 7a33411f21454bbed542f9d47fbcd98e37000484 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Tue, 9 Jun 2026 10:28:41 +0200 Subject: [PATCH 08/10] Simplifying return type hint --- Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php index fc40d0287..fc7a1f7b9 100644 --- a/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php +++ b/Tests/Functional/ApiPlatform/CustomItemFunctionalTest.php @@ -101,7 +101,7 @@ public function testOwnedCustomItemAllowsOwnScopedOperations(callable $operation } /** - * @return iterable, 3: int}> + * @return iterable, int}> */ public function ownedCustomItemOperationsDataProvider(): iterable { From b6b4ee40eb8619523721c23585564b7f8a053824 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Tue, 16 Jun 2026 11:56:06 +0200 Subject: [PATCH 09/10] Fixing const and param type --- EventListener/ApiPlatformPermissionContextSubscriber.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php index 4f903e5c4..5b7608c19 100644 --- a/EventListener/ApiPlatformPermissionContextSubscriber.php +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -4,6 +4,8 @@ namespace MauticPlugin\CustomObjectsBundle\EventListener; +use Mautic\ApiBundle\ApiEvents; +use Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; @@ -12,11 +14,11 @@ final class ApiPlatformPermissionContextSubscriber implements EventSubscriberInt public static function getSubscribedEvents(): array { return [ - 'mautic.api_platform_permission_context' => ['onApiPlatformPermissionContext', 0], + ApiEvents::API_PLATFORM_PERMISSION_CONTEXT => ['onApiPlatformPermissionContext', 0], ]; } - public function onApiPlatformPermissionContext(object $event): void + public function onApiPlatformPermissionContext(ApiPlatformPermissionContextEvent $event): void { if (!method_exists($event, 'getPermission') || !method_exists($event, 'setPermission') From 3029d37ae302f7ab856c72843725b77ed6267a12 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Mon, 22 Jun 2026 16:21:25 +0200 Subject: [PATCH 10/10] Making the new subscriber compatible with older Mautic versions --- .../ApiPlatformPermissionContextSubscriber.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/EventListener/ApiPlatformPermissionContextSubscriber.php b/EventListener/ApiPlatformPermissionContextSubscriber.php index 5b7608c19..1c5d073d0 100644 --- a/EventListener/ApiPlatformPermissionContextSubscriber.php +++ b/EventListener/ApiPlatformPermissionContextSubscriber.php @@ -4,8 +4,6 @@ namespace MauticPlugin\CustomObjectsBundle\EventListener; -use Mautic\ApiBundle\ApiEvents; -use Mautic\ApiBundle\Event\ApiPlatformPermissionContextEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; @@ -14,11 +12,15 @@ final class ApiPlatformPermissionContextSubscriber implements EventSubscriberInt public static function getSubscribedEvents(): array { return [ - ApiEvents::API_PLATFORM_PERMISSION_CONTEXT => ['onApiPlatformPermissionContext', 0], + 'mautic.api_platform_permission_context' => ['onApiPlatformPermissionContext', 0], ]; } - public function onApiPlatformPermissionContext(ApiPlatformPermissionContextEvent $event): void + /* + * Note, the new ApiPlatformPermissionContextEvent will be available in Mautic 7. + * Till then we have to go with a plain object type. + */ + public function onApiPlatformPermissionContext(object $event): void { if (!method_exists($event, 'getPermission') || !method_exists($event, 'setPermission')