Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
eceeb4a
return if there is no item for the audit notification
Godmartinz Nov 20, 2025
e598ef6
adds tests, webhook enable settings
Godmartinz Nov 20, 2025
24d7ae4
added google chat to audit notifications"
Godmartinz Nov 20, 2025
1116da3
add google chat to audit notification test
Godmartinz Nov 20, 2025
cd42760
notes
Godmartinz Nov 20, 2025
12491f2
adjusted Tests
JonasGoseberg Nov 21, 2025
b9933cd
implemented LegacyAdvancedSearch again
JonasGoseberg Nov 21, 2025
fd76410
Merge pull request #105 from imbus/SIT-173-OldAdvancedSearchDoesntWor…
cz-lucas Nov 21, 2025
48270cb
url trim
Godmartinz Nov 21, 2025
33839d7
Re-added the horizontal class
snipe Nov 22, 2025
644ef04
Merge pull request #18226 from Godmartinz/audit-notification-rb-19608
snipe Nov 22, 2025
aa959fb
Added filter form request to validate JSON
snipe Nov 22, 2025
69994e0
Merge pull request #18232 from grokability/check-for-valid-json-on-fi…
snipe Nov 22, 2025
7f76198
Fixed RB-20501 - correctly return error response when license+seat do…
snipe Nov 23, 2025
7a1ccb0
Nicer query
snipe Nov 23, 2025
e288c94
Bump actions/checkout from 5 to 6
dependabot[bot] Nov 24, 2025
3e343fe
Merge pull request #18236 from grokability/dependabot/github_actions/…
snipe Nov 24, 2025
a1c67b5
Fixed user details toggle
snipe Nov 24, 2025
8231407
Updated translations
snipe Nov 24, 2025
c7cb467
Bumped version
snipe Nov 24, 2025
fea2888
Merge remote-tracking branch 'upstream/develop' into rebase-20251124
cz-lucas Nov 24, 2025
d778a24
Merge pull request #108 from imbus/rebase-20251124
cz-lucas Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/Http/Controllers/Api/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ public function index(Request $request, $action = null, $upcoming_status = null)
$filter = json_decode($request->input('filter'), true);
}

if (!isset($filter[0]['field'])){
$filter = array_filter($filter, function ($key) use ($allowed_columns){
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}

$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
Expand Down
233 changes: 232 additions & 1 deletion app/Models/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -1908,9 +1908,19 @@ function ($query) use ($search) {

public function scopeByFilter($query, array $filters)
{
// Check if the filters are in Snipe-IT's original format (key => value)
if ($this->isLegacyFilterFormat($filters)) {
return $this->applyLegacyFilters($query, $filters);
}
return $this->filterService()->searchByFilter($query, $filters);
}

private function isLegacyFilterFormat($filters)
{
// Snipe-IT filters are simple key/value (not arrays with 'field')
return !isset($filters[0]['field']);
}

/**
* Query builder scope to order on model
*
Expand Down Expand Up @@ -2159,6 +2169,227 @@ public function scopeByDepreciationId($query, $search)
* @return \Illuminate\Database\Eloquent\Builder Modified query builder
*/

public function applyLegacyFilters($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $key => $search_val) {

$fieldname = str_replace('custom_fields.', '', $key);

if ($fieldname == 'asset_tag') {
$query->where('assets.asset_tag', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'name') {
$query->where('assets.name', 'LIKE', '%'.$search_val.'%');
}


if ($fieldname =='serial') {
$query->where('assets.serial', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'purchase_date') {
$query->where('assets.purchase_date', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'purchase_cost') {
$query->where('assets.purchase_cost', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'notes') {
$query->where('assets.notes', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'order_number') {
$query->where('assets.order_number', 'LIKE', '%'.$search_val.'%');
}

if ($fieldname == 'status_label') {
$query->whereHas(
'assetstatus', function ($query) use ($search_val) {
$query->where('status_labels.name', 'LIKE', '%'.$search_val.'%');
}
);
}

if ($fieldname == 'location') {
$query->whereHas(
'location', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
);
}

if ($fieldname == 'rtd_location') {
$query->whereHas(
'defaultLoc', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
);
}

}
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.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.'%');
}
);
}
);
}


if ($fieldname == 'manufacturer') {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->whereHas(
'manufacturer', function ($query) use ($search_val) {
$query->where(
function ($query) use ($search_val) {
$query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
);
}
);
}

if ($fieldname == 'category') {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->whereHas(
'category', function ($query) use ($search_val) {
$query->where(
function ($query) use ($search_val) {
$query->where('categories.name', 'LIKE', '%'.$search_val.'%')
->orWhere('models.name', 'LIKE', '%'.$search_val.'%')
->orWhere('models.model_number', 'LIKE', '%'.$search_val.'%');
}
);
}
);
}
);
}

if ($fieldname == 'model') {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.name', 'LIKE', '%'.$search_val.'%');
}
);
}


if ($fieldname == 'model_number') {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.model_number', 'LIKE', '%'.$search_val.'%');
}
);
}


