App
Refer to the App Base Guide for general Information. For a fast and a bit expanded lookup are on the bottom of this doku the Example-Controllers attached.
File structure in shop
For installing an App you have to create a manifest.xml-file. So, create in your shop, under the custom folder a new folder, called apps. Then another folder and name it as your required app-name (MyExampleApp). Inside this folder create the manifest.xml-file. Your folder/file structure should look like this:
- config
- custom
- apps
- MyExampleApp
- manifest.xml
- MyExampleApp
- plugins
- apps
- data
The manifest.xml file
The manifest.xml-file is on the shop-side the only file you need for installing your app and communicating with it, which we will care about later.
Open the manifest.xml-file and copy the following code and save it.
Show .xml content (Click to expand)
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
<name>MyExampleApp</name>
<label>Label</label>
<label lang="de-DE">Name</label>
<description>A description of the app</description>
<description lang="de-DE">Eine Beschreibung der App</description>
<author>Your Company Ltd.</author>
<copyright>(c) by Your Company Ltd.</copyright>
<version>1.0.0</version>
<license>MIT</license>
</meta>
<setup>
<registrationUrl>http://a536b0122bbf.ngrok.io/MySampleApp/public/registration</registrationUrl>
</setup>
</manifest>
This is the minimum you need for installing an app. Now we will go through the relevant tags and add some more.
<name>MyExampleApp</name>
Inside the <name>-tag you have to write the name you gave while creating the app folder (MyExampleApp). This is important and we will need it later in the app itself again.
<description>A description of the app</description>
<description lang="de-DE">Eine Beschreibung der App</description>
Inside the description-tags you can write a short description, maybe what your app is for. And you can translate it in several languages. Just like the other-tags it is self-explanatory. That's all the changes you need inside the <meta>-tag.
<registrationUrl>http://a536b0122bbf.ngrok.io/MySampleApp/public/registration</registrationUrl>
Inside the <registrationUrl>-tag you have to address the endpoint, where the Registration-Request will be send. This is the first endpoint which will be contacted from the shop while installing. Keep an eye that this tag is inside the <setup>-tags.
(Side-info: The http://a536b0122bbf.ngrok.io/ -url is only a ssh-reverse-tunnel created with ngrok, so the app is visible to the shop, which is on a server. In reality the app is on localhost)
More tags
Next, you have to add some more tags, if your app want to do more than only be installed. Depending on what your app should be able to do, or not, the next steps may vary.
Develop on local
If you want to upload your app to the store you will get an app-secret that's provided by the Shopware Account. This app-secret is unique per app and is used to authenticate between the shops and the app. But if you want to develop your app on localhost you are not in possession of the app-secret, so to make your development on localhost possible, you have to set another tag, called <secret>, inside the setup-tag:
<setup>
<registrationUrl>http://a536b0122bbf.ngrok.io/MySampleApp/public/registration</registrationUrl>
<secret>aaabbbcccxxx</secret>
</setup>
The aaabbbcccxxx-value is any string you can choose, but keep that in mind you will need that later in your app as well. This <secret> is a hardcoded representation of the app-secret but this wont work if you want to upload your app to the store, then you need to use the app-secret. And then you don't need an app-secret-tag inside the setup-tag, a hash of the app-secret will be sent automatically.
Webhook Event
In this example-app we want to listen on events in the shop and handle with the data we will receive. To make that possible we have to tell the shop which event we want to listen and where the information should be sent to. For that you have to create another tag, called <webhooks>. Inside this tag you can add all the events you want to listen. A list of all available events can you find in the App Base Guide.
We want to listen to any product changes, for that you have to add following tag:
<webhooks>
<webhook name="product-changed" url="http://a536b0122bbf.ngrok.io/MySampleApp/public/event/product-changed" event="product.written" />
</webhooks>
In the <webhook>-tag you have to set a name of the event, an url-endpoint which will be informed and the event itself (here: product.written).
Permissions
To obtain the permissions for receiving webhooks or accessing the shop-api, you have to set the <permissions>-tag. Inside this you have to set for each entity (product, order, ect.) the <permission>-tag you want to permit to the app (<read>, <create>, <update>, <delete>).
For example:
<permissions>
<read>product</read>
<update>product</update>
<create>order</create>
<delete>order</delete>
</permissions>
Pay attention! If you have a webhook-event that notifies your app about every product changes and on following your app wants to update any product, then it will results in an endless-loop, which will probably takes your shop down. Keep that in mind, especially if you working when the app or the shop is on live.
(Side-info: A cache:clear solved the caused problems of an endless-loop, but in a real shop it may be more difficult.)
We want to keep the app in this example simple, that's why we only need following permissions:
<permissions>
<read>product</read>
</permissions>
The "manifest.xml"-file is for our purpose finished and should now look like this (Click to expand)
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
<name>MyExampleApp</name>
<label>Label</label>
<label lang="de-DE">Name</label>
<description>A description of the app</description>
<description lang="de-DE">Eine Beschreibung der App</description>
<author>Your Company Ltd.</author>
<copyright>(c) by Your Company Ltd.</copyright>
<version>1.0.0</version>
<license>MIT</license>
</meta>
<setup>
<registrationUrl>http://a536b0122bbf.ngrok.io/MySampleApp/public/registration</registrationUrl>
<secret>aaabbbcccxxx</secret>
</setup>
<webhooks>
<webhook name="product-changed" url="http://a536b0122bbf.ngrok.io/MySampleApp/public/event/product-changed" event="product.written" />
</webhooks>
<permissions>
<read>product</read>
</permissions>
</manifest>
A full list of the possible options/tags of the manifest.xml-file you can find in the Manifest Reference.
We are now ready on the shop-side for the installation of the app. The only thing we need to do, but we will keep that for later, is to execute the installation via commandline: bin/console app:install --activate MyExampleApp
The app is not ready for the installation, so we have to prepare that first.
Setup the App
Recap
After executing the install-command the shop and the app interact with requests and responses, where we have no influence after executing. So, we need to prepare all request-/response interfaces in beforehand. The shop-side is done already, now lets start with the app-side.
Intro
The following schema illustrates each request and response, which we will cover step by step.
< img src="/docs/_images/app1.png">
The Registration-Request, Registration-Response and Confirmation-Request is the part of the installation. After that we will trigger a Webhook-Event and handle the data, then we will fetch a Bearer-Token and finally ask for further data against the api.
App language
We will code this example-app with symfony, but as long as you can provide the required requests, responses and data, you can write the app in any language you want.
Step 0: Prepare a new project (if necessary)
If you are familiar with symfony you can start within any project. For those, who are new in symfony, you should setup a new project, because you have then a clean project and no other implemantation can interfere to your app and vice versa.
Step 1: Registration-Request
Like said before, after executing the install-command the app will get a Registration-Request to the endpoint you have entered in the manifest.xml-file: <registrationUrl>http:.../registration</registrationUrl>
For that, you have to create a route to /registration, you can name it as you like, but it has to be the same as you entered in the manifest.xml-file. And you have to create the appropriate controller, we named it RegistartionController.php. In the controller we have to add the necessary namespace and use statements, also the extending class and a routing-annotation, if you want to use that, and of course a function(registration), which gets a request and returns a response(=>it will be the Registration-Response=>Step 2). It should look like this:
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class RegistrationController extends AbstractController{
/**
* @Route("/registration", name="registration", methods={"GET"})
*/
public function registration(Request $request):Response{
The Registration-Request is a GET-request where there is some data sent within the request:
< img src="/docs/_images/app2.png">
With almost every request from one (or later from more) shop(s) you will get data and it has to be organized and stored somewhere. Some data should - because of security-reasons - be handled sensitively, which is up to you how you want care about it. In this example-app we will create an entity-class which stores all necessary data to the database. The Entity-Class, named SwRegData, will not be covered in this documentation to keep this doku comprehensible. If you want, you can lookup in the Example-Download-file above.
<?php
use App\Entity\SwRegData;
When we get a request, the first thing to do is normally to check if it comes really from a shop and has a correct secret-key. Thats why we should here hash our app-secret and compare it with the incoming shopware-app-signature, which is contained in the request headers. Because we are working on a localhost and are not in possession of the app-secret, we cant check at this point. But keep that in mind and do it as soon as possible. Something similar we will do, when we get further requests, for that we will create later a shop-secret.
Now we have to store the incoming data:
<?php
$entityObj = new SwRegData;
#Data in Header
$entityObj->setSignatureReq($request->headers->get('shopware-app-signature'));
#Works with SW-Verion:6.4.1.0 (and higher)
#$entityObj->setShopVersion($request->headers->get('sw-version'));
#Data in Query
$entityObj->setShopId($request->query->get('shop-id'));
$entityObj->setShopUrl($request->query->get('shop-url'));
$entityObj->setTimestampReq($request->query->get('timestamp'));
Step 2: Registration-Response
Before, we got some data, and now we have to send some data => The Response. The shop expects a json-string, that has to be like this:
{
"proof": "94b42d39280141de84bd6fc8e538946ccdd182e4558f1e690eabb94f924e7bc7",
"secret": "random secret string",
"confirmation_url": "https://my.example.com/registration/confirm"
}
So, we have 3 things to do:
Create a proof
The shop will compare his own app-secret with the proof that we will send to him, and when it matches then we will get the Confirmation-Request that we will cover in Step 3. If you remember, we have no app-secret, because we develop on localhost. So, here comes the <secret>aaabbbcccxxx</secret>-tag in use, which we created in the manifest.xml-file. The proof has to be hashed and for that the algorithm has to be the sha256-algorithm, the data is a string of shop-id, shop-url and the app-name. And, as mentioned, the app-secret as the secret key. In our example it should look like this:
<?php
$proof = \hash_hmac(
'sha256',
$entityObj->getShopId() . $entityObj->getShopUrl() . "MyExampleApp",
"aaabbbcccxxx"
);
Create a secret
When the app will get further requests/responses from the shop, the app has to check the authentication of the shop. For that, every shop that uses this app has to become an own secret, that has to be saved to the according shops data. That's called the shop-secret. In this example, we will create a random string and save it in the database, where all other information from the shop is already stored in Step 1.
<?php
#create random string
$length = 10;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
#save random string as shop-secret to db
$entityObj->setShopSecret($randomString);
Call an endpoint for the Confirmation-Request
To handle the Confirmation-Request the shop needs an endpoint where it can send his request.
<?php
$confUrl = "http://a536b0122bbf.ngrok.io/MySampleApp/public/registration/confirm";
There is only the response left:
<?php
$registrationResponseData = [
"proof" => $proof,
"secret" => $entityObj->getShopSecret(),
"confirmation_url" => $confUrl
];
return $this->json($registrationResponseData);
Side-info: The setup is not finished, but you can try an install to look if everything till now is working. For that use: bin/console app:install --activate MyExampleApp on commandline of the shop. To lookup for or to debug the data you can write the variables in a file and see what happens:)
<?php
$handle = fopen("data/Registration.txt", "w");
fwrite($handle, $data);
fclose($handle);
Step 3: Confirmation-Request
Just like the RegistartionController.php we have to create a ConfirmationController.php and add the head of the controller-script:
<?php
namespace App\Controller;
use App\Entity\SwRegData;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ConfirmationController extends AbstractController{
/**
* @Route("/registration/confirm", name="confirm", methods={"POST"})
*/
public function confirm(Request $request):Response{
The Confirmation-Request is in contrast to the Registration-Request, which was a GET-Request, a POST-Request and comes with the following data:
< img src="/docs/_images/app3.png">
To save (later) the incoming data to the according shop, we have to identify the shop and look in our database if it is existing and get the correct entity.
<?php
// get data from Post-Request
$data = json_decode($request->getContent(), true);
// get DB-Obj
$entityObj = $this->getDoctrine()->getRepository(SwRegData::class)->find($data['shopId']);
After we get the entity, we have to check if the shop has the correct authentication. For that the shop provides in the request-headers a shopware-shop-signature. In contrast to the shopware-app-signature, which was hashed with the app-secret and came with the Registration-Response, the shopware-shop-signature uses the shop-secret, which we created in Step 2. And now, we are able to check if the shop is allowed to send us data:
<?php
#check if shop-signature is correct
$hmac = \hash_hmac('sha256', $request->getContent(), $entityObj->getShopSecret());
$shopSignature = $request->headers->get('shopware-shop-signature');
if(hash_equals($hmac, $shopSignature)){
(Side-info: Don't use if($hmac === $shopSignature), the hash_equals-method is (more) secure against time-attacks.)
Now we are ready to save all relevant data to the database.
<?php
#Data in Header
$entityObj->setSignatureConf($request->headers->get('shopware-shop-signature'));
#available on SW6.4.1.0 (and higher)
#$entityObj->setSignatureConf($request->headers->get('sw-version'));
$entityObj->setApiKey($data['apiKey']);
$entityObj->setSecretKey($data['secretKey']);
$entityObj->setTimestampConf($data['timestamp']);
#already in db, entered by RegistrationController
#$entityObj->setShopId($data['shopId']);
#$entityObj->setShopUrl($data['shopUrl']);
The really relevant data is the apiKey and the secretKey, which we will need to get the Bearer-Token in Step 5. The other data can you save or not, just as you like or need.
One last thing left to complete this Step: We have to return an Ok-Response, it can also be an empty-Response.
<?php
return new Response;
Now the setup part is finished. You can now execute the install-command on the console in your shop:
bin/console app:install --activate MyExampleApp
To uninstall use:
bin/console app:uninstall MyExampleApp
You can install and uninstall as often as you like, just try it out till it works. You should get the following output on your console:
< img src="/docs/_images/app4.png">
Step 4: Webhook-Event
Before we can trigger an event, again we have to create a controller, which can catch the event and handle it. In the manifest.xml-file we have already entered an endpoint where the shop will send the webhook-event:
<webhook name="..." url="http://.../event/product-changed" event="..." />
So it's obvious to create the controller and a route for this endpoint, we call it ProductWrittenEventController.php, and add the head of the class with the according independencies:
<?php
namespace App\Controller;
use App\Entity\SwRegData;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ProductWrittenEventController extends AbstractController{
/**
* @Route("/event/product-changed", name="event-productChanged", methods={"GET","POST"})
*/
public function productChanged(Request $request):Response{
.
.
.
return new Response;
}
}
Just like we did in Step 3 (Confirmation-Request), we have to identify and get the correct shop-entity and check the shopware-shop-signature:
<?php
$data = json_decode($request->getContent(), true);
#get correct entity-object from db with shopId
$entityObj = $this->getDoctrine()->getRepository(SwRegData::class)->find($data['source']['shopId']);
#check if shop-signature is correct
$hmac = \hash_hmac('sha256', $request->getContent(), $entityObj->getShopSecret());
$shopSignature = $request->headers->get('shopware-shop-signature');
if(hash_equals($hmac, $shopSignature)){
The shop can now send an event, but when it arrives to the controller we wouldn't notify it. An easy way to show if an event called the controller is to write something in a file. That's only for debug and is not necessary to implement it:
<?php
$handle = fopen("data/Event.txt", "w");
fwrite($handle, "Cool, an Event was triggered!");
fclose($handle);
You can now go to the Shop, Catalogues, Products, then edit any product (e.g. change the name or add s.th. to the description). If you click the save button, then it should appear (maybe after a few seconds) in your app-project the Event.txt document with the according text inside.
There is also some data in the request:
<?php
$handle2 = fopen("data/EventWithIdOfProduct.txt", "w");
fwrite($handle2, $data['data']['payload'][0]['primaryKey']);
fclose($handle2);
This example should output the id of the product you entered. Such an event request body looks like following json-content:
{
"data":{
"payload":[
{
"entity":"product",
"operation":"delete",
"primaryKey":"7b04ebe416db4ebc93de4d791325e1d9",
"updatedFields":[
]
}
],
"event":"product.written"
},
"source":{
"url":"http:\/\/localhost:8000",
"appVersion":"0.0.1",
"shopId":"dgrH7nLU6tlE"
},
"timestamp": 123123123
}
We want to go a little bit further and call for more data about the product we are changing on the shop. For that the next Steps are required.
Step 5: Bearer Token -Request
To get the authorization to send a request to the api of the shop, we need a Bearer-Token. For that, we have to proof the shop that our app is permitted to fetch data from the api and here comes the mentioned apiKey and secretKey in play. We have to send a POST-Request and put in the body-field the apiKey as client_id and the secretKey as client_secret. In addition the grant_type has to be set to client_credential. You can read more about the machine-to-machine communication in the Authentication Docu.
<?php
$url = $entityObj->getShopUrl()."api/oauth/token";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POSTFIELDS, array(
'grant_type' => 'client_credentials',
'client_id' => $entityObj->getApiKey(),
'client_secret' => $entityObj->getSecretKey()
));
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
#set correct string-form, which is needed in further requests
$token = "Authorization: Bearer ".$response['access_token'];
Step 6: Bearer Token -Response
The response is a string, which should be added to the Authorization: Bearer -string, because with this form you can directly use it in your further requests.
The Bearer-Token has a validationtime of 10 minutes, after that you have to request a new one. It is up to you and your usecase if you prefer to save the token and check the validationtime or fetch everytime a new token from the shop.
Step 7: Api-Request
Now, we are ready to fetch some data from the shops api. To lookup about the api-endpoints you can use your Your Shops Swagger Html (fit the url) or lookup in the Shopware Store API.
After triggered the product.written event in the shop we got the id of the product we changed in our controller. Now, lets call for the name and the description of the product we triggered. Therefore we will send a GET-Request to the /api/product/{id}-endpoint. For the request-itself you can use cURL, like here, but any other request-command/library should work either.
<?php
$productId = $data['data']['payload'][0]['primaryKey'];
$url = $entityObj->getShopUrl()."api/product/".$productId;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
$token,
'Content-Type: application/json'
));
$data = json_decode(curl_exec($curl), true);
$productName = $data['data']['attributes']['name'];
$productDescription = $data['data']['attributes']['description'];
Step 8: Api-Response
After the execution of the cURL-command in Step 7 we got some data, which we saved in the $data-varibale. Now you have access to a bunch of data about the product you triggered in your shop (e.g.: $productName, $productDescription). According to your usecase and your intent you can now write the data to a file, show it in a twig-template or interact with the api and get some more data.
Attachments
RegistrationController.php
Click to expand
<?php
namespace App\Controller;
use App\Entity\SwRegData;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class RegistrationController extends AbstractController{
/**
* @Route("/registration", name="registration", methods={"GET"})
*/
public function registration(Request $request):Response{
#Hardcoded data ->toDo: app-secret->Hash it!
$appName = 'MyExampleApp';
$appSecret = 'aaabbbcccxxx';
$confUrl = "http://a536b0122bbf.ngrok.io/MySampleApp/public/registration/confirm";
#check if get-Request comes from shopware with correct signature
#recalculate with own app-secret ->for security, maybe only compare with hash of app-secret ->keep app-secret secret
#$signature = hash_hmac('sha256', $queryString, $appSecret); #->when uploading to account possible and neccessary!
#if(signatre is correct)
#save Requested data to Database
$entityObj = new SwRegData;
$this->saveHardcodedData($entityObj, $appName, $appSecret, $confUrl);
$this->saveRegistrationRequestData($entityObj, $request);
$this->createAndSaveProof($entityObj);
$this->createAndSaveShopSecret($entityObj);
$this->saveAllDataInDB($entityObj);
#Response Data ->Registration-Response
$registrationResponseData = $this->getRegistrationResponseData($entityObj);
#return Response
return $this->json($registrationResponseData);
#else ->ToDo: Response Error
// $response = new Response;
// return $response;
}
public function saveAllDataInDB($entityObj){
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($entityObj);
$entityManager->flush();
}
public function getRegistrationResponseData($entityObj){
$responseArray = [
"proof" => $entityObj->getProof(),
"secret" => $entityObj->getShopSecret(),
"confirmation_url" => $entityObj->getConfUrl()
];
return $responseArray;
}
public function saveRegistrationRequestData($entityObj, $request){
#Data in Header
$entityObj->setSignatureReq($request->headers->get('shopware-app-signature'));
#Works with SW-Verion:6.4.1.0 (and higher)
#$entityObj->setShopVersion($request->headers->get('sw-version'));
#Data in Query
$entityObj->setShopId($request->query->get('shop-id'));
$entityObj->setShopUrl($request->query->get('shop-url'));
$entityObj->setTimestampReq($request->query->get('timestamp'));
}
public function saveHardcodedData($entityObj, $appName, $appSecret, $confUrl){
$entityObj->setAppName($appName);
$entityObj->setAppSecret($appSecret);
$entityObj->setConfUrl($confUrl);
}
public function createAndSaveProof($entityObj){
$proof = \hash_hmac('sha256', $entityObj->getShopId() .
$entityObj->getShopUrl() .
$entityObj->getAppName()
, $entityObj->getAppSecret()
);
$entityObj->setProof($proof);
}
public function createAndSaveShopSecret($entityObj){
#create random string
$length = 10;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
#save random string as shop-secret to db
$entityObj->setShopSecret($randomString);
}
}
ConfirmationController.php
Click to expand
<?php
namespace App\Controller;
use App\Entity\SwRegData;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ConfirmationController extends AbstractController{
/**
* @Route("/registration/confirm", name="confirm", methods={"POST"})
*/
public function confirm(Request $request):Response{
#get data from Post-Request
$data = json_decode($request->getContent(), true);
#get DB-Obj
$entityObj = $this->getDoctrine()->getRepository(SwRegData::class)->find($data['shopId']);
#check if shop-signature is correct
$hmac = \hash_hmac('sha256', $request->getContent(), $entityObj->getShopSecret());
$shopSignature = $request->headers->get('shopware-shop-signature');
if(hash_equals($hmac, $shopSignature)){
#$entityObj = $this->getDoctrine()->getRepository(SwRegData::class)->find($data['shopId']);
$this->saveConfirmationRequestData($entityObj, $data, $request);
$this->saveAllDataInDB($entityObj);
#Ok-Response ->can be empty
$response = new Response;
return $response;
}
else{
//->Error Response
$response = new Response;
return $response;
}
}
public function saveAllDataInDB($entityObj){
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($entityObj);
$entityManager->flush();
}
public function saveConfirmationRequestData($entityObj, $data, $request){
#Data in Header
$entityObj->setSignatureConf($request->headers->get('shopware-shop-signature'));
#available on SW6.4.1.0 (and higher)
#$entityObj->setSignatureConf($request->headers->get('sw-version'));
#Data in Body
$entityObj->setApiKey($data['apiKey']);
$entityObj->setSecretKey($data['secretKey']);
$entityObj->setTimestampConf($data['timestamp']);
#already in Db, entered by RegistrationController
#$entityObj->setShopId($data['shopId']);
#$entityObj->setShopUrl($data['shopUrl']);
}
}
ProductWrittenController.php
Click to expand
<?php
namespace App\Controller;
use App\Entity\SwRegData;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ProductWrittenEventController extends AbstractController{
/**
* @Route("/event/product-changed", name="event-productChanged", methods={"GET","POST"})
*/
public function productChanged(Request $request):Response{
#get data from post-event
$data = json_decode($request->getContent(), true);
#check access-method
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
#get correct entity-object from db with shopId
$entityObj = $this->getDoctrine()->getRepository(SwRegData::class)->find($data['source']['shopId']);
#check if shop-signature is correct
$hmac = \hash_hmac('sha256', $request->getContent(), $entityObj->getShopSecret());
$shopSignature = $request->headers->get('shopware-shop-signature');
if(hash_equals($hmac, $shopSignature)){
#get bearer token from shop
$token = $this->getBearerToken($entityObj);
#->ToDo: save in Db evtl. get from Shop if neccessary
#get the id of the updated product
$productString = $data['data']['payload'][0]['primaryKey'];
$productDataArray = $this->getProductData($productString, $token, $entityObj);
#to show on browser -> save data in file (->kind of for debug)
$this->writeDataToFileWithArray($productDataArray, "dataOfProduct");
return new Response;
}
else{
//->TODO: Error Response
return new Response;
}
}
else{
#-->Access with Get-Method ->Browser (->only for debug)
#show data in browser via twig ->passing variable to twig-template
$productData = json_decode(file_get_contents('data/dataOfProduct.txt'));
return $this->render('customTwigDir/productWritten.html.twig', [
'productName' => $productData->productName,
'productDescription' => $productData->productDescription,
]);
}
}
public function getBearerToken($entityObj):String {
$url = $entityObj->getShopUrl()."api/oauth/token";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POSTFIELDS, array(
'grant_type' => 'client_credentials',
'client_id' => $entityObj->getApiKey(),
'client_secret' => $entityObj->getSecretKey()
));
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
#set correct string-form, which is needed in further requests
$token = "Authorization: Bearer ".$response['access_token'];
return $token;
}
public function getProductData($productString, $token, $entityObj){
$url = $entityObj->getShopUrl()."api/product/".$productString;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
$token,
'Content-Type: application/json'
));
$data = json_decode(curl_exec($ch), true);
$productName = $data['data']['attributes']['name'];
$productDescription = $data['data']['attributes']['description'];
return [
'productName' => $productName,
'productDescription' => $productDescription
];
}
#for debug only! --> $this->writeDataToFile($data, "fileName");
public function writeDataToFileWithArray($data, $file){
$fileName = "data/".$file.".txt";
$handle = fopen($fileName, "w");
fwrite($handle, json_encode($data));
fclose($handle);
}
}