diff --git a/CHANGELOG.md b/CHANGELOG.md index f0298d42..2d8aa1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Add - Add cart button for product list +- Implement proxy - more information in README file ### Change - Support tab navigation for search, suggest and paging components diff --git a/README.md b/README.md index c9d3a468..898a2971 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ For more advanced features please check our official [WebComponnents documentati - [Test FTP Connection Button](#test-ftp-connection) - [Update Field Roles Button](#update-field-roles) - [Advanced Settings](#advanced-settings) + - [Proxy](#proxy) - [Features Settings](#features-settings) - [Using FACT-FinderĀ® on category pages](#using-fact-finder-on-category-pages) - [Feed Settings](#feed-settings) @@ -39,6 +40,8 @@ For more advanced features please check our official [WebComponnents documentati - [Click on Product](#click-on-product) - [Add Product to Cart](#add-product-to-cart) - [Place an Order](#place-an-order) +- [Modification Examples](#modifications-examples) + - [Enrich data received from FACT-Finder](#enrich-data-received-from-fact-finder) - [Contribute](#contribute) - [License](#license) @@ -121,10 +124,44 @@ This functionality uses form data, so there is no need to save first. ### Advanced Settings ![Advanced Settings](docs/assets/advanced-settings.png "Advanced settings") -* `Anonymize User ID?` - check this option if you want to send user id with tracking requests in anonymized form. By default the regular id field from user table is sent. +* `Anonymize User ID?` - check this option if you want to send user id with tracking requests in anonymized form. By default, the regular id field from user table is sent. +* `Use Proxy` - check this option if you want each request sends by Web Components first reach the dedicated module controller which forwards it to the FACT-Finder. + **Note:** If you plan to use proxy, consider reading below paragraph as it requires full instruction how to enable it properly. * `How to count single click on "Add to cart" button?` - select how would you like to count single click on "Add to cart" button * `Send the SID as userId when user not logged in?` +#### Proxy +Proxy feature adds a oxid controller which serves as a middleware between Web Components and FACT-FinderĀ®. +The data flow with proxy enabled is illustrated by the graph below. +![Communication Overview](docs/assets/communication-overview.png "Communication Overview") +Having a middleware controller brings many possibilities to customize the request and the response. You can use `EnrichProxyDataEvent` to enrich data received from FACT-Finder. You can find more +details about implementation [here](#enrich-data-received-from-fact-finder-in-proxycontroller). +In addition, if forwarded request does not result with a correct response, you can implement fallback strategy, starting from this point. + +```php + //src/Controller/SearchResultController.php:84 + protected function fallback(): void + { + //this function could be used to implement fallback logic in case of any communication error. + $this->showJsonAndExit('Error: Unable to process the request.'); + } +``` + +To enable proxy you need to change your HTTP server configuration by adding rewrite rules. +This is necessary because Web Components appends a URL parts to the base URL making it unreadable by the Oxid. +This is because Oxid use query parameters `cl` and `fnc` to instantiate specific controller and execute its function. +There is no routing that use url parts, hence any AJAX requests must target index.php file with the aforementioned parameters. +Without these rules any request will lead to 404. + +APACHE + +```apache + RewriteRule ^rest/v5/(.*)$ index.php?cl=search_result&fnc=proxy&$1 [L] +``` + +**Note:** Sending each request to FACT-Finder instance trough Shopware, you lose on performance as each request need to be handled first by HTTP server and then, by Shopware itself. This additional traffic could be easily avoided by not activating this feature if there's no clear reason to use it. + + ### Features Settings ![Features Settings](docs/assets/features-settings.png "Features settings") @@ -245,6 +282,35 @@ We offer a `registerAddToCartListener` function which helps to register `click` ### Place an Order This event is tracked by the `ff-checkout-tracking` element which is implemented on order confirmation page +## Modifications Examples + +### Enrich data received from FACT-Finder + +```php + +use Omikron\FactFinder\Oxid\Event\EnrichProxyDataEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class EnrichProxyDataEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [EnrichProxyDataEvent::class => 'enrichData']; + } + + public function enrichData(EnrichProxyDataEvent $event): void + { + $data = $event->getData(); + $data['example_data'] = [ + 'some_data' => 'data_1', + 'next_data' => 'data_2', + ]; + $event->setData($data); + } +} + +``` + ## Contribute For more information, click [here](.github/CONTRIBUTING.md) diff --git a/metadata.php b/metadata.php index 9d72ea20..37f2c0f2 100755 --- a/metadata.php +++ b/metadata.php @@ -83,14 +83,13 @@ 'value' => true, 'position' => $settingPosition++, ], -// TODO Refactor proxy -// [ -// 'group' => 'ffAdvanced', -// 'name' => 'ffUseProxy', -// 'type' => 'bool', -// 'value' => false, -// 'position' => $settingPosition++, -// ], + [ + 'group' => 'ffAdvanced', + 'name' => 'ffUseProxy', + 'type' => 'bool', + 'value' => false, + 'position' => $settingPosition++, + ], [ 'group' => 'ffAdvanced', 'name' => 'ffSidAsUserId', diff --git a/src/Controller/SearchResultController.php b/src/Controller/SearchResultController.php index 14771a7c..458c0938 100644 --- a/src/Controller/SearchResultController.php +++ b/src/Controller/SearchResultController.php @@ -6,11 +6,13 @@ use Omikron\FactFinder\Communication\Client\ClientBuilder; use Omikron\FactFinder\Communication\Version; +use Omikron\FactFinder\Oxid\Event\EnrichProxyDataEvent; +use Omikron\FactFinder\Oxid\Subscriber\EnrichProxyDataEventSubscriber; use OxidEsales\Eshop\Application\Controller\FrontendController; use OxidEsales\Eshop\Core\Registry; use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; use OxidEsales\EshopCommunity\Internal\Framework\Module\Facade\ModuleSettingServiceInterface; -use Psr\Http\Message\ResponseInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; class SearchResultController extends FrontendController { @@ -40,20 +42,32 @@ public function proxy(): void switch ($httpMethod) { case 'GET': - $query = (string) $this->removeOxidParams(parse_url($currentUrl, PHP_URL_QUERY)); - $this->showJsonAndExit($this->unwrapResponse($client->request('GET', $endpoint . '?' . $query))); + $query = (string) $this->removeOxidParams(parse_url($currentUrl, PHP_URL_QUERY)); + $response = $client->request('GET', $endpoint . '?' . $query); break; case 'POST': - $this->showJsonAndExit($this->unwrapResponse($client->request('POST', $endpoint, [ - 'body' => $this->getRequest()->getContent(), + $rawBody = file_get_contents('php://input'); + $body = json_decode($rawBody, true) ?: []; + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception('Invalid JSON in request body'); + } + + $response = $client->request('POST', $endpoint, [ + 'body' => json_encode($body), 'headers' => ['Content-Type' => 'application/json'], - ]))); + ]); break; default: throw new \Exception(sprintf('HTTP Method %s is not supported', $httpMethod)); } + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addSubscriber(new EnrichProxyDataEventSubscriber()); + $event = new EnrichProxyDataEvent(json_decode($response->getBody()->getContents(), true) ?? []); + $eventDispatcher->dispatch($event, EnrichProxyDataEvent::class); + $this->showJsonAndExit(json_encode($event->getData())); } catch (\Exception $e) { $this->fallback(); echo json_encode(['error' => $e->getMessage()]); @@ -71,7 +85,7 @@ protected function showJsonAndExit(string $jsonResponse): void protected function fallback(): void { // this function could be used to implement fallback logic in case of any communication error. - $this->showJsonAndExit(''); + $this->showJsonAndExit('Error: Unable to process the request.'); } protected function getConfigParam(string $key): string @@ -86,16 +100,16 @@ protected function getConfigParam(string $key): string private function getEndpoint(string $currentUrl): string { preg_match('#/([A-Za-z]+\.ff|rest/v[^?]*)#', $currentUrl, $match); + return $match[1] ?? ''; } - private function removeOxidParams(string $queryString): string + private function removeOxidParams(?string $queryString): string { - return preg_replace('/(fnc|cl)=[A-Za-z0-9_]*&?/', '', $queryString); - } + if ($queryString === null) { + return ''; + } - private function unwrapResponse(ResponseInterface $response): string - { - return $response->getBody()->getContents(); + return preg_replace('/(fnc|cl)=[A-Za-z0-9_]*&?/', '', $queryString); } } diff --git a/src/Event/EnrichProxyDataEvent.php b/src/Event/EnrichProxyDataEvent.php new file mode 100644 index 00000000..c1207db4 --- /dev/null +++ b/src/Event/EnrichProxyDataEvent.php @@ -0,0 +1,27 @@ +data = $data; + } + + public function getData(): array + { + return $this->data; + } + + public function setData(array $data): void + { + $this->data = $data; + } +} diff --git a/src/Model/Config/Communication.php b/src/Model/Config/Communication.php index 3acc3821..0e279ed2 100755 --- a/src/Model/Config/Communication.php +++ b/src/Model/Config/Communication.php @@ -70,12 +70,9 @@ protected function getLocale(string $abbr): string protected function getServerUrl(): string { - return (string) $this->moduleSettingService->getString('ffServerUrl', 'ffwebcomponents'); - - // TODO Refactor proxy - // return (string) $this->moduleSettingService->getBoolean('ffUseProxy', 'ffwebcomponents') ? - // 'index.php' : - // (string) $this->moduleSettingService->getString('ffServerUrl', 'ffwebcomponents'); + return (string) $this->moduleSettingService->getBoolean('ffUseProxy', 'ffwebcomponents') ? + '' : + (string) $this->moduleSettingService->getString('ffServerUrl', 'ffwebcomponents'); } protected function getCategoryPath(Category $category): string @@ -118,8 +115,6 @@ protected function getApiVersion(): string private function useProxy(): bool { - return false; - // TODO Refactor proxy - // return (bool) $this->moduleSettingService->getBoolean('ffUseProxy', 'ffwebcomponents'); + return (bool) $this->moduleSettingService->getBoolean('ffUseProxy', 'ffwebcomponents'); } } diff --git a/src/Subscriber/EnrichProxyDataEventSubscriber.php b/src/Subscriber/EnrichProxyDataEventSubscriber.php new file mode 100644 index 00000000..0caf2be9 --- /dev/null +++ b/src/Subscriber/EnrichProxyDataEventSubscriber.php @@ -0,0 +1,26 @@ + 'enrichData']; + } + + public function enrichData(EnrichProxyDataEvent $event): void + { + $data = $event->getData(); + $data['example_data'] = [ + 'some_data' => 'data_1', + 'some_data2' => 'data_2', + ]; + $event->setData($data); + } +} diff --git a/tests/Unit/Export/Exporter/ExportEntitiesTest.php b/tests/Unit/Export/Exporter/ExportEntitiesTest.php index ac02bbfd..255a1479 100644 --- a/tests/Unit/Export/Exporter/ExportEntitiesTest.php +++ b/tests/Unit/Export/Exporter/ExportEntitiesTest.php @@ -23,6 +23,9 @@ class ExportEntitiesTest extends TestCase /** @var CsvVariant */ private $stream; + /** @var string[] */ + private array $columns; + protected function setUp(): void { $this->columns = [ diff --git a/views/twig/extensions/themes/default/layout/base.html.twig b/views/twig/extensions/themes/default/layout/base.html.twig index 415ebfe9..ff6f191f 100644 --- a/views/twig/extensions/themes/default/layout/base.html.twig +++ b/views/twig/extensions/themes/default/layout/base.html.twig @@ -17,7 +17,7 @@ document.addEventListener(`ffCoreReady`, ({ factfinder, init, initialSearch }) => { init({ ff: { - url: `{{oViewConf.getFFStringConfigParam('ffServerUrl')}}`, + url: `{{communicationParams['url']}}`, channel: `{{communicationParams['channel']}}`, apiKey: `{{oViewConf.getFFStringConfigParam('ffApiKey')}}`, }, @@ -137,25 +137,6 @@ } } }); - -{# TODO Implement proxy #} -{# {% if oViewConf.getFFBoolConfigParam('ffUseProxy') %}#} -{# factfinder.__experimental.sandboxMode.enable = true;#} - -{# factfinder.eventAggregator.addBeforeDispatchingCallback(function (event) {#} -{# event.cl = 'search_result';#} -{# event.fnc = 'proxy';#} -{# });#} - -{# document.addEventListener('ffUrlWrite', function (event, historyState) {#} -{# let url = event.url.replace(/([?&]?)fnc=proxy/, '');#} -{# {% if oView.getClassKey() == "alist" %}#} -{# url = url.replace(/[?&]?cl=search_result/, '');#} -{# {% endif %}#} -{# history.replaceState(historyState, "", url);#} -{# });#} -{# {% endif %}#} - {% if oView.getClassKey() == "details" %}