Elastic search

This commit is contained in:
jitendra 2022-10-20 18:33:11 +05:30
parent c457b98b8e
commit 71d6198e03
12 changed files with 1151 additions and 234 deletions

View File

@ -10,10 +10,12 @@
"require": { "require": {
"php": "^8.1", "php": "^8.1",
"astrotomic/laravel-translatable": "^11.0.0", "astrotomic/laravel-translatable": "^11.0.0",
"bagisto/laravel-datafaker": "^2.0@alpha",
"bagisto/rest-api": "^1.0", "bagisto/rest-api": "^1.0",
"bagistobrasil/bagisto-product-social-share": "^0.1.2", "bagistobrasil/bagisto-product-social-share": "^0.1.2",
"barryvdh/laravel-debugbar": "^3.1", "barryvdh/laravel-debugbar": "^3.1",
"barryvdh/laravel-dompdf": "^2.0.0", "barryvdh/laravel-dompdf": "^2.0.0",
"cviebrock/laravel-elasticsearch": "^9.0",
"diglactic/laravel-breadcrumbs": "^7.0", "diglactic/laravel-breadcrumbs": "^7.0",
"doctrine/dbal": "^2.9", "doctrine/dbal": "^2.9",
"enshrined/svg-sanitize": "^0.15.0", "enshrined/svg-sanitize": "^0.15.0",
@ -27,7 +29,6 @@
"konekt/concord": "^1.2", "konekt/concord": "^1.2",
"laravel/framework": "^9.0", "laravel/framework": "^9.0",
"laravel/sanctum": "^2.12", "laravel/sanctum": "^2.12",
"laravel/scout": "^9.0",
"laravel/socialite": "^5.0", "laravel/socialite": "^5.0",
"laravel/tinker": "^2.0", "laravel/tinker": "^2.0",
"laravel/ui": "^3.0", "laravel/ui": "^3.0",

455
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "24a0dc2b81de04eed28797f623305a33", "content-hash": "f97b453abb0e5218dd71c940aaa63d00",
"packages": [ "packages": [
{ {
"name": "astrotomic/laravel-translatable", "name": "astrotomic/laravel-translatable",
@ -97,6 +97,59 @@
], ],
"time": "2022-02-05T10:42:52+00:00" "time": "2022-02-05T10:42:52+00:00"
}, },
{
"name": "bagisto/laravel-datafaker",
"version": "v2.0.0-ALPHA1",
"source": {
"type": "git",
"url": "https://github.com/bagisto/laravel-data-faker.git",
"reference": "09c3587a6cef80e84ddc16ea30cd2f0ade1d09b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bagisto/laravel-data-faker/zipball/09c3587a6cef80e84ddc16ea30cd2f0ade1d09b2",
"reference": "09c3587a6cef80e84ddc16ea30cd2f0ade1d09b2",
"shasum": ""
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Webkul\\Faker\\Providers\\FakerServiceProvider"
],
"aliases": []
}
},
"autoload": {
"psr-4": {
"Webkul\\Faker\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jitendra Singh",
"email": "jitendra@webkul.in",
"homepage": "https://bagisto.com"
}
],
"description": "Create fake customers, categories and products in Bagisto.",
"homepage": "https://github.com/bagisto/laravel-data-faker",
"keywords": [
"bagisto",
"fake categories",
"fake customers",
"fake products"
],
"support": {
"issues": "https://github.com/bagisto/laravel-data-faker/issues",
"source": "https://github.com/bagisto/laravel-data-faker/tree/v2.0.0-ALPHA1"
},
"time": "2022-10-14T10:10:16+00:00"
},
{ {
"name": "bagisto/rest-api", "name": "bagisto/rest-api",
"version": "v1.0.0", "version": "v1.0.0",
@ -405,6 +458,84 @@
], ],
"time": "2021-08-15T20:50:18+00:00" "time": "2021-08-15T20:50:18+00:00"
}, },
{
"name": "cviebrock/laravel-elasticsearch",
"version": "9.0.2",
"source": {
"type": "git",
"url": "https://github.com/cviebrock/laravel-elasticsearch.git",
"reference": "e638493bb3a4d75a2a62b9d0db6234b8a5684917"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cviebrock/laravel-elasticsearch/zipball/e638493bb3a4d75a2a62b9d0db6234b8a5684917",
"reference": "e638493bb3a4d75a2a62b9d0db6234b8a5684917",
"shasum": ""
},
"require": {
"elasticsearch/elasticsearch": "^7.11",
"ext-json": "*",
"guzzlehttp/psr7": "^1.7|^2.0",
"illuminate/contracts": "^7.0|^8.0|^9.0",
"illuminate/support": "^7.0|^8.0|^9.0",
"php": "^7.3|^8.0",
"psr/http-message": "^1.0"
},
"require-dev": {
"limedeck/phpunit-detailed-printer": "^6.0",
"mockery/mockery": "^1.4.3",
"orchestra/testbench": "^6.5|^7.0",
"phpunit/phpunit": "^9.4"
},
"suggest": {
"aws/aws-sdk-php": "Required to connect to an Elasticsearch host on AWS (^3.80)"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Cviebrock\\LaravelElasticsearch\\ServiceProvider"
],
"aliases": {
"Elasticsearch": "Cviebrock\\LaravelElasticsearch\\Facade"
}
}
},
"autoload": {
"psr-4": {
"Cviebrock\\LaravelElasticsearch\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Colin Viebrock",
"email": "colin@viebrock.ca"
}
],
"description": "An easy way to use the official PHP ElasticSearch client in your Laravel applications.",
"homepage": "https://github.com/cviebrock/laravel-elasticsearch",
"keywords": [
"client",
"elasticsearch",
"laravel",
"search"
],
"support": {
"issues": "https://github.com/cviebrock/laravel-elasticsearch/issues",
"source": "https://github.com/cviebrock/laravel-elasticsearch/tree/9.0.2"
},
"funding": [
{
"url": "https://github.com/cviebrock",
"type": "github"
}
],
"time": "2022-07-19T02:25:49+00:00"
},
{ {
"name": "dflydev/dot-access-data", "name": "dflydev/dot-access-data",
"version": "v3.0.1", "version": "v3.0.1",
@ -1256,6 +1387,69 @@
], ],
"time": "2022-06-18T20:57:19+00:00" "time": "2022-06-18T20:57:19+00:00"
}, },
{
"name": "elasticsearch/elasticsearch",
"version": "v7.17.1",
"source": {
"type": "git",
"url": "git@github.com:elastic/elasticsearch-php.git",
"reference": "f1b8918f411b837ce5f6325e829a73518fd50367"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/f1b8918f411b837ce5f6325e829a73518fd50367",
"reference": "f1b8918f411b837ce5f6325e829a73518fd50367",
"shasum": ""
},
"require": {
"ext-json": ">=1.3.7",
"ezimuel/ringphp": "^1.1.2",
"php": "^7.3 || ^8.0",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"ext-yaml": "*",
"ext-zip": "*",
"mockery/mockery": "^1.2",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.4",
"symfony/finder": "~4.0"
},
"suggest": {
"ext-curl": "*",
"monolog/monolog": "Allows for client-level logging and tracing"
},
"type": "library",
"autoload": {
"files": [
"src/autoload.php"
],
"psr-4": {
"Elasticsearch\\": "src/Elasticsearch/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0",
"LGPL-2.1-only"
],
"authors": [
{
"name": "Zachary Tong"
},
{
"name": "Enrico Zimuel"
}
],
"description": "PHP Client for Elasticsearch",
"keywords": [
"client",
"elasticsearch",
"search"
],
"time": "2022-09-30T12:28:55+00:00"
},
{ {
"name": "enshrined/svg-sanitize", "name": "enshrined/svg-sanitize",
"version": "0.15.4", "version": "0.15.4",
@ -1301,6 +1495,116 @@
}, },
"time": "2022-02-21T09:13:59+00:00" "time": "2022-02-21T09:13:59+00:00"
}, },
{
"name": "ezimuel/guzzlestreams",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/guzzlestreams.git",
"reference": "abe3791d231167f14eb80d413420d1eab91163a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/abe3791d231167f14eb80d413420d1eab91163a8",
"reference": "abe3791d231167f14eb80d413420d1eab91163a8",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"stream"
],
"support": {
"source": "https://github.com/ezimuel/guzzlestreams/tree/3.0.1"
},
"time": "2020-02-14T23:11:50+00:00"
},
{
"name": "ezimuel/ringphp",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/ezimuel/ringphp.git",
"reference": "92b8161404ab1ad84059ebed41d9f757e897ce74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezimuel/ringphp/zipball/92b8161404ab1ad84059ebed41d9f757e897ce74",
"reference": "92b8161404ab1ad84059ebed41d9f757e897ce74",
"shasum": ""
},
"require": {
"ezimuel/guzzlestreams": "^3.0.1",
"php": ">=5.4.0",
"react/promise": "~2.0"
},
"replace": {
"guzzlehttp/ringphp": "self.version"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~9.0"
},
"suggest": {
"ext-curl": "Guzzle will use specific adapters if cURL is present"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Ring\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php",
"support": {
"source": "https://github.com/ezimuel/ringphp/tree/1.2.0"
},
"time": "2021-11-16T11:51:30+00:00"
},
{ {
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
"version": "v4.14.0", "version": "v4.14.0",
@ -2687,78 +2991,6 @@
}, },
"time": "2022-04-08T13:39:49+00:00" "time": "2022-04-08T13:39:49+00:00"
}, },
{
"name": "laravel/scout",
"version": "v9.4.9",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
"reference": "61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/scout/zipball/61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8",
"reference": "61ce79ce87fbebb28dcc0dd8f95776aa0dec00c8",
"shasum": ""
},
"require": {
"illuminate/bus": "^8.0|^9.0",
"illuminate/contracts": "^8.0|^9.0",
"illuminate/database": "^8.0|^9.0",
"illuminate/http": "^8.0|^9.0",
"illuminate/pagination": "^8.0|^9.0",
"illuminate/queue": "^8.0|^9.0",
"illuminate/support": "^8.0|^9.0",
"php": "^7.3|^8.0"
},
"require-dev": {
"meilisearch/meilisearch-php": "^0.19",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^6.17|^7.0",
"phpunit/phpunit": "^9.3"
},
"suggest": {
"algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).",
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.23)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Scout\\ScoutServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Scout\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
"keywords": [
"algolia",
"laravel",
"search"
],
"support": {
"issues": "https://github.com/laravel/scout/issues",
"source": "https://github.com/laravel/scout"
},
"time": "2022-05-05T14:24:18+00:00"
},
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
"version": "v1.2.0", "version": "v1.2.0",
@ -5802,6 +6034,82 @@
], ],
"time": "2022-03-27T21:42:02+00:00" "time": "2022-03-27T21:42:02+00:00"
}, },
{
"name": "react/promise",
"version": "v2.9.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910",
"reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v2.9.0"
},
"funding": [
{
"url": "https://github.com/WyriHaximus",
"type": "github"
},
{
"url": "https://github.com/clue",
"type": "github"
}
],
"time": "2022-02-11T10:27:51+00:00"
},
{ {
"name": "sabberworm/php-css-parser", "name": "sabberworm/php-css-parser",
"version": "8.4.0", "version": "8.4.0",
@ -12361,6 +12669,7 @@
"aliases": [], "aliases": [],
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"bagisto/laravel-datafaker": 15,
"flynsarmy/db-blade-compiler": 20, "flynsarmy/db-blade-compiler": 20,
"codeception/codeception": 20, "codeception/codeception": 20,
"codeception/module-laravel": 20 "codeception/module-laravel": 20

210
config/elasticsearch.php Normal file
View File

@ -0,0 +1,210 @@
<?php
return [
/**
* You can specify one of several different connections when building an
* Elasticsearch client.
*
* Here you may specify which of the connections below you wish to use
* as your default connection when building an client. Of course you may
* use create several clients at once, each with different configurations.
*/
'defaultConnection' => 'default',
/**
* These are the connection parameters used when building a client.
*/
'connections' => [
'default' => [
/**
* Hosts
*
* This is an array of hosts that the client will connect to. It can be a
* single host, or an array if you are running a cluster of Elasticsearch
* instances.
*
* This is the only configuration value that is mandatory.
*
* Presently using "extended" host configuration method
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_extended_host_configuration
*
* There is also the shorter "inline" configuration method available
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_inline_host_configuration
*/
'hosts' => [
[
'host' => env('ELASTICSEARCH_HOST', 'localhost'),
// For local development, the default Elasticsearch port is 9200.
// If you are connecting to an Elasticsearch instance on AWS, you probably want to set this to null
'port' => env('ELASTICSEARCH_PORT', 9200),
'scheme' => env('ELASTICSEARCH_SCHEME', null),
'user' => env('ELASTICSEARCH_USER', null),
'pass' => env('ELASTICSEARCH_PASS', null),
// If you are connecting to an Elasticsearch instance on AWS, you will need these values as well
'aws' => env('AWS_ELASTICSEARCH_ENABLED', false),
'aws_region' => env('AWS_REGION', ''),
'aws_key' => env('AWS_ACCESS_KEY_ID', ''),
'aws_secret' => env('AWS_SECRET_ACCESS_KEY', ''),
'aws_credentials' => null,
'aws_session_token' => env('AWS_SESSION_TOKEN', null),
],
],
/**
* SSL
*
* If your Elasticsearch instance uses an out-dated or self-signed SSL
* certificate, you will need to pass in the certificate bundle. This can
* either be the path to the certificate file (for self-signed certs), or a
* package like https://github.com/Kdyby/CurlCaBundle. See the documentation
* below for all the details.
*
* If you are using SSL instances, and the certificates are up-to-date and
* signed by a public certificate authority, then you can leave this null and
* just use "https" in the host path(s) above and you should be fine.
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_security.html#_ssl_encryption_2
*/
'sslVerification' => null,
/**
* Logging
*
* Logging is handled by passing in an instance of Monolog\Logger (which
* coincidentally is what Laravel's default logger is).
*
* If logging is enabled, you either need to set the path and log level
* (some defaults are given for you below), or you can use a custom logger by
* setting 'logObject' to an instance of Psr\Log\LoggerInterface. In fact,
* if you just want to use the default Laravel logger, then set 'logObject'
* to \Log::getMonolog().
*
* Note: 'logObject' takes precedent over 'logPath'/'logLevel', so set
* 'logObject' null if you just want file-based logging to a custom path.
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#enabling_logger
*/
'logging' => false,
// If you have an existing instance of Monolog you can use it here.
// 'logObject' => \Log::getMonolog(),
'logPath' => storage_path('logs/elasticsearch.log'),
'logLevel' => Monolog\Logger::INFO,
/**
* Retries
*
* By default, the client will retry n times, where n = number of nodes in
* your cluster. If you would like to disable retries, or change the number,
* you can do so here.
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_set_retries
*/
'retries' => null,
/**
* The remainder of the configuration options can almost always be left
* as-is unless you have specific reasons to change them. Refer to the
* appropriate sections in the Elasticsearch documentation for what each option
* does and what values it expects.
*/
/**
* Sniff On Start
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html
*/
'sniffOnStart' => false,
/**
* HTTP Handler
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_configure_the_http_handler
* @see http://ringphp.readthedocs.org/en/latest/client_handlers.html
*/
'httpHandler' => null,
/**
* Connection Pool
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_setting_the_connection_pool
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_connection_pool.html
*/
'connectionPool' => null,
/**
* Connection Selector
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_setting_the_connection_selector
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_selectors.html
*/
'connectionSelector' => null,
/**
* Serializer
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_setting_the_serializer
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_serializers.html
*/
'serializer' => null,
/**
* Connection Factory
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_setting_a_custom_connectionfactory
*/
'connectionFactory' => null,
/**
* Endpoint
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/6.0/_configuration.html#_set_the_endpoint_closure
*/
'endpoint' => null,
/**
* Register additional namespaces
*
* An array of additional namespaces to register.
*
* @example 'namespaces' => [XPack::Security(), XPack::Watcher()]
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/ElasticsearchPHP_Endpoints.html#Elasticsearch_ClientBuilderregisterNamespace_registerNamespace
*/
'namespaces' => [],
/**
* Tracer
*
* Tracer is handled by passing in a name of the class implements Psr\Log\LoggerInterface.
*
* @see https://www.elastic.co/guide/en/elasticsearch/client/php-api/2.0/_configuration.html#_setting_a_custom_connectionfactory
*/
'tracer' => null,
],
],
];

View File

@ -41,7 +41,7 @@ class Indexer extends Command
*/ */
public function handle() public function handle()
{ {
$indexers = ['price', 'inventory']; $indexers = ['inventory', 'price', 'elastic'];
if (! empty($this->option('type'))) { if (! empty($this->option('type'))) {
$indexers = $this->option('type'); $indexers = $this->option('type');

View File

@ -2,33 +2,33 @@
namespace Webkul\Product\Helpers; namespace Webkul\Product\Helpers;
use Webkul\Core\Repositories\ChannelRepository;
use Webkul\Customer\Repositories\CustomerGroupRepository; use Webkul\Customer\Repositories\CustomerGroupRepository;
use Webkul\Product\Repositories\ProductPriceIndexRepository; use Webkul\Product\Repositories\ProductPriceIndexRepository;
use Webkul\Product\Repositories\ProductInventoryIndexRepository; use Webkul\Product\Repositories\ProductInventoryIndexRepository;
use Webkul\Product\Helpers\Indexers\Flat\Product as FlatIndexer; use Webkul\Product\Helpers\Indexers\Flat\Product as FlatIndexer;
use Webkul\Product\Helpers\Indexers\Inventory\Product as InventoryIndexer; use Webkul\Product\Helpers\Indexers\Inventory\Product as InventoryIndexer;
use Webkul\Product\Helpers\Indexers\ElasticSearch\Product as ElasticSearchIndexer;
class Indexer class Indexer
{ {
/** /**
* Create a new command instance. * Create a new command instance.
* *
* @param \Webkul\Core\Repositories\ChannelRepository $channelRepository
* @param \Webkul\Customer\Repositories\CustomerGroupRepository $customerGroupRepository * @param \Webkul\Customer\Repositories\CustomerGroupRepository $customerGroupRepository
* @param \Webkul\Product\Repositories\ProductPriceIndexRepository $productPriceIndexRepository * @param \Webkul\Product\Repositories\ProductPriceIndexRepository $productPriceIndexRepository
* @param \Webkul\Product\Repositories\ProductInventoryIndexRepository $productInventoryIndexRepository * @param \Webkul\Product\Repositories\ProductInventoryIndexRepository $productInventoryIndexRepository
* @param \Webkul\Product\Helpers\Indexers\Flat\Product $flatIndexer * @param \Webkul\Product\Helpers\Indexers\Flat\Product $flatIndexer
* @param \Webkul\Product\Helpers\Indexers\Inventory\Product $inventoryIndexer * @param \Webkul\Product\Helpers\Indexers\Inventory\Product $inventoryIndexer
* @param \Webkul\Product\Helpers\Indexers\ElasticSearch\Product $elasticSearchIndexer
* @return void * @return void
*/ */
public function __construct( public function __construct(
protected ChannelRepository $channelRepository,
protected CustomerGroupRepository $customerGroupRepository, protected CustomerGroupRepository $customerGroupRepository,
protected ProductPriceIndexRepository $productPriceIndexRepository, protected ProductPriceIndexRepository $productPriceIndexRepository,
protected ProductInventoryIndexRepository $productInventoryIndexRepository, protected ProductInventoryIndexRepository $productInventoryIndexRepository,
protected FlatIndexer $flatIndexer, protected FlatIndexer $flatIndexer,
protected InventoryIndexer $inventoryIndexer protected InventoryIndexer $inventoryIndexer,
protected ElasticSearchIndexer $elasticSearchIndexer
) )
{ {
} }
@ -40,14 +40,18 @@ class Indexer
* @param array $indexers * @param array $indexers
* @return void * @return void
*/ */
public function refresh($product, array $indexers = ['price', 'inventory']) public function refresh($product, array $indexers = ['inventory', 'price', 'elastic'])
{ {
if (in_array('inventory', $indexers)) {
$this->refreshInventory($product);
}
if (in_array('price', $indexers)) { if (in_array('price', $indexers)) {
$this->refreshPrice($product); $this->refreshPrice($product);
} }
if (in_array('inventory', $indexers)) { if (in_array('elastic', $indexers)) {
$this->refreshInventory($product); $this->refreshElasticSearch($product);
} }
} }
@ -96,13 +100,30 @@ class Indexer
return; return;
} }
$channels = $this->channelRepository->all(); foreach (core()->getAllChannels() as $channel) {
foreach ($channels as $channel) {
$this->productInventoryIndexRepository->updateOrCreate([ $this->productInventoryIndexRepository->updateOrCreate([
'channel_id' => $channel->id, 'channel_id' => $channel->id,
'product_id' => $product->id, 'product_id' => $product->id,
], $this->inventoryIndexer->setProduct($product)->getIndices($channel)); ], $this->inventoryIndexer->setProduct($product)->getIndices($channel));
} }
} }
/**
* Refresh elastic search indices
*
* @param \Webkul\Product\Contracts\Product $product
* @return void
*/
public function refreshElasticSearch($product)
{
foreach (core()->getAllChannels() as $channel) {
foreach ($channel->locales as $locale) {
$this->elasticSearchIndexer
->setProduct($product)
->setChannel($channel)
->setLocale($locale)
->refresh();
}
}
}
} }

View File

@ -0,0 +1,193 @@
<?php
namespace Webkul\Product\Helpers\Indexers\ElasticSearch;
use Illuminate\Support\Arr;
use Elasticsearch;
use Webkul\Attribute\Repositories\AttributeRepository;
class Product
{
/**
* Create a new indexer instance.
*
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @return void
*/
public function __construct(protected AttributeRepository $attributeRepository)
{
}
/**
* Product instance.
*
* @var \Webkul\Product\Contracts\Product
*/
protected $product;
/**
* Channel instance.
*
* @var \Webkul\Core\Contracts\Channel
*/
protected $channel;
/**
* Locale instance.
*
* @var \Webkul\Core\Contracts\Locale
*/
protected $locale;
/**
* Set current product
*
* @param \Webkul\Product\Contracts\Product $product
* @return \Webkul\Product\Helpers\Indexers\ElasticSearch\Product
*/
public function setProduct($product)
{
$this->product = $product;
return $this;
}
/**
* Set Channel
*
* @param \Webkul\Product\Contracts\Product $product
* @return \Webkul\Product\Helpers\Indexers\ElasticSearch\Product
*/
public function setChannel($channel)
{
$this->channel = $channel;
return $this;
}
/**
* Set Locale
*
* @param \Webkul\Product\Contracts\Product $product
* @return \Webkul\Product\Helpers\Indexers\ElasticSearch\Product
*/
public function setLocale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Refresh product indices
*
* @return void
*/
public function refresh()
{
$params = [
'index' => $this->getIndexName(),
'id' => $this->product->id,
'body' => $this->getElasticProperties(),
];
Elasticsearch::index($params);
}
/**
* Refresh product indices
*
* @return void
*/
public function getIndexName()
{
return 'products_' . $this->channel->code . '_' . $this->locale->code . '_index';
}
/**
* Returns filterable attribute values
*
* @return void
*/
public function getElasticProperties()
{
$properties = [
'id' => $this->product->id,
'category_ids' => $this->product->categories->pluck('id')->toArray(),
'created_at' => $this->product->created_at,
];
$attributes = $this->attributeRepository->scopeQuery(function ($query) {
return $query->where(function ($ab) {
return $ab->orWhereIn('code', [
'sku',
'name',
'status',
'visible_individually',
'url_key',
'short_description',
'description',
])
->orWhere('is_filterable', 1);
});
})->get();
foreach ($attributes as $attribute) {
$attributeValue = $this->getAttributeValue($attribute);
if ($attribute->code == 'price') {
$properties[$attribute->code] = (float) $this->product->getTypeInstance()->getMinimalPrice();
} elseif ($attribute->type == 'boolean') {
$properties[$attribute->code] = intval($attributeValue?->{$attribute->column_name});
} else {
$properties[$attribute->code] = $attributeValue?->{$attribute->column_name};
}
}
foreach ($this->product->super_attributes as $attribute) {
foreach ($this->product->variants as $variant) {
$properties['ca_' . $attribute->code][] = $variant->{$attribute->code};
}
}
return $properties;
}
/**
* Returns filterable attribute values
*
* @param \Webkul\Attribute\Contracts\Attribute $attribute
* @param \Webkul\Product\Contracts\ProductAttributeValue
* @return void
*/
public function getAttributeValue($attribute)
{
if ($attribute->value_per_channel) {
if ($attribute->value_per_locale) {
$attributeValue = $this->product->attribute_values
->where('channel', $this->channel->code)
->where('locale', $this->locale->code)
->where('attribute_id', $attribute->id)
->first();
} else {
$attributeValue = $this->product->attribute_values
->where('channel', $this->channel->code)
->where('attribute_id', $attribute->id)
->first();
}
} else {
if ($attribute->value_per_locale) {
$attributeValue = $this->product->attribute_values
->where('locale', $this->locale->code)
->where('attribute_id', $attribute->id)
->first();
} else {
$attributeValue = $this->product->attribute_values
->where('attribute_id', $attribute->id)
->first();
}
}
return $attributeValue;
}
}

View File

@ -46,21 +46,21 @@ class Product
} }
/** /**
* Refresh product indexer indexes * Refresh product flat indices
* *
* @param \Webkul\Product\Contracts\Product $product * @param \Webkul\Product\Contracts\Product $product
* @return void * @return void
*/ */
public function refresh($product) public function refresh($product)
{ {
$this->updateCreate($product); $this->updateOrCreate($product);
if (! ProductType::hasVariants($product->type)) { if (! ProductType::hasVariants($product->type)) {
return; return;
} }
foreach ($product->variants()->get() as $variant) { foreach ($product->variants()->get() as $variant) {
$this->updateCreate($variant, $product); $this->updateOrCreate($variant, $product);
} }
} }
@ -71,7 +71,7 @@ class Product
* @param \Webkul\Product\Contracts\Product $parentProduct * @param \Webkul\Product\Contracts\Product $parentProduct
* @return void * @return void
*/ */
public function updateCreate($product, $parentProduct = null) public function updateOrCreate($product, $parentProduct = null)
{ {
$familyAttributes = $this->getCachedFamilyAttributes($product); $familyAttributes = $this->getCachedFamilyAttributes($product);

View File

@ -45,9 +45,11 @@ class Product
{ {
$this->indexer->refreshFlat($product); $this->indexer->refreshFlat($product);
$this->refreshInventoryIndices($product);
$this->refreshPriceIndices($product); $this->refreshPriceIndices($product);
$this->indexer->refreshInventory($product); $this->refreshElasticSearchIndices($product);
} }
/** /**
@ -58,25 +60,78 @@ class Product
*/ */
public function refreshPriceIndices($product) public function refreshPriceIndices($product)
{ {
$products = [$product]; $products = $this->getAllRelatedProducts($product);
if ($product->type == 'simple') {
if ($product->parent_id) {
$products[] = $product->parent;
}
$products = array_merge(
$products,
$this->getParentBundleProducts($product),
$this->getParentGroupProducts($product)
);
}
foreach ($products as $product) { foreach ($products as $product) {
$this->indexer->refreshPrice($product); $this->indexer->refreshPrice($product);
} }
} }
/**
* Update or create product inventory indices
*
* @param \Webkul\Product\Contracts\Product $product
* @return void
*/
public function refreshInventoryIndices($product)
{
$products = $this->getAllRelatedProducts($product);
foreach ($products as $product) {
$this->indexer->refreshInventory($product);
}
}
/**
* Update or create product ElasticSearch indices
*
* @param \Webkul\Product\Contracts\Product $product
* @return void
*/
public function refreshElasticSearchIndices($product)
{
$products = $this->getAllRelatedProducts($product);
foreach ($products as $product) {
$this->indexer->refreshElasticSearch($product);
}
}
/**
* Returns parents bundle products associated with simple product
*
* @param \Webkul\Product\Contracts\Product $product
* @return array
*/
public function getAllRelatedProducts($product)
{
static $products = [];
if (array_key_exists($product->id, $products)) {
return $products[$product->id];
}
$products[$product->id] = [$product];
if ($product->type == 'simple') {
if ($product->parent_id) {
$products[$product->id][] = $product->parent;
}
$products[$product->id] = array_merge(
$products[$product->id],
$this->getParentBundleProducts($product),
$this->getParentGroupProducts($product)
);
} elseif ($product->type == 'configurable') {
foreach ($product->variants as $variant) {
$products[$product->id][] = $variant;
}
}
return $products[$product->id];
}
/** /**
* Returns parents bundle products associated with simple product * Returns parents bundle products associated with simple product
* *

View File

@ -60,28 +60,6 @@ class Product extends Model implements ProductContract
*/ */
public static $loadedAttributeValues = []; public static $loadedAttributeValues = [];
/**
* The `booted` method of the model.
*
* @return void
*/
protected static function booted(): void
{
parent::boot();
static::deleting(function ($product) {
foreach ($product->product_flats as $productFlat) {
$productFlat->unsearchable();
}
foreach ($product->variants as $variant) {
foreach ($variant->product_flats as $productFlat) {
$productFlat->unsearchable();
}
}
});
}
/** /**
* Get the product flat entries that are associated with product. * Get the product flat entries that are associated with product.
* May be one for each locale and each channel. * May be one for each locale and each channel.

View File

@ -3,14 +3,11 @@
namespace Webkul\Product\Models; namespace Webkul\Product\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
use Webkul\Attribute\Repositories\AttributeRepository; use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Contracts\ProductFlat as ProductFlatContract; use Webkul\Product\Contracts\ProductFlat as ProductFlatContract;
class ProductFlat extends Model implements ProductFlatContract class ProductFlat extends Model implements ProductFlatContract
{ {
use Searchable;
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -40,16 +37,6 @@ class ProductFlat extends Model implements ProductFlatContract
'attribute_family_id', 'attribute_family_id',
]; ];
/**
* Get the index name for the model.
*
* @return string
*/
public function searchableAs()
{
return 'products_index';
}
/** /**
* Get an attribute from the model. * Get an attribute from the model.
* *

View File

@ -0,0 +1,114 @@
<?php
namespace Webkul\Product\Repositories;
use Elasticsearch;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Webkul\Attribute\Repositories\AttributeRepository;
class ElasticSearchRepository
{
/**
* Create a new repository instance.
*
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @return void
*/
public function __construct(protected AttributeRepository $attributeRepository)
{
}
/**
* Returns product ids from Elasticsearch
*
* @param integer $categoryId
* @param array $options
* @return array
*/
public function search($categoryId, $options)
{
$from = ($options['page'] * $options['limit']) - $options['limit'];
$filters = $this->addFilters();
$params = [
'index' => $this->getIndexName(),
'body' => [
'from' => $from,
'stored_fields' => [],
'query' => [
'bool' => [
'filter' => $filters,
]
],
'sort' => [
$options['sort'] . '.keyword' => [
'order' => $options['order'],
],
],
],
];
dd($params);
$results = Elasticsearch::search($params);
dd($results);
return [
'total' => $results['hits']['total']['value'],
'ids' => collect($results['hits']['hits'])->pluck('_id')->toArray()
];
}
/**
* Refresh product indices
*
* @return void
*/
public function getIndexName()
{
return 'products_' . core()->getRequestedChannelCode() . '_' . core()->getRequestedLocaleCode() . '_index';
}
/**
* Refresh product indices
*
* @return void
*/
public function addFilters()
{
$params = request()->input();
$filterableAttributes = $this->attributeRepository
->getProductDefaultAttributes(array_keys($params));
$filters = [];
foreach ($filterableAttributes as $attribute) {
switch ($attribute->type) {
case 'price':
$range = explode(',', $params[$attribute->code]);
// $filters['range'][$attribute->code] = [
// 'gte' => core()->convertToBasePrice(current($range)),
// 'lte' => core()->convertToBasePrice(end($range)),
// ];
break;
case 'text':
$filters['match_phrase_prefix'][$attribute->code] = $params[$attribute->code];
break;
case 'select':
$filters['term'][$attribute->code] = $params[$attribute->code];
break;
}
}
return $filters;
}
}

View File

@ -19,6 +19,7 @@ class ProductRepository extends Repository
* @param \Webkul\Customer\Repositories\CustomerRepository $customerRepository * @param \Webkul\Customer\Repositories\CustomerRepository $customerRepository
* @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository * @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param \Webkul\Product\Repositories\ProductFlatRepository $productFlatRepository * @param \Webkul\Product\Repositories\ProductFlatRepository $productFlatRepository
* @param \Webkul\Product\Repositories\ElasticSearchRepository $elasticSearchRepository
* @param \Illuminate\Container\Container $container * @param \Illuminate\Container\Container $container
* @return void * @return void
*/ */
@ -26,6 +27,7 @@ class ProductRepository extends Repository
protected CustomerRepository $customerRepository, protected CustomerRepository $customerRepository,
protected AttributeRepository $attributeRepository, protected AttributeRepository $attributeRepository,
protected ProductFlatRepository $productFlatRepository, protected ProductFlatRepository $productFlatRepository,
protected ElasticSearchRepository $elasticSearchRepository,
Container $container Container $container
) )
{ {
@ -178,12 +180,29 @@ class ProductRepository extends Repository
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getAll($categoryId = null) public function getAll($categoryId = null)
{
$engine = 'elastic';
if ($engine == 'db') {
return $this->searchFromDatabase($categoryId);
}
return $this->searchFromElastic($categoryId);
}
/**
* Search product from database
*
* @param string $categoryId
* @return \Illuminate\Support\Collection
*/
public function searchFromDatabase($categoryId = null)
{ {
$params = request()->input(); $params = request()->input();
$query = $this->productFlatRepository->with([ $query = $this->productFlatRepository->with([
'images', 'images',
'product.videos', 'videos',
'product.attribute_values', 'product.attribute_values',
'product.price_indices', 'product.price_indices',
'product.inventory_indices', 'product.inventory_indices',
@ -272,28 +291,31 @@ class ProductRepository extends Repository
} }
#Sort collection #Sort collection
$sortOptions = explode('-', core()->getConfigData('catalog.products.storefront.sort_by') ?: 'name-desc'); $sortOptions = $this->getSortOptions($params);
$orderDirection = empty($params['order']) ? end($sortOptions) : $params['order'];
$this->checkSortAttributeAndGenerateQuery($qb, $params['sort'] ?? $sortOptions[0], $orderDirection);
if ($sortOptions['order'] != 'rand') {
$attribute = $this->attributeRepository->findOneByField('code', $sortOptions['sort']);
if ($attribute) {
if ($attribute->code === 'price') {
$qb->orderBy('product_price_indices.min_price', $sortOptions['order']);
} else {
$qb->orderBy($attribute->code, $sortOptions['order']);
}
} else {
/* `created_at` is not an attribute so it will be in else case */
$qb->orderBy('product_flat.created_at', $sortOptions['order']);
}
} else {
return $qb->inRandomOrder();
}
return $qb->groupBy('product_flat.id'); return $qb->groupBy('product_flat.id');
}); });
if (core()->getConfigData('catalog.products.storefront.products_per_page')) {
$pages = explode(',', core()->getConfigData('catalog.products.storefront.products_per_page'));
$perPage = ! empty($params['limit']) ? $params['limit'] : current($pages);
} else {
$perPage = ! empty($params['limit']) ? $params['limit'] : 9;
}
# apply scope query so we can fetch the raw sql and perform a count # apply scope query so we can fetch the raw sql and perform a count
$query->applyScope(); $query->applyScope();
$page = Paginator::resolveCurrentPage('page');
$countQuery = clone $query->model; $countQuery = clone $query->model;
$count = collect( $count = collect(
@ -303,16 +325,20 @@ class ProductRepository extends Repository
$items = []; $items = [];
$limit = $this->getPerPageLimit($params);
$currentPage = Paginator::resolveCurrentPage('page');
if ($count > 0) { if ($count > 0) {
# apply a new scope query to limit results to one page # apply a new scope query to limit results to one page
$query->scopeQuery(function ($query) use ($page, $perPage) { $query->scopeQuery(function ($query) use ($currentPage, $limit) {
return $query->forPage($page, $perPage); return $query->forPage($currentPage, $limit);
}); });
$items = $query->get(); $items = $query->get();
} }
$results = new LengthAwarePaginator($items, $count, $perPage, $page, [ $results = new LengthAwarePaginator($items, $count, $limit, $currentPage, [
'path' => request()->url(), 'path' => request()->url(),
'query' => request()->query(), 'query' => request()->query(),
]); ]);
@ -321,33 +347,95 @@ class ProductRepository extends Repository
} }
/** /**
* Check sort attribute and generate query. * Search product from elastic search
* *
* @param object $query * @param string $categoryId
* @param string $sort * @return \Illuminate\Support\Collection
* @param string $direction
* @return object
*/ */
private function checkSortAttributeAndGenerateQuery($query, $sort, $direction) public function searchFromElastic($categoryId)
{ {
if ($direction == 'rand') { $params = request()->input();
return $query->inRandomOrder();
$currentPage = Paginator::resolveCurrentPage('page');
$limit = $this->getPerPageLimit($params);
$sortOptions = $this->getSortOptions($params);
$indices = $this->elasticSearchRepository->search($categoryId, [
'page' => $currentPage,
'limit' => $limit,
'sort' => $sortOptions['sort'],
'order' => $sortOptions['order'],
]);
$query = $this->productFlatRepository->with([
'images',
'videos',
'product.attribute_values',
'product.price_indices',
'product.inventory_indices',
'product.reviews',
])->scopeQuery(function ($query) use ($indices) {
$qb = $query->distinct()
->whereIn('product_flat.product_id', $indices['ids'])
->where('product_flat.channel', core()->getRequestedChannelCode())
->where('product_flat.locale', core()->getRequestedLocaleCode());
#Sort collection
$qb->orderBy(DB::raw('FIELD(product_id, ' . implode(',', $indices['ids']) . ')'));
return $qb;
});
$items = $indices['total'] ? $query->get() : [];
$currentPage = Paginator::resolveCurrentPage('page');
$limit = $this->getPerPageLimit($params);
$results = new LengthAwarePaginator($items, $indices['total'], $limit, $currentPage, [
'path' => request()->url(),
'query' => request()->query(),
]
);
return $results;
}
/**
* Products to show per page
*
* @param array $params
* @return integer
*/
public function getPerPageLimit($params)
{
$limit = $params['limit'] ?? 9;
if (core()->getConfigData('catalog.products.storefront.products_per_page')) {
$pages = explode(',', core()->getConfigData('catalog.products.storefront.products_per_page'));
$limit = $params['limit'] ?? current($pages);
} }
$attribute = $this->attributeRepository->findOneByField('code', $sort); return $limit;
}
if ($attribute) { /**
if ($attribute->code === 'price') { * Products to show per page
$query->orderBy('product_price_indices.min_price', $direction); *
} else { * @param array $params
$query->orderBy($attribute->code, $direction); * @return array
} */
} else { public function getSortOptions($params)
/* `created_at` is not an attribute so it will be in else case */ {
$query->orderBy('product_flat.created_at', $direction); $sortOptions = explode('-', core()->getConfigData('catalog.products.storefront.sort_by') ?: 'name-desc');
}
return $query; return [
'sort' => $params['sort'] ?? current($sortOptions),
'order' => $params['order'] ?? end($sortOptions),
];
} }
/** /**
@ -384,65 +472,26 @@ class ProductRepository extends Repository
*/ */
public function searchProductByAttribute($term) public function searchProductByAttribute($term)
{ {
$channel = core()->getRequestedChannelCode(); $results = $this->productFlatRepository
->scopeQuery(function ($query) use ($term) {
$query = $query->distinct()
->addSelect('product_flat.*')
->where('product_flat.channel', core()->getRequestedChannelCode())
->where('product_flat.locale', core()->getRequestedLocaleCode())
->whereNotNull('product_flat.url_key');
$locale = core()->getRequestedLocaleCode(); return $query->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where(function ($subQuery) use ($term) {
$queries = explode('_', $term);
if (config('scout.driver') == 'algolia') { foreach (array_map('trim', $queries) as $value) {
$results = $this->productFlatRepository $subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%')
->getModel()::search('query', function ($searchDriver, string $query, array $options) use ($term, $channel, $locale) { ->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%');
$queries = explode('_', $term); }
})
$options['similarQuery'] = array_map('trim', $queries); ->orderBy('product_id', 'desc');
})->paginate(16);
$searchDriver->setSettings([
'attributesForFaceting' => [
'searchable(locale)',
'searchable(channel)',
],
]);
$options['facetFilters'] = ['locale:' . $locale, 'channel:' . $channel];
return $searchDriver->search($query, $options);
})
->where('status', 1)
->where('visible_individually', 1)
->orderBy('product_id', 'desc')
->paginate(16);
} elseif (config('scout.driver') == 'elastic') {
$queries = explode('_', $term);
$results = $this->productFlatRepository
->getModel()::search(implode(' OR ', $queries))
->where('status', 1)
->where('visible_individually', 1)
->where('channel', $channel)
->where('locale', $locale)
->orderBy('product_id', 'desc')
->paginate(16);
} else {
$results = $this->productFlatRepository
->scopeQuery(function ($query) use ($term, $channel, $locale) {
$query = $query->distinct()
->addSelect('product_flat.*')
->where('product_flat.channel', $channel)
->where('product_flat.locale', $locale)
->whereNotNull('product_flat.url_key');
return $query->where('product_flat.status', 1)
->where('product_flat.visible_individually', 1)
->where(function ($subQuery) use ($term) {
$queries = explode('_', $term);
foreach (array_map('trim', $queries) as $value) {
$subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%')
->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%');
}
})
->orderBy('product_id', 'desc');
})->paginate(16);
}
return $results; return $results;
} }