<?php declare(strict_types=1);
namespace LoyxxSW6CategoryTreeTeaser;
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotCollection;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Shopware\Core\Framework\Plugin\Context\UpdateContext;
use Shopware\Core\System\CustomField\Aggregate\CustomFieldSet\CustomFieldSetEntity;
use Shopware\Core\System\CustomField\CustomFieldEntity;
use Shopware\Core\System\CustomField\CustomFieldTypes;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
class LoyxxSW6CategoryTreeTeaser extends Plugin
{
public const CUSTOM_FIELD_PREFIX = 'loyxx_category_tree_teaser';
public $iconFile = __DIR__ . '/Resources/config/storefront.icons.json';
public $shoppingWorldIconFile = __DIR__ . '/Resources/config/storefront.shopping.world.icons.json';
public $icons = [];
public $shoppingWorldIcons = [];
public function install(InstallContext $installContext): void
{
parent::install($installContext);
$this->_updateCMSSlotName($installContext->getContext());
$this->_prepareStorefrontIcons();
$this->createCustomFieldSystem($installContext);
}
public function uninstall(UninstallContext $uninstallContext): void
{
parent::uninstall($uninstallContext);
$this->removeCustomFieldSystem($uninstallContext);
}
public function update(UpdateContext $updateContext): void
{
parent::update($updateContext);
$this->_updateCMSSlotName($updateContext->getContext());
$this->_prepareStorefrontIcons();
$this->updateCustomFieldSystem($updateContext);
}
private function createCustomFieldSystem(InstallContext $installContext)
{
$customFieldSetRepository = $this->container->get('custom_field_set.repository');
$context = $installContext->getContext();
//check if the custom field set is defined
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('name', static::CUSTOM_FIELD_PREFIX));
$customFieldSetEntity = $customFieldSetRepository->searchIds($criteria, $context);
if (!$customFieldSetEntity->getTotal()) {
$customFieldSetRepository->create(
[
[
'name' => static::CUSTOM_FIELD_PREFIX,
'customFields' => $this->_getCustomFieldItems(),
'relations' => [
['entityName' => 'category']
],
'config' => [
'description' => [
'en-GB' => 'Category tree teaser manager.',
'de-DE' => 'Kategorie-Teaser-Manager'
],
'label' => [
'en-GB' => 'Category Tree Teaser',
'de-DE' => 'Kategorie-Teaser'
],
'translated' => true
]
]
],
$context
);
}
}
private function removeCustomFieldSystem(UninstallContext $uninstallContext)
{
$customFieldSetRepository = $this->container->get('custom_field_set.repository');
$context = $uninstallContext->getContext();
//check if the custom field set is defined
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('name', static::CUSTOM_FIELD_PREFIX));
$customFieldSetEntity = $customFieldSetRepository->searchIds($criteria, $context);
if ($customFieldSetEntity->getTotal()) {
$customFieldSetRepository->delete(array_values($customFieldSetEntity->getData()), $context);
}
// Remove icon file
$this->deleteIconFiles();
}
private function _updateCMSSlotName(Context $context)
{
$cmsSlotRepository = $this->container->get('cms_slot.repository');
// take old cms repositories
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('type', 'category-tree-teaser'));
$criteria->addFilter(new EqualsFilter('slot', 'content'));
/** @var CmsSlotCollection $slots */
$slots = $cmsSlotRepository->search($criteria, $context);
if ($slots->count() !== 0) {
foreach ($slots->getElements() as $slot) {
// update name
$currentConfig = $slot->getConfig();
if (!isset($currentConfig['categories'])) {
$currentConfig['categories'] = [
"value" => [],
"source" => "static"
];
$currentConfig['enableCustomCategories'] = [
"value" => false,
"source" => "static"
];
}
$cmsSlotRepository->update([
['id' => $slot->getId(), 'slot' => 'categoryTreeTeaser', 'config' => $currentConfig]
], $context);
}
}
}
private function _prepareStorefrontIcons()
{
// refresh icons
$this->deleteIconFiles();
// get all icons from
$storefrontIconPath = rtrim($this->container->getParameter('storefrontRoot'), '/') . '/Resources/app/storefront/dist/assets/icon/default';
$storefrontIconBundlePath = $this->getThemeDirectory();
$finder = new Finder();
$finder->files()
->in($storefrontIconPath)
->name('*.svg')
->ignoreDotFiles(true)
->ignoreVCS(true)
->sortByName();
if ($finder->hasResults()) {
foreach ($finder as $file) {
$filename = $file->getBasename();
$name = $file->getBasename('.' . $file->getExtension());
$icon = $storefrontIconBundlePath . $filename;
$this->icons[] = compact('name', 'filename', 'icon');
if (strtolower(strtolower(substr($name, 0, 5))) === 'arrow') {
$this->shoppingWorldIcons[] = compact('name', 'filename', 'icon');
}
}
}
if (empty($this->icons)) {
foreach (['arrow-left.svg', 'arrow-right.svg', 'arrow-down.svg', 'arrow-up.svg', 'bag.svg', 'bag-product.svg'] as $file) {
$icon = [
'name' => basename($file, '.svg'),
'filename' => $file,
'icon' => $storefrontIconBundlePath . $file
];
$this->icons[] = $icon;
$this->shoppingWorldIcons[] = $icon;
}
}
$fs = new Filesystem();
$fs->dumpFile($this->iconFile, (new JsonEncoder())->encode($this->icons, JsonEncoder::FORMAT));
$fs->dumpFile($this->shoppingWorldIconFile, (new JsonEncoder())->encode($this->shoppingWorldIcons, JsonEncoder::FORMAT));
}
private function getThemeDirectory($default = true,$absolutePath = false)
{
if ($default){
return '/bundles/storefront/assets/icon/default/';
}
$finder = new Finder();
$finder->files()
->in($this->container->getParameter('shopware.filesystem.public.config.root') . '/theme')
->name("*.svg")
->ignoreDotFiles(true)
->ignoreVCS(true)
->sortByName();
foreach ($finder as $file) {
if (!is_null($file)){
break;
}
}
return $absolutePath ? $file->getPath() : '/theme/'.$file->getRelativePath() .'/';
}
private function updateCustomFieldSystem(UpdateContext $updateContext)
{
$customFieldSetRepository = $this->container->get('custom_field_set.repository');
$context = $updateContext->getContext();
//check if the custom field set is defined
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('name', static::CUSTOM_FIELD_PREFIX));
$criteria->addAssociation('customFields');
/** @var CustomFieldSetEntity $customFieldSetEntity */
$customFieldSetEntity = $customFieldSetRepository->search($criteria, $context)->first();
if ($customFieldSetEntity) {
$customFields = $this->_getCustomFieldItems();
/** @var CustomFieldEntity $item */
foreach ($customFieldSetEntity->getCustomFields() as $item) {
$key = array_search($item->getName(), array_column($customFields, 'name')) ;
if ($key !== FALSE){
$customFields[$key]['id'] = $item->getId();
}
}
$customFieldSetRepository->upsert(
[
[
'id' => $customFieldSetEntity->getId(),
'name' => static::CUSTOM_FIELD_PREFIX,
'customFields' => $customFields
]
],
$context
);
}
}
private function deleteIconFiles()
{
$fs = new Filesystem();
$fs->remove([
$this->iconFile,
$this->shoppingWorldIconFile
]);
}
private function _getCustomFieldItems() : array
{
$options = [];
$options[] = [
"label" => [
"de-DE" => 'Keine',
"en-GB" => 'None'
],
"value" => 'none'
];
if ($this->icons) {
foreach ($this->icons as $icon) {
$name = ucwords(str_replace('-', ' ', $icon['name']));
$options[] = [
"label" => [
"de-DE" => $name,
"en-GB" => $name
],
"value" => $icon['name']
];
}
}
return [
[
'name' => static::CUSTOM_FIELD_PREFIX . '_disabled',
'type' => CustomFieldTypes::BOOL,
'config' => [
'type' => 'switch',
'label' => [
'en-GB' => 'Hide category teaser element for this category entirely',
'de-DE' => 'Kategorie-Teaser-Element für diese Kategorie vollständig ausblenden'
],
'componentName' => 'sw-field',
'customFieldType' => 'switch',
'customFieldPosition' => 1,
'helpText' => [
'en-GB' => 'After enabling this option, the whole category teaser is no longer visible in this category',
'de-DE' => 'Nachdem Sie diese Option aktiviert haben, ist der gesamte Kategorie Teaser in dieser Kategorie nicht mehr sichtbar'
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_hidden_from_teaser',
'type' => CustomFieldTypes::BOOL,
'config' => [
'type' => 'switch',
'label' => [
'en-GB' => 'Exclude this category from category teaser element',
'de-DE' => 'Diese Kategorie aus dem Kategorie Teaser Element ausschließen'
],
'componentName' => 'sw-field',
'customFieldType' => 'switch',
'customFieldPosition' => 2,
'helpText' => [
'en-GB' => 'After enabling this option this category will not be visible inside the category teaser',
'de-DE' => 'Nachdem Sie diese Option aktiviert haben, ist diese Kategorie im Kategorie Teaser nicht mehr sichtbar'
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_teaser',
'type' => CustomFieldTypes::HTML,
'config' => [
'type' => 'textEditor',
'label' => [
'en-GB' => 'Teaser Text',
'de-DE' => 'Teaser-Text'
],
'componentName' => 'sw-text-editor',
'customFieldType' => 'textEditor',
'customFieldPosition' => 3,
'helpText' => [
'en-GB' => ' If you enable this option, an additional teaser text is displayed below the categories name. To add this text, go to the custom field section under Catalog > Categories > Select your category. Keep the teaser text as short as possible',
'de-DE' => 'Wenn Sie diese Option aktivieren, wird unterhalb des Kategorie Names ein zusätzlicher Teaser Text angezeigt. Um diesen Text hinzuzufügen, gehen Sie unter Kataloge > Kategorien > jeweilige Kategorie auswählen in den Bereich der Zusatzfelder. Halten Sie den Teaser Text möglichst kurz'
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_icons',
'type' => CustomFieldTypes::SELECT,
'config' => [
'type' => 'select',
'label' => [
'en-GB' => 'Custom Icon',
'de-DE' => ' Zusätzliches Icon'
],
"options" => $options,
"placeholder" => [
"en-GB" => null,
"de-DE" => null
],
'componentName' => 'sw-single-select',
'customFieldType' => 'select',
'customFieldPosition' => 4,
'helpText' => [
'en-GB' => 'If you select a custom icon, it will be displayed before the category name within the teaser block ',
'de-DE' => 'Wenn Sie ein benutzerdefiniertes Zusätzliches Icon auswählen, wird es vor dem Kategorienamen innerhalb des Teaser-Blocks angezeigt '
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_hide_shopping_world_icon',
'type' => CustomFieldTypes::BOOL,
'config' => [
'type' => 'switch',
'label' => [
'en-GB' => 'Hide icon assigned through shopping world configuration',
'de-DE' => 'Durch die Konfiguration der Einkaufswelt zugewiesenes Symbol ausblenden'
],
'componentName' => 'sw-field',
'customFieldType' => 'switch',
'customFieldPosition' => 5,
'helpText' => [
'en-GB' => 'When you enable this option, the icon assigned to the teaser block through shopping world configuration will be hidden',
'de-DE' => 'Wenn Sie diese Option aktivieren, wird das Zusätzliches Icon, das dem Teaser-Block durch die Konfiguration der Einkaufswelt zugewiesen wurde, ausgeblendet'
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_hide_label',
'type' => CustomFieldTypes::BOOL,
'config' => [
'type' => 'switch',
'label' => [
'en-GB' => 'Hide category label',
'de-DE' => 'Kategoriebeschriftung ausblenden'
],
'componentName' => 'sw-field',
'customFieldType' => 'switch',
'customFieldPosition' => 6,
'helpText' => [
'en-GB' => 'If you activate this option, the category name is not displayed at the bottom of the category image.',
'de-DE' => 'Wenn Sie diese Option aktivieren, wird der Kategoriename nicht am unteren Rand des Kategoriebildes angezeigt.'
],
'translated' => true
]
],
[
'name' => static::CUSTOM_FIELD_PREFIX . '_label_image',
'type' => 'media',
'config' => [
'type' => 'media',
'label' => [
'en-GB' => 'Select an optional caption image',
'de-DE' => 'Wählen Sie ein optionales Untertitel-Bild aus.'
],
'componentName' => 'sw-media-field',
'customFieldType' => 'media',
'customFieldPosition' => 7,
'helpText' => [
'en-GB' => 'The caption image is optional and appears between the main category image and the category name. (Ideal for displaying brand logos below the main image of the category teaser).',
'de-DE' => 'Das Bild ist optional und wird zwischen dem Hauptkategoriebild und dem Kategorienamen angezeigt. (Ideal für die Anzeige von Markenlogos unterhalb des Hauptbildes des Kategorie-Teasers).'
],
'translated' => true
]
]
];
}
}