if ($fieldname == 'company') {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}

if ($fieldname == 'supplier') {
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}

if ($fieldname == 'status_label') {
$query->whereHas(
'assetstatus', function ($query) use ($search_val) {
$query->where('status_labels.name', 'LIKE', '%'.$search_val.'%');
}
);
}

if ($fieldname == 'jobtitle') {
$query->where(function ($query) use ($search_val) {
if (is_array($search_val)) {
$query->whereHasMorph(
'assignedTo',
[User::class],
function ($query) use ($search_val) {
$query->whereIn('users.jobtitle', $search_val);
}
);
} else {
$query->whereHasMorph(
'assignedTo',
[User::class],
function ($query) use ($search_val) {
$query->where(function ($query) use ($search_val) {
$query->where('users.jobtitle', 'LIKE', '%' . $search_val . '%');
});
}
);
}
});
}


/**
* THIS CLUNKY BIT IS VERY IMPORTANT
*
* Although inelegant, this section matters a lot when querying against fields that do not
* exist on the asset table. There's probably a better way to do this moving forward, for
* example using the Schema:: methods to determine whether or not a column actually exists,
* or even just using the $searchableRelations variable earlier in this file.
*
* In short, this set of statements tells the query builder to ONLY query against an
* actual field that's being passed if it doesn't meet known relational fields. This
* allows us to query custom fields directly in the assets table
* (regardless of their name) and *skip* any fields that we already know can only be
* searched through relational searches that we do earlier in this method.
*
* For example, we do not store "location" as a field on the assets table, we store
* that relationship through location_id on the assets table, therefore querying
* assets.location would fail, as that field doesn't exist -- plus we're already searching
* against those relationships earlier in this method.
*
* - snipe
*/

if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
&& ($fieldname!='status_label') && ($fieldname!='assigned_to') && ($fieldname!='model') && ($fieldname!='jobtitle') && ($fieldname!='company') && ($fieldname!='manufacturer')
) {
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
}
}
}
);
}
}
2 changes: 1 addition & 1 deletion tests/Feature/AssetQuery/CombinedQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ public function testFilterAssetNoFiltersReturnsAll()
$assetC = Asset::factory()->create();

// No filters applied
$filter = [[]];
$filter = [];
$results = Asset::query()->byFilter($filter)->get();

$this->assertCount(3, $results);
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/AssetQuery/CustomFieldQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function testFilterWithEmptyArrayLeavesResultsUnchanged()
$a = Asset::factory()->create(['custom_text' => 'A']);
$b = Asset::factory()->create(['custom_text' => 'B']);

$filter = [[]];
$filter = [];

$results = Asset::query()->byFilter($filter)->get();

Expand Down
88 changes: 88 additions & 0 deletions tests/Feature/AssetQuery/LegacyFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
namespace Tests\Feature\AssetQuery;

use App\Models\Asset;
use App\Models\Company;
use Tests\TestCase;


class LegacyFilterTest extends TestCase
{
public function testFilterAssetsByCompanyId()
{
$companyA = Company::factory()->create();
$companyB = Company::factory()->create();

$assetA = Asset::factory()->create(['company_id' => $companyA->id]);
$assetB = Asset::factory()->create(['company_id' => $companyB->id]);

$filter = [
'company_id' => $companyA->id
];

$results = Asset::query()->byFilter($filter)->get();

$this->assertCount(1, $results);
$this->assertTrue($results->contains($assetA));
}

public function testFilterAssetsByAssetTag()
{
$assetA = Asset::factory()->create(['asset_tag' => 'A1']);
$assetB = Asset::factory()->create(['asset_tag' => 'B1']);

$filter = [
'asset_tag' => 'A1'
];

$results = Asset::query()->byFilter($filter)->get();

$this->assertCount(1, $results);
$this->assertTrue($results->contains($assetA));
}

public function testFilterAssetsByCompanyAndAssetTag()
{
$companyA = Company::factory()->create();
$companyB = Company::factory()->create();

$assetA = Asset::factory()->create([
'company_id' => $companyA->id,
'asset_tag' => 'X1'
]);

$assetB = Asset::factory()->create([
'company_id' => $companyA->id,
'asset_tag' => 'Y1'
]);

$assetC = Asset::factory()->create([
'company_id' => $companyB->id,
'asset_tag' => 'X1'
]);

$filter = [
'company_id' => $companyA->id,
'asset_tag' => 'X1'
];

$results = Asset::query()->byFilter($filter)->get();

$this->assertCount(1, $results);
$this->assertTrue($results->contains($assetA));
}

public function testReturnsAllAssetsWhenFilterIsEmpty()
{
$assetA = Asset::factory()->create();
$assetB = Asset::factory()->create();

$filter = [];

$results = Asset::query()->byFilter($filter)->get();

$this->assertCount(2, $results);
$this->assertTrue($results->contains($assetA));
$this->assertTrue($results->contains($assetB));
}
}