From 4ae4af5c1034ce2d70d9bb5652b4bb30b073b7a0 Mon Sep 17 00:00:00 2001 From: snipe Date: Tue, 7 Oct 2025 15:39:09 +0100 Subject: [PATCH 1/3] Fixed #17670 - advanced search on relationships not working --- app/Http/Controllers/Api/AssetsController.php | 17 ++++++++++++++++ app/Models/Asset.php | 20 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index e3bd87bb26..1a34e3f62f 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -116,6 +116,22 @@ class AssetsController extends Controller 'asset_eol_date', 'requestable', 'jobtitle', + // These are *relationships* so we wouldn't normally include them in this array, + // since they would normally create a `column not found` error, + // BUT we account for them in the ordering switch down at the end of this method + // DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW! + 'company', + 'model', + 'location', + 'rtd_location', + 'category', + 'status_label', + 'manufacturer', + 'supplier', + 'jobtitle', + 'assigned_to', + 'created_by', + ]; $all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load @@ -132,6 +148,7 @@ class AssetsController extends Controller $filter = array_filter($filter, function ($key) use ($allowed_columns) { return in_array($key, $allowed_columns); }, ARRAY_FILTER_USE_KEY); + } $assets = Asset::select('assets.*') diff --git a/app/Models/Asset.php b/app/Models/Asset.php index 9ef8cd28c9..70941f62e9 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -1881,16 +1881,32 @@ class Asset extends Depreciable ); } - if ($fieldname =='assigned_to') { + if ($fieldname == 'assigned_to') { $query->whereHasMorph( 'assignedTo', [User::class], function ($query) use ($search_val) { $query->where( function ($query) use ($search_val) { $query->where('users.first_name', 'LIKE', '%'.$search_val.'%') - ->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%'); + ->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%') + ->orWhere('users.username', 'LIKE', '%'.$search_val.'%'); } ); } + )->orWhereHasMorph( + 'assignedTo', [Location::class], function ($query) use ($search_val) { + $query->where('locations.name', 'LIKE', '%'.$search_val.'%'); + } + )->orWhereHasMorph( + 'assignedTo', [Asset::class], function ($query) use ($search_val) { + $query->where( + function ($query) use ($search_val) { + // Don't use the asset table prefix here because it will pull from the original asset, + // not the subselect we're doing here to get the assigned asset + $query->where('name', 'LIKE', '%'.$search_val.'%') + ->orWhere('asset_tag', 'LIKE', '%'.$search_val.'%'); + } + ); + } ); } From ee499c1385bf1764dbf1df907f7708442b9ab96b Mon Sep 17 00:00:00 2001 From: Brady Wetherington Date: Tue, 7 Oct 2025 16:00:16 +0100 Subject: [PATCH 2/3] Fix base64-encoded image files for asset creation; add test --- app/Http/Requests/StoreAssetRequest.php | 1 + app/Http/Traits/ConvertsBase64ToFiles.php | 15 +++++-------- tests/Feature/Assets/Api/StoreAssetTest.php | 25 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 66179ac739..e920f09a00 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -26,6 +26,7 @@ class StoreAssetRequest extends ImageUploadRequest public function prepareForValidation(): void { + parent::prepareForValidation(); // call ImageUploadRequest thing // Guard against users passing in an array for company_id instead of an integer. // If the company_id is not an integer then we simply use what was // provided to be caught by model level validation later. diff --git a/app/Http/Traits/ConvertsBase64ToFiles.php b/app/Http/Traits/ConvertsBase64ToFiles.php index 704610ddbf..4e023da267 100644 --- a/app/Http/Traits/ConvertsBase64ToFiles.php +++ b/app/Http/Traits/ConvertsBase64ToFiles.php @@ -2,6 +2,7 @@ namespace App\Http\Traits; +use Illuminate\Validation\ValidationException; use Illuminate\Http\UploadedFile; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -38,20 +39,13 @@ trait ConvertsBase64ToFiles if (!$base64Contents) { return; } - - // autogenerate filenames - if ($filename == 'auto'){ - $header = explode(';', $base64Contents, 2)[0]; - // Grab the image type from the header while we're at it. - $filename = $key . '.' . substr($header, strpos($header, '/')+1); - } // Generate a temporary path to store the Base64 contents $tempFilePath = tempnam(sys_get_temp_dir(), $filename); - // Store the contents using a stream, or by decoding manually + // Store the contents using a stream, or throw an Error (which doesn't do anything?) if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) { - $source = fopen($base64Contents, 'r'); + $source = fopen($base64Contents, 'r'); // PHP has special processing for "data:" URL's $destination = fopen($tempFilePath, 'w'); stream_copy_to_stream($source, $destination); @@ -59,7 +53,8 @@ trait ConvertsBase64ToFiles fclose($source); fclose($destination); } else { - file_put_contents($tempFilePath, base64_decode($base64Contents, true)); + // TODO - to get a better error message here, can we maybe do something with modifying the errorBag? + throw new ValidationException("Need Base64 URL starting with 'data:'"); // This doesn't actually throw? } $uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true); diff --git a/tests/Feature/Assets/Api/StoreAssetTest.php b/tests/Feature/Assets/Api/StoreAssetTest.php index 67ef19cbb6..29d643e1bb 100644 --- a/tests/Feature/Assets/Api/StoreAssetTest.php +++ b/tests/Feature/Assets/Api/StoreAssetTest.php @@ -11,6 +11,7 @@ use App\Models\Statuslabel; use App\Models\Supplier; use App\Models\User; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Storage; use Illuminate\Testing\Fluent\AssertableJson; use PHPUnit\Framework\Attributes\DataProvider; use Tests\TestCase; @@ -831,4 +832,28 @@ class StoreAssetTest extends TestCase $asset = Asset::findOrFail($response['payload']['id']); $this->assertEquals('This is encrypted field', Crypt::decrypt($asset->{$field->db_column_name()})); } + + public function testBase64AssetImages() + { + $status = Statuslabel::factory()->readyToDeploy()->create(); + $model = AssetModel::factory()->create(); + $superuser = User::factory()->superuser()->create(); + + $response = $this->actingAsForApi($superuser) + ->postJson(route('api.assets.store'), [ + 'model_id' => $model->id, + 'status_id' => $status->id, + 'asset_tag' => '1234', + 'image' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEsAQMAAADXeXeBAAAABlBMVEX+AAD///+KQee0AAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH5QQbCAoNcoiTQAAAACZJREFUaN7twTEBAAAAwqD1T20JT6AAAAAAAAAAAAAAAAAAAICnATvEAAEnf54JAAAAAElFTkSuQmCC' + ]) + ->assertStatusMessageIs('success') + ->assertOk() + ->json(); + + $asset = Asset::findOrFail($response['payload']['id']); + $this->assertEquals($asset->asset_tag, '1234'); + $image_data = Storage::disk('public')->get(app('assets_upload_path') . e($asset->image)); + //$this->assertEquals('3d67fb99a0b6926e350f7b71397525d7a6b936c1', sha1($image_data)); //this doesn't work because the image gets resized - use the resized hash instead + $this->assertEquals('db2e13ba04318c99058ca429d67777322f48566b', sha1($image_data)); + } } From 9a477f227be88d2d5d2e3266415c7b37de4b7086 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 8 Oct 2025 05:09:04 +0100 Subject: [PATCH 3/3] Fixed #18002 - use correct path for audit images to determine validity --- app/Models/Actionlog.php | 4 ++++ resources/views/hardware/view.blade.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 59e7b8244a..3d5820f730 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -493,6 +493,10 @@ class Actionlog extends SnipeModel return 'private_uploads/eula-pdfs/'.$this->filename; } + if ($this->action_type == 'audit') { + return 'private_uploads/audits/'.$this->filename; + } + switch ($this->item_type) { case Accessory::class: return 'private_uploads/accessories/'.$this->filename; diff --git a/resources/views/hardware/view.blade.php b/resources/views/hardware/view.blade.php index fedf92d4b6..59086075be 100755 --- a/resources/views/hardware/view.blade.php +++ b/resources/views/hardware/view.blade.php @@ -1391,7 +1391,7 @@ {{ trans('admin/hardware/table.icon') }} {{ trans('general.date') }} {{ trans('general.created_by') }} - {{ trans('general.file_name') }} + {{ trans('general.file_name') }} {{ trans('general.notes') }} {{ trans('general.download') }} {{ trans('admin/hardware/table.changed')}}