integration_documentation:plugin:en:integration:shopware_5:extension
Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
integration_documentation:plugin:en:integration:shopware_5:extension [2021/11/30 17:12] tobi |
integration_documentation:plugin:en:integration:shopware_5:extension [2022/04/21 18:17] daniel |
||
---|---|---|---|
Line 11: | Line 11: | ||
1. Download the latest zip file from our [GitHub release](https://github.com/findologic/plugin-shopware-5-extension/releases) page. | 1. Download the latest zip file from our [GitHub release](https://github.com/findologic/plugin-shopware-5-extension/releases) page. | ||
- | 2. Open the Plugin Manager | + | |
- | 3. Click "Upload plugin" -> select downloaded zip file -> Click "Upload plugin" again | + | 2. Open the Plugin Manager in the Shopware backend. |
+ | |||
+ | 3. Click **Upload plugin** and select downloaded zip file. | ||
![](https://docs.findologic.com/lib/exe/fetch.php?cache=&media=integration_documentation:plugin:en:integration:shopware_5:plugin_installation.png) | ![](https://docs.findologic.com/lib/exe/fetch.php?cache=&media=integration_documentation:plugin:en:integration:shopware_5:plugin_installation.png) | ||
Line 117: | Line 119: | ||
} | } | ||
- | /* | + | //... |
- | * Example on how to add custom properties to the item | + | |
- | */ | + | |
- | public function setProperties() | + | |
- | { | + | |
- | parent::setProperties(); | + | |
- | } | + | |
- | + | ||
- | /* | + | |
- | * Direct Integration only: | + | |
- | * Example on how to add a property with all variants as a JSON. | + | |
- | * Each variant includes the corresponding URL and assigned images. | + | |
- | * | + | |
- | * Example: | + | |
- | * { | + | |
- | * "red": { | + | |
- | * "productUrl": "https://www.example.com/de/jacket?number=SW10000-01", | + | |
- | * "images": [ | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-red-01.jpg", | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-red-02.jpg", | + | |
- | * ], | + | |
- | * "thumbnails": [ | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-red-01_200x200.jpg", | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-red-02_200x200.jpg", | + | |
- | * ], | + | |
- | * }, | + | |
- | * "black": { | + | |
- | * "productUrl": "https://www.example.com/de/jacket?number=SW10000-02", | + | |
- | * "images": [ | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-black-01.jpg", | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-black-02.jpg", | + | |
- | * ], | + | |
- | * "thumbnails": [ | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-black-01_200x200.jpg", | + | |
- | * "https://www.example.com/media/image/73/bc/ef/jacket-black-02_200x200.jpg", | + | |
- | * ], | + | |
- | * } | + | |
- | * } | + | |
- | */ | + | |
- | protected function addVariantsJson() | + | |
- | { | + | |
- | $variants = []; | + | |
- | + | ||
- | /** @var Detail $variant */ | + | |
- | foreach ($this->variantArticles as $variant) { | + | |
- | if (!$variant->getActive() || | + | |
- | count($variant->getConfiguratorOptions()) === 0 || | + | |
- | (Shopware()->Config()->get('hideNoInStock') && $variant->getInStock() < 1) | + | |
- | ) { | + | |
- | continue; | + | |
- | } | + | |
- | + | ||
- | foreach ($variant->getConfiguratorOptions() as $option) { | + | |
- | if (StaticHelper::isEmpty($option->getName()) || | + | |
- | StaticHelper::isEmpty($option->getGroup()->getName()) | + | |
- | ) { | + | |
- | continue; | + | |
- | } | + | |
- | + | ||
- | $variantType = StaticHelper::removeControlCharacters($option->getGroup()->getName()); | + | |
- | $variantValue = StaticHelper::removeControlCharacters($option->getName()); | + | |
- | + | ||
- | if ($variantType === self::VARIANT_TYPE && !isset($variants[$variantValue])) { | + | |
- | $variants[$variantValue] = $this->getNewVariant($variant); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | if (!StaticHelper::isEmpty($variants)) { | + | |
- | $this->xmlArticle->addProperty( | + | |
- | new Property('variants', ['' => json_encode($variants)]) | + | |
- | ); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * @param Detail $variant | + | |
- | * @return array | + | |
- | */ | + | |
- | protected function getNewVariant($variant) | + | |
- | { | + | |
- | return [ | + | |
- | 'productUrl' => $this->getUrlByVariant($variant), | + | |
- | 'images' => $this->getVariantImages($variant), | + | |
- | 'thumbnails' => $this->getVariantImages($variant, true), | + | |
- | ]; | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * @param Detail $variant | + | |
- | * @param boolean $getThumbnail | + | |
- | * @return string[] | + | |
- | */ | + | |
- | protected function getVariantImages($variant, $getThumbnail = false) | + | |
- | { | + | |
- | $images = []; | + | |
- | + | ||
- | /** @var Image $image */ | + | |
- | foreach ($variant->getImages() as $image) { | + | |
- | if (!$image->getParent() || $image->getParent()->getMedia() === null) { | + | |
- | continue; | + | |
- | } | + | |
- | + | ||
- | $imageUrl = $this->getImageUrlByImage($image->getParent(), $getThumbnail); | + | |
- | if ($imageUrl) { | + | |
- | $images[$image->getPosition()] = $imageUrl; | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | return array_values($images); | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * @param Image $image | + | |
- | * @param boolean $getThumbnail | + | |
- | * @return string|null | + | |
- | */ | + | |
- | protected function getImageUrlByImage($image, $getThumbnail = false) | + | |
- | { | + | |
- | /** @var Image $imageRaw */ | + | |
- | $imageRaw = $image->getMedia(); | + | |
- | if (!($imageRaw instanceof Media) || $imageRaw === null) { | + | |
- | return null; | + | |
- | } | + | |
- | + | ||
- | try { | + | |
- | $imageDetails = $imageRaw->getThumbnailFilePaths(); | + | |
- | $imageDefault = $imageRaw->getPath(); | + | |
- | } catch (Exception $ex) { | + | |
- | // Entity removed | + | |
- | return null; | + | |
- | } | + | |
- | + | ||
- | if (count($imageDetails) > 0) { | + | |
- | return $getThumbnail | + | |
- | ? strtr($this->mediaService->getUrl(array_values($imageDetails)[0]), self::URL_REPLACEMENTS) | + | |
- | : strtr($this->mediaService->getUrl($imageDefault), self::URL_REPLACEMENTS); | + | |
- | } | + | |
- | + | ||
- | return null; | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * @param Detail $variant | + | |
- | * @return string | + | |
- | */ | + | |
- | protected function getUrlByVariant($variant) | + | |
- | { | + | |
- | $baseLink = $this->getBaseLinkByVariant($variant); | + | |
- | + | ||
- | return Shopware()->Modules()->Core()->sRewriteLink($baseLink, $variant->getArticle()->getName()); | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * @param Detail $variant | + | |
- | * @return string | + | |
- | */ | + | |
- | protected function getBaseLinkByVariant($variant) | + | |
- | { | + | |
- | return sprintf( | + | |
- | '%s?sViewport=detail&sArticle=%s&number=%s', | + | |
- | Shopware()->Config()->get('baseFile'), | + | |
- | $variant->getArticle()->getId(), | + | |
- | $variant->getNumber() | + | |
- | ); | + | |
- | } | + | |
} | } | ||
Line 331: | Line 168: | ||
## Add a custom free text field | ## Add a custom free text field | ||
- | As stated [here](https://docs.findologic.com/doku.php?id=integration_documentation:plugin:en:integration:shopware_5:export_information#attributes), due to a limitation of Shopware, only the free text field from `attr1` to `attr20` are exported by default. Additionally added free text fields need to be exported manually. | + | The default [attributes export](https://docs.findologic.com/doku.php?id=integration_documentation:plugin:en:integration:shopware_5:export_information#attributes) contains the free text field from `attr1` to `attr20`. Additional and custom free text fields can be exported using the example below. |
```php | ```php | ||
Line 354: | Line 191: | ||
public function addAdditionalFreeTextFields(): void | public function addAdditionalFreeTextFields(): void | ||
{ | { | ||
- | $free_text_fields = $this->baseVariant->getAttribute(); | + | $freeTextFields = $this->baseVariant->getAttribute(); |
| | ||
- | if(is_callable([$free_text_fields, 'getCustomRatingCount'])) { | + | // Add free text field as property (without usergroup) |
- | $customRatingCount = $free_text_fields->getCustomRatingCount(); | + | if(is_callable([$freeTextFields, 'getCustomRatingCount'])) { |
+ | $customRatingCount = $freeTextFields->getCustomRatingCount(); | ||
if (!StaticHelper::isEmpty($customRatingCount)) { | if (!StaticHelper::isEmpty($customRatingCount)) { | ||
$this->xmlArticle->addProperty( | $this->xmlArticle->addProperty( | ||
Line 364: | Line 202: | ||
} | } | ||
} | } | ||
- | + | ||
- | if(is_callable([$free_text_fields, 'getCustomRatingAvg'])) { | + | // Add free text field as attribute |
- | $customRatingAvg = $free_text_fields->getCustomRatingAvg(); | + | if(is_callable([$freeTextFields, 'getCustomRatingAvg'])) { |
+ | $customRatingAvg = $freeTextFields->getCustomRatingAvg(); | ||
if (!StaticHelper::isEmpty($customRatingAvg)) { | if (!StaticHelper::isEmpty($customRatingAvg)) { | ||
$this->xmlArticle->addAttribute( | $this->xmlArticle->addAttribute( | ||
Line 377: | Line 216: | ||
``` | ``` | ||
- | This function names are dynamically generated by Shopware. Those functions are accessible as PascalCase, prefixed with "get". So the getter for the free text field `custom_rating_count`, is generated as `getCustomRatingCount`. | + | The function names are dynamically generated by Shopware. They are accessible as PascalCase, prefixed with "get". So the getter for the free text field `custom_rating_count`, is generated as `getCustomRatingCount`. |
## Add variant data to the export | ## Add variant data to the export | ||
- | The default extension plugin already includes the code to export variants as a JSON string, this code just needs a small adaption: | + | The default extension plugin already includes the code to export variants as a JSON string, the provided code just needs a small adaption: |
1. Check out the variant configuration and get the property name, which defines your variants. | 1. Check out the variant configuration and get the property name, which defines your variants. | ||
Line 397: | Line 236: | ||
``` | ``` | ||
- | ### Example JSON | + | The exported variant JSON could look like: |
```json | ```json | ||
Line 425: | Line 264: | ||
} | } | ||
``` | ``` | ||
+ | |||
+ | ## Add custom sorting options for API Integration | ||
+ | |||
+ | </markdown> | ||
+ | <note important>Requires Findologic Plugin v11.5.2 or above</note> | ||
+ | <markdown> | ||
+ | |||
+ | The Findologic plugin already provides sorting options for the most common use-cases. The plugin uses `SortingHandler` to send the currently selected sorting option via API parameters to the Findologic Search-API (see [all available SortingHandlers](https://github.com/findologic/plugin-shopware-5/tree/main/FinSearchUnified/Bundle/SearchBundleFindologic/SortingHandler)). | ||
+ | |||
+ | To handle custom sorting options, create a custom `SortingHandler` in the extension plugin, and override the responsible `QueryBuilderFactory` to include the created `SortingHandler`. | ||
+ | |||
+ | Supported Shopware 5 sortings by default: | ||
+ | - Release date | ||
+ | - Popularity | ||
+ | - Product name | ||
+ | - Price | ||
+ | - Relevance | ||
+ | |||
+ | ### Prerequisites | ||
+ | |||
+ | Before a custom sorting can be used, make sure to export the value for the custom sort in the `<sort>` field in the export. See the [XML Format documentation](:xml_export_documentation:xml_format#sorts) for further details. | ||
+ | |||
+ | ### Implementation | ||
+ | |||
+ | Example implementation for sorting according to the stock value. This can be done for any non-supported sorting option, or with some sorting from a third-party plugin. | ||
+ | |||
+ | **Step 1: Export the sort value** | ||
+ | |||
+ | The export of the stock value needs to be added to `ExtendFinSearchUnified/BusinessLogic/Models/FindologicArticleModel.php` | ||
+ | |||
+ | ```php | ||
+ | <?php | ||
+ | |||
+ | namespace ExtendFinSearchUnified\BusinessLogic\Models; | ||
+ | |||
+ | use FINDOLOGIC\Export\Data\Sort; | ||
+ | use FinSearchUnified\BusinessLogic\Models\FindologicArticleModel as OriginalFindologicArticleModel; | ||
+ | use Shopware\Models\Article\Article; | ||
+ | use Shopware\Models\Category\Category; | ||
+ | |||
+ | class FindologicArticleModel extends OriginalFindologicArticleModel | ||
+ | { | ||
+ | public function __construct( | ||
+ | Article $shopwareArticle, | ||
+ | $shopKey, | ||
+ | array $allUserGroups, | ||
+ | array $salesFrequency, | ||
+ | Category $baseCategory | ||
+ | ) { | ||
+ | parent::__construct($shopwareArticle, $shopKey, $allUserGroups, $salesFrequency, $baseCategory); | ||
+ | |||
+ | $this->setSort(); | ||
+ | } | ||
+ | |||
+ | public function setSort() | ||
+ | { | ||
+ | $sort = new Sort(); | ||
+ | $sort->setValue($this->baseVariant->getInStock()); | ||
+ | |||
+ | $this->xmlArticle->setSort($sort); | ||
+ | } | ||
+ | } | ||
+ | ``` | ||
+ | |||
+ | **Step 2: Find the correct Shopware/Third-party sorting class** | ||
+ | |||
+ | The available Shopware sorting classes can be found [here](https://github.com/shopware/shopware/tree/5.7/engine/Shopware/Bundle/SearchBundle/Sorting). This is needed for the compatibility check inside the `SortingHandler`. | ||
+ | |||
+ | Using `ProductStockSorting` for this example. | ||
+ | |||
+ | **Step 2: Create a `SortingHandler`** | ||
+ | |||
+ | Create the folder structure `Bundle/SearchBundleFindologic/SortingHandler` in the extension plugin, and add a custom sorting handler class. In this example it will be `ProductStockSortingHandler`: | ||
+ | |||
+ | ```php | ||
+ | <?php | ||
+ | |||
+ | namespace ExtendFinSearchUnified\Bundle\SearchBundleFindologic\SortingHandler; | ||
+ | |||
+ | use FinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilder; | ||
+ | use FinSearchUnified\Bundle\SearchBundleFindologic\SortingHandlerInterface; | ||
+ | use Shopware\Bundle\SearchBundle\Sorting\ProductStockSorting; | ||
+ | use Shopware\Bundle\SearchBundle\SortingInterface; | ||
+ | use Shopware\Bundle\StoreFrontBundle\Struct\ShopContextInterface; | ||
+ | |||
+ | class ProductStockSortingHandler implements SortingHandlerInterface | ||
+ | { | ||
+ | /** | ||
+ | * Checks if the passed sorting can be handled by this class | ||
+ | * | ||
+ | * @param SortingInterface $sorting | ||
+ | * | ||
+ | * @return bool | ||
+ | */ | ||
+ | public function supportsSorting(SortingInterface $sorting) | ||
+ | { | ||
+ | return $sorting instanceof ProductStockSorting; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Handles the passed sorting object. | ||
+ | * | ||
+ | * @param SortingInterface $sorting | ||
+ | * @param QueryBuilder $query | ||
+ | * @param ShopContextInterface $context | ||
+ | */ | ||
+ | public function generateSorting(SortingInterface $sorting, QueryBuilder $query, ShopContextInterface $context) | ||
+ | { | ||
+ | /** @var ProductStockSorting $sorting */ | ||
+ | $query->addOrder('shopsort ' . $sorting->getDirection()); | ||
+ | } | ||
+ | } | ||
+ | ``` | ||
+ | |||
+ | **Step 3: Decorate the `QueryBuilderFactory`** | ||
+ | |||
+ | The `QueryBuilderFactory` registers all the available sorting options. Add your custom sort to this factory. | ||
+ | Create the folder structure `Bundle/SearchBundleFindologic/QueryBuilder` in the extension plugin, and add the file `QueryBuilderFactory`. Override the `registerSortingHandlers` method and add your handler to the array. | ||
+ | |||
+ | ```php | ||
+ | <?php | ||
+ | |||
+ | namespace ExtendFinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder; | ||
+ | |||
+ | use ExtendFinSearchUnified\Bundle\SearchBundleFindologic\SortingHandler\ProductStockSortingHandler; | ||
+ | use FinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilderFactory as OriginalQueryBuilderFactory; | ||
+ | use FinSearchUnified\Bundle\SearchBundleFindologic\SortingHandlerInterface; | ||
+ | |||
+ | class QueryBuilderFactory extends OriginalQueryBuilderFactory | ||
+ | { | ||
+ | /** | ||
+ | * @return SortingHandlerInterface[] | ||
+ | */ | ||
+ | protected function registerSortingHandlers() | ||
+ | { | ||
+ | $sortingHandlers = parent::registerSortingHandlers(); | ||
+ | |||
+ | $sortingHandlers[] = new ProductStockSortingHandler(); | ||
+ | |||
+ | return $sortingHandlers; | ||
+ | } | ||
+ | } | ||
+ | ``` | ||
+ | |||
+ | |||
+ | **Step 4: Add the decorated factory to the services.xml** | ||
+ | |||
+ | Finally decorate it in your `Resources/services.xml`: | ||
+ | |||
+ | ```xml | ||
+ | <?xml version="1.0" encoding="utf-8"?> | ||
+ | <container xmlns="http://symfony.com/schema/dic/services" | ||
+ | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
+ | xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
+ | <services> | ||
+ | <service | ||
+ | id="extend_fin_search_unified.article_model_factory" | ||
+ | class="ExtendFinSearchUnified\BusinessLogic\FindologicArticleFactory" | ||
+ | decorates="fin_search_unified.article_model_factory" | ||
+ | > | ||
+ | </service> | ||
+ | |||
+ | <service | ||
+ | id="extend_fin_search_unified.query_builder_factory" | ||
+ | class="ExtendFinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilderFactory" | ||
+ | decorates="fin_search_unified.query_builder_factory" | ||
+ | > | ||
+ | <argument type="service" id="shopware_plugininstaller.plugin_manager" /> | ||
+ | <argument type="service" id="config" /> | ||
+ | </service> | ||
+ | </services> | ||
+ | </container> | ||
+ | ``` | ||
+ | |||
+ | Once this step is done, selecting your relevant sorting option will send the order parameter to the Findologic API. | ||
</markdown> | </markdown> |