From 457d961e7b9e1faff812fc9d9d6cc590b3759baa Mon Sep 17 00:00:00 2001 From: pxpm Date: Tue, 26 Aug 2025 15:48:20 +0100 Subject: [PATCH 1/4] wip --- src/app/Library/CrudPanel/Traits/Search.php | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/Library/CrudPanel/Traits/Search.php b/src/app/Library/CrudPanel/Traits/Search.php index 92fe3c62c4..953275d042 100644 --- a/src/app/Library/CrudPanel/Traits/Search.php +++ b/src/app/Library/CrudPanel/Traits/Search.php @@ -68,7 +68,19 @@ public function applySearchLogicForColumn($query, $column, $searchTerm) case 'email': case 'text': case 'textarea': - $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name']), $searchOperator, '%'.$searchTerm.'%'); + case 'url': + case 'summernote': + case 'wysiwyg': + // Check if the column is translatable + if (method_exists($this->model, 'translationEnabled') && + $this->model->translationEnabled() && + $this->model->isTranslatableAttribute($column['name']) && + $this->isJsonColumnType($column['name']) + ) { + $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name'].'->'.app()->getLocale()), $searchOperator, '%'.$searchTerm.'%'); + } else { + $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name']), $searchOperator, '%'.$searchTerm.'%'); + } break; case 'date': @@ -79,7 +91,16 @@ public function applySearchLogicForColumn($query, $column, $searchTerm) break; } - $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name']), Carbon::parse($searchTerm)); + // Check if the column is translatable + if (method_exists($this->model, 'translationEnabled') && + $this->model->translationEnabled() && + $this->model->isTranslatableAttribute($column['name']) && + $this->isJsonColumnType($column['name']) + ) { + $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name'].'->'.app()->getLocale()), Carbon::parse($searchTerm)); + } else { + $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name']), Carbon::parse($searchTerm)); + } break; case 'select': From ce4b20b74f2316df3a2e20174a546a3e4de629fb Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 3 Sep 2025 10:08:24 +0100 Subject: [PATCH 2/4] wip --- src/app/Library/CrudPanel/Traits/Search.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/Library/CrudPanel/Traits/Search.php b/src/app/Library/CrudPanel/Traits/Search.php index 953275d042..9ec4bea6b7 100644 --- a/src/app/Library/CrudPanel/Traits/Search.php +++ b/src/app/Library/CrudPanel/Traits/Search.php @@ -70,7 +70,6 @@ public function applySearchLogicForColumn($query, $column, $searchTerm) case 'textarea': case 'url': case 'summernote': - case 'wysiwyg': // Check if the column is translatable if (method_exists($this->model, 'translationEnabled') && $this->model->translationEnabled() && From 958ddd112b7cf98018ced1bd2f82a61853629b52 Mon Sep 17 00:00:00 2001 From: pxpm Date: Wed, 3 Sep 2025 10:26:31 +0100 Subject: [PATCH 3/4] wip --- src/app/Library/CrudPanel/Traits/Search.php | 143 +++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/src/app/Library/CrudPanel/Traits/Search.php b/src/app/Library/CrudPanel/Traits/Search.php index 9ec4bea6b7..a1f336424d 100644 --- a/src/app/Library/CrudPanel/Traits/Search.php +++ b/src/app/Library/CrudPanel/Traits/Search.php @@ -76,7 +76,7 @@ public function applySearchLogicForColumn($query, $column, $searchTerm) $this->model->isTranslatableAttribute($column['name']) && $this->isJsonColumnType($column['name']) ) { - $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name'].'->'.app()->getLocale()), $searchOperator, '%'.$searchTerm.'%'); + $this->applyTranslatableSearch($query, $column['name'], $searchTerm, $searchOperator); } else { $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name']), $searchOperator, '%'.$searchTerm.'%'); } @@ -96,7 +96,7 @@ public function applySearchLogicForColumn($query, $column, $searchTerm) $this->model->isTranslatableAttribute($column['name']) && $this->isJsonColumnType($column['name']) ) { - $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name'].'->'.app()->getLocale()), Carbon::parse($searchTerm)); + $this->applyTranslatableDateSearch($query, $column['name'], $searchTerm); } else { $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name']), Carbon::parse($searchTerm)); } @@ -439,6 +439,145 @@ public function getColumnWithTableNamePrefixed($query, $column) return $query->getModel()->getTable().'.'.$column; } + /** + * Apply case-insensitive search for translatable attributes. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $searchColumn + * @param string $searchTerm + * @param string $searchOperator + * @return void + */ + private function applyTranslatableSearch($query, $searchColumn, $searchTerm, $searchOperator) + { + $currentLocale = app()->getLocale(); + $fallbackLocale = config('app.fallback_locale'); + $availableLocales = array_keys(config('backpack.crud.locales', [])); + + $searchLocales = array_unique(array_filter([ + $currentLocale, + $fallbackLocale, + ...$availableLocales + ])); + + $columnType = $this->model->getColumnType($searchColumn); + $isJsonColumn = $this->isJsonColumnType($searchColumn); + + $query->orWhere(function ($subQuery) use ($searchColumn, $searchTerm, $searchOperator, $searchLocales, $isJsonColumn) { + $this->applyLocaleSearchConditions($subQuery, $searchColumn, $searchTerm, $searchOperator, $searchLocales, $isJsonColumn); + }); + } + + /** + * Apply date search for translatable attributes. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $searchColumn + * @param string $searchTerm + * @return void + */ + private function applyTranslatableDateSearch($query, $searchColumn, $searchTerm) + { + $currentLocale = app()->getLocale(); + $fallbackLocale = config('app.fallback_locale'); + $availableLocales = array_keys(config('backpack.crud.locales', [])); + + $searchLocales = array_unique(array_filter([ + $currentLocale, + $fallbackLocale, + ...$availableLocales + ])); + + $isJsonColumn = $this->isJsonColumnType($searchColumn); + $parsedDate = Carbon::parse($searchTerm); + + $query->orWhere(function ($subQuery) use ($searchColumn, $parsedDate, $searchLocales, $isJsonColumn) { + $isFirst = true; + foreach ($searchLocales as $locale) { + $localeOperation = $isFirst ? 'where' : 'orWhere'; + $isFirst = false; + + if ($isJsonColumn) { + $subQuery->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $parsedDate) { + $localeQuery->whereNotNull("{$searchColumn}->{$locale}") + ->whereDate("{$searchColumn}->{$locale}", $parsedDate); + }); + } else { + // For non-JSON columns storing date as string in serialized data + $subQuery->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $parsedDate) { + $dateString = $parsedDate->format('Y-m-d'); + $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"' . $dateString . '%"%']) + ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:' . strlen($locale) . ':"' . $locale . '"%' . $dateString . '%']); + }); + } + } + }); + } + + /** + * Apply locale-specific search conditions to a query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $searchColumn + * @param string $searchTerm + * @param string $searchOperator + * @param array $searchLocales + * @param bool $isJsonColumn + * @return void + */ + private function applyLocaleSearchConditions($query, $searchColumn, $searchTerm, $searchOperator, $searchLocales, $isJsonColumn) + { + $isFirst = true; + foreach ($searchLocales as $locale) { + $localeOperation = $isFirst ? 'where' : 'orWhere'; + $isFirst = false; + + if ($searchOperator === 'LIKE' || strtolower($searchOperator) === 'like') { + $query->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $searchTerm, $isJsonColumn) { + $this->applyLocaleSearch($localeQuery, $searchColumn, $locale, $searchTerm, $isJsonColumn); + }); + } else { + $query->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $searchTerm, $searchOperator, $isJsonColumn) { + if ($isJsonColumn) { + $localeQuery->whereNotNull("{$searchColumn}->{$locale}") + ->where("{$searchColumn}->{$locale}", $searchOperator, $searchTerm); + } else { + // For non-JSON columns, we need to search within the serialized/JSON string + $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"' . $searchTerm . '"%']); + } + }); + } + } + } + + /** + * Apply case-insensitive locale-specific search based on column type. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $searchColumn + * @param string $locale + * @param string $searchTerm + * @param bool $isJsonColumn + * @return void + */ + private function applyLocaleSearch($query, $searchColumn, $locale, $searchTerm, $isJsonColumn) + { + if ($isJsonColumn) { + // Use proper JSON functions for JSON columns with case-insensitive search + $query->whereRaw("JSON_EXTRACT({$searchColumn}, '$.{$locale}') IS NOT NULL") + ->whereRaw("LOWER(JSON_UNQUOTE(JSON_EXTRACT({$searchColumn}, '$.{$locale}'))) LIKE LOWER(?)", ['%' . $searchTerm . '%']); + } else { + // For text columns storing JSON as string, search within the serialized data + // This handles both PHP serialized arrays and JSON strings with case-insensitive search + $query->where(function ($subQuery) use ($searchColumn, $locale, $searchTerm) { + // Search for JSON format: "locale":"value" (case-insensitive) + $subQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"%' . $searchTerm . '%"%']) + // Also search for PHP serialized format (case-insensitive) + ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:' . strlen($locale) . ':"' . $locale . '"%' . $searchTerm . '%']); + }); + } + } + private function isJsonColumnType(string $columnName) { return $this->model->getDbTableSchema()->getColumnType($columnName) === 'json'; From 6c0f5452831db9392e7cb8338c2df57b9a49e24a Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 3 Sep 2025 09:26:50 +0000 Subject: [PATCH 4/4] Apply fixes from StyleCI [ci skip] [skip ci] --- src/app/Library/CrudPanel/Traits/Search.php | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/Library/CrudPanel/Traits/Search.php b/src/app/Library/CrudPanel/Traits/Search.php index a1f336424d..0b0a7d4f00 100644 --- a/src/app/Library/CrudPanel/Traits/Search.php +++ b/src/app/Library/CrudPanel/Traits/Search.php @@ -453,16 +453,16 @@ private function applyTranslatableSearch($query, $searchColumn, $searchTerm, $se $currentLocale = app()->getLocale(); $fallbackLocale = config('app.fallback_locale'); $availableLocales = array_keys(config('backpack.crud.locales', [])); - + $searchLocales = array_unique(array_filter([ $currentLocale, $fallbackLocale, - ...$availableLocales + ...$availableLocales, ])); $columnType = $this->model->getColumnType($searchColumn); $isJsonColumn = $this->isJsonColumnType($searchColumn); - + $query->orWhere(function ($subQuery) use ($searchColumn, $searchTerm, $searchOperator, $searchLocales, $isJsonColumn) { $this->applyLocaleSearchConditions($subQuery, $searchColumn, $searchTerm, $searchOperator, $searchLocales, $isJsonColumn); }); @@ -481,22 +481,22 @@ private function applyTranslatableDateSearch($query, $searchColumn, $searchTerm) $currentLocale = app()->getLocale(); $fallbackLocale = config('app.fallback_locale'); $availableLocales = array_keys(config('backpack.crud.locales', [])); - + $searchLocales = array_unique(array_filter([ $currentLocale, $fallbackLocale, - ...$availableLocales + ...$availableLocales, ])); $isJsonColumn = $this->isJsonColumnType($searchColumn); $parsedDate = Carbon::parse($searchTerm); - + $query->orWhere(function ($subQuery) use ($searchColumn, $parsedDate, $searchLocales, $isJsonColumn) { $isFirst = true; foreach ($searchLocales as $locale) { $localeOperation = $isFirst ? 'where' : 'orWhere'; $isFirst = false; - + if ($isJsonColumn) { $subQuery->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $parsedDate) { $localeQuery->whereNotNull("{$searchColumn}->{$locale}") @@ -506,8 +506,8 @@ private function applyTranslatableDateSearch($query, $searchColumn, $searchTerm) // For non-JSON columns storing date as string in serialized data $subQuery->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $parsedDate) { $dateString = $parsedDate->format('Y-m-d'); - $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"' . $dateString . '%"%']) - ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:' . strlen($locale) . ':"' . $locale . '"%' . $dateString . '%']); + $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"'.$locale.'":"'.$dateString.'%"%']) + ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:'.strlen($locale).':"'.$locale.'"%'.$dateString.'%']); }); } } @@ -531,7 +531,7 @@ private function applyLocaleSearchConditions($query, $searchColumn, $searchTerm, foreach ($searchLocales as $locale) { $localeOperation = $isFirst ? 'where' : 'orWhere'; $isFirst = false; - + if ($searchOperator === 'LIKE' || strtolower($searchOperator) === 'like') { $query->$localeOperation(function ($localeQuery) use ($searchColumn, $locale, $searchTerm, $isJsonColumn) { $this->applyLocaleSearch($localeQuery, $searchColumn, $locale, $searchTerm, $isJsonColumn); @@ -543,7 +543,7 @@ private function applyLocaleSearchConditions($query, $searchColumn, $searchTerm, ->where("{$searchColumn}->{$locale}", $searchOperator, $searchTerm); } else { // For non-JSON columns, we need to search within the serialized/JSON string - $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"' . $searchTerm . '"%']); + $localeQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"'.$locale.'":"'.$searchTerm.'"%']); } }); } @@ -565,15 +565,15 @@ private function applyLocaleSearch($query, $searchColumn, $locale, $searchTerm, if ($isJsonColumn) { // Use proper JSON functions for JSON columns with case-insensitive search $query->whereRaw("JSON_EXTRACT({$searchColumn}, '$.{$locale}') IS NOT NULL") - ->whereRaw("LOWER(JSON_UNQUOTE(JSON_EXTRACT({$searchColumn}, '$.{$locale}'))) LIKE LOWER(?)", ['%' . $searchTerm . '%']); + ->whereRaw("LOWER(JSON_UNQUOTE(JSON_EXTRACT({$searchColumn}, '$.{$locale}'))) LIKE LOWER(?)", ['%'.$searchTerm.'%']); } else { // For text columns storing JSON as string, search within the serialized data // This handles both PHP serialized arrays and JSON strings with case-insensitive search $query->where(function ($subQuery) use ($searchColumn, $locale, $searchTerm) { // Search for JSON format: "locale":"value" (case-insensitive) - $subQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"' . $locale . '":"%' . $searchTerm . '%"%']) + $subQuery->whereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%"'.$locale.'":"%'.$searchTerm.'%"%']) // Also search for PHP serialized format (case-insensitive) - ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:' . strlen($locale) . ':"' . $locale . '"%' . $searchTerm . '%']); + ->orWhereRaw("LOWER({$searchColumn}) LIKE LOWER(?)", ['%s:'.strlen($locale).':"'.$locale.'"%'.$searchTerm.'%']); }); } }