diff --git a/src/Enum/UpsertBehaviorEnum.php b/src/Enum/UpsertBehaviorEnum.php new file mode 100644 index 00000000..649101fb --- /dev/null +++ b/src/Enum/UpsertBehaviorEnum.php @@ -0,0 +1,32 @@ +client = $client; + $this->upsertBehavior = UpsertBehaviorEnum::CreateOrUpdate; } /** @@ -107,7 +115,7 @@ public function Create( Entity $entity ): string { $translatedData = $serializer->serializeEntity( $entity ); $collectionName = $this->client->getMetadata()->getEntitySetName( $entity->LogicalName ); - + $this->updateWebApiHeaderRequest(); $responseId = $this->client->create( $collectionName, $translatedData ); $entity->getAttributeState()->reset(); @@ -233,9 +241,8 @@ public function Retrieve( string $entityName, string $entityId, ColumnSet $colum $response = $this->client->getRecord( $collectionName, $entityId, $options ); $serializer = new SerializationHelper( $this->client ); - $entity = $serializer->deserializeEntity( $response, new EntityReference( $entityName, $entityId ) ); - return $entity; + return $serializer->deserializeEntity( $response, new EntityReference( $entityName, $entityId ) ); } catch ( ODataException $e ) { if ( $e->getCode() === 404 ) { return null; @@ -388,7 +395,7 @@ protected function retrieveViaQueryByAttribute( QueryByAttribute $query ): Entit $queryValue = "'{$value}'"; break; case is_bool( $value ): - $queryValue = $value? 'true' : 'false'; + $queryValue = $value ? 'true' : 'false'; break; case $value === null: $queryValue = 'null'; @@ -491,6 +498,30 @@ protected function retrieveViaQueryByAttribute( QueryByAttribute $query ): Entit } } + /** + * Updating WebApi Request Headers + * + * @return void + */ + private function updateWebApiHeaderRequest(): void { + switch ( $this->upsertBehavior ) { + case UpsertBehaviorEnum::PreventCreate: + $this->client->setWebApiHeaderByName( "If-Match", "*" ); + break; + case UpsertBehaviorEnum::PreventUpdate: + $this->client->setWebApiHeaderByName( "If-None-Match", "*" ); + break; + default: + break; + } + + if ( $this->callerObjectId !== null ) { + $this->client->setWebApiHeaderByName( 'CallerObjectId', $this->callerObjectId ); + } elseif ( $this->MSCRMCallerID !== null ) { + $this->client->setWebApiHeaderByName( 'MSCRMCallerID', $this->MSCRMCallerID ); + } + } + /** * Updates an existing record. * @@ -508,6 +539,7 @@ public function Update( Entity $entity ): void { $collectionName = $this->client->getMetadata()->getEntitySetName( $entity->LogicalName ); + $this->updateWebApiHeaderRequest(); $this->client->update( $collectionName, $entity->Id, $translatedData ); $entity->getAttributeState()->reset(); @@ -536,4 +568,34 @@ public function getCachePool(): CacheItemPoolInterface { return $this->client->getCachePool(); } + public function getMSCRMCallerID(): ?string { + return $this->MSCRMCallerID; + } + + public function setMSCRMCallerID( ?string $MSCRMCallerID ): void { + try { + //search Microsoft Entra ID object ID + $entity = $this->Retrieve( 'systemuser', $MSCRMCallerID, new ColumnSet( [ 'azureactivedirectoryobjectid' ] ) ); + $this->callerObjectId = $entity?->Attributes['azureactivedirectoryobjectid']; + } catch ( Exception ) { + } + $this->MSCRMCallerID = $MSCRMCallerID; + } + + public function getCallerObjectId(): ?string { + return $this->callerObjectId; + } + + public function setCallerObjectId( ?string $callerObjectId ): void { + $this->callerObjectId = $callerObjectId; + } + + public function getUpsertBehavior(): UpsertBehaviorEnum { + return $this->upsertBehavior; + } + + public function setUpsertBehavior( UpsertBehaviorEnum $upsertBehavior ): void { + $this->upsertBehavior = $upsertBehavior; + } + } diff --git a/src/WebAPI/OData/Client.php b/src/WebAPI/OData/Client.php index b5ebf271..94fcd649 100644 --- a/src/WebAPI/OData/Client.php +++ b/src/WebAPI/OData/Client.php @@ -34,6 +34,7 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\HandlerStack; use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; @@ -66,6 +67,13 @@ class Client { */ protected array $middlewares = []; + /** + * header to Web API request + * + * @var array + */ + protected array $webApiHeaders = []; + /** * Client constructor. * @@ -129,8 +137,11 @@ public function getHttpClient(): HttpClient { /** * Retrieves OData Service Metadata. * + * @return Metadata * @throws AuthenticationException + * @throws GuzzleException * @throws TransportException + * @throws InvalidArgumentException */ public function getMetadata(): Metadata { if ( $this->metadata instanceof Metadata ) { @@ -178,6 +189,34 @@ public function getMetadata(): Metadata { return $this->metadata; } + /** + * Get header for Web Api Request + * + * @param array|null $headers + * @param string|null $method + * + * @return array + */ + private function getRequestHeaders( ?array $headers = [], ?string $method = 'GET' ): array { + if ( in_array( $method, [ 'POST', 'PATCH' ] ) ) { + $headers = array_merge( [ 'Content-Type' => 'application/json' ], $headers ); + } + + //Odata/Client/Setting more important than the headers in the request + if ( $this->settings->callerObjectId || $this->settings->callerID ) { + $this->unsetWebApiHeaderByName( 'CallerObjectId' ); + $this->unsetWebApiHeaderByName( 'MSCRMCallerID' ); + } + + if ( $this->settings->callerObjectId !== null ) { + $headers = array_merge( [ 'CallerObjectId' => $this->settings->callerObjectId ], $headers ); + } elseif ( $this->settings->callerID !== null ) { + $headers = array_merge( [ 'MSCRMCallerID' => $this->settings->callerID ], $headers ); + } + + return $headers; + } + /** * @param string $method * @param string $url @@ -190,13 +229,7 @@ public function getMetadata(): Metadata { * @throws TransportException */ private function doRequest( string $method, string $url, $data = null, array $headers = [] ): ResponseInterface { - if ( in_array( $method, [ 'POST', 'PATCH' ] ) ) { - $headers = array_merge( [ 'Content-Type' => 'application/json' ], $headers ); - } - - if ( $this->settings->callerID !== null ) { - $headers = array_merge( [ 'MSCRMCallerID' => '$this->settings->callerID' ], $headers ); - } + $headers = $this->getRequestHeaders( $headers, $method ); try { $payload = [ @@ -577,7 +610,7 @@ public function associate( string $fromEntityId, string $navProperty, string $toEntityCollection, - string $toEntityId + string $toEntityId, ): void { $url = sprintf( '%s%s(%s)/%s/$ref', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty ); $data = [ Annotation::ODATA_ID => sprintf( '%s%s(%s)', $this->settings->getEndpointURI(), $toEntityCollection, $toEntityId ) ]; @@ -600,9 +633,10 @@ public function disassociate( string $fromEntityId, string $navProperty, string $toEntityCollection, - string $toEntityId + string $toEntityId, ): void { - $url = sprintf( '%s%s(%s)/%s/$ref?$id=%s%s(%s)', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty, $this->settings->getEndpointURI(), $toEntityCollection, $toEntityId ); + $url = sprintf( '%s%s(%s)/%s/$ref?$id=%s%s(%s)', $this->settings->getEndpointURI(), $fromEntityCollection, $fromEntityId, $navProperty, $this->settings->getEndpointURI(), + $toEntityCollection, $toEntityId ); $this->doRequest( 'DELETE', $url ); } @@ -795,4 +829,20 @@ protected static function getEntityId( ResponseInterface $response ): ?string { return $id; } + public function getWebApiHeaderByName( $headerName ): array { + return $this?->webApiHeaders[ $headerName ]; + } + + public function unsetWebApiHeaderByName( $headerName ): void { + unset( $this->webApiHeaders[ $headerName ] ); + } + + public function setWebApiHeaderByName( $headerName, $headerValue ): void { + $this->webApiHeaders[ $headerName ] = $headerValue; + } + + public function getWebApiHeaders(): array { + return $this->webApiHeaders; + } + } diff --git a/src/WebAPI/OData/Settings.php b/src/WebAPI/OData/Settings.php index 1fe5dd6b..ff8da4eb 100644 --- a/src/WebAPI/OData/Settings.php +++ b/src/WebAPI/OData/Settings.php @@ -75,12 +75,19 @@ abstract class Settings implements LoggerAwareInterface { public LoggerInterface $logger; /** - * ID of the user to impersonate during calls. - * + * ID of the user (systemuserid) to impersonate during calls. + * @deprecated * Null value means impersonation is not performed. */ public ?string $callerID = null; + /** + * Microsoft Entra ID object ID (Azure AD Object ID) - azureactivedirectoryobjectid + * + * Null value means impersonation is not performed. + */ + public ?string $callerObjectId = null; + /** * Settings constructor. */