custom/plugins/SEMKNOX/src/Framework/SemknoxsearchHelper.php line 1013

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace semknox\search\Framework;
  3. use Psr\Log\LoggerInterface;
  4. use Shopware\Core\Framework\Context;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  10. use Shopware\Core\System\SystemConfig\SystemConfigDefinition;
  11. use Shopware\Core\System\SystemConfig\SystemConfigService;
  12. use Shopware;
  13. use semknox\search\Exception\NoIndexedDocumentsException;
  14. use semknox\search\Exception\ServerNotAvailableException;
  15. use semknox\search\Framework\DataAbstractionLayer\CriteriaParser;
  16. use semknox\search\api\Client;
  17. use semknox\search\api\Searchbody;
  18. use Doctrine\DBAL\Configuration;
  19. use Doctrine\DBAL\Connection;
  20. use Doctrine\DBAL\DriverManager;
  21. use Doctrine\DBAL\FetchMode;
  22. use Shopware\Core\PlatformRequest;
  23. use Shopware\Core\Framework\Uuid\Uuid;
  24. use Shopware\Core\Defaults;
  25. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  26. use Shopware\Core\Framework\Api\Context\ContextSource;
  27. use function GuzzleHttp\json_encode;
  28. use Symfony\Component\Console\Output\OutputInterface;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Shopware\Core\Content\Product\ProductDefinition;
  31. use PackageVersions\Versions;
  32. use Symfony\Component\HttpFoundation\RequestStack;
  33. use Shopware\Core\Framework\Struct\ArrayEntity;
  34. use Composer\InstalledVersions;
  35. class SemknoxsearchHelper
  36. {
  37.     const FilterPrefix '~';
  38.     const FilterListSeparator '|';
  39.     /**
  40.      * @var Client
  41.      */
  42.     private $client;
  43.     /**
  44.      * @var string
  45.      */
  46.     private $environment;
  47.     /**
  48.      * @var LoggerInterface
  49.      */
  50.     private $logger;
  51.     /**
  52.      * 
  53.      * @var EntityRepositoryInterface
  54.     */ 
  55.     private $logRepository null;
  56.     /**
  57.      * @var SystemConfigService
  58.      */
  59.     private $systemConfigService=null;
  60.     /**
  61.      * @var string
  62.      */
  63.     private $prefix ''
  64.     /**
  65.      * root-Dir of shopware-installation
  66.      * @var string
  67.      */
  68.     private $rootDir='';
  69.     /**
  70.      * ID of the current saleschannel
  71.      * Default-Saleschannel: DEFAULTS::SALES_CHANNEL
  72.      * @var string
  73.      */
  74.     private $salesChannelID '';
  75.     private $salesChannelContext null;
  76.     private $productDefinition;
  77.     private $sessionId '';
  78.     private $pluginVersion '';
  79.     /**
  80.      * @var Connection|null
  81.      */
  82.     protected static $connection=null;
  83.     /**
  84.      * @var RequestStack
  85.      */
  86.     private $requestStack;
  87.     private $searchEnabled true;
  88.     private $supportedControllers = array('Shopware\Storefront\Controller\SearchController::search''Shopware\Storefront\Controller\SearchController::pagelet''Shopware\Storefront\Controller\SearchController::suggest''Shopware\Storefront\Controller\SearchController::ajax''Shopware\Storefront\Controller\SearchController::filter''siteSearchCMSController');
  89.     private $supportedControllersInListing = array('Shopware\Storefront\Controller\NavigationController::index''Shopware\Storefront\Controller\CmsController::category');
  90.     private $langTrans = []; 
  91.     private $langTransX = []; 
  92.     private $mainConfigVars null
  93.     private $shopwareConfig null
  94.     private $outputInt null
  95.     /**
  96.      * 
  97.       public $calculator;
  98.     */
  99.     public $logDir='';
  100.     public function __construct(
  101.         string $environment,
  102.         Client $client,
  103.         LoggerInterface $logger,
  104.         SystemConfigDefinition $SystemConfigDefinition,
  105.         CriteriaParser $parser,
  106.         ProductDefinition $pd,
  107.         string $rootDir,
  108.         RequestStack $requestStack,
  109.         SystemConfigService $systemConfigService
  110.     ) {
  111.         $this->requestStack $requestStack;
  112.         $this->client $client;
  113.         $this->environment $environment;
  114.         $this->parser $parser;
  115.         $this->logger $logger;
  116.         $this->productDefinition $pd;
  117.         $this->rootDir $rootDir;
  118.         $this->logDir $this->add_ending_slash($rootDir).'semknox';
  119.         $this->systemConfigService $systemConfigService;
  120.         $this->getConnection();
  121.         $this->getSemknoxDBConfig();
  122.         $this->getShopwareConfig();
  123.     }
  124.     public function logOrThrowException(\Throwable $exception): bool
  125.     {
  126.         if ($this->environment !== 'prod') {
  127.             throw new \RuntimeException($exception->getMessage());
  128.         }
  129.         $this->logger->error($exception->getMessage());
  130.         return false;
  131.     }
  132.     public function setLogRepository(EntityRepositoryInterface $logRepo) : void 
  133.     {
  134.         $this->logRepository $logRepo;
  135.     }
  136.     public function setOutputInterface(OutputInterface $oi)
  137.     {
  138.       $this->outputInt $oi;  
  139.     }
  140.     /**
  141.      * logging considering logtypes to internal logs, stdout and/or DB
  142.      * @param int $logType  = 0 -> only shopware-logging = 10 -> enable output-logging = 100->DB-logging
  143.      * @param string $entryName
  144.      * @param array $additionalData
  145.      * @param int $doShow
  146.      * @param int $logLevel
  147.      * @param string $logTitle
  148.      */
  149.     public function logData(int $logTypestring $entryName, array $additionalData = [], int $logLevel 100string $logTitle='') : void
  150.     {
  151.         if ($logType 1) {
  152.             switch ($logLevel) {
  153.                 case 800    :  $this->logger->emergency(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  154.                 case 700    :  $this->logger->alert(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  155.                 case 600    :  $this->logger->critical(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  156.                 case 500    :  $this->logger->error(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  157.                 case 400    :  $this->logger->warning(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  158.                 case 300    :  $this->logger->notice(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  159.                 case 200    :  $this->logger->info(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));break;
  160.                 default        :  $this->logger->debug(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));
  161.             }
  162.         }
  163.         if ($logType 0) {
  164.             $outT=date('Y-m-d H:i:s').' :: '.$entryName;
  165.             if (is_null($this->outputInt)) {
  166.                 echo "\n$outT";
  167.             } else {
  168.                 $this->outputInt->writeln($outT);
  169.             }
  170.         }
  171.         if ( ($this->logRepository === null) || ($logType 100) )  { return; }
  172.         if ($logTitle == '') { $logTitle $entryName; }
  173.         $this->logRepository->create(
  174.             [
  175.                 ['logType'=>$entryName'logStatus'=>$logLevel'logTitle'=>$logTitle'logDescr'=>json_encode($additionalData)]
  176.             ], \Shopware\Core\Framework\Context::createDefaultContext());
  177.     }
  178.     /** should be replaced by logdata! deprecated!
  179.      * logged in die interne DB von Shopware
  180.      * @param string $entryName
  181.      * @param array $additionalData
  182.      */
  183.     public function log2ShopDB(string $entryName, array $additionalData = [], int $doShow=0int $logLevel 100string $logTitle='') : void
  184.     {   
  185.         $this->logger->debug(trim('semknox.'.$entryName." (".$logLevel.") ".$logTitle));
  186.         if ($doShow) {
  187.             if (is_null($this->outputInt)) {
  188.                 echo "\n$entryName";
  189.             }
  190.         }
  191.         if (!is_null($this->outputInt)) {
  192.             $this->outputInt->writeln($entryName);
  193.         }
  194.         if ($this->logRepository === null) { return; }
  195.         if ($logTitle == '') { $logTitle $entryName; }
  196.         $this->logRepository->create(
  197.             [ 
  198.                 ['logType'=>$entryName'logStatus'=>$logLevel'logTitle'=>$logTitle'logDescr'=>json_encode($additionalData)] 
  199.             ], \Shopware\Core\Framework\Context::createDefaultContext());
  200.         /* loggt leider nicht im scheduler...
  201.         $this->monologger->addRecord(
  202.             $logLevel,
  203.             'semknox.search.'.$entryName,
  204.             [
  205.                 'source' => 'semknox',
  206.                 'environment' => $this->environment ,
  207.                 'additionalData' => $additionalData,
  208.             ]
  209.             );
  210.         */            
  211.     }
  212.     public function getIndexName(EntityDefinition $definitionstring $languageId): string
  213.     {
  214.         return $this->prefix '_' $definition->getEntityName() . '_' $languageId;
  215.     }
  216.     public function allowIndexing(): bool
  217.     {
  218.         if (!$this->indexingEnabled) {
  219.             return false;
  220.         }
  221.         if (!$this->client->ping()) {
  222.             return $this->logOrThrowException(new ServerNotAvailableException());
  223.         }
  224.         return true;
  225.     }
  226.     public  function getLanguageCodeByID($id) : string 
  227.     {
  228.         if ( (is_array($this->langTrans)) && (isset($this->langTrans[$id])) ) {
  229.             return $this->langTrans[$id];
  230.         }
  231.         $ret='';
  232.         $this->getConnection();
  233.         $q "SELECT lang.name, loc.code FROM language lang, locale loc WHERE loc.id = lang.locale_id AND lang.id = 0x$id ";
  234.         $ta self::$connection->executeQuery($q)->fetchAll(FetchMode::ASSOCIATIVE);
  235.         foreach ($ta as $it) {
  236.             $ret $it['code'];
  237.         }
  238.         if (!empty($ret)) {
  239.             $this->langTrans[$id] = $ret;
  240.         }
  241.         return $ret;
  242.     }
  243.     public  function getLanguageIDByCode($code) : string
  244.     {
  245.         if ( (is_array($this->langTransX)) && (isset($this->langTransX[$code])) ) {
  246.             return $this->langTransX[$code];
  247.         }
  248.         $ret='';
  249.         $this->getConnection();
  250.         $q "SELECT lang.name, lang.id FROM language lang, locale loc WHERE loc.id = lang.locale_id AND loc.code = '$code' ";
  251.         $ta self::$connection->executeQuery($q)->fetchAll(FetchMode::ASSOCIATIVE);
  252.         foreach ($ta as $it) {
  253.             $id $this->getDBConfigChannelID($it['id']);
  254.             if (substr($id,0,2)=='0x') {
  255.                 $ret substr($id,2,10000);                
  256.             } else {
  257.                 $ret $id;
  258.             }
  259.         }
  260.         if (!empty($ret)) {
  261.             $this->langTransX[$code] = $ret;
  262.         }
  263.         return $ret;
  264.     }
  265.     public function getQueryResult(String $query) : array
  266.     {
  267.         $ret=[];
  268.         $this->getConnection();
  269.         $ta self::$connection->executeQuery($query)->fetchAll(FetchMode::ASSOCIATIVE);
  270.         foreach ($ta as $it) {
  271.             $ret[] = $it;
  272.         }
  273.         return $ret;
  274.     }
  275.     public function execQuery(String $query) : int
  276.     {
  277.         $ret=0;
  278.         $this->getConnection();
  279.         $ta self::$connection->executeQuery($query);
  280.         $ret=1;
  281.         return $ret;
  282.     }
  283.     /**
  284.      * returns the array of semknox-preferences for the whole system
  285.      * @return array
  286.      */
  287.     public function getPreferences() : array {
  288.         $ret=['semknoxUpdateCronTime' => 0'semknoxUpdateCronInterval' => 24'semknoxUpdateBlocksize' => 500'semknoxUpdateUseVariantMaster' => false];
  289.         $h $this->getMainConfigParams('00000000000000000000000000000000''00000000000000000000000000000000');
  290.         if ( (isset ($h['semknoxUpdateCronTime'])) && 
  291.                   ($h['semknoxUpdateCronTime']>-1) &&
  292.                   ($h['semknoxUpdateCronTime']<24) ) {
  293.                       $ret['semknoxUpdateCronTime'] = intval($h['semknoxUpdateCronTime']);  
  294.         }
  295.         if ( (isset ($h['semknoxUpdateCronInterval'])) &&
  296.                 ($h['semknoxUpdateCronInterval']>2) &&
  297.                 ($h['semknoxUpdateCronInterval']<25) ) {
  298.                     $ret['semknoxUpdateCronInterval'] = intval($h['semknoxUpdateCronInterval']);
  299.         }
  300.         if ( (isset ($h['semknoxUpdateBlocksize'])) &&
  301.                 ($h['semknoxUpdateBlocksize'] > 20) &&
  302.                 ($h['semknoxUpdateBlocksize'] < 200000) ) {
  303.                     $ret['semknoxUpdateBlocksize'] = intval($h['semknoxUpdateBlocksize']);
  304.         }
  305.         if (isset ($h['semknoxUpdateUseVariantMaster'])) {
  306.             $ret['semknoxUpdateUseVariantMaster'] = $h['semknoxUpdateUseVariantMaster'];
  307.         }
  308.         $ret['semknoxUpdateCronTimeList']=[];
  309.         $i=$ret['semknoxUpdateCronTime'];
  310.         do {
  311.             $start $i;
  312.             $i -= $ret['semknoxUpdateCronInterval'];
  313.         } while ($i > -1);
  314.         do {
  315.             $ret['semknoxUpdateCronTimeList'][]=$start;
  316.             $start += $ret['semknoxUpdateCronInterval'];
  317.         } while ($start 24);
  318.         return $ret;
  319.     }
  320.     /**
  321.      * returns semknox-configuration as array by salesChannel and language if correct else null
  322.      * @param string $salesChannelID
  323.      * @param string $domainID
  324.      * @param number $doUpdate
  325.      * @return NULL|NULL|mixed
  326.      */
  327.     public function allowSalesChannel($scID$domainID$doUpdate=0) {
  328.         $ret null;
  329.         $ret $this->getMainConfigParams($scID$domainID);
  330.         if (is_null($ret)) {
  331.             return null;
  332.         }
  333.         if  (!$ret['valid']) {
  334.             return null;
  335.         }
  336.         if ($doUpdate) {
  337.             if ( (!$ret['semknoxActivate']) && (!$ret['semknoxActivateUpdate']) ) {
  338.                 $ret=null;
  339.             }
  340.         } else {
  341.             if (!$ret['semknoxActivate']) {
  342.                 $ret=null;
  343.             }
  344.         }
  345.         return $ret;
  346.     }
  347.     public function allowSearchByContext(EntityDefinition $definitionContext $contextstring $controller=''): ?array
  348.     {
  349.         $scId $this->getSalesChannelFromSCContext($context);
  350.         if ($scId=='') { return null; }
  351.         $domainId $this->getDomainFromSCContext($context);
  352.         return $this->allowSearch($definition$context$scId$domainId$controller);
  353.     }
  354.     /**
  355.      * function checks, if the use of sitesearch is allowed for saleschannel, language and controller
  356.      * @param SalesChannelContext $context
  357.      * @param Request $request
  358.      * @return boolean
  359.      */
  360.     public function useSiteSearch(SalesChannelContext $contextRequest $request, ?EntityDefinition $definition=null) {
  361.         $this->setSessionID($request);
  362.         $scID=$this->getSalesChannelFromSCContext($context);
  363.         $domainID $this->getDomainFromSCContext($context);
  364.         $contr=$request->attributes->get('_controller');
  365.         if (is_null($definition)) { $definition $this->productDefinition; }
  366.         $mainConfig=$this->allowSearch($definition$context->getContext(), $scID$domainID$contr);
  367.         if ($mainConfig===null) {
  368.             return false;
  369.         }
  370.         return true;
  371.     }
  372.     public function useSiteSearchInListing(SalesChannelContext $contextRequest $request, ?EntityDefinition $definition=null) {
  373.         $this->setSessionID($request);
  374.         $scID=$this->getSalesChannelFromSCContext($context);
  375.         $domainID $this->getDomainFromSCContext($context);
  376.         $contr=$request->attributes->get('_controller');
  377.         if (is_null($definition)) { $definition $this->productDefinition; }
  378.         if (is_null($contr)) { return false; }
  379.         if (is_null($domainID)) { return false; }
  380.         if (is_null($scID)) { return false; }
  381.         $mainConfig=$this->allowCatListing($definition$context->getContext(), $scID$domainID$contr);
  382.         if ($mainConfig===null) {
  383.             return false;
  384.         }
  385.         return true;
  386.     }
  387.     /**
  388.      * Validates if it is allowed do execute the search request over semknoxsearch
  389.      * used in ProductSearchbuilder und SemknoxsearchEntityServer
  390.      */
  391.     public function allowSearch(EntityDefinition $definitionContext $contextstring $salesChannelID=''string $domainID=''string $controller=''): ?array
  392.     {
  393.         $ret null;
  394.         if (!$this->searchEnabled) {
  395.             return $ret;
  396.         }
  397.         $ret $this->getMainConfigParams($salesChannelID$domainID);
  398.         if (is_null($ret)) {
  399.             return $ret;
  400.         }
  401.         if ( (!$ret['valid']) || (!$ret['semknoxActivate']) ) {
  402.             $ret=null;
  403.         }
  404.         if (!$this->isSupported($definition$controller)) {
  405.             $ret=null;
  406.         }
  407.         return $ret;
  408.         return $this->logOrThrowException(new NoIndexedDocumentsException($definition->getEntityName()));
  409.     }
  410.     /**
  411.      * Validates if it is allowed do execute the cat-listing request over sitesearch360
  412.      */
  413.     public function allowCatListing(EntityDefinition $definitionContext $contextstring $salesChannelID=''string $domainID=''string $controller=''): ?array
  414.     {
  415.         $ret null;
  416.         if (!$this->searchEnabled) {
  417.             return $ret;
  418.         }
  419.         $ret $this->getMainConfigParams($salesChannelID$domainID);
  420.         if (is_null($ret)) {
  421.             return $ret;
  422.         }
  423.         if ( (!$ret['valid']) || (!$ret['semknoxActivate']) || (!$ret['semknoxActivateCategoryListing']) ) {
  424.             $ret=null;
  425.         }
  426.         if (!$this->isSupportedInListing($definition$controller)) {
  427.             $ret=null;
  428.         }
  429.         return $ret;
  430.     }
  431.     public function handleIds(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  432.     {
  433.         return;
  434.     }
  435.     public function addFilters(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  436.     {
  437.         return;
  438.     }
  439.     /**
  440.      * function used to extract filters
  441.      * @param EntityDefinition $definition
  442.      * @param Criteria $criteria
  443.      * @param Searchbody $search
  444.      * @param Context $context
  445.      */
  446.     public function addPostFilters(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  447.     {
  448.         $postFilters $criteria->getPostFilters();
  449.         if (!empty($postFilters)) {
  450.             $pr null;
  451.             foreach ($postFilters as $filter) {
  452.                 foreach ($filter->getFields() as $f) {
  453.                     if ($f=='product.listingPrices') {
  454.                         $pr $filter;break;
  455.                     }
  456.                 }
  457.                 if ($pr === null) { continue; }
  458.             }
  459.             if ($pr !== null ) {                
  460.                 $search->addSearchFilter(['type'=>'minmax''key'=>'price''value'=>null'minValue'=>$pr->getVars()['parameters']['gte'], 'maxValue'=>$pr->getVars()['parameters']['lte']]);
  461.             }
  462.         }
  463.         $semknoxFilter $criteria->getExtension('semknoxDataFilter');
  464.         if ( ($semknoxFilter===null) ) {
  465.             return;
  466.         }
  467.         $semknoxData $semknoxFilter->getVars();        
  468.         if ( (!is_array($semknoxData['data'])) || (!is_array($semknoxData['data']['filter'])) ) {
  469.             return;
  470.         }
  471.         foreach ($semknoxData['data']['filter'] as $fi) {
  472.             $filter = ['type'=>'''key'=>'''value'=>'''minValue'=>0'maxValue'=>0];
  473.             $filter['key'] = $fi['name'];
  474.             $filter['name'] = $fi['name'];
  475.             $filter['value'] = $fi['value'];
  476.             $filter['valueList'] = $fi['valueList'];
  477.             $filter['type']=$fi['valType'];
  478.             if (in_array(trim($fi['valType']), ['min''max'])) {
  479.                 $filter['type']='minmax';
  480.                 $filter['minValue'] = $fi['minValue'];                
  481.                 $filter['maxValue'] = $fi['maxValue'];
  482.             }
  483.             $search->addSearchFilter($filter) ;
  484.         }
  485.     }
  486.     public function addTerm(Criteria $criteriaSearchbody $searchContext $context): void
  487.     {
  488.         $search->addTerm('');
  489.         if (!$criteria->getTerm()) {
  490.             return;
  491.         }
  492.         $term $criteria->getTerm();
  493.         if (trim($term)=='') { return; }
  494.         $search->addTerm($term);        
  495.         return;
  496.         $reg $this->getConfigSemknoxRegEx();
  497.         $regRepl $this->getConfigSemknoxRegExRepl();
  498.         if ( ($reg!='') && ($regRepl!='') ) {
  499.                 try {
  500.                     $term preg_replace($reg$regRepl $term);
  501.                 } catch (\Throwable $e) {
  502.                     $this->logOrThrowException($e);
  503.                 }
  504.             }        
  505.         $search->addTerm($term);
  506.     }
  507.     public function addQueries(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  508.     {
  509.         $queries $criteria->getQueries();
  510.         if (empty($queries)) {
  511.             return;
  512.         }
  513.     }
  514.     public function addSortings(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  515.     {
  516.         foreach ($criteria->getSorting() as $sorting) {
  517.             /**
  518.              * S6-Standards: direction: ASC DESC
  519.              *                      name: _score 
  520.              *                               product.name
  521.              *                               product.listingPrices 
  522.              */
  523.             $search->addSorting(
  524.                 $this->parser->parseSorting($sorting$definition$context)
  525.             );
  526.         }
  527.     }
  528.     public function addAggregations(EntityDefinition $definitionCriteria $criteriaSearchbody $searchContext $context): void
  529.     {
  530.         return;
  531.     }
  532.     /**
  533.      * Only used for unit tests because the container parameter bag is frozen and can not be changed at runtime.
  534.      * Therefore this function can be used to test different behaviours
  535.      *
  536.      * @internal
  537.      */
  538.     public function setEnabled(bool $enabled): self
  539.     {
  540.         $this->searchEnabled $enabled;
  541.         $this->indexingEnabled $enabled;
  542.         return $this;
  543.     }
  544.     public function isSupported(EntityDefinition $definitionstring $controller): bool
  545.     {                        
  546.         foreach ($this->supportedControllers as $k) {
  547.             if ($k === $controller) {
  548.                 return true;
  549.             }
  550.         }
  551.         return false;
  552.     }
  553.     public function isSupportedInListing(EntityDefinition $definitionstring $controller): bool
  554.     {
  555.         foreach ($this->supportedControllersInListing as $k) {
  556.             if ($k === $controller) {
  557.                 return true;
  558.             }
  559.         }
  560.         return false;
  561.     }
  562.     /**
  563.      * returns int-values of  bool/int/_woso_-values
  564.      * @param bool|string $v
  565.      * @param number $def
  566.      * @return number|bool|string
  567.      */
  568.     private function getConfigSelectIntValue($v,$def=0) {
  569.         $ret=$v;
  570.         if (is_bool($v)) {
  571.             $v ?  $ret=$ret=0;
  572.         } else {
  573.             if (trim($v)=='') { $ret=$def; } else {
  574.                 if (substr($v,0,6)=='_woso_') { $v=substr($v,6); }
  575.                 if (!(ctype_digit($v))) { $ret=$def; } else { $ret=intval($v); }
  576.             }
  577.         }
  578.         return $ret;
  579.     }
  580.     /**
  581.      * returns semknox-api-url of config-id
  582.      * @param int $id
  583.      * @return string
  584.      */
  585.     private function getBaseURLByID($id) {
  586.         $ret="stage-shopware.semknox.com/";
  587.         switch ($id) {
  588.             case 0  
  589.             case 1  $ret="https://api-shopware.sitesearch360.com/"; break;
  590.         }
  591.         return $ret;
  592.     }    
  593.     public static function getConnection(): Connection
  594.     {
  595.         if (!self::$connection) {
  596.             $url $_ENV['DATABASE_URL']
  597.             ?? $_SERVER['DATABASE_URL']
  598.             ?? getenv('DATABASE_URL');
  599.             $parameters = [
  600.                 'url' => $url,
  601.                 'charset' => 'utf8mb4',
  602.             ];
  603.             self::$connection DriverManager::getConnection($parameters, new Configuration());
  604.         }
  605.         return self::$connection;
  606.     }
  607.     /** 
  608.      * returns value of a config-parameter 
  609.      * @param string $value
  610.      */
  611.     private function getDBConfigValue($value
  612.     {
  613.         $value=trim($value);
  614.         $ret=$value;
  615.         try {
  616.             $h=json_decode($valuetrue);
  617.             if ( (is_array($h)) && (count($h)==1) && (isset($h['_value']))) {
  618.                 if (is_string($h['_value'])) { $h['_value'] = trim($h['_value']); }
  619.                 $ret=$h['_value'];
  620.             }
  621.         } catch (\Throwable $e) {
  622.             $this->logOrThrowException($e);
  623.         }
  624.         return $ret;
  625.     }
  626.     /**
  627.      * returns hex-transformed channelID 
  628.      * @param string $channelID
  629.      */
  630.     private function getDBConfigChannelID($channelID)
  631.     {
  632.         return UUID::fromBytesToHex($channelID);
  633.     }
  634.     /**
  635.      * returns hex-transformed UUID
  636.      * @param string $id
  637.      */
  638.     private function getHexUUID($id)
  639.     {
  640.         return UUID::fromBytesToHex($id);
  641.     }
  642.     /**
  643.      * returns shortened config-key
  644.      * @param string $key
  645.      */
  646.     private function getDBConfigKey(string $key
  647.     {
  648.         return substr($key,21,1000);        
  649.     }
  650.     public function getShopwareConfigValue(string $configKeystring $scID 'null'$def null) {
  651.         $ret $def;
  652.         if (isset($this->shopwareConfig[$configKey])) {
  653.             if (isset($this->shopwareConfig[$configKey][$scID])) {
  654.                 $ret $this->shopwareConfig[$configKey][$scID];
  655.             } else {
  656.                 if (isset($this->shopwareConfig[$configKey]['null'])) {
  657.                     $ret $this->shopwareConfig[$configKey]['null'];
  658.                 }
  659.             }
  660.         }
  661.         return $ret;
  662.     }
  663.     /**
  664.      * setup Shopware-Config-Data
  665.      * [config-key => [ salesChannel => value] 
  666.      */
  667.     private function getShopwareConfig() {
  668.         if (! is_null($this->shopwareConfig)) return;
  669.         $this->shopwareConfig=[];
  670.         $ta self::$connection->executeQuery('
  671.             SELECT *
  672.             FROM `system_config`
  673.             WHERE `configuration_key` LIKE "core.%"
  674.         ')->fetchAll(FetchMode::ASSOCIATIVE);
  675.         foreach ($ta as $it) {
  676.             $key $it['configuration_key'];
  677.             if ($it['sales_channel_id']) {
  678.                 $scid $this->getHexUUID($it['sales_channel_id']);
  679.             } else { $scid 'null'; }
  680.             $val $this->getDBConfigValue($it['configuration_value']);
  681.             if (!isset($this->shopwareConfig[$key])) { $this->shopwareConfig[$key] = []; }
  682.             $this->shopwareConfig[$key][$scid] = $val;
  683.         }
  684.     }
  685.     /**
  686.      * selects data of plugin-configuration direct from database
  687.      */
  688.     private function getSemknoxDBConfig()
  689.     {
  690.         if (! is_null($this->mainConfigVars)) return;
  691.         $this->mainConfigVars=[];
  692.         $ta self::$connection->executeQuery('
  693.             SELECT *
  694.             FROM `semknox_config`
  695.             WHERE `configuration_key` LIKE "semknoxSearch%"
  696.         ')->fetchAll(FetchMode::ASSOCIATIVE);
  697.         $defFound=0;
  698.         foreach ($ta as $it) {
  699.             $k1='';$k2='';                
  700.             if ($it['sales_channel_id']) {
  701.                 $k1=$this->getHexUUID($it['sales_channel_id']);
  702.             }
  703.             if ($it['domain_id']) {
  704.                 $k2=$this->getHexUUID($it['domain_id']);
  705.             }
  706.             if ($it['language_id']) {
  707.                 $k3=$this->getHexUUID($it['language_id']);
  708.             }            
  709.             if (($k1!='') && ($k2!='')) {
  710.                 $this->mainConfigVars[$k1][$k2][$this->getDBConfigKey($it['configuration_key'])] = $this->getDBConfigValue($it['configuration_value']);
  711.                 if ( ($k3!='') && (!isset($this->mainConfigVars[$k1][$k2]['lang_id'])) ) {
  712.                     $this->mainConfigVars[$k1][$k2]['language_id'] = $k3;
  713.                 }
  714.             }
  715.         }
  716.         $this->checkMainSemknoxConfig($this->mainConfigVars);
  717.         $this->setPluginVersion();
  718.     }
  719.     /**
  720.      * checking main config, sets the baseURL and valid-tag
  721.      * @param array $config
  722.      */
  723.     private function checkMainSemknoxConfig(&$config) {
  724.         foreach($config as $k1 => &$sce) {
  725.             if ($k1 == '00000000000000000000000000000000') { continue; }
  726.             foreach($sce as $k2 => &$lange) {
  727.                 $lange['valid']=false;$valid=true;
  728.                 $lange['semknoxBaseUrlID'] = 1;
  729.                 if ($lange['semknoxBaseUrlID']>-1) { $lange['semknoxBaseUrl'] = $this->getBaseURLByID($lange['semknoxBaseUrlID']); }
  730.                 $lange['semknoxCustomerId'] = $lange['semknoxC01CustomerId']; unset ($lange['semknoxC01CustomerId']);
  731.                 $lange['semknoxApiKey'] = $lange['semknoxC01ApiKey']; unset ($lange['semknoxC01ApiKey']);
  732.                 $lange['semknoxLang'] = $this->getLanguageCodeById($lange['language_id']);
  733.                 if (trim($lange['semknoxBaseUrl'])=='') { $valid=false; }
  734.                 if (trim($lange['semknoxCustomerId'])=='') { $valid=false; }
  735.                 if (trim($lange['semknoxApiKey'])=='') { $valid=false; }
  736.                 if (empty($lange['semknoxUpdateBlocksize'])) { $lange['semknoxUpdateBlocksize']=500; }
  737.                 if (empty($lange['semknoxActivateCategoryListing'])) { $lange['semknoxActivateCategoryListing']=false; }
  738.                 if (empty($lange['semknoxActivateSearchTemplate'])) { $lange['semknoxActivateSearchTemplate']=false; }
  739.                 if (empty($lange['semknoxActivateAutosuggest'])) { $lange['semknoxActivateAutosuggest']=false; }
  740.                 $lange['semknoxUpdateBlocksize'] = intval($lange['semknoxUpdateBlocksize']);
  741.                 if ($valid) {
  742.                     $lange['valid'] = true;
  743.                 } else {
  744.                     $lange['semknoxActivate'] = false;
  745.                     $lange['semknoxActivateUpdate'] = false;
  746.                     $lange['semknoxActivateCategoryListing'] = false;
  747.                     $lange['semknoxActivateSearchTemplate'] = false;
  748.                 }
  749.                 unset($lange);
  750.             }
  751.             unset($sce);
  752.         }        
  753.     }
  754.     /**
  755.      * checking main config, sets the baseURL and valid-tag
  756.      * @param array $config
  757.      */
  758.     public function checkMainConfig(&$config) {
  759.         $config['valid']=false;$valid=true;
  760.         if ($config['semknoxBaseUrlID']>-1) { $config['semknoxBaseUrl'] = $this->getBaseURLByID($config['semknoxBaseUrlID']); }
  761.         if (trim($config['semknoxBaseUrl'])=='') { $valid=false; }
  762.         if (trim($config['semknoxCustomerId'])=='') { $valid=false; }
  763.         if (trim($config['semknoxApiKey'])=='') { $valid=false; }
  764.         if ($valid) {
  765.             $config['valid'] = true;
  766.         } else {
  767.             $config['semknoxActivate'] = false;
  768.             $config['semknoxActivateUpdate'] = false;
  769.             $config['semknoxActivateCategoryListing'] = false;
  770.             $config['semknoxActivateSearchTemplate'] = false;            
  771.         }
  772.     }
  773.     /**
  774.      * returns base-config by saleschannelID and LanguageID or null if not set
  775.      * @param string $scID
  776.      * @param string $domainID
  777.      */
  778.     public function getMainConfigParams(string $scIDstring $domainID) {
  779.         $ret=null;
  780.         if ( (empty($scID)) || (empty($domainID)) ) { return $ret; }
  781.         if ( (is_array($this->mainConfigVars)) && (isset($this->mainConfigVars[$scID])) && (isset($this->mainConfigVars[$scID][$domainID])) ) {
  782.             $ret=null;
  783.             if ( (is_array($this->mainConfigVars[$scID][$domainID])) ) {
  784.                 $ret $this->mainConfigVars[$scID][$domainID];
  785.             }
  786.             return $ret;
  787.         }
  788.         return $ret;
  789.     }
  790.     /**
  791.      * returns currently used slaesChannelID
  792.      * is useDefault = true, returns default saleschannel if there is none found
  793.      * @param bool $useDefault
  794.      * @return string
  795.      */
  796.     public function getSalesChannelID(bool $useDefault=true) {
  797.         if (trim($this->salesChannelID)!='') {
  798.             return $this->salesChannelID;
  799.         } elseif ($useDefault) {
  800.             return DEFAULTS::SALES_CHANNEL;
  801.         }
  802.         return '';
  803.     }
  804.     public function setSalesChannelID(string $scID) : void
  805.     {
  806.         $this->salesChannelID $scID;
  807.     }
  808.     /**
  809.      * returns ID of salesChannel from Contents
  810.      * @param SalesChannelContext $context
  811.      * @return string
  812.      */
  813.     public function getSalesChannelFromSCContext(SalesChannelContext $context) : string
  814.     {
  815.         $ret='';
  816.         $sc $context->getSalesChannel();
  817.         if (is_object($sc)) {
  818.             $ret=$sc->getId();
  819.         } else {
  820.         }
  821.         return $ret;
  822.     }
  823.     /**
  824.      * returns languageID from context
  825.      * @param SalesChannelContext $context
  826.      * @return string
  827.      */
  828.     public function getLanguageFromSCContext(SalesChannelContext $context) : string
  829.     {
  830.         $ret='';
  831.         $sc $context->getSalesChannel();
  832.         if (is_object($sc)) {
  833.             $ret $sc->getLanguageId();
  834.         } else {
  835.         }
  836.         return $ret;
  837.     }
  838.     public function setDomainToSCContextExt(SalesChannelContext $context, array $data) {
  839.         $context->addExtension('semknoxDataDomain', new ArrayEntity(
  840.             [
  841.                 'data' => $data
  842.             ]));
  843.     }
  844.     public function getDomainFromSCContextExt(SalesChannelContext $context): string {
  845.         $ret='';
  846.         $domData $context->getExtension('semknoxDataDomain');
  847.         if ( ($domData===null) ) {
  848.             return $ret;
  849.         }
  850.         $domain $domData->getVars();
  851.         if ( (!is_array($domain)) || (!isset($domain['data']))   ) {
  852.             return $ret;
  853.         }
  854.         $data = [];
  855.         if (isset($domain['data']['data'])) {
  856.             if (!is_array($domain['data']['data'])) {
  857.                 return $ret;
  858.             }
  859.             $data=$domain['data']['data'];
  860.         } else {
  861.             if (isset($domain['data'])) {
  862.                 if (!is_array($domain['data'])) {
  863.                     return $ret;
  864.                 }
  865.                 $data=$domain['data'];
  866.             }
  867.         }
  868.         if (empty($data)) { return $ret; }
  869.         if (isset($data['domainId'])) {
  870.             $ret=$data['domainId'];
  871.         }
  872.         return $ret;
  873.     }
  874.     public function getDomainURLFromSCContext(SalesChannelContext $context) : string
  875.     {
  876.         $ret='';
  877.         if (method_exists($context'getSalesChannel')) {
  878.             $sc=$context->getSalesChannel();
  879.             if (method_exists($sc'getDomains')) {
  880.                 $ret $sc->getDomains()->first()->getUrl();
  881.             }
  882.           }
  883.           return $ret;
  884.         }    
  885.     /**
  886.      * returns languageID from context
  887.      * @param SalesChannelContext $context
  888.      * @return string
  889.      */
  890.     public function getDomainFromSCContext(SalesChannelContext $context) : string
  891.     {
  892.         $ret='';
  893.         if (method_exists($context'getDomainId')) {
  894.             $ret $context->getDomainId();
  895.         } else {
  896.             $h=$this->requestStack->getCurrentRequest()->get('sw-domain-id');            
  897.             if ( (!is_null($h)) && (trim($h)!='') ) {
  898.                 $ret $h;
  899.                 return $ret;
  900.             }
  901.             $shopDom=$this->requestStack->getCurrentRequest()->get('sw-sales-channel-absolute-base-url');
  902.             if ( (is_null($shopDom)) || (trim($shopDom)!='') ) {
  903.                 $shopDom=$this->requestStack->getCurrentRequest()->get('sw-storefront-url');
  904.             }
  905.             $ret='';
  906.             if ($shopDom) {
  907.                 $domListObj = ($context->getSalesChannel()->getDomains());
  908.                 if (is_object($domListObj)) {
  909.                     $domList $domListObj->getElements();
  910.                     foreach($domList as $domId => $dom) {
  911.                         if ($dom->getUrl() == $shopDom) {
  912.                             $ret=$domId;
  913.                             break;
  914.                         }
  915.                     }
  916.                 }
  917.             }
  918.         }
  919.         return $ret;
  920.     }
  921.     /**
  922.      * returns ID of salesChannel from Context
  923.      * @param Context $context
  924.      * @return string
  925.      */
  926.     private function getSalesChannelFromContext(Context $context) : string
  927.     {
  928.         $ret='';
  929.         $contextSource $context->getSource();
  930.         if (is_object($contextSource)) {
  931.             var_dump($contextSource);            
  932.         } else {
  933.         }
  934.         return $ret;
  935.     }
  936.     public function getDBData(string $query, array $resultsstring $key='') : array
  937.     {
  938.         $ta self::$connection->executeQuery($query)->fetchAll(FetchMode::ASSOCIATIVE);
  939.         $res=array();
  940.         foreach ($ta as $it) {
  941.             if (count($results)==0) {
  942.               $r=$ta;  
  943.             } else {
  944.                 $r=[];
  945.                 foreach($results as $k) {
  946.                     $r[$k] = $it[$k];
  947.                 }
  948.             }
  949.             if ($key=='') { $res[]=$r; } else { $res[$it[$key]]=$r; }
  950.         }
  951.         return $res;
  952.     }
  953.     /**
  954.      * @return null|mixed
  955.      */
  956.     public function extractArgument(array &$paramsstring $arg)
  957.     {
  958.         if (array_key_exists($arg$params) === true) {
  959.             $value $params[$arg];
  960.             $value is_object($value) ? (array) $value $value;
  961.             unset($params[$arg]);
  962.             return $value;
  963.         } else {
  964.             return null;
  965.         }
  966.     }
  967.     /**
  968.      * returns the shopware-version as a string if not composer2-InstalledVersions is used (pre shopware 6.3)
  969.      * @return string
  970.      */
  971.     public static function getShopwareVersion_compo1(): string {
  972.         $versions Versions::VERSIONS;
  973.         if (isset($versions['shopware/core'])) {
  974.             $shopwareVersion Versions::getVersion('shopware/core');
  975.         } else {
  976.             $shopwareVersion Versions::getVersion('shopware/platform');
  977.         }
  978.         $shopwareVersion ltrim($shopwareVersion'v');
  979.         $shopwareVersion substr($shopwareVersion0strpos($shopwareVersion'@'));
  980.         return $shopwareVersion;
  981.     }
  982.     /**
  983.      * returns the shopware-version as a string
  984.      * @return string
  985.      */
  986.     public static function getShopwareVersion(): string {
  987.         if (class_exists('Composer\InstalledVersions'false) === false) {
  988.             return self::getShopwareVersion_compo1();
  989.         }
  990.         if (InstalledVersions::isInstalled('shopware/core')) {
  991.             $shopwareVersion InstalledVersions::getVersion('shopware/core');
  992.         } else {
  993.             $shopwareVersion InstalledVersions::getVersion('shopware/platform');
  994.         }
  995.         $shopwareVersion ltrim($shopwareVersion'v');
  996.         return $shopwareVersion;
  997.     }
  998.     /**
  999.      * compare $version with shopware-version.
  1000.      * return true, if compare (<>=)  is correct, false else
  1001.      * @param string $version
  1002.      * @param string $compare
  1003.      * @return bool
  1004.      */
  1005.      public static function shopwareVersionCompare(string $versionstring $compare): bool
  1006.      {
  1007.          $shopwareVersion self::getShopwareVersion();
  1008.          return version_compare($shopwareVersion$version$compare);
  1009.      }
  1010.     private function setSessionID($request) {
  1011.         $session $request->hasSession() ? $request->getSession() : null;
  1012.         if (!is_null($session)) {
  1013.             $this->sessionId $session->get('sessionId') ;
  1014.         }
  1015.     }
  1016.     public function getCurrentSessionId() : string {
  1017.         $ret='';
  1018.         return $ret;
  1019.     }
  1020.     private function setPluginVersion() {
  1021.         $res=$this->getQueryResult("SELECT * FROM plugin WHERE name='semknoxSearch'");
  1022.         if (count($res)) {
  1023.             $this->pluginVersion $res[0]['version'];
  1024.         }
  1025.     }
  1026.     public function getHeaderInfoData() : array {
  1027.         $ip '';
  1028.         if (!empty($_SERVER['REMOTE_ADDR'])) { $ip $_SERVER['REMOTE_ADDR']; }
  1029.         $ret= [
  1030.             'shopsys' => 'SHOPWARE',
  1031.             'shopsysver' => $this->getShopwareVersion(),
  1032.             'clientip' => $ip,
  1033.             'sessionid'=>$this->sessionId,
  1034.             'extver' => $this->pluginVersion
  1035.         ];
  1036.         return $ret;
  1037.     }
  1038.     public function add_ending_slash(string $path) : string
  1039.     {
  1040.         $slash_type = (strpos($path'\\')===0) ? 'win' 'unix';
  1041.         $last_char substr($pathstrlen($path)-11);
  1042.         if ($last_char != '/' and $last_char != '\\') {
  1043.             $path .= ($slash_type == 'win') ? '\\' '/';
  1044.         }
  1045.         return $path;
  1046.     }
  1047.     /**
  1048.      * returns time of last update from db-semknox-logs.
  1049.      * no update running - return 0
  1050.      * if last entry = update.finished, return 0
  1051.      */
  1052.     public function getUpdateRunning() : Int
  1053.     {
  1054.         $lastentries $this->getQueryResult("SELECT logtype, status, created_at from semknox_logs WHERE logtype like 'update.%' order by created_at desc LIMIT 3");
  1055.         $ret=0;
  1056.         foreach ($lastentries as $ent) {
  1057.             if ($ent['logtype']!='update.finished') {
  1058.                 $ret strtotime($ent['created_at']);
  1059.             }
  1060.             break;
  1061.         }
  1062.         return $ret;
  1063.     }
  1064.     /**
  1065.      * returns log-entry of running sitesearch-update-process from semknox-log-db.
  1066.      * if there is none running, return []
  1067.      */
  1068.     public function getLastUpdateStart() : array
  1069.     {
  1070.         $lastentries $this->getQueryResult("SELECT logtype, status, logdescr, created_at from semknox_logs WHERE logtype like 'update.start' order by created_at desc LIMIT 3");
  1071.         $ret=[];
  1072.         foreach ($lastentries as $ent) {
  1073.             if (!empty($ent['logdescr'])) {
  1074.                 $ret json_decode($ent['logdescr'], true);
  1075.                 $ret['time'] = strtotime($ent['created_at']);
  1076.             }
  1077.             break;
  1078.         }
  1079.         return $ret;
  1080.     }
  1081.     /**
  1082.      * returns log-entry of whole process of sitesearch-update from semknox-log-db.
  1083.      * if there is none running, return []
  1084.      */
  1085.     public function getLastUpdateProcessStart(?int $minCreate=0) : array
  1086.     {
  1087.         if ($minCreate) {
  1088.             $lastentries $this->getQueryResult("SELECT logtype, status, logdescr, created_at from semknox_logs WHERE logtype like 'update.process.start' AND created_at < '".date("Y-m-d H:i:s.u"$minCreate)."' order by created_at desc LIMIT 3");
  1089.         } else {
  1090.             $lastentries $this->getQueryResult("SELECT logtype, status, logdescr, created_at from semknox_logs WHERE logtype like 'update.process.start' order by created_at desc LIMIT 3");
  1091.         }
  1092.         $ret=[];
  1093.         foreach ($lastentries as $ent) {
  1094.             $ret['time'] = strtotime($ent['created_at']);
  1095.             break;
  1096.         }
  1097.         return $ret;
  1098.     }
  1099.     /**
  1100.      * returns log-entry of running sitesearch-update-process from semknox-log-db.
  1101.      * if there is none running, return []
  1102.      */
  1103.     public function getLastUpdateFinished() : array
  1104.     {
  1105.         $lastentries $this->getQueryResult("SELECT logtype, status, logdescr, created_at from semknox_logs WHERE logtype like 'update.finished' order by created_at desc LIMIT 3");
  1106.         $ret=[];
  1107.         foreach ($lastentries as $ent) {
  1108.             if (!empty($ent['logdescr'])) {
  1109.                 $ret json_decode($ent['logdescr'], true);
  1110.                 $ret['time'] = strtotime($ent['created_at']);
  1111.             }
  1112.             break;
  1113.         }
  1114.         return $ret;
  1115.     }
  1116.     /**
  1117.      * returns a subpath of the current log-dir
  1118.      * doCreate = 1 creates the new path incl. subdirs, append / to path to create last dir too!
  1119.      * @param string $subpath
  1120.      * @param int $doCreate
  1121.      * @return string
  1122.      */
  1123.     public function getLogDirSubPath(string $subpathint $doCreate=0) : string 
  1124.     {
  1125.         $ret =  $this->add_ending_slash($this->logDir).$subpath;
  1126.         if ($doCreate) {
  1127.             $p dirname($ret);
  1128.             if (!is_dir($p)) {
  1129.                 mkdir($p,0777true);
  1130.             }
  1131.         }
  1132.         return $ret;
  1133.     }
  1134.     public function getLimit(Request $requestSalesChannelContext $context): int
  1135.     {
  1136.         $limit $request->query->getInt('limit'0);
  1137.         if ($request->isMethod(Request::METHOD_POST)) {
  1138.             $limit $request->request->getInt('limit'$limit);
  1139.         }
  1140.         $limit 0;
  1141.         $limit $limit $limit $this->systemConfigService->getInt('core.listing.productsPerPage'$context->getSalesChannel()->getId());
  1142.         return $limit <= 24 $limit;
  1143.     }
  1144.     public function getPage(Request $request): int
  1145.     {
  1146.         $page $request->query->getInt('p'1);
  1147.         if ($request->isMethod(Request::METHOD_POST)) {
  1148.             $page $request->request->getInt('p'$page);
  1149.         }
  1150.         return $page <= $page;
  1151.     }
  1152.     /**
  1153.      * add const filterPrefix to String
  1154.      * @param string $str
  1155.      * @return string
  1156.      */
  1157.     public function addFilterPrefix(string $str) : string {
  1158.         if (is_null($str)) return $str;
  1159.         return self::FilterPrefix.$str;
  1160.     }
  1161.     /**
  1162.      * returns List of filter-properties
  1163.      * @param string $queryString
  1164.      * @return array
  1165.      */
  1166.     public function getFilterPropertiesList(string $queryString) : array {
  1167.         return explode(self::FilterListSeparator$queryString);
  1168.     }
  1169.     /**
  1170.      * returns array of feature [1] = value [2]
  1171.      * @param string $queryString
  1172.      * @return array
  1173.      */
  1174.     public function getFilterPropertiesEntity(string $queryString) : array {
  1175.         return explode(self::FilterPrefix$queryString);
  1176.     }
  1177. }