diff --git a/bundles/AdminBundle/public/js/pimcore/document/editables/image.js b/bundles/AdminBundle/public/js/pimcore/document/editables/image.js index 4b915b4d1fe..4788c3a7f39 100644 --- a/bundles/AdminBundle/public/js/pimcore/document/editables/image.js +++ b/bundles/AdminBundle/public/js/pimcore/document/editables/image.js @@ -242,7 +242,17 @@ pimcore.document.editables.image = Class.create(pimcore.document.editable, { } catch (e) { console.log(e); } - }.bind(this)); + }.bind(this), + function (res) { + const response = Ext.decode(res.response.responseText); + if (response && response.success === false) { + pimcore.helpers.showNotification(t("error"), response.message, "error", + res.response.responseText); + } else { + pimcore.helpers.showNotification(t("error"), res, "error", + res.response.responseText); + } + }.bind(this), [] ,this.getType()); }, onNodeOver: function(target, dd, e, data) { diff --git a/bundles/AdminBundle/public/js/pimcore/element/helpers/gridColumnConfig.js b/bundles/AdminBundle/public/js/pimcore/element/helpers/gridColumnConfig.js index cf21841182b..e72578e8fb5 100644 --- a/bundles/AdminBundle/public/js/pimcore/element/helpers/gridColumnConfig.js +++ b/bundles/AdminBundle/public/js/pimcore/element/helpers/gridColumnConfig.js @@ -713,6 +713,7 @@ pimcore.element.helpers.gridColumnConfig = { settings = Ext.encode(settings); params["settings"] = settings; Ext.Ajax.request({ + method: 'POST', url: this.exportPrepareUrl, params: params, success: function (response) { diff --git a/bundles/AdminBundle/public/js/pimcore/helpers.js b/bundles/AdminBundle/public/js/pimcore/helpers.js index c884f35f0aa..ca615d4c9f9 100644 --- a/bundles/AdminBundle/public/js/pimcore/helpers.js +++ b/bundles/AdminBundle/public/js/pimcore/helpers.js @@ -949,7 +949,7 @@ pimcore.helpers.openMemorizedTabs = function () { } }; -pimcore.helpers.assetSingleUploadDialog = function (parent, parentType, success, failure, context) { +pimcore.helpers.assetSingleUploadDialog = function (parent, parentType, success, failure, context, type) { var params = {}; params['parent' + ucfirst(parentType)] = parent; @@ -959,6 +959,10 @@ pimcore.helpers.assetSingleUploadDialog = function (parent, parentType, success, url += "&context=" + Ext.encode(context); } + if(type) { + url += "&type=" + type; + } + pimcore.helpers.uploadDialog(url, 'Filedata', success, failure); }; diff --git a/bundles/AdminBundle/public/js/pimcore/object/helpers/classTree.js b/bundles/AdminBundle/public/js/pimcore/object/helpers/classTree.js index 9515f7a35ae..2ef6df013dc 100644 --- a/bundles/AdminBundle/public/js/pimcore/object/helpers/classTree.js +++ b/bundles/AdminBundle/public/js/pimcore/object/helpers/classTree.js @@ -192,7 +192,7 @@ pimcore.object.helpers.classTree = Class.create({ newNode = fn(); - if (con.children) { + if (con.children && newNode) { for (var i = 0; i < con.children.length; i++) { this.recursiveAddNode(con.children[i], newNode, brickDescriptor, config); } diff --git a/bundles/AdminBundle/public/js/pimcore/object/tags/advancedManyToManyRelation.js b/bundles/AdminBundle/public/js/pimcore/object/tags/advancedManyToManyRelation.js index 26867c4a3d7..270eb128b25 100644 --- a/bundles/AdminBundle/public/js/pimcore/object/tags/advancedManyToManyRelation.js +++ b/bundles/AdminBundle/public/js/pimcore/object/tags/advancedManyToManyRelation.js @@ -113,10 +113,14 @@ pimcore.object.tags.advancedManyToManyRelation = Class.create(pimcore.object.tag var renderer = null; var listeners = null; - if (this.fieldConfig.columns[i].type == "number" && !readOnly) { - cellEditor = function () { - return new Ext.form.NumberField({}); - }; + if (this.fieldConfig.columns[i].type == "number") { + if(!readOnly) { + cellEditor = function () { + return new Ext.form.NumberField({}); + }; + } + + renderer = Ext.util.Format.numberRenderer(); } else if (this.fieldConfig.columns[i].type == "text" && !readOnly) { cellEditor = function () { return new Ext.form.TextField({}); @@ -820,7 +824,7 @@ pimcore.object.tags.advancedManyToManyRelation = Class.create(pimcore.object.tag uploadDialog: function () { if (!this.fieldConfig.allowMultipleAssignments || (this.fieldConfig["maxItems"] && this.fieldConfig["maxItems"] >= 1)) { - if ((this.store.getData().getSource() || this.store.getData()).count() >= this.fieldConfig.maxItems) { + if (this.fieldConfig.maxItems && (this.store.getData().getSource() || this.store.getData()).count() >= this.fieldConfig.maxItems) { Ext.Msg.alert(t("error"), t("limit_reached")); return true; } diff --git a/bundles/AdminBundle/public/js/pimcore/object/tags/image.js b/bundles/AdminBundle/public/js/pimcore/object/tags/image.js index 79fdc6fc867..fedd81ed67f 100644 --- a/bundles/AdminBundle/public/js/pimcore/object/tags/image.js +++ b/bundles/AdminBundle/public/js/pimcore/object/tags/image.js @@ -275,7 +275,7 @@ pimcore.object.tags.image = Class.create(pimcore.object.tags.abstract, { pimcore.helpers.showNotification(t("error"), res, "error", res.response.responseText); } - }.bind(this), this.context); + }.bind(this), this.context, this.type); }, addDataFromSelector: function (item) { diff --git a/bundles/AdminBundle/public/js/pimcore/object/tags/manyToManyRelation.js b/bundles/AdminBundle/public/js/pimcore/object/tags/manyToManyRelation.js index 740b72bee44..594b7aea6d5 100644 --- a/bundles/AdminBundle/public/js/pimcore/object/tags/manyToManyRelation.js +++ b/bundles/AdminBundle/public/js/pimcore/object/tags/manyToManyRelation.js @@ -490,7 +490,7 @@ pimcore.object.tags.manyToManyRelation = Class.create(pimcore.object.tags.abstra uploadDialog: function () { if (!this.fieldConfig.allowMultipleAssignments || (this.fieldConfig["maxItems"] && this.fieldConfig["maxItems"] >= 1)) { - if ((this.store.getData().getSource() || this.store.getData()).count() >= this.fieldConfig.maxItems) { + if (this.fieldConfig.maxItems && (this.store.getData().getSource() || this.store.getData()).count() >= this.fieldConfig.maxItems) { Ext.Msg.alert(t("error"), t("limit_reached")); return true; } diff --git a/bundles/AdminBundle/public/js/pimcore/object/tags/objectbricks.js b/bundles/AdminBundle/public/js/pimcore/object/tags/objectbricks.js index 29d0cad544a..9877591b7d5 100644 --- a/bundles/AdminBundle/public/js/pimcore/object/tags/objectbricks.js +++ b/bundles/AdminBundle/public/js/pimcore/object/tags/objectbricks.js @@ -18,6 +18,7 @@ pimcore.object.tags.objectbricks = Class.create(pimcore.object.tags.abstract, { dirty: false, addedTypes: {}, preventDelete: {}, + inheritedCount: 0, initialize: function (data, fieldConfig) { @@ -236,7 +237,12 @@ pimcore.object.tags.objectbricks = Class.create(pimcore.object.tags.abstract, { if (!this.layoutDefinitions[type]) { return; } - if (this.fieldConfig.maxItems && this.getCurrentElementsCount() >= this.fieldConfig.maxItems) { + + if(blockData && blockData.inherited) { + this.inheritedCount++; + } + + if (this.fieldConfig.maxItems && this.getCurrentElementsCount() >= this.fieldConfig.maxItems + this.inheritedCount) { Ext.Msg.alert(t("error"), t("limit_reached")); return; } diff --git a/bundles/AdminBundle/public/js/pimcore/settings/docTypes.js b/bundles/AdminBundle/public/js/pimcore/settings/docTypes.js index 93e4cdb9f93..b57353f933e 100644 --- a/bundles/AdminBundle/public/js/pimcore/settings/docTypes.js +++ b/bundles/AdminBundle/public/js/pimcore/settings/docTypes.js @@ -243,11 +243,11 @@ pimcore.settings.document.doctypes = Class.create({ handler: function (grid, rowIndex) { var rec = grid.getStore().getAt(rowIndex); try { - pimcore.globalmanager.get("translationadminmanager").activate(rec.data.name); + pimcore.globalmanager.get("translationdomainmanager").activate(rec.data.name); } catch (e) { - pimcore.globalmanager.add("translationadminmanager", - new pimcore.settings.translation.admin(rec.data.name)); + pimcore.globalmanager.add("translationdomainmanager", + new pimcore.settings.translation.domain("admin",rec.data.name)); } }.bind(this) }] diff --git a/bundles/AdminBundle/public/js/pimcore/settings/properties/predefined.js b/bundles/AdminBundle/public/js/pimcore/settings/properties/predefined.js index 7f16ad3c0e7..0316ee055e6 100644 --- a/bundles/AdminBundle/public/js/pimcore/settings/properties/predefined.js +++ b/bundles/AdminBundle/public/js/pimcore/settings/properties/predefined.js @@ -180,11 +180,10 @@ pimcore.settings.properties.predefined = Class.create({ handler: function(grid, rowIndex){ var rec = grid.getStore().getAt(rowIndex); try { - pimcore.globalmanager.get("translationadminmanager").activate(rec.data.name); - } - catch (e) { - pimcore.globalmanager.add("translationadminmanager", - new pimcore.settings.translation.admin(rec.data.name)); + pimcore.globalmanager.get("translationdomainmanager").activate(rec.data.name); + } catch (e) { + pimcore.globalmanager.add("translationdomainmanager", + new pimcore.settings.translation.domain("admin", rec.data.name)); } }.bind(this) }] diff --git a/bundles/AdminBundle/src/Controller/Admin/Asset/AssetController.php b/bundles/AdminBundle/src/Controller/Admin/Asset/AssetController.php index 6970ec3a0b8..3f35ea67c94 100644 --- a/bundles/AdminBundle/src/Controller/Admin/Asset/AssetController.php +++ b/bundles/AdminBundle/src/Controller/Admin/Asset/AssetController.php @@ -532,6 +532,17 @@ protected function addAsset(Request $request, Config $config): array throw new \Exception('Something went wrong, please check upload_max_filesize and post_max_size in your php.ini as well as the write permissions of your temporary directories.'); } + // check if there is a requested type and if matches the asset type of the uploaded file + $type = $request->get('type'); + if ($type) { + $mimetype = MimeTypes::getDefault()->guessMimeType($sourcePath); + $assetType = Asset::getTypeFromMimeMapping($mimetype, $filename); + + if ($type !== $assetType) { + throw new \Exception("Mime type does not match with asset type: $type"); + } + } + if ($request->get('allowOverwrite') && Asset\Service::pathExists($parentAsset->getRealFullPath().'/'.$filename)) { $asset = Asset::getByPath($parentAsset->getRealFullPath().'/'.$filename); $asset->setStream(fopen($sourcePath, 'rb', false, File::getContext())); diff --git a/bundles/AdminBundle/src/Controller/Admin/Asset/AssetHelperController.php b/bundles/AdminBundle/src/Controller/Admin/Asset/AssetHelperController.php index 3ce8cc6ff52..be35eb1f2d0 100644 --- a/bundles/AdminBundle/src/Controller/Admin/Asset/AssetHelperController.php +++ b/bundles/AdminBundle/src/Controller/Admin/Asset/AssetHelperController.php @@ -681,7 +681,7 @@ protected function updateGridConfigFavourites(?GridConfig $gridConfig, array $me } /** - * @Route("/get-export-jobs", name="pimcore_admin_asset_assethelper_getexportjobs", methods={"GET"}) + * @Route("/get-export-jobs", name="pimcore_admin_asset_assethelper_getexportjobs", methods={"POST"}) * * @param Request $request * @param GridHelperService $gridHelperService diff --git a/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectActionsTrait.php b/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectActionsTrait.php index 5092610f187..a27a39d4082 100644 --- a/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectActionsTrait.php +++ b/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectActionsTrait.php @@ -163,14 +163,13 @@ private function prepareObjectData( LocaleServiceInterface $localeService ): array { $user = Tool\Admin::getCurrentUser(); - $allLanguagesAllowed = false; $languagePermissions = []; if (!$user->isAdmin()) { $languagePermissions = $object->getPermissions('lEdit', $user); - //sets allowed all languages modification when the lEdit column is empty - $allLanguagesAllowed = $languagePermissions['lEdit'] == ''; - $languagePermissions = explode(',', $languagePermissions['lEdit']); + if ($languagePermissions['lEdit']) { + $languagePermissions = explode(',', $languagePermissions['lEdit']); + } } $class = $object->getClass(); @@ -258,7 +257,7 @@ private function prepareObjectData( $brick->$valueSetter($value); } } else { - if (!$user->isAdmin() && $languagePermissions) { + if ($languagePermissions) { $fd = $class->getFieldDefinition($key); if (!$fd) { // try to get via localized fields @@ -267,7 +266,7 @@ private function prepareObjectData( $field = $localized->getFieldDefinition($key); if ($field) { $currentLocale = $localeService->findLocale(); - if (!$allLanguagesAllowed && !in_array($currentLocale, $languagePermissions)) { + if (!in_array($currentLocale, $languagePermissions)) { continue; } } diff --git a/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectHelperController.php b/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectHelperController.php index abe27b06cc2..a58d143d173 100644 --- a/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectHelperController.php +++ b/bundles/AdminBundle/src/Controller/Admin/DataObject/DataObjectHelperController.php @@ -1280,7 +1280,7 @@ protected function getCsvFile(string $fileHandle): string } /** - * @Route("/get-export-jobs", name="getexportjobs", methods={"GET"}) + * @Route("/get-export-jobs", name="getexportjobs", methods={"POST"}) * * @param Request $request * @param GridHelperService $gridHelperService diff --git a/bundles/AdminBundle/src/Controller/Admin/Document/NewsletterController.php b/bundles/AdminBundle/src/Controller/Admin/Document/NewsletterController.php index e88089b2d94..f3168604618 100644 --- a/bundles/AdminBundle/src/Controller/Admin/Document/NewsletterController.php +++ b/bundles/AdminBundle/src/Controller/Admin/Document/NewsletterController.php @@ -117,11 +117,14 @@ public function saveAction(Request $request): JsonResponse 'treeData' => $treeData, ]); } else { - $draftData = [ - 'id' => $version->getId(), - 'modificationDate' => $version->getDate(), - 'isAutoSave' => $version->isAutoSave(), - ]; + $draftData = []; + if ($version) { + $draftData = [ + 'id' => $version->getId(), + 'modificationDate' => $version->getDate(), + 'isAutoSave' => $version->isAutoSave(), + ]; + } return $this->adminJson(['success' => true, 'draft' => $draftData]); } diff --git a/bundles/AdminBundle/src/Controller/Admin/Document/PageController.php b/bundles/AdminBundle/src/Controller/Admin/Document/PageController.php index a5d2223907d..f48e28150af 100644 --- a/bundles/AdminBundle/src/Controller/Admin/Document/PageController.php +++ b/bundles/AdminBundle/src/Controller/Admin/Document/PageController.php @@ -23,6 +23,7 @@ use Pimcore\Document\StaticPageGenerator; use Pimcore\Http\Request\Resolver\DocumentResolver; use Pimcore\Http\Request\Resolver\EditmodeResolver; +use Pimcore\Localization\LocaleService; use Pimcore\Messenger\GeneratePagePreviewMessage; use Pimcore\Model\Document; use Pimcore\Model\Document\Targeting\TargetingDocumentInterface; @@ -392,6 +393,7 @@ public function qrCodeAction(Request $request): BinaryFileResponse * @param Environment $twig * @param EditableRenderer $editableRenderer * @param DocumentResolver $documentResolver + * @param LocaleService $localeService * * @return JsonResponse * @@ -404,7 +406,8 @@ public function areabrickRenderIndexEditmode( EditmodeEditableDefinitionCollector $definitionCollector, Environment $twig, EditableRenderer $editableRenderer, - DocumentResolver $documentResolver + DocumentResolver $documentResolver, + LocaleService $localeService ): JsonResponse { $blockStateStackData = json_decode($request->get('blockStateStack'), true); $blockStateStack->loadArray($blockStateStackData); @@ -425,6 +428,9 @@ public function areabrickRenderIndexEditmode( // so we use the attribute as a workaround $request->attributes->set(EditmodeResolver::ATTRIBUTE_EDITMODE, true); + // setting locale manually here before rendering, to make sure editables use the right locale from document + $localeService->setLocale($document->getProperty('language')); + $areaBlockConfig = json_decode($request->get('areablockConfig'), true); /** @var Document\Editable\Areablock $areablock */ $areablock = $editableRenderer->getEditable($document, 'areablock', $request->get('realName'), $areaBlockConfig, true); diff --git a/bundles/AdminBundle/src/Controller/Admin/Document/SnippetController.php b/bundles/AdminBundle/src/Controller/Admin/Document/SnippetController.php index ef25900087d..d2b8784f409 100644 --- a/bundles/AdminBundle/src/Controller/Admin/Document/SnippetController.php +++ b/bundles/AdminBundle/src/Controller/Admin/Document/SnippetController.php @@ -131,11 +131,14 @@ public function saveAction(Request $request): JsonResponse } else { $this->saveToSession($snippet); - $draftData = [ - 'id' => $version->getId(), - 'modificationDate' => $version->getDate(), - 'isAutoSave' => $version->isAutoSave(), - ]; + $draftData = []; + if ($version) { + $draftData = [ + 'id' => $version->getId(), + 'modificationDate' => $version->getDate(), + 'isAutoSave' => $version->isAutoSave(), + ]; + } return $this->adminJson(['success' => true, 'draft' => $draftData]); } diff --git a/bundles/AdminBundle/src/Controller/Admin/LoginController.php b/bundles/AdminBundle/src/Controller/Admin/LoginController.php index 921195e496d..bc00bec0688 100644 --- a/bundles/AdminBundle/src/Controller/Admin/LoginController.php +++ b/bundles/AdminBundle/src/Controller/Admin/LoginController.php @@ -247,7 +247,7 @@ public function deeplinkAction(Request $request, EventDispatcherInterface $event if (preg_match('/(document|asset|object)_([0-9]+)_([a-z]+)/', $queryString, $deeplink)) { $deeplink = $deeplink[0]; - $perspective = strip_tags($request->get('perspective')); + $perspective = strip_tags($request->get('perspective', '')); if (strpos($queryString, 'token')) { $event = new LoginRedirectEvent('pimcore_admin_login', [ diff --git a/bundles/AdminBundle/src/Controller/Admin/TagsController.php b/bundles/AdminBundle/src/Controller/Admin/TagsController.php index 6163c2236b9..6d9706f0703 100644 --- a/bundles/AdminBundle/src/Controller/Admin/TagsController.php +++ b/bundles/AdminBundle/src/Controller/Admin/TagsController.php @@ -43,7 +43,7 @@ public function addAction(Request $request): JsonResponse { try { $tag = new Tag(); - $tag->setName(strip_tags($request->get('text'))); + $tag->setName(strip_tags($request->get('text', ''))); $tag->setParentId((int)$request->get('parentId')); $tag->save(); @@ -92,7 +92,7 @@ public function updateAction(Request $request): JsonResponse $tag->setParentId((int)$parentId); } if ($request->get('text')) { - $tag->setName(strip_tags($request->get('text'))); + $tag->setName(strip_tags($request->get('text', ''))); } $tag->save(); @@ -114,7 +114,7 @@ public function treeGetChildrenByIdAction(Request $request): JsonResponse { $showSelection = $request->get('showSelection') == 'true'; $assignmentCId = (int)$request->get('assignmentCId'); - $assignmentCType = strip_tags($request->get('assignmentCType')); + $assignmentCType = strip_tags($request->get('assignmentCType', '')); $recursiveChildren = false; $assignedTagIds = []; @@ -201,7 +201,7 @@ protected function convertTagToArray(Tag $tag, bool $showSelection, array $assig public function loadTagsForElementAction(Request $request): JsonResponse { $assginmentCId = (int)$request->get('assignmentCId'); - $assginmentCType = strip_tags($request->get('assignmentCType')); + $assginmentCType = strip_tags($request->get('assignmentCType', '')); $assignedTagArray = []; if ($assginmentCId && $assginmentCType) { @@ -225,7 +225,7 @@ public function loadTagsForElementAction(Request $request): JsonResponse public function addTagToElementAction(Request $request): JsonResponse { $assginmentCId = (int)$request->get('assignmentElementId'); - $assginmentCType = strip_tags($request->get('assignmentElementType')); + $assginmentCType = strip_tags($request->get('assignmentElementType', '')); $tagId = (int)$request->get('tagId'); $tag = Tag::getById($tagId); @@ -248,7 +248,7 @@ public function addTagToElementAction(Request $request): JsonResponse public function removeTagFromElementAction(Request $request): JsonResponse { $assginmentCId = (int)$request->get('assignmentElementId'); - $assginmentCType = strip_tags($request->get('assignmentElementType')); + $assginmentCType = strip_tags($request->get('assignmentElementType', '')); $tagId = (int)$request->get('tagId'); $tag = Tag::getById($tagId); @@ -272,7 +272,7 @@ public function removeTagFromElementAction(Request $request): JsonResponse public function getBatchAssignmentJobsAction(Request $request, EventDispatcherInterface $eventDispatcher): JsonResponse { $elementId = (int)$request->get('elementId'); - $elementType = strip_tags($request->get('elementType')); + $elementType = strip_tags($request->get('elementType', '')); $idList = []; switch ($elementType) { @@ -409,7 +409,7 @@ private function getSubDocumentIds(\Pimcore\Model\Document $document, EventDispa */ public function doBatchAssignmentAction(Request $request): JsonResponse { - $cType = strip_tags($request->get('elementType')); + $cType = strip_tags($request->get('elementType', '')); $assignedTags = json_decode($request->get('assignedTags')); $elementIds = json_decode($request->get('childrenIds')); $doCleanupTags = $request->get('removeAndApply') == 'true'; diff --git a/bundles/AdminBundle/src/Security/Authenticator/AdminAbstractAuthenticator.php b/bundles/AdminBundle/src/Security/Authenticator/AdminAbstractAuthenticator.php index b21a0a8e6e4..6e2f86bbe87 100644 --- a/bundles/AdminBundle/src/Security/Authenticator/AdminAbstractAuthenticator.php +++ b/bundles/AdminBundle/src/Security/Authenticator/AdminAbstractAuthenticator.php @@ -121,7 +121,7 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token, } else { $url = $this->router->generate('pimcore_admin_index', [ '_dc' => time(), - 'perspective' => strip_tags($request->get('perspective')), + 'perspective' => strip_tags($request->get('perspective', '')), ]); } diff --git a/bundles/CoreBundle/config/pimcore/test.yaml b/bundles/CoreBundle/config/pimcore/test.yaml index dc98a7a4049..e47df1f7987 100644 --- a/bundles/CoreBundle/config/pimcore/test.yaml +++ b/bundles/CoreBundle/config/pimcore/test.yaml @@ -1,7 +1,5 @@ framework: test: ~ - session: - storage_factory_id: session.storage.factory.mock_file profiler: false mailer: transports: diff --git a/bundles/EcommerceFrameworkBundle/config/index_service_configs_workers.yaml b/bundles/EcommerceFrameworkBundle/config/index_service_configs_workers.yaml index 8b996a771a5..c82a490f4c6 100644 --- a/bundles/EcommerceFrameworkBundle/config/index_service_configs_workers.yaml +++ b/bundles/EcommerceFrameworkBundle/config/index_service_configs_workers.yaml @@ -68,5 +68,8 @@ services: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\ElasticSearch\DefaultElasticSearch7: parent: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\AbstractWorker + Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\ElasticSearch\DefaultElasticSearch8: + parent: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\AbstractWorker + Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\DefaultFindologic: parent: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\AbstractWorker diff --git a/bundles/EcommerceFrameworkBundle/src/DependencyInjection/PimcoreEcommerceFrameworkExtension.php b/bundles/EcommerceFrameworkBundle/src/DependencyInjection/PimcoreEcommerceFrameworkExtension.php index 29a91e1e145..3fc86f7f020 100644 --- a/bundles/EcommerceFrameworkBundle/src/DependencyInjection/PimcoreEcommerceFrameworkExtension.php +++ b/bundles/EcommerceFrameworkBundle/src/DependencyInjection/PimcoreEcommerceFrameworkExtension.php @@ -31,6 +31,7 @@ use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceSystemLocator; use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PricingManagerLocator; use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PricingManagerLocatorInterface; +use Pimcore\Bundle\ElasticsearchClientBundle\DependencyInjection\PimcoreElasticsearchClientExtension; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -466,6 +467,13 @@ private function registerIndexServiceConfig(ContainerBuilder $container, array $ $worker->setArgument('$tenantConfig', new Reference($configId)); $worker->addTag('pimcore_ecommerce.index_service.worker', ['tenant' => $tenant]); + if (!empty($tenantConfig['config_options']['es_client_name'])) { + $worker->addMethodCall( + 'setElasticSearchClient', + [new Reference(PimcoreElasticsearchClientExtension::CLIENT_SERVICE_PREFIX . $tenantConfig['config_options']['es_client_name'])] + ); + } + $container->setDefinition($configId, $config); $container->setDefinition($workerId, $worker); } diff --git a/bundles/EcommerceFrameworkBundle/src/IndexService/Config/ElasticSearch.php b/bundles/EcommerceFrameworkBundle/src/IndexService/Config/ElasticSearch.php index 0202e993d74..648ef85ea06 100644 --- a/bundles/EcommerceFrameworkBundle/src/IndexService/Config/ElasticSearch.php +++ b/bundles/EcommerceFrameworkBundle/src/IndexService/Config/ElasticSearch.php @@ -157,6 +157,12 @@ protected function configureOptionsResolver(string $resolverName, OptionsResolve $resolver->setDefault('store', true); $resolver->setAllowedTypes('store', 'bool'); + + $resolver->setDefined('es_client_name'); + $resolver->setAllowedTypes('es_client_name', 'string'); + + //set options to deprecated + $resolver->setDeprecated('es_client_params'); } protected function extractPossibleFirstSubFieldnameParts(string $fieldName): array diff --git a/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch7.php b/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch7.php index 2c5c82eedc7..60c78314ca7 100644 --- a/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch7.php +++ b/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch7.php @@ -16,6 +16,9 @@ namespace Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\ProductList\ElasticSearch; +/** + * @deprecated + */ class DefaultElasticSearch7 extends AbstractElasticSearch { } diff --git a/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch8.php b/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch8.php new file mode 100644 index 00000000000..1d97281ae66 --- /dev/null +++ b/bundles/EcommerceFrameworkBundle/src/IndexService/ProductList/ElasticSearch/DefaultElasticSearch8.php @@ -0,0 +1,66 @@ +tenantConfig->getTenantWorker()->getElasticSearchClient(); + $result = []; + + if ($esClient instanceof Client) { + if ($this->doScrollRequest) { + $params = array_merge(['scroll' => $this->scrollRequestKeepAlive], $params); + //kind of dirty hack :/ + $params['body']['size'] = $this->getLimit(); + } + + $result = $esClient->search($params)->asArray(); + + if ($this->doScrollRequest) { + $additionalHits = []; + $scrollId = $result['_scroll_id']; + + while (true) { + $additionalResult = $esClient->scroll(['scroll_id' => $scrollId, 'scroll' => $this->scrollRequestKeepAlive])->asArray(); + + if (count($additionalResult['hits']['hits'])) { + $additionalHits = array_merge($additionalHits, $additionalResult['hits']['hits']); + $scrollId = $additionalResult['_scroll_id']; + } else { + break; + } + } + $result['hits']['hits'] = array_merge($result['hits']['hits'], $additionalHits); + } + } + + return $result; + } +} diff --git a/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch7.php b/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch7.php index 0124aab378a..bed3efa28f2 100644 --- a/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch7.php +++ b/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch7.php @@ -17,7 +17,9 @@ namespace Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\ElasticSearch; /** - * Use this for ES Version >= 7 + * Use this for ES Version = 7 + * + * @deprecated */ class DefaultElasticSearch7 extends AbstractElasticSearch { diff --git a/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch8.php b/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch8.php new file mode 100644 index 00000000000..e7552ac4ed2 --- /dev/null +++ b/bundles/EcommerceFrameworkBundle/src/IndexService/Worker/ElasticSearch/DefaultElasticSearch8.php @@ -0,0 +1,532 @@ +tenantConfig); + } + + /** + * @return int + */ + public function getIndexVersion() + { + if ($this->indexVersion === null) { + $this->indexVersion = 0; + $esClient = $this->getElasticSearchClient(); + + try { + $result = $esClient->indices()->getAlias([ + 'name' => $this->indexName, + ])->asArray(); + + if (is_array($result)) { + $aliasIndexName = array_key_first($result); + preg_match('/'.$this->indexName.'-(\d+)/', $aliasIndexName, $matches); + if (is_array($matches) && count($matches) > 1) { + $version = (int)$matches[1]; + if ($version > $this->indexVersion) { + $this->indexVersion = $version; + } + } + } + } catch (ClientResponseException $e) { + if ($e->getCode() === 404) { + $this->indexVersion = 0; + } else { + throw $e; + } + } + } + + return $this->indexVersion; + } + + /** + * @param Client|null $elasticSearchClient + */ + public function setElasticSearchClient(?Client $elasticSearchClient): void + { + $this->elasticSearchClient = $elasticSearchClient; + } + + /** + * @return Client|null + */ + public function getElasticSearchClient() + { + return $this->elasticSearchClient; + } + + /** + * actually sending data to elastic search + */ + public function commitBatchToIndex(): void + { + if (count($this->bulkIndexData)) { + $esClient = $this->getElasticSearchClient(); + $responses = $esClient->bulk([ + 'body' => $this->bulkIndexData, + ])->asArray(); + + // save update status + foreach ($responses['items'] as $response) { + $operation = null; + if (isset($response['index'])) { + $operation = 'index'; + } elseif (isset($response['delete'])) { + $operation = 'delete'; + } + + if ($operation) { + $data = [ + 'update_status' => $response[$operation]['status'], + 'update_error' => null, + 'metadata' => isset($this->indexStoreMetaData[$response[$operation]['_id']]) ? $this->indexStoreMetaData[$response[$operation]['_id']] : null, + ]; + if (isset($response[$operation]['error']) && $response[$operation]['error']) { + $data['update_error'] = json_encode($response[$operation]['error']); + $data['crc_index'] = 0; + Logger::error( + 'Failed to Index Object with Id:' . $response[$operation]['_id'], + json_decode($data['update_error'], true) + ); + + $this->db->update( + $this->getStoreTableName(), + $data, + ['o_id' => $response[$operation]['_id'], 'tenant' => $this->name] + ); + } else { + //update crc sums in store table to mark element as indexed + $this->db->executeQuery( + 'UPDATE ' . $this->getStoreTableName() . ' SET crc_index = crc_current, update_status = ?, update_error = ?, metadata = ? WHERE o_id = ? and tenant = ?', + [$data['update_status'], $data['update_error'], $data['metadata'], $response[$operation]['_id'], $this->name] + ); + } + } else { + throw new \Exception('Unkown operation in response: ' . print_r($response, true)); + } + } + } + + // reset + $this->bulkIndexData = []; + $this->indexStoreMetaData = []; + } + + /** + * Sets the alias to the current index-version and deletes the old indices + * + * @throws \Exception + */ + public function switchIndexAlias() + { + Logger::info('Index-Actions - Switching Alias'); + $esClient = $this->getElasticSearchClient(); + + $params['body'] = [ + 'actions' => [ + [ + 'remove' => [ + 'index' => '*', + 'alias' => $this->indexName, + ], + ], + [ + 'add' => [ + 'index' => $this->getIndexNameVersion(), + 'alias' => $this->indexName, + ], + ], + ], + ]; + $result = $esClient->indices()->updateAliases($params)->asArray(); + if (!$result['acknowledged']) { + //set current index version + throw new \Exception('Switching Alias failed for ' . $this->getIndexNameVersion()); + } + + //delete old indices + $this->cleanupUnusedEsIndices(); + } + + protected function cleanupUnusedEsIndices(): void + { + $esClient = $this->getElasticSearchClient(); + $stats = $esClient->indices()->stats()->asArray(); + foreach ($stats['indices'] as $key => $data) { + preg_match('/'.$this->indexName.'-(\d+)/', $key, $matches); + if (is_array($matches) && count($matches) > 1) { + $version = (int)$matches[1]; + if ($version != $this->indexVersion) { + $indexNameVersion = $this->getIndexNameVersion($version); + Logger::info('Index-Actions - Delete old Index ' . $indexNameVersion); + $this->deleteEsIndexIfExisting($indexNameVersion); + } + } + } + } + + /** + * @param int $objectId + * @param IndexableInterface|null $object + * + * @throws \Exception + */ + protected function doDeleteFromIndex($objectId, IndexableInterface $object = null) + { + $esClient = $this->getElasticSearchClient(); + + $storeEntry = \Pimcore\Db::get()->fetchAssociative('SELECT * FROM ' . $this->getStoreTableName() . ' WHERE o_id=? AND tenant=? ', [$objectId, $this->getTenantConfig()->getTenantName()]); + if ($storeEntry) { + $isLocked = $this->checkIndexLock(false); + if ($isLocked) { + throw new \Exception('Delete not possible due to product index lock. Please re-try later.'); + } + + try { + $tenantConfig = $this->getTenantConfig(); + if (!$tenantConfig instanceof ElasticSearchConfigInterface) { + throw new \Exception('Expected a ElasticSearchConfigInterface'); + } + $esClient->delete([ + 'index' => $this->getIndexNameVersion(), + 'type' => $tenantConfig->getElasticSearchClientParams()['indexType'], + 'id' => $objectId, + $this->routingParamName => $storeEntry['o_virtualProductId'], + ]); + } catch (ClientResponseException $e) { + if ($e->getCode() !== 404) { + throw $e; + } + } + $this->deleteFromStoreTable($objectId); + } + } + + protected function doCreateOrUpdateIndexStructures() + { + $this->checkIndexLock(true); + + $this->createOrUpdateStoreTable(); + + $esClient = $this->getElasticSearchClient(); + + $result = $esClient->indices()->exists(['index' => $this->getIndexNameVersion()])->asBool(); + + if (!$result) { + $indexName = $this->getIndexNameVersion(); + $this->createEsIndex($indexName); + + //index didn't exist -> reset index queue to make sure all products get re-indexed + $this->resetIndexingQueue(); + + $this->createEsAliasIfMissing(); + } + + try { + $this->putIndexMapping($this->getIndexNameVersion()); + + $configuredSettings = $this->tenantConfig->getIndexSettings(); + $synonymSettings = $this->extractMinimalSynonymFiltersTreeFromTenantConfig(); + if (isset($synonymSettings['analysis'])) { + $configuredSettings['analysis']['filter'] = array_replace_recursive($configuredSettings['analysis']['filter'], $synonymSettings['analysis']['filter']); + } + + $currentSettings = $esClient->indices()->getSettings([ + 'index' => $this->getIndexNameVersion(), + ])->asArray(); + $currentSettings = $currentSettings[$this->getIndexNameVersion()]['settings']['index']; + + $settingsIntersection = array_intersect_key($currentSettings, $configuredSettings); + if ($settingsIntersection != $configuredSettings) { + $esClient->indices()->putSettings([ + 'index' => $this->getIndexNameVersion(), + 'body' => [ + 'index' => $this->tenantConfig->getIndexSettings(), + ], + ]); + Logger::info('Index-Actions - updated settings for Index: ' . $this->getIndexNameVersion()); + } else { + Logger::info('Index-Actions - no settings update necessary for Index: ' . $this->getIndexNameVersion()); + } + } catch (\Exception $e) { + Logger::info("Index-Actions - can't create Mapping - trying reindexing " . $e->getMessage()); + Logger::info('Index-Actions - Perform native reindexing for Index: ' . $this->getIndexNameVersion()); + + $this->startReindexMode(); + } + } + + /** + * Retrieve the currently active index name from ES based on the alias. + * + * @return string|null null if no index is found. + */ + public function fetchEsActiveIndex(): ?string + { + $esClient = $this->getElasticSearchClient(); + + try { + $result = $esClient->indices()->getAlias(['index' => $this->indexName])->asArray(); + } catch (\Exception $e) { + Logger::error((string) $e); + + return null; + } + + reset($result); + $currentIndexName = key($result); + + return $currentIndexName; + } + + /** + * Create the index alias on demand. + * + * @throws \Exception if alias could not be created. + */ + protected function createEsAliasIfMissing() + { + $esClient = $this->getElasticSearchClient(); + //create alias for new index if alias doesn't exist so far + $aliasExists = $esClient->indices()->existsAlias(['name' => $this->indexName])->asBool(); + if (!$aliasExists) { + Logger::info("Index-Actions - create alias for index since it doesn't exist at all. Name: " . $this->indexName); + $params['body'] = [ + 'actions' => [ + [ + 'add' => [ + 'index' => $this->getIndexNameVersion(), + 'alias' => $this->indexName, + ], + ], + ], + ]; + $result = $esClient->indices()->updateAliases($params)->asArray(); + if (!$result) { + throw new \Exception('Alias '.$this->indexName.' could not be created.'); + } + } + } + + /** + * Create an ES index with the specified version. + * + * @param string $indexName the name of the index. + * + * @throws \Exception is thrown if index cannot be created, for instance if connection fails or index is already existing. + */ + protected function createEsIndex(string $indexName) + { + $esClient = $this->getElasticSearchClient(); + + Logger::info('Index-Actions - creating new Index. Name: ' . $indexName); + + $configuredSettings = $this->tenantConfig->getIndexSettings(); + $synonymSettings = $this->extractMinimalSynonymFiltersTreeFromTenantConfig(); + if (isset($synonymSettings['analysis'])) { + $configuredSettings['analysis']['filter'] = array_replace_recursive($configuredSettings['analysis']['filter'], $synonymSettings['analysis']['filter']); + } + + $result = $esClient->indices()->create([ + 'index' => $indexName, + 'body' => ['settings' => $configuredSettings], + ])->asArray(); + + if (!$result['acknowledged']) { + throw new \Exception('Index creation failed. IndexName: ' . $indexName); + } + } + + /** + * puts current mapping to index with given name + * + * @param string $indexName + * + * @throws \Exception + */ + protected function putIndexMapping(string $indexName) + { + $esClient = $this->getElasticSearchClient(); + + $params = $this->getMappingParams(); + $params['index'] = $indexName; + $result = $esClient->indices()->putMapping($params)->asArray(); + + if (!$result['acknowledged']) { + throw new \Exception('Putting mapping to index failed. IndexName: ' . $indexName); + } + + Logger::info('Index-Actions - updated Mapping for Index: ' . $indexName); + } + + /** + * Delete an ES index if existing. + * + * @param string $indexName the name of the index. + */ + protected function deleteEsIndexIfExisting(string $indexName) + { + $esClient = $this->getElasticSearchClient(); + $result = $esClient->indices()->exists(['index' => $indexName])->asBool(); + if ($result) { + Logger::info('Deleted index '.$indexName.'.'); + $result = $esClient->indices()->delete(['index' => $indexName])->asArray(); + if (!array_key_exists('acknowledged', $result) && !$result['acknowledged']) { + Logger::error("Could not delete index {$indexName} while cleanup. Please remove the index manually."); + } + } + } + + /** + * Blocks all write operations + * + * @param string $indexName the name of the index. + */ + protected function blockIndexWrite(string $indexName) + { + $esClient = $this->getElasticSearchClient(); + $result = $esClient->indices()->exists(['index' => $indexName])->asBool(); + if ($result) { + Logger::info('Block write index '.$indexName.'.'); + $esClient->indices()->putSettings([ + 'index' => $indexName, + 'body' => [ + 'index.blocks.write' => true, + ], + ]); + + $esClient->indices()->refresh([ + 'index' => $indexName, + ]); + } + } + + /** + * Unblocks all write operations + * + * @param string $indexName the name of the index. + */ + protected function unblockIndexWrite(string $indexName) + { + $esClient = $this->getElasticSearchClient(); + $result = $esClient->indices()->exists(['index' => $indexName])->asBool(); + if ($result) { + Logger::info('Unlock write index '.$indexName.'.'); + $esClient->indices()->putSettings([ + 'index' => $indexName, + 'body' => [ + 'index.blocks.write' => false, + ], + ]); + + $esClient->indices()->refresh([ + 'index' => $indexName, + ]); + } + } + + /** + * + * Perform a synonym update on the currently selected ES index, if necessary. + * + * Attention: the current index will be closed and opened, so it won't be available for a tiny moment (typically some milliseconds). + * + * @param string $indexNameOverride if given, then that index will be used instead of the current index. + * @param bool $skipComparison if explicitly set to true, then the comparison whether the synonyms between the current index settings + * and the local index settings vary, will be skipped, and the index settings will be updated regardless. + * @param bool $skipLocking if explictly set to true, then no global lock will be activated / released. + * + * @throws \Exception is thrown if the synonym transmission fails. + */ + public function updateSynonyms(string $indexNameOverride = '', bool $skipComparison = false, bool $skipLocking = true) + { + try { + if (!$skipLocking) { + $this->activateIndexLock(); //lock all other processes + } + + $indexName = $indexNameOverride ?: $this->getIndexNameVersion(); + + $indexSettingsSynonymPartLocalConfig = $this->extractMinimalSynonymFiltersTreeFromTenantConfig(); + if (empty($indexSettingsSynonymPartLocalConfig)) { + Logger::info('No index update required, as no synonym providers are configured. '. + 'If filters have been removed, then reindexing will help to get rid of old configurations.' + ); + + return; + } + + $esClient = $this->getElasticSearchClient(); + + if (!$skipComparison) { + $settings = $esClient->indices()->getSettings(['index' => $indexName])->asArray(); + $indexSettingsCurrentEs = $settings[$indexName]['settings']['index']; + $indexSettingsSynonymPartEs = $this->extractMinimalSynonymFiltersTreeFromIndexSettings($indexSettingsCurrentEs); + + if ($indexSettingsSynonymPartEs == $indexSettingsSynonymPartLocalConfig) { + Logger::info(sprintf('The synonyms in ES index "%s" are identical with those of the local configuration. '. + 'No update required.', $indexName)); + + return; + } + } + + Logger::info(sprintf('Update synonyms in "%s"...', $indexName)); + $esClient->indices()->close(['index' => $indexName]); + + $result = $esClient->indices()->putSettings([ + 'index' => $indexName, + 'body' => [ + 'index' => $indexSettingsSynonymPartLocalConfig, + ], + ])->asArray(); + + $esClient->indices()->open(['index' => $indexName]); + + if (!$result['acknowledged']) { + //exception must be thrown after re-opening the index! + throw new \Exception('Index synonym settings update failed. IndexName: ' . $indexName); + } + } finally { + if (!$skipLocking) { + $this->releaseIndexLock(); + } + } + } +} diff --git a/composer.json b/composer.json index 2c243a5977c..421e8d8a84c 100644 --- a/composer.json +++ b/composer.json @@ -147,16 +147,18 @@ "conflict": { "symfony/symfony": "*", "doctrine/doctrine-migrations-bundle": "3.1.0", - "sabre/dav": "4.2.2" + "sabre/dav": "4.2.2", + "thecodingmachine/safe": "<2.0" }, "require-dev": { "codeception/codeception": "^5.0.3", "codeception/phpunit-wrapper": "^9", - "phpstan/phpstan": "1.9.2", + "pimcore/elasticsearch-client": "^1.0.0", + "phpstan/phpstan": "^1.9.3", "phpstan/phpstan-symfony": "^1.2.14", "phpunit/phpunit": "^9.3", "spiritix/php-chrome-html2pdf": "^1.6", - "elasticsearch/elasticsearch": "^7.11", + "elasticsearch/elasticsearch": "^8.0", "composer/composer": "*", "chrome-php/chrome": "^1.4.0", "webmozarts/console-parallelization": "^2.0.0-beta.2" diff --git a/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/01_Configuration_Details.md b/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/01_Configuration_Details.md index 8bf256a1b3f..27804c4b5fc 100644 --- a/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/01_Configuration_Details.md +++ b/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/01_Configuration_Details.md @@ -6,7 +6,7 @@ Following aspects need to be considered in index configuration: In the `config_options` area general elasticsearch settings can be made - like hosts, index settings, etc. ##### `client_config` -- `logging`: `true`/`false` to activate logging of elasticsearch client +- `logging`: (deprecated, for Elasticsearch 7 only) `true`/`false` to activate logging of elasticsearch client - `indexName`: index name to be used, if not provided tenant name is used as index name ##### `index_settings` @@ -14,8 +14,26 @@ Index settings that are used when creating a new index. They are passed 1:1 as settings param to the body of the create index command. Details see also [elasticsearch Docs](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_index_management_operations.html). +#### `es_client_name` (for Elasticsearch 8 only) +Elasticsearch 8 client configuration takes place via +[Pimcore Elasticsearch Client Bundle](https://github.com/pimcore/elasticsearch-client) and has two parts. + +1) Configuring an elasticsearch client in separate configuration +```yaml +# Configure an elasticsearch client +pimcore_elasticsearch_client: + es_clients: + default: + hosts: ['elastic:9200'] + username: 'elastic' + password: 'somethingsecret' + logger_channel: 'pimcore.elasticsearch' +``` + +2) Define the client name to be used by an elasticsearch tenant. This will be done via the `es_client_name` configuration + in the `config_options`. -##### `es_client_params` +##### `es_client_params` (deprecated, for Elasticsearch 7 only) - `hosts`: Array of hosts of the elasticsearch cluster to use. - `timeoutMs`: optional parameter for setting the client timeout (frontend) in milliseconds. - `timeoutMsBackend`: optional parameter for setting the client timeout (CLI) in milliseconds. This value is typically higher than ``timeoutMs``. @@ -30,11 +48,18 @@ pimcore_ecommerce_framework: index_service: tenants: MyEsTenant: + worker_id: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Worker\ElasticSearch\DefaultElasticSearch8 + config_id: Pimcore\Bundle\EcommerceFrameworkBundle\IndexService\Config\ElasticSearch + config_options: client_config: logging: false indexName: 'ecommerce-demo-elasticsearch' + # elasticsearch client name, for Elasticsearch 8 only + es_client_name: default + + # deprecated, for Elasticsearch 7 only es_client_params: hosts: - '%elasticsearch.host%' @@ -69,8 +94,7 @@ pimcore_ecommerce_framework: ## Data Types for attributes -The type of the data attributes needs to be set to elasticsearch data types. Be careful, some types changed between -elasticsearch 5 and 6 (like string vs. keyword/text). +The type of the data attributes needs to be set to elasticsearch data types.. ```yml pimcore_ecommerce_framework: diff --git a/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/README.md b/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/README.md index 180e6b57b86..b7fa247b704 100644 --- a/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/README.md +++ b/doc/Development_Documentation/10_E-Commerce_Framework/05_Index_Service/01_Product_Index_Configuration/07_Elastic_Search/README.md @@ -1,10 +1,15 @@ # Special Aspects for Elasticsearch Basically Elasticsearch worker works as described in the [optimized architecture](../README.md). -Currently, Elasticsearch 7 is supported. +Currently, Elasticsearch 7 (deprecated) and Elasticsearch 8 are supported. ## Installation + +### Elasticsearch 7 (deprecated) To work properly Pimcore requires the Elasticsearch bindings, install them with: `composer require elasticsearch/elasticsearch`. +### Elasticsearch 8 +To work properly Pimcore requires the Elasticsearch client, install them with: `composer require pimcore/elasticsearch-client`. + ## Index Configuration Elasticsearch provides a couple of additional configuration options for the index to utilize elasticsearch features. See [Configuration Details](01_Configuration_Details.md) for more information. diff --git a/doc/Development_Documentation/10_E-Commerce_Framework/09_Working_with_Prices/07_Vouchers.md b/doc/Development_Documentation/10_E-Commerce_Framework/09_Working_with_Prices/07_Vouchers.md index 054b5bdec0b..bc3b5a7f59d 100644 --- a/doc/Development_Documentation/10_E-Commerce_Framework/09_Working_with_Prices/07_Vouchers.md +++ b/doc/Development_Documentation/10_E-Commerce_Framework/09_Working_with_Prices/07_Vouchers.md @@ -48,10 +48,10 @@ A voucher token is always applied to a cart. To do so, use following snippet. ```php get('voucher-code'))) { +if ($token = strip_tags($request->get('voucher-code', ''))) { try { $success = $cart->addVoucherToken($token); - if($success) { + if ($success) { $this->addFlash('success', $translator->trans('cart.voucher-code-added')); } else { $this->addFlash('danger', $translator->trans('cart.voucher-code-cound-not-be-added')); @@ -100,7 +100,7 @@ See an sample snippet to display the voucher information to the customer: ```twig
- {% if(cart.pricingManagerTokenInformationDetails | length > 0) %} + {% if (cart.pricingManagerTokenInformationDetails | length > 0) %}