Route all file requests through FilesApiController/ServeFile

This commit is contained in:
Bernd Bestel
2026-06-16 20:25:51 +02:00
parent d565abc3aa
commit 67db3c5f8a
6 changed files with 68 additions and 90 deletions
+9 -29
View File
@@ -47,45 +47,25 @@ class FilesApiController extends BaseApiController
throw new \Exception('Invalid file group');
}
$fileName = $this->CheckFileName($args['fileName']);
$filePath = $this->GetFilePath($args['group'], $fileName, $request->getQueryParams());
if (file_exists($filePath))
if (str_contains($args['fileName'], '_'))
{
$response = $response->withHeader('Cache-Control', 'max-age=2592000');
$response = $response->withHeader('Content-Type', mime_content_type($filePath));
$response = $response->withHeader('Content-Disposition', 'inline; filename="' . $fileName . '"');
return $response->withBody(new Stream(fopen($filePath, 'rb')));
$fileInfo = explode('_', $args['fileName']);
$fileName = $this->CheckFileName($fileInfo[0]);
$fileNameOutput = $this->CheckFileName($fileInfo[1]);
$filePath = $this->GetFilePath($args['group'], $fileName, $request->getQueryParams());
}
else
{
throw new HttpNotFoundException($request, 'File not found');
$fileName = $this->CheckFileName($args['fileName']);
$fileNameOutput = $fileName;
$filePath = $this->GetFilePath($args['group'], $fileName, $request->getQueryParams());
}
}
catch (\Exception $ex)
{
throw new HttpNotFoundException($request, $ex->getMessage(), $ex);
}
}
public function ShowFile(Request $request, Response $response, array $args)
{
try
{
if (!in_array($args['group'], $this->GetOpenApispec()->components->schemas->FileGroups->enum))
{
throw new \Exception('Invalid file group');
}
$fileInfo = explode('_', $args['fileName']);
$fileName = $this->CheckFileName($fileInfo[1]);
$filePath = $this->GetFilePath($args['group'], base64_decode($fileInfo[0]), $request->getQueryParams());
if (file_exists($filePath))
{
$response = $response->withHeader('Cache-Control', 'max-age=2592000');
$response = $response->withHeader('Content-Type', mime_content_type($filePath));
$response = $response->withHeader('Content-Disposition', 'inline; filename="' . $fileName . '"');
$response = $response->withHeader('Content-Disposition', 'inline; filename="' . $fileNameOutput . '"');
return $response->withBody(new Stream(fopen($filePath, 'rb')));
}
else
+39 -39
View File
@@ -1,6 +1,6 @@
Grocy.Components.UserfieldsForm = {};
Grocy.Components.UserfieldsForm.Save = function(success, error)
Grocy.Components.UserfieldsForm.Save = function (success, error)
{
if (!$("#userfields-form").length)
{
@@ -24,7 +24,7 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
return;
}
editedUserfieldInputs.each(function(index, item)
editedUserfieldInputs.each(function (index, item)
{
var jsonData = {};
var input = $(this);
@@ -68,22 +68,22 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
}
Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, jsonData,
function(result)
function (result)
{
if (typeof newFile !== 'undefined' && typeof oldFile !== 'undefined') // Delete and Upload
{
Grocy.Api.DeleteFile(oldFile, 'userfiles',
function(result)
function (result)
{
Grocy.Api.UploadFile(input[0].files[0], 'userfiles', newFile,
function(result2)
function (result2)
{
if (success && index === editedUserfieldInputs.length - 1) // Last item
{
success();
}
},
function(xhr)
function (xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
if (error && index === editedUserfieldInputs.length - 1) // Last item
@@ -93,7 +93,7 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
}
);
},
function(xhr)
function (xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
if (error && index === editedUserfieldInputs.length - 1) // Last item
@@ -106,14 +106,14 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
else if (typeof newFile !== 'undefined') // Upload only
{
Grocy.Api.UploadFile(input[0].files[0], 'userfiles', newFile,
function(result2)
function (result2)
{
if (success && index === editedUserfieldInputs.length - 1) // Last item
{
success();
}
},
function(xhr)
function (xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
if (error && index === editedUserfieldInputs.length - 1) // Last item
@@ -126,14 +126,14 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
else if (typeof oldFile !== 'undefined') // Delete only
{
Grocy.Api.DeleteFile(oldFile, 'userfiles',
function(result)
function (result)
{
if (success && index === editedUserfieldInputs.length - 1) // Last item
{
success();
}
},
function(xhr)
function (xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
if (error && index === editedUserfieldInputs.length - 1) // Last item
@@ -151,7 +151,7 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
}
}
},
function(xhr)
function (xhr)
{
if (error && index === editedUserfieldInputs.length - 1) // Last item
{
@@ -162,7 +162,7 @@ Grocy.Components.UserfieldsForm.Save = function(success, error)
});
}
Grocy.Components.UserfieldsForm.Load = function()
Grocy.Components.UserfieldsForm.Load = function ()
{
if (!$("#userfields-form").length)
{
@@ -174,9 +174,9 @@ Grocy.Components.UserfieldsForm.Load = function()
// Init fields by configured default values
Grocy.Api.Get("objects/userfields?query[]=entity=" + $("#userfields-form").data("entity"),
function(result)
function (result)
{
$.each(result, function(key, userfield)
$.each(result, function (key, userfield)
{
var input = $(".userfield-input[data-userfield-name='" + userfield.name + "']");
@@ -191,19 +191,19 @@ Grocy.Components.UserfieldsForm.Load = function()
else if (userfield.type == "checkbox" && userfield.input_required == 1)
{
input.prop("indeterminate", true);
input.on("change", function()
input.on("change", function ()
{
input.removeAttr("required");
});
}
});
$("form").each(function()
$("form").each(function ()
{
Grocy.FrontendHelpers.ValidateForm(this.id);
});
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
@@ -214,9 +214,9 @@ Grocy.Components.UserfieldsForm.Load = function()
// Load object field values
Grocy.Api.Get('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId,
function(result)
function (result)
{
$.each(result, function(key, value)
$.each(result, function (key, value)
{
var input = $(".userfield-input[data-userfield-name='" + key + "']");
@@ -248,12 +248,12 @@ Grocy.Components.UserfieldsForm.Load = function()
var formGroup = input.parent().parent().parent();
formGroup.find("label.custom-file-label").text(fileName);
formGroup.find(".userfield-file-show").attr('href', U('/files/userfiles/' + value));
formGroup.find(".userfield-file-show").attr('href', U('/api/files/userfiles/' + value));
formGroup.find('.userfield-file-show').removeClass('d-none');
formGroup.find('img.userfield-current-file').attr('src', U('/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
formGroup.find('img.userfield-current-file').attr('src', U('/api/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
formGroup.find('.userfield-file-delete').click(
function()
function ()
{
formGroup.find("label.custom-file-label").text(__t("No file selected"));
formGroup.find(".userfield-file-show").addClass('d-none');
@@ -262,7 +262,7 @@ Grocy.Components.UserfieldsForm.Load = function()
}
);
input.on("change", function(e)
input.on("change", function (e)
{
formGroup.find(".userfield-file-show").addClass('d-none');
});
@@ -287,12 +287,12 @@ Grocy.Components.UserfieldsForm.Load = function()
}
});
$("form").each(function()
$("form").each(function ()
{
Grocy.FrontendHelpers.ValidateForm(this.id);
});
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
@@ -300,7 +300,7 @@ Grocy.Components.UserfieldsForm.Load = function()
}
}
Grocy.Components.UserfieldsForm.Clear = function()
Grocy.Components.UserfieldsForm.Clear = function ()
{
if (!$("#userfields-form").length)
{
@@ -308,9 +308,9 @@ Grocy.Components.UserfieldsForm.Clear = function()
}
Grocy.Api.Get('objects/userfields?query[]=entity=' + $("#userfields-form").data("entity"),
function(result)
function (result)
{
$.each(result, function(key, userfield)
$.each(result, function (key, userfield)
{
var input = $(".userfield-input[data-userfield-name='" + userfield.name + "']");
@@ -328,13 +328,13 @@ Grocy.Components.UserfieldsForm.Clear = function()
var formGroup = input.parent().parent().parent();
formGroup.find("label.custom-file-label").text("");
formGroup.find(".userfield-file-show").attr('href', U('/files/userfiles/' + value));
formGroup.find(".userfield-file-show").attr('href', U('/api/files/userfiles/' + value));
formGroup.find('.userfield-file-show').removeClass('d-none');
formGroup.find('img.userfield-current-file')
.attr('src', U('/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
.attr('src', U('/api/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
formGroup.find('.userfield-file-delete').click(
function()
function ()
{
formGroup.find("label.custom-file-label").text(__t("No file selected"));
formGroup.find(".userfield-file-show").addClass('d-none');
@@ -342,7 +342,7 @@ Grocy.Components.UserfieldsForm.Clear = function()
}
);
input.on("change", function(e)
input.on("change", function (e)
{
formGroup.find(".userfield-file-show").addClass('d-none');
});
@@ -361,19 +361,19 @@ Grocy.Components.UserfieldsForm.Clear = function()
}
});
$("form").each(function()
$("form").each(function ()
{
Grocy.FrontendHelpers.ValidateForm(this.id);
});
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
);
}
$(".userfield-link").keyup(function(e)
$(".userfield-link").keyup(function (e)
{
var formRow = $(this).parent().parent();
var title = formRow.find(".userfield-link-title").val();
@@ -387,15 +387,15 @@ $(".userfield-link").keyup(function(e)
formRow.find(".userfield-input").val(JSON.stringify(value)).addClass("is-dirty");
});
$(".userfield-input").change(function(e)
$(".userfield-input").change(function (e)
{
$("form").each(function()
$("form").each(function ()
{
Grocy.FrontendHelpers.ValidateForm(this.id);
});
});
$(".userfield-input.selectpicker").on("changed.bs.select", function()
$(".userfield-input.selectpicker").on("changed.bs.select", function ()
{
$(this).addClass("is-dirty");
});
+16 -16
View File
@@ -8,7 +8,7 @@
style: 'single',
selector: 'tr td:not(:first-child)'
},
'initComplete': function()
'initComplete': function ()
{
this.api().row({ order: 'current' }, 0).select();
DisplayEquipment($('#equipment-table tbody tr:eq(0)').data("equipment-id"));
@@ -17,7 +17,7 @@
$('#equipment-table tbody').removeClass("d-none");
equipmentTable.columns.adjust().draw();
equipmentTable.on('select', function(e, dt, type, indexes)
equipmentTable.on('select', function (e, dt, type, indexes)
{
if (type === 'row')
{
@@ -29,7 +29,7 @@ equipmentTable.on('select', function(e, dt, type, indexes)
function DisplayEquipment(id)
{
Grocy.Api.Get('objects/equipment/' + id,
function(equipmentItem)
function (equipmentItem)
{
$(".selected-equipment-name").text(equipmentItem.name);
$("#description-tab-content").html(equipmentItem.description);
@@ -59,14 +59,14 @@ function DisplayEquipment(id)
if (equipmentItem.userfields != null)
{
Grocy.Api.Get('objects/userfields?query[]=entity=equipment&query[]=type=file',
function(result)
function (result)
{
$.each(result, function(key, userfield)
$.each(result, function (key, userfield)
{
var userfieldFile = equipmentItem.userfields[userfield.name];
if (userfieldFile)
{
var pdfUrl = U('/files/userfiles/' + userfieldFile);
var pdfUrl = U('/api/files/userfiles/' + userfieldFile);
$("#file-userfield-" + userfield.name + "-embed").attr("src", pdfUrl);
$("#file-userfield-" + userfield.name + "-download-button").attr("href", pdfUrl);
$("#file-userfield-" + userfield.name + "-embed").removeClass("d-none");
@@ -82,21 +82,21 @@ function DisplayEquipment(id)
}
});
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
);
}
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
);
}
$("#search").on("keyup", Delay(function()
$("#search").on("keyup", Delay(function ()
{
var value = $(this).val();
if (value === "all")
@@ -107,13 +107,13 @@ $("#search").on("keyup", Delay(function()
equipmentTable.search(value).draw();
}, Grocy.FormFocusDelay));
$("#clear-filter-button").on("click", function()
$("#clear-filter-button").on("click", function ()
{
$("#search").val("");
equipmentTable.search("").draw();
});
$(document).on('click', '.equipment-delete-button', function(e)
$(document).on('click', '.equipment-delete-button', function (e)
{
var objectName = $(e.currentTarget).attr('data-equipment-name');
var objectId = $(e.currentTarget).attr('data-equipment-id');
@@ -131,16 +131,16 @@ $(document).on('click', '.equipment-delete-button', function(e)
className: 'btn-danger'
}
},
callback: function(result)
callback: function (result)
{
if (result === true)
{
Grocy.Api.Delete('objects/equipment/' + objectId, {},
function(result)
function (result)
{
window.location.href = U('/equipment');
},
function(xhr)
function (xhr)
{
console.error(xhr);
}
@@ -150,7 +150,7 @@ $(document).on('click', '.equipment-delete-button', function(e)
});
});
$(".selectedEquipmentInstructionManualToggleFullscreenButton").on('click', function(e)
$(".selectedEquipmentInstructionManualToggleFullscreenButton").on('click', function (e)
{
var button = $(e.currentTarget);
var card = button.closest(".selectedEquipmentInstructionManualCard");
@@ -163,7 +163,7 @@ $(".selectedEquipmentInstructionManualToggleFullscreenButton").on('click', funct
ResizeResponsiveEmbeds();
});
$("#selectedEquipmentDescriptionToggleFullscreenButton").on('click', function(e)
$("#selectedEquipmentDescriptionToggleFullscreenButton").on('click', function (e)
{
$("#selectedEquipmentDescriptionCard").toggleClass("fullscreen");
$("#selectedEquipmentDescriptionCard .card-header").toggleClass("fixed-top");
-2
View File
@@ -57,8 +57,6 @@ $app->group('', function (RouteCollectorProxy $group)
$group->get('/user/{userId}/permissions', [UsersController::class, 'PermissionList']);
$group->get('/usersettings', [UsersController::class, 'UserSettings']);
$group->get('/files/{group}/{fileName}', [FilesApiController::class, 'ShowFile']);
// Stock master data routes
$group->get('/products', [StockController::class, 'ProductsList']);
$group->get('/product/{productId}', [StockController::class, 'ProductEditForm']);
+3 -3
View File
@@ -38,12 +38,12 @@ $excludeFieldTypes = [];
<a href="{{ $link }}"
target="_blank">{{ $title }}</a>
@elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_FILE && !empty($userfieldObject->value))
<a href="{{ $U('/files/userfiles/'. $userfieldObject->value) }}"
<a href="{{ $U('/api/files/userfiles/'. $userfieldObject->value) }}"
target="_blank">{{ base64_decode(explode('_', $userfieldObject->value)[1]) }}</a>
@elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_IMAGE && !empty($userfieldObject->value))
<a class="show-as-dialog-link"
href="{{ $U('/files/userfiles/'. $userfieldObject->value . '?force_serve_as=picture') }}">
<img src="{{ $U('/files/userfiles/'. $userfieldObject->value . '?force_serve_as=picture&best_fit_width=32&best_fit_height=32') }}"
href="{{ $U('/api/files/userfiles/'. $userfieldObject->value . '?force_serve_as=picture') }}">
<img src="{{ $U('/api/files/userfiles/'. $userfieldObject->value . '?force_serve_as=picture&best_fit_width=32&best_fit_height=32') }}"
title="{{ base64_decode(explode('_', $userfieldObject->value)[1]) }}"
alt="{{ base64_decode(explode('_', $userfieldObject->value)[1]) }}"
loading="lazy">
+1 -1
View File
@@ -476,7 +476,7 @@
<i class="fa-solid fa-user"></i>
@else
<img class="rounded-circle"
src="{{ $U('/files/userpictures/' . base64_encode(GROCY_USER_PICTURE_FILE_NAME) . '_' . base64_encode(GROCY_USER_PICTURE_FILE_NAME) . '?force_serve_as=picture&best_fit_width=32&best_fit_height=32') }}"
src="{{ $U('/api/files/userpictures/' . base64_encode(GROCY_USER_PICTURE_FILE_NAME) . '?force_serve_as=picture&best_fit_width=32&best_fit_height=32') }}"
loading="lazy">
@endif
{{ GROCY_USER_USERNAME }}