fix(search): match Arabic ye/kaf with Persian letters in admin search
Customer names stored with ي/ك were missed when typing ی/ک; fold both sides of LIKE. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
namespace App\DB\Te;
|
namespace App\DB\Te;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Support\PersianText;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||||
@@ -37,7 +38,11 @@ class QuerySelector
|
|||||||
}
|
}
|
||||||
$cols = explode(',', $conf->cols);
|
$cols = explode(',', $conf->cols);
|
||||||
if (isset($input['query'])) {
|
if (isset($input['query'])) {
|
||||||
$query = $conf->model::where($cols[0], 'like', '%' . $input['query'] . '%');
|
$needle = PersianText::likePattern(strtolower((string) $input['query']));
|
||||||
|
$query = $conf->model::whereRaw(
|
||||||
|
PersianText::sqlFoldExpr('lower(`'.$cols[0].'`)').' like ?',
|
||||||
|
[$needle]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+16
-14
@@ -1,5 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\DB\Te;
|
namespace App\DB\Te;
|
||||||
|
|
||||||
|
use App\Support\PersianText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PhpStorm.
|
* Created by PhpStorm.
|
||||||
* User: AraNote
|
* User: AraNote
|
||||||
@@ -13,7 +16,8 @@ class Search
|
|||||||
|
|
||||||
public static function query(&$query, $cols, $search)
|
public static function query(&$query, $cols, $search)
|
||||||
{
|
{
|
||||||
$query->where(function ($q) use ($cols, $search) {
|
$like = PersianText::likePattern(strtolower((string) $search));
|
||||||
|
$query->where(function ($q) use ($cols, $like) {
|
||||||
$first = true;
|
$first = true;
|
||||||
foreach ($cols as $col) {
|
foreach ($cols as $col) {
|
||||||
if (self::hasJoin($col)) {
|
if (self::hasJoin($col)) {
|
||||||
@@ -25,11 +29,9 @@ class Search
|
|||||||
$whereHas = 'orWhereHas';
|
$whereHas = 'orWhereHas';
|
||||||
}
|
}
|
||||||
$q->$whereHas
|
$q->$whereHas
|
||||||
($exp[0], function ($q) use ($exp, $search) {
|
($exp[0], function ($q) use ($exp, $like) {
|
||||||
return $q->whereHas($exp[1], function ($q2) use ($exp, $search) {
|
return $q->whereHas($exp[1], function ($q2) use ($exp, $like) {
|
||||||
|
return $q2->whereRaw(self::foldedLikeSql($exp[2]), [$like]);
|
||||||
// return $q2->where($exp[2], 'LIKE', '%' . $search . '%');
|
|
||||||
return $q2->whereRaw('lower(`'.$exp[2].'`) like ?', ['%' . strtolower($search) . '%']);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -39,10 +41,8 @@ class Search
|
|||||||
$whereHas1 = 'orWhereHas';
|
$whereHas1 = 'orWhereHas';
|
||||||
}
|
}
|
||||||
$q->$whereHas1
|
$q->$whereHas1
|
||||||
// $q->{($first) ? 'whereHas' : 'orWhereHas'}
|
($exp[0], function ($q) use ($exp, $like) {
|
||||||
($exp[0], function ($q) use ($exp, $search) {
|
return $q->whereRaw(self::foldedLikeSql($exp[1]), [$like]);
|
||||||
return $q->whereRaw('lower(`'.$exp[1].'`) like ?', ['%' . strtolower($search) . '%']);
|
|
||||||
// return $q->where($exp[1], 'LIKE', '%' . $search . '%');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -51,10 +51,7 @@ class Search
|
|||||||
}else{
|
}else{
|
||||||
$where = 'orWhereRaw';
|
$where = 'orWhereRaw';
|
||||||
}
|
}
|
||||||
// return $q->whereRaw('lower(`'.$col.'`) like ?', ['%' . strtolower($search) . '%']);
|
$q->$where(self::foldedLikeSql($col), [$like]);
|
||||||
$q->$where('lower(`'.$col.'`) like ?', ['%' . strtolower($search) . '%']);
|
|
||||||
// $q->{($first) ? 'where' : 'orWhere'}($col, 'LIKE', '%' . $search . '%');
|
|
||||||
// $q->{($first) ? 'whereRaw ' : 'orWhereRaw '}($col, 'LIKE', '%' . $search . '%');
|
|
||||||
}
|
}
|
||||||
if ($first) {
|
if ($first) {
|
||||||
$first = false;
|
$first = false;
|
||||||
@@ -63,6 +60,11 @@ class Search
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function foldedLikeSql(string $column): string
|
||||||
|
{
|
||||||
|
return PersianText::sqlFoldExpr('lower(`'.$column.'`)').' like ?';
|
||||||
|
}
|
||||||
|
|
||||||
public static function hasJoin($col)
|
public static function hasJoin($col)
|
||||||
{
|
{
|
||||||
return sizeof(explode('.', $col)) > 1;
|
return sizeof(explode('.', $col)) > 1;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Models\AppNotificationRecipient;
|
|||||||
use App\Models\Customer;
|
use App\Models\Customer;
|
||||||
use App\Services\AppNotificationAudienceService;
|
use App\Services\AppNotificationAudienceService;
|
||||||
use App\Services\PriceStreamService;
|
use App\Services\PriceStreamService;
|
||||||
|
use App\Support\PersianText;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
@@ -67,7 +68,10 @@ class AppNotificationController extends Controller
|
|||||||
->where('is_archive', false)
|
->where('is_archive', false)
|
||||||
->when($q !== '', function ($query) use ($q) {
|
->when($q !== '', function ($query) use ($q) {
|
||||||
$query->where(function ($inner) use ($q) {
|
$query->where(function ($inner) use ($q) {
|
||||||
$inner->where('name', 'like', '%' . $q . '%')
|
$inner->whereRaw(
|
||||||
|
PersianText::sqlFoldExpr('lower(`name`)').' like ?',
|
||||||
|
[PersianText::likePattern(strtolower($q))]
|
||||||
|
)
|
||||||
->orWhere('mobile', 'like', '%' . $q . '%')
|
->orWhere('mobile', 'like', '%' . $q . '%')
|
||||||
->orWhere('id', $q);
|
->orWhere('id', $q);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\CustomerSpecialPricePackage;
|
|||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Services\ConfigService;
|
use App\Services\ConfigService;
|
||||||
use App\Services\CustomerSpecialPricingService;
|
use App\Services\CustomerSpecialPricingService;
|
||||||
|
use App\Support\PersianText;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
@@ -163,7 +164,10 @@ class CustomerSpecialPricePackageController extends Controller
|
|||||||
})
|
})
|
||||||
->when($q !== '', function ($query) use ($q) {
|
->when($q !== '', function ($query) use ($q) {
|
||||||
$query->where(function ($inner) use ($q) {
|
$query->where(function ($inner) use ($q) {
|
||||||
$inner->where('name', 'like', '%' . $q . '%')
|
$inner->whereRaw(
|
||||||
|
PersianText::sqlFoldExpr('lower(`name`)').' like ?',
|
||||||
|
[PersianText::likePattern(strtolower($q))]
|
||||||
|
)
|
||||||
->orWhere('mobile', 'like', '%' . $q . '%')
|
->orWhere('mobile', 'like', '%' . $q . '%')
|
||||||
->orWhere('id', $q);
|
->orWhere('id', $q);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ی/ک عربی (ي ك ى) را با معادل فارسی یکی میکند تا جستجو و مقایسه قاطی نشود.
|
||||||
|
*/
|
||||||
|
class PersianText
|
||||||
|
{
|
||||||
|
public static function fold(string $value): string
|
||||||
|
{
|
||||||
|
return str_replace(['ي', 'ى', 'ك'], ['ی', 'ی', 'ک'], $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** عبارت SQL روی ستون مورد اعتماد (نه ورودی کاربر). */
|
||||||
|
public static function sqlFoldExpr(string $sqlExpr): string
|
||||||
|
{
|
||||||
|
return "REPLACE(REPLACE(REPLACE({$sqlExpr}, 'ي', 'ی'), 'ى', 'ی'), 'ك', 'ک')";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function likePattern(string $search): string
|
||||||
|
{
|
||||||
|
return '%'.self::fold($search).'%';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user