diff --git a/migrations/2025_06_12_112027_add_is_searchable_to_custom_fields_table.php b/migrations/2025_06_12_112027_add_is_searchable_to_custom_fields_table.php new file mode 100644 index 0000000..ead4f07 --- /dev/null +++ b/migrations/2025_06_12_112027_add_is_searchable_to_custom_fields_table.php @@ -0,0 +1,32 @@ +boolean('is_searchable')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fields', function (Blueprint $table) { + $table->dropColumn('is_searchable'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 89044bb..bcb51bb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,6 +47,7 @@ Route::match(['put', 'patch'], 'remote/{remote_type}', [RemoteCustomFieldController::class, 'update'])->name('remote.update'); Route::get('remote/{remote_type}/resolve', [RemoteCustomFieldController::class, 'resolve'])->name('remote.resolve'); Route::get('remote/{remote_type}/resolve/{identifier_value}', [RemoteCustomFieldController::class, 'resolveByIdentifierValue'])->name('remote.resolveByIdentifierValue'); + Route::get('remote/{remote_type}/search/{q?}', [RemoteCustomFieldController::class, 'search'])->name('remote.search'); Route::get('selection', [SelectionCustomFieldController::class, 'index'])->name('selection.index'); Route::post('selection/{plain_type}', [SelectionCustomFieldController::class, 'store'])->name('selection.store'); diff --git a/src/App/Http/Controllers/RemoteCustomFieldController.php b/src/App/Http/Controllers/RemoteCustomFieldController.php index 816fce8..875a4f5 100644 --- a/src/App/Http/Controllers/RemoteCustomFieldController.php +++ b/src/App/Http/Controllers/RemoteCustomFieldController.php @@ -11,6 +11,7 @@ use Asseco\CustomFields\App\Http\Requests\RemoteTypeRequest; use Asseco\CustomFields\App\Traits\TransformsOutput; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; @@ -120,7 +121,7 @@ public function resolve(RemoteType $remoteType): JsonResponse */ public function resolveByIdentifierValue(RemoteType $remoteType, string $identifierValue): JsonResponse { - $data = $remoteType->getRemoteData(); + $data = $remoteType->getRemoteData($identifierValue); $data = $remoteType->data_path ? Arr::get($data, $remoteType->data_path) : $data; @@ -130,4 +131,20 @@ public function resolveByIdentifierValue(RemoteType $remoteType, string $identif return response()->json($transformed); } + + public function search(Request $request, RemoteType $remoteType, string $q = ''): JsonResponse + { + // check query parameter + $q = $q ?: $request->input('q'); + if (!empty($q)) { + $data = $remoteType->searchRemoteData($q); + } else { + $data = $remoteType->getRemoteData(); + } + + $data = $remoteType->data_path ? Arr::get($data, $remoteType->data_path) : $data; + $transformed = $this->transform($data, $remoteType->mappings); + + return response()->json($transformed); + } } diff --git a/src/App/Models/CustomField.php b/src/App/Models/CustomField.php index 7137389..2501204 100644 --- a/src/App/Models/CustomField.php +++ b/src/App/Models/CustomField.php @@ -29,6 +29,18 @@ * @method static Builder remote() * @method static Builder selection() * + * @property string $id + * @property string $name + * @property string $label + * @property string $placeholder + * @property string $model + * @property bool $required + * @property bool $hidden + * @property bool $is_searchable + * @property string $group + * @property int $order + * @property string $renderer + * * Class CustomField */ class CustomField extends Model implements CustomFieldContract diff --git a/src/App/Models/RemoteType.php b/src/App/Models/RemoteType.php index 4288199..3837f18 100644 --- a/src/App/Models/RemoteType.php +++ b/src/App/Models/RemoteType.php @@ -29,6 +29,9 @@ class RemoteType extends ParentType implements \Asseco\CustomFields\App\Contract 'mappings' => 'array', ]; + const DEFAULT_IDENTIFIER_PROPERTY = 'id'; + const DEFAULT_SEARCH_QUERY_PARAMETER = 'q'; + protected static function newFactory() { return RemoteTypeFactory::new(); @@ -44,23 +47,55 @@ public function getNameAttribute() return 'remote'; } - public function getRemoteData() + private function fetchData(?string $value = null, bool $search = false) { - $cacheKey = 'remote_custom_field_' . $this->id; + $qParam = $this->identifier_property ?: self::DEFAULT_IDENTIFIER_PROPERTY; + if ($search) { + $qParam = self::DEFAULT_SEARCH_QUERY_PARAMETER; + } + + $body = $this->body; + $url = $this->url; + + if ($value) { + // get by ID + if ($this->method == 'POST') { + empty($body) ? ($body = [$qParam => $value]) : ($body[$qParam] = $value); + } else { + $parsed = parse_url($url); + parse_str($parsed['query'] ?? '', $params); + $params[$qParam] = $value; + $url = $parsed['scheme'] . '://' . $parsed['host']; + if (!empty($parsed['port'])) { + $url .= ':' . $parsed['port']; + } + $url .= $parsed['path'] . '?' . http_build_query($params); + } + } + + return Http::withHeaders($this->getHeaders() ?: []) + ->withBody($body, 'application/json') + ->{$this->method}($url)->throw()->json(); + } + public function getRemoteData(?string $identifierValue = null) + { + $cacheKey = 'remote_custom_field_' . $this->id; if (config('asseco-custom-fields.should_cache_remote') && Cache::has($cacheKey)) { return Cache::get($cacheKey); } - $response = Http::withHeaders($this->getHeaders() ?: []) - ->withBody($this->body, 'application/json') - ->{$this->method}($this->url)->throw()->json(); - + $response = $this->fetchData($identifierValue, false); Cache::put($cacheKey, $response, config('asseco-custom-fields.remote_cache_ttl')); return $response; } + public function searchRemoteData(string $searchString) + { + return $this->fetchData($searchString, true); + } + protected function getHeaders() { return $this->headers; diff --git a/src/App/Traits/TransformsOutput.php b/src/App/Traits/TransformsOutput.php index a92f3d2..2b1aecd 100644 --- a/src/App/Traits/TransformsOutput.php +++ b/src/App/Traits/TransformsOutput.php @@ -28,7 +28,6 @@ protected function transform(array $response, ?array $mappings): array protected function mapSingle(array $mappings, array $item): array { $data = []; - foreach ($mappings as $remoteKey => $localKey) { if (!array_key_exists($remoteKey, $item)) { continue;