<?php declare(strict_types=1); /* * This file is part of vonRotenberg Shopware API Bundle. * * (c) vonRotenberg * * @license proprietary */ namespace vonRotenberg\ShopwareApiBundle\API; use Contao\System; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; class Shopware { protected $serializer; protected $httpClient; protected $clientId; protected $clientSecret; protected $apiEndpoint; private $token; private $tokenType; public function __construct(SerializerInterface $serializer, HttpClientInterface $httpClient) { $this->serializer = $serializer; $this->httpClient = $httpClient; $this->clientId = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.client_id'); $this->clientSecret = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.client_secret'); $this->apiEndpoint = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.api_endpoint'); } protected function getClientId() { return $this->clientId; } protected function getClientSecret() { return $this->clientSecret; } protected function getApiEndpoint() { return rtrim($this->apiEndpoint,'/'); } protected function sendRequest(string $relEndpoint, array $options, string $method = 'GET',bool $blnFQDNEndpoint=false) { if ($blnFQDNEndpoint) { return $this->httpClient->request($method,$relEndpoint,$options); } $relEndpoint = '/' . ltrim($relEndpoint,'/'); $request = $this->httpClient->request($method,$this->getApiEndpoint().$relEndpoint,$options); if ($request->getStatusCode() == 401) { throw new \RuntimeException('Unauthorized'); } return $request; } protected function getAccessToken() { $options = [ 'body' => json_encode([ 'grant_type' => 'client_credentials', 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret ]), 'headers' => ['Content-Type: application/json', 'Accept: application/json'], ]; $response = $this->sendRequest('oauth/token',$options,'POST'); if ($response->getStatusCode() == 200) { $content = $response->getContent(); return json_decode($content); } throw new \RuntimeException('Can\'t retrieve access token. Check credentials.'); } protected function getAuthentication() { $Token = $this->getAccessToken(); return 'Authorization: ' . $Token->token_type . ' ' . $Token->access_token; } public function isShopwareRunning(): bool { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ], ]; $response = $this->sendRequest('_info/health-check',$options,'GET'); if ($response->getStatusCode() == 200) { return true; } return false; } public function getProductsForSku(string $strSku, bool $blnWildcardSearch=false, bool $blnShort=false): ResponseInterface { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ], 'body' => json_encode([ 'filter' => [ [ 'type' => !$blnWildcardSearch ? 'equals' : 'contains', 'field' => 'productNumber', 'value' => $strSku ] ], 'sort' => [ [ 'field' => 'productNumber', 'order' => 'ASC', 'naturalSorting' => true ] ] ]) ]; if ($blnShort) { $options['body'] = json_encode( array_merge( json_decode($options['body'],true), [ 'fields' => [ 'name' ] ] ) ); } return $this->sendRequest('search/product',$options,'POST'); } public function findOrdersByFilter(array $filter, bool $blnShort=false): ResponseInterface { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ], 'body' => json_encode([ 'filter' => $filter, 'sort' => [ [ 'field' => 'createdAt', 'order' => 'ASC', 'naturalSorting' => true ] ], 'includes' => [ 'order' => ['id', 'orderNumber', 'createdAt', 'billingAddress', 'deliveries', 'lineItems','taxStatus','orderCustomer','amountTotal','shippingTotal','transactions'], 'order_delivery' => ['shippingOrderAddress','shippingMethod'], 'country' => ['name','iso'], 'order_line_item' => ['position', 'quantity', 'label', 'unitPrice', 'totalPrice', 'price','priceDefinition', 'productId', 'product', 'type', 'payload'], 'product' => ['ean','productNumber','name'], 'payment_method' => ['id','name','shortName'], 'shipping_method' => ['id','name'], 'order_transaction' => ['amount','paymentMethod'], ], 'associations' => [ 'transactions' => [ 'associations' => [ 'paymentMethod' => [] ] ], 'billingAddress' => [ 'associations' => [ 'country' => [] ] ], 'deliveries' => [ 'associations' => [ 'shippingOrderAddress' => [ 'associations' => [ 'country' => [] ] ], 'shippingMethod' => [] ] ], 'lineItems' => [ 'associations' => [ 'product' => [] ] ], ] ]) ]; if ($blnShort) { $options['body'] = json_encode( array_merge( json_decode($options['body'],true), [ 'fields' => [ 'orderNumber' ] ] ) ); } return $this->sendRequest('search/order',$options,'POST'); } public function getOrderAddressById(string $strId, bool $blnShort=false): ResponseInterface { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ] ]; return $this->sendRequest('order-address/'.$strId,$options,'GET'); } public function getOrder(string $strId, ?string $pathSuffix=null): ResponseInterface { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ] ]; return $this->sendRequest('order/'.$strId.($pathSuffix ? '/'.$pathSuffix : ''),$options,'GET'); } public function getOrderLineItemsByFilter(array $filter, bool $blnShort=false): ResponseInterface { $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ], 'body' => json_encode([ 'filter' => $filter, 'sort' => [ [ 'field' => 'createdAt', 'order' => 'ASC', 'naturalSorting' => true ] ] ]) ]; if ($blnShort) { $options['body'] = json_encode( array_merge( json_decode($options['body'],true), [ 'fields' => [ 'id', 'label', 'quantity', ] ] ) ); } return $this->sendRequest('search/order-line-item',$options,'POST'); } public function queryAPI(string $strUrlFragment, null|string|array $body, string $strMethod = 'GET', bool $blnAuthenticate=true): ResponseInterface { $options = [ 'headers' => [ 'Content-Type: application/json', 'Accept: application/json' ] ]; if ($blnAuthenticate) { $options['headers'][] = $this->getAuthentication(); } if ($body !== null) { if (is_array($body)) { $options['body'] = json_encode($body); } else { $options['body'] = $body; } } return $this->sendRequest($strUrlFragment,$options,$strMethod); } protected function checkBySkuIfExists(string $strSku): bool { $Result = $this->getProductsForSku($strSku,false,true); if ($Result->getStatusCode() == 200) { $Content = json_decode($Result->getContent()); if ($Content->total) { return true; } } return false; } public function addOrUpdateProductBySku(string $strSku, array $arrData): bool { $blnInsert = true; // Update or insert check if (($searchResponse = $this->getProductsForSku($strSku))->getStatusCode() == 200) { $Product = json_decode($searchResponse->getContent()); if ($Product->total) { $blnInsert = false; } } // Prepare product data $arrData = array_merge([ 'id' => $blnInsert ? $this->createUuid() : $Product->data[0]->id ], $arrData); $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json' ], 'body' => json_encode($arrData) ]; if ($blnInsert) { $response = $this->sendRequest('product',$options,'POST'); } else { $response = $this->sendRequest('product/'.$Product->data[0]->id,$options,'PATCH'); } return in_array($response->getStatusCode(),[200,201,202,203,204]); } public function truncatePropertiesForProductBySku(string $strSku, string|array|null $groupIds = null): bool { if ($groupIds !== null && !is_array($groupIds)) { $groupIds = (array) $groupIds; } // Look for product if (($searchResponse = $this->getProductsForSku($strSku))->getStatusCode() == 200) { $Product = json_decode($searchResponse->getContent()); if (!$Product->total) { return false; } // Get property ids $arrPropertiesPayload = []; foreach (System::getContainer()->getParameter('vonrotenberg_shopware_api.mappings.properties') as $groupId => $properties) { if ($groupIds !== null && !in_array($groupId,$groupIds)) { continue; } foreach (array_keys($properties) as $propertyId) { $arrPropertiesPayload[] = [ 'productId' => $Product->data[0]->id, 'optionId' => $propertyId ]; } } // Build request data $arrData = [ [ 'entity' => 'product_property', 'action' => 'delete', 'payload' => $arrPropertiesPayload ] ]; $options = [ 'headers' => [ $this->getAuthentication(), 'Content-Type: application/json', 'Accept: application/json', 'fail-on-error: false' ], 'body' => json_encode($arrData) ]; // Send request $response = $this->sendRequest('_action/sync',$options,'POST'); return in_array($response->getStatusCode(),[200]); } return false; } protected function createUuid($data = null) { // Generate 16 bytes (128 bits) of random data or use the data passed into the function. $data = $data ?? random_bytes(16); assert(strlen($data) == 16); // Set version to 0100 $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // Set bits 6-7 to 10 $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // Output the 36 character UUID. return bin2hex($data); } }