diff --git a/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandler.php b/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandler.php index 4d87904..e3f957a 100644 --- a/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandler.php +++ b/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandler.php @@ -114,7 +114,7 @@ public function getFields(RemoteGetFieldsAction $action): Result { */ public function getCreateForm(RemoteGetCreateFormAction $action): array { $grantResult = $this->profile->isCreateGranted($action->getArguments(), $action->getResolvedContactId()); - if (!$grantResult->granted) { + if (!$grantResult->showForm) { throw new UnauthorizedException($grantResult->message ?? E::ts('Permission to create entity is missing')); } @@ -133,7 +133,7 @@ public function getCreateForm(RemoteGetCreateFormAction $action): array { public function getUpdateForm(RemoteGetUpdateFormAction $action): array { $entityValues = $this->getEntityById($action->getId(), 'update', $action->getResolvedContactId()); $grantResult = $this->profile->isUpdateGranted($entityValues, $action->getResolvedContactId()); - if (NULL === $entityValues || !$grantResult->granted) { + if (NULL === $entityValues || !$grantResult->showForm) { throw new UnauthorizedException($grantResult->message ?? E::ts('Permission to update entity is missing')); } diff --git a/Civi/RemoteTools/EntityProfile/Authorization/GrantResult.php b/Civi/RemoteTools/EntityProfile/Authorization/GrantResult.php index 7ca6bcc..1bf735c 100644 --- a/Civi/RemoteTools/EntityProfile/Authorization/GrantResult.php +++ b/Civi/RemoteTools/EntityProfile/Authorization/GrantResult.php @@ -26,11 +26,14 @@ final class GrantResult { public bool $granted; + public bool $showForm; + public ?string $message; - private function __construct(bool $granted, ?string $message) { + private function __construct(bool $granted, ?string $message, ?bool $showForm = NULL) { $this->granted = $granted; $this->message = $message; + $this->showForm = $showForm ?? $granted; } public static function newPermitted(): self { @@ -45,4 +48,16 @@ public static function newDenied(?string $message = NULL): self { return new self(FALSE, $message); } + /** + * Performing the actual action will be denied, but the form will still be + * delivered. This can be used to provide a "form" with only custom markup + * (no fields). + * + * @param string|null $message + * If no message is specified, a generic message might be used. + */ + public static function newDeniedWithForm(?string $message = NULL): self { + return new self(FALSE, $message, TRUE); + } + } diff --git a/tests/phpunit/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandlerTest.php b/tests/phpunit/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandlerTest.php index b91c254..7f31a8b 100644 --- a/tests/phpunit/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandlerTest.php +++ b/tests/phpunit/Civi/RemoteTools/ActionHandler/AbstractProfileEntityActionsHandlerTest.php @@ -160,7 +160,41 @@ public function testGetCreateForm(): void { static::assertSame(['form' => 'Test'], $this->handler->getCreateForm($actionMock)); } - public function testGetCreateFormNotAllowed(): void { + public function testGetCreateFormDeiniedWithForm(): void { + $actionMock = $this->createActionMock(RemoteGetCreateFormAction::class); + $arguments = ['key' => 'value']; + $actionMock->setArguments($arguments); + + $this->profileMock->method('isCreateGranted') + ->with($arguments, self::RESOLVED_CONTACT_ID) + ->willReturn(GrantResult::newDeniedWithForm()); + $this->profileMock->method('isFormSpecNeedsFieldOptions')->willReturn(FALSE); + + $entityFields = [ + 'foo' => ['name' => 'foo'], + 'bar' => ['name' => 'bar'], + ]; + $this->api4Mock->method('execute') + ->with('Entity', 'getFields', [ + 'loadOptions' => FALSE, + 'values' => [], + 'checkPermissions' => FALSE, + ]) + ->willReturn(new Result(array_values($entityFields))); + + $formSpec = new FormSpec('Title'); + $this->profileMock->method('getCreateFormSpec') + ->with($arguments, $entityFields, self::RESOLVED_CONTACT_ID) + ->willReturn($formSpec); + + $this->handler->method('convertToGetFormActionResult') + ->with($formSpec) + ->willReturn(['form' => 'Test']); + + static::assertSame(['form' => 'Test'], $this->handler->getCreateForm($actionMock)); + } + + public function testGetCreateFormDenied(): void { $actionMock = $this->createActionMock(RemoteGetCreateFormAction::class); $arguments = ['key' => 'value']; $actionMock->setArguments($arguments); @@ -277,7 +311,62 @@ public function testGetUpdateForm(): void { static::assertSame(['form' => 'Test'], $this->handler->getUpdateForm($actionMock)); } - public function testGetUpdateFormNotAllowed(): void { + public function testGetUpdateFormDeniedWithForm(): void { + $actionMock = $this->createActionMock(RemoteGetUpdateFormAction::class); + $actionMock->setId(12); + + $entityValues = ['foo' => 'f']; + $entityFields = [ + 'foo' => ['name' => 'foo'], + 'bar' => ['name' => 'bar'], + ]; + + $this->profileMock->method('getSelectFieldNames') + ->with(['*'], 'update', [], self::RESOLVED_CONTACT_ID) + ->willReturn(['foo']); + $this->profileMock->method('isUpdateGranted') + ->with($entityValues, self::RESOLVED_CONTACT_ID) + ->willReturn(GrantResult::newDeniedWithForm()); + $this->profileMock->method('isFormSpecNeedsFieldOptions')->willReturn(TRUE); + + $valueMap = [ + [ + 'Entity', + 'get', + [ + 'select' => ['foo'], + 'where' => [['id', '=', 12]], + 'checkPermissions' => FALSE, + ], + new Result([$entityValues]), + ], + [ + 'Entity', + 'getFields', + [ + 'loadOptions' => TRUE, + 'values' => ['id' => 12], + 'checkPermissions' => FALSE, + ], + new Result(array_values($entityFields)), + ], + ]; + $this->api4Mock->method('execute') + ->willReturnMap($valueMap); + + $formSpec = new FormSpec('Title'); + $this->profileMock->method('getUpdateFormSpec') + ->with($entityValues, $entityFields, self::RESOLVED_CONTACT_ID) + ->willReturn($formSpec); + + $this->handler->method('convertToGetFormActionResult') + ->with($formSpec) + ->willReturn(['form' => 'Test']); + + static::assertSame(['form' => 'Test'], $this->handler->getUpdateForm($actionMock)); + } + + public function testGetUpdateFormDenied(): void { $actionMock = $this->createActionMock(RemoteGetUpdateFormAction::class); $actionMock->setId(12);