Merge pull request #17539 from grokability/add-file-uploads-to-maintenances

WIP: Add file uploads to maintenances
This commit is contained in:
snipe
2025-08-10 11:13:19 +01:00
committed by GitHub
31 changed files with 485 additions and 1335 deletions

View File

@@ -242,6 +242,7 @@ class RestoreFromBackup extends Command
$private_dirs = [
'storage/private_uploads/accessories',
'storage/private_uploads/asset_maintenances',
'storage/private_uploads/assetmodels',
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
'storage/private_uploads/audits',
@@ -260,9 +261,9 @@ class RestoreFromBackup extends Command
];
$public_dirs = [
'public/uploads/accessories',
'public/uploads/asset_maintenances',
'public/uploads/assets', // these are asset _pictures_, not asset files
'public/uploads/avatars',
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
'public/uploads/categories',
'public/uploads/companies',
'public/uploads/components',

View File

@@ -1,132 +0,0 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
* @param UploadFileRequest $request
* @param int $accessoryId
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
}
$accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) {
$this->authorize('accessories.files', $accessory);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/accessories')) {
Storage::makeDirectory('private_uploads/accessories', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Deletes the selected accessory file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $accessoryId
* @param int $fileId
*/
public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
{
if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('update', $accessory);
if ($log = Actionlog::find($fileId)) {
if (Storage::exists('private_uploads/accessories/'.$log->filename)) {
try {
Storage::delete('private_uploads/accessories/' . $log->filename);
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
} catch (\Exception $e) {
Log::debug($e);
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
}
}
}
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $accessoryId
* @param int $fileId
*/
public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{
// the accessory is valid
if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
$file = 'private_uploads/accessories/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
}

View File

@@ -10,6 +10,7 @@ use App\Http\Transformers\UploadedFilesTransformer;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
@@ -27,9 +28,9 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'maintenances' => AssetMaintenance::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
@@ -42,6 +43,7 @@ class UploadedFilesController extends Controller
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'maintenances' => 'private_uploads/asset_maintenances/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
@@ -54,6 +56,7 @@ class UploadedFilesController extends Controller
static $map_file_prefix= [
'accessories' => 'accessory',
'maintenances' => 'maintenance',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',

View File

@@ -1,115 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetModelsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('update', $model);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
$model->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$this->authorize('view', $model);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that model/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/assetmodels/'.$log->filename;
if (! Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(AssetModel $model, $fileId = null) : RedirectResponse
{
$rel_path = 'private_uploads/assetmodels';
$this->authorize('update', $model);
$log = Actionlog::find($fileId);
if ($log) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}

View File

@@ -1,108 +0,0 @@
<?php
namespace App\Http\Controllers\Assets;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use \Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $assetId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse
{
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
*/
public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
$this->authorize('view', $asset);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(Asset $asset, $fileId = null) : RedirectResponse
{
$this->authorize('update', $asset);
$rel_path = 'private_uploads/assets';
if ($log = Actionlog::find($fileId)) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
}

View File

@@ -1,138 +0,0 @@
<?php
namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
* @param UploadFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('components.show', ['component'=>$componentId])->with('error', trans('general.feature_disabled'));
}
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('update', $component);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/components')) {
Storage::makeDirectory('private_uploads/components', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('components.show', $component->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('components.show', $component->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('components.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected component file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $componentId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($componentId = null, $fileId = null)
{
$component = Component::find($componentId);
// the asset is valid
if (isset($component->id)) {
$this->authorize('update', $component);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('components/'.$log->filename)) {
try {
Storage::delete('components/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $componentId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null, $fileId = null)
{
Log::debug('Private filesystem is: '.config('filesystems.default'));
// the component is valid
if ($component = Component::find($componentId)) {
$this->authorize('view', $component);
$this->authorize('components.files', $component);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
$file = 'private_uploads/components/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}

View File

@@ -1,134 +0,0 @@
<?php
namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
* @param UploadFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
}
$consumable = Consumable::find($consumableId);
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/consumables')) {
Storage::makeDirectory('private_uploads/consumables', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('consumables.show', $consumable->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('consumables.show', $consumable->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('consumables.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected consumable file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $consumableId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
// the asset is valid
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('consumables/'.$log->filename)) {
try {
Storage::delete('consumables/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $consumableId
* @param int $fileId
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
// the consumable is valid
if (isset($consumable->id)) {
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
$file = 'private_uploads/consumables/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}

View File

@@ -1,132 +0,0 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
* @param UploadFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
if (isset($license->id)) {
$this->authorize('update', $license);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/licenses')) {
Storage::makeDirectory('private_uploads/licenses', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles'));
}
// Prepare the error message
return redirect()->route('licenses.index')
->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Deletes the selected license file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($licenseId = null, $fileId = null)
{
if ($license = License::find($licenseId)) {
$this->authorize('update', $license);
if ($log = Actionlog::find($fileId)) {
// Remove the file if one exists
if (Storage::exists('licenses/'.$log->filename)) {
try {
Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $licenseId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
// the license is valid
if (isset($license->id)) {
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
$file = 'private_uploads/licenses/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
}
}

View File

@@ -1,111 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Location;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class LocationsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, Location $location) : RedirectResponse
{
$this->authorize('update', $location);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/locations')) {
Storage::makeDirectory('private_uploads/locations', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file);
$location->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$this->authorize('view', $location);
if (! $log = Actionlog::find($fileId)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file record');
}
$file = 'private_uploads/locations/'.$log->filename;
if (! Storage::exists($file)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file on server');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(Location $location, $fileId = null) : RedirectResponse
{
$rel_path = 'private_uploads/locations';
$this->authorize('update', $location);
$log = Actionlog::find($fileId);
if ($log) {
// This should be moved to purge
// if (Storage::exists($rel_path.'/'.$log->filename)) {
// Storage::delete($rel_path.'/'.$log->filename);
// }
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}

View File

@@ -17,13 +17,11 @@ use App\Models\Depreciation;
use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;

View File

@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Supplier;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;

View File

@@ -0,0 +1,209 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* This controller provide the health route for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*
* @return \Illuminate\Http\JsonResponse
*/
class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'maintenances' => AssetMaintenance::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'maintenances' => 'private_uploads/asset_maintenances/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/assetmodels/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'maintenances' => 'maintenance',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to store so we can check permisisons
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $object_type, $id) : RedirectResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', $object);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// If the file storage directory doesn't exist, create it
if (! Storage::exists(self::$map_storage_path[$object_type])) {
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
}
if ($request->hasFile('file')) {
// Loop over the attached files and add them to the object
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.upload.success', count($files)));
}
// No files were submitted
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.nofiles'));
}
/**
* Check for permissions and display the file.
* This isn't currently used, but is here for future use.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to show from the action_logs table
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// Check that the file being requested exists for the object
if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id))
{
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.invalid_id'));
}
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename))
{
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.file_not_found'));
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
}
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
}
/**
* Delete the associated file
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function destroy($object_type, $id, $file_id) : RedirectResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// Check for the file
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)->first();
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
}
// Delete the record of the file
if ($log->delete()) {
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
}
}
// The file doesn't seem to really exist, so report an error
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.error', 1));
}
}

View File

@@ -1,119 +0,0 @@
<?php
namespace App\Http\Controllers\Users;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\User;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller
{
/**
* Return JSON response with a list of user details for the getIndex() view.
*
* @param UploadFileRequest $request
* @param int $userId
* @return string JSON
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
*/
public function store(UploadFileRequest $request, User $user)
{
$this->authorize('update', $user);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/users')) {
Storage::makeDirectory('private_uploads/users', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/users/','user-'.$user->id, $file);
$user->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
/**
* Delete file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param int $userId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($userId = null, $fileId = null)
{
if ($user = User::find($userId)) {
$this->authorize('delete', $user);
$rel_path = 'private_uploads/users';
if ($log = Actionlog::find($fileId)) {
$filename = $log->filename;
$log->delete();
if (Storage::exists($rel_path.'/'.$filename)) {
Storage::delete($rel_path.'/'.$filename);
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.deletefile.success'));
}
}
// The log record doesn't exist somehow
return redirect()->back()->with('success', trans('admin/users/message.deletefile.success'));
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
}
/**
* Display/download the uploaded file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param int $userId
* @param int $fileId
* @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show(User $user, $fileId = null)
{
if (empty($fileId)) {
return redirect()->route('users.show')->with('error', 'Invalid file request');
}
$this->authorize('view', $user);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
}
}

View File

@@ -7,6 +7,7 @@ use App\Presenters\Presentable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -462,26 +463,21 @@ class Actionlog extends SnipeModel
return route('log.storedeula.download', ['filename' => $this->filename]);
}
switch ($this->item_type) {
case Accessory::class:
return route('show.accessoryfile', [$this->item_id, $this->id]);
case Asset::class:
return route('show/assetfile', [$this->item_id, $this->id]);
case AssetModel::class:
return route('show/modelfile', [$this->item_id, $this->id]);
case Consumable::class:
return route('show.consumablefile', [$this->item_id, $this->id]);
case Component::class:
return route('show.componentfile', [$this->item_id, $this->id]);
case License::class:
return route('show.licensefile', [$this->item_id, $this->id]);
case Location::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case User::class:
return route('show/userfile', [$this->item_id, $this->id]);
default:
return null;
$object = Str::snake(str_plural(str_replace("App\Models\\", '', $this->item_type)));
if ($object == 'asset_models') {
$object = 'models';
}
if ($object == 'asset_maintenances') {
$object = 'maintenances';
}
return route('ui.files.show', [
'object_type' => $object,
'id' => $this->item_id,
'file_id' => $this->id,
]);
}
public function uploads_file_path()
@@ -494,6 +490,8 @@ class Actionlog extends SnipeModel
switch ($this->item_type) {
case Accessory::class:
return 'private_uploads/accessories/'.$this->filename;
case AssetMaintenance::class:
return 'private_uploads/asset_maintenances/'.$this->filename;
case Asset::class:
return 'private_uploads/assets/'.$this->filename;
case AssetModel::class:
@@ -514,11 +512,6 @@ class Actionlog extends SnipeModel
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{

View File

@@ -4,22 +4,25 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;
use App\Models\Traits\HasUploads;
/**
* Model for Asset Maintenances.
*
* @version v1.0
*/
class AssetMaintenance extends Model implements ICompanyableChild
class AssetMaintenance extends SnipeModel implements ICompanyableChild
{
use HasFactory;
use HasUploads;
use SoftDeletes;
use CompanyableChildTrait;
use ValidatingTrait;
use Loggable, Presentable;
@@ -187,6 +190,11 @@ class AssetMaintenance extends Model implements ICompanyableChild
->withTrashed();
}
public function getDisplayNameAttribute()
{
return $this->title;
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES

View File

@@ -14,196 +14,205 @@ use Carbon\Carbon;
<div class="row">
<div class="col-md-9">
<div class="box box-default">
<div class="box-body">
<div class="row-new-striped">
<div class="row">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs hidden-print">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.asset_maintenance_type') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->asset_maintenance_type }}
</div>
<li class="active">
<a href="#info" data-toggle="tab">
<span class="hidden-lg hidden-md">
<x-icon type="info-circle" class="fa-2x" />
</span>
<span class="hidden-xs hidden-sm">{{ trans('admin/users/general.info') }}</span>
</a>
</li>
</div> <!-- /row -->
<li>
<a href="#files" data-toggle="tab">
<span class="hidden-lg hidden-md">
<x-icon type="files" class="fa-2x" />
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.file_uploads') }}
{!! ($assetMaintenance->uploads->count() > 0 ) ? '<span class="badge badge-secondary">'.number_format($assetMaintenance->uploads->count()).'</span>' : '' !!}
</span>
</a>
</li>
<div class="row">
<div class="col-md-3">
{{ trans('general.asset') }}
</div>
<div class="col-md-9">
<a href="{{ route('hardware.show', $assetMaintenance->asset_id) }}">
{{ $assetMaintenance->asset->present()->fullName }}
</a>
</div>
</div> <!-- /row -->
@if ($assetMaintenance->asset->model)
<div class="row">
<div class="col-md-3">
{{ trans('general.asset_model') }}
</div>
<div class="col-md-9">
<a href="{{ route('models.show', $assetMaintenance->asset->model_id) }}">
{{ $assetMaintenance->asset->model->name }}
</a>
</div>
</div> <!-- /row -->
@endif
@if ($assetMaintenance->asset->company)
<div class="row">
<div class="col-md-3">
{{ trans('general.company') }}
</div>
<div class="col-md-9">
<a href="{{ route('companies.show', $assetMaintenance->asset->company_id) }}">
{{ $assetMaintenance->asset->company->name }}
</a>
</div>
</div> <!-- /row -->
@endif
@if ($assetMaintenance->supplier)
<div class="row">
<div class="col-md-3">
{{ trans('general.supplier') }}
</div>
<div class="col-md-9">
<a href="{{ route('suppliers.show', $assetMaintenance->supplier_id) }}">
{{ $assetMaintenance->supplier->name }}
</a>
</div>
</div> <!-- /row -->
@endif
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.start_date') }}
</div>
<div class="col-md-9">
{{ Helper::getFormattedDateObject($assetMaintenance->start_date, 'date', false) }}
</div>
</div> <!-- /row -->
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.completion_date') }}
</div>
<div class="col-md-9">
@if ($assetMaintenance->completion_date)
{{ Helper::getFormattedDateObject($assetMaintenance->completion_date, 'date', false) }}
@else
{{ trans('admin/asset_maintenances/message.asset_maintenance_incomplete') }}
@endif
</div>
</div> <!-- /row -->
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.asset_maintenance_time') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->asset_maintenance_time }}
</div>
</div> <!-- /row -->
@if ($assetMaintenance->cost > 0)
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.cost') }}
</div>
<div class="col-md-9">
{{ \App\Models\Setting::getSettings()->default_currency .' '. Helper::formatCurrencyOutput($assetMaintenance->cost) }}
</div>
</div> <!-- /row -->
@endif
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.is_warranty') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->is_warranty ? trans('admin/asset_maintenances/message.warranty') : trans('admin/asset_maintenances/message.not_warranty') }}
</div>
</div> <!-- /row -->
@if ($assetMaintenance->notes)
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.notes') }}
</div>
<div class="col-md-9">
{!! nl2br(Helper::parseEscapedMarkedownInline($assetMaintenance->notes)) !!}
</div>
</div> <!-- /row -->
@endif
</div><!-- /row-new-striped -->
</div><!-- /box-body -->
</div><!-- /box -->
</div> <!-- col-md-9 end -->
<div class="col-md-3">
@if ($assetMaintenance->image!='')
<div class="col-md-12 text-center" style="padding-bottom: 17px;">
<img src="{{ Storage::disk('public')->url(app('asset_maintenances_path').e($assetMaintenance->image)) }}" class="img-responsive img-thumbnail" style="width:100%" alt="{{ $assetMaintenance->name }}">
</div>
@endif
<div class="col-md-12">
<ul class="list-unstyled" style="line-height: 22px; padding-bottom: 20px;">
@if ($assetMaintenance->notes)
<li>
<strong>{{ trans('general.notes') }}</strong>:
{!! nl2br(Helper::parseEscapedMarkedownInline($assetMaintenance->notes)) !!}
</li>
@endif
@if ($assetMaintenance->address!='')
<li>{{ $assetMaintenance->address }}</li>
@endif
@if ($assetMaintenance->address2!='')
<li>{{ $assetMaintenance->address2 }}</li>
@endif
@if (($assetMaintenance->city!='') || ($assetMaintenance->state!='') || ($assetMaintenance->zip!=''))
<li>{{ $assetMaintenance->city }} {{ $assetMaintenance->state }} {{ $assetMaintenance->zip }}</li>
@endif
@if ($assetMaintenance->manager)
<li>{{ trans('admin/users/table.manager') }}: {!! $assetMaintenance->manager->present()->nameUrl() !!}</li>
@endif
@if ($assetMaintenance->company)
<li>{{ trans('admin/companies/table.name') }}: {!! $assetMaintenance->company->present()->nameUrl() !!}</li>
@endif
@if ($assetMaintenance->parent)
<li>{{ trans('admin/locations/table.parent') }}: {!! $assetMaintenance->parent->present()->nameUrl() !!}</li>
@endif
@if ($assetMaintenance->ldap_ou)
<li>{{ trans('admin/locations/table.ldap_ou') }}: {{ $assetMaintenance->ldap_ou }}</li>
@endif
@if ((($assetMaintenance->address!='') && ($assetMaintenance->city!='')) || ($assetMaintenance->state!='') || ($assetMaintenance->country!=''))
<li>
<a href="https://maps.google.com/?q={{ urlencode($assetMaintenance->address.','. $assetMaintenance->city.','.$assetMaintenance->state.','.$assetMaintenance->country.','.$assetMaintenance->zip) }}" target="_blank">
{!! trans('admin/locations/message.open_map', ['map_provider_icon' => '<i class="fa-brands fa-google" aria-hidden="true"></i>']) !!}
<x-icon type="external-link"/>
@can('update', $assetMaintenance)
<li class="pull-right">
<a href="#" data-toggle="modal" data-target="#uploadFileModal">
<span class="hidden-lg hidden-xl hidden-md">
<x-icon type="paperclip" class="fa-2x" />
</span>
<span class="hidden-xs hidden-sm">
<x-icon type="paperclip" />
{{ trans('button.upload') }}
</span>
</a>
</li>
<li>
<a href="https://maps.apple.com/?q={{ urlencode($assetMaintenance->address.','. $assetMaintenance->city.','.$assetMaintenance->state.','.$assetMaintenance->country.','.$assetMaintenance->zip) }}" target="_blank">
{!! trans('admin/locations/message.open_map', ['map_provider_icon' => '<i class="fa-brands fa-apple" aria-hidden="true" style="font-size: 18px"></i>']) !!}
<x-icon type="external-link"/></a>
</li>
@endif
@endcan
</ul>
<div class="tab-content">
<div class="tab-pane active" id="info">
<div class="row-new-striped">
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.asset_maintenance_type') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->asset_maintenance_type }}
</div>
</div> <!-- /row -->
<div class="row">
<div class="col-md-3">
{{ trans('general.asset') }}
</div>
<div class="col-md-9">
<a href="{{ route('hardware.show', $assetMaintenance->asset_id) }}">
{{ $assetMaintenance->asset->present()->fullName }}
</a>
</div>
</div> <!-- /row -->
@if ($assetMaintenance->asset->model)
<div class="row">
<div class="col-md-3">
{{ trans('general.asset_model') }}
</div>
<div class="col-md-9">
<a href="{{ route('models.show', $assetMaintenance->asset->model_id) }}">
{{ $assetMaintenance->asset->model->name }}
</a>
</div>
</div> <!-- /row -->
@endif
@if ($assetMaintenance->asset->company)
<div class="row">
<div class="col-md-3">
{{ trans('general.company') }}
</div>
<div class="col-md-9">
<a href="{{ route('companies.show', $assetMaintenance->asset->company_id) }}">
{{ $assetMaintenance->asset->company->name }}
</a>
</div>
</div> <!-- /row -->
@endif
@if ($assetMaintenance->supplier)
<div class="row">
<div class="col-md-3">
{{ trans('general.supplier') }}
</div>
<div class="col-md-9">
<a href="{{ route('suppliers.show', $assetMaintenance->supplier_id) }}">
{{ $assetMaintenance->supplier->name }}
</a>
</div>
</div> <!-- /row -->
@endif
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.start_date') }}
</div>
<div class="col-md-9">
{{ Helper::getFormattedDateObject($assetMaintenance->start_date, 'date', false) }}
</div>
</div> <!-- /row -->
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.completion_date') }}
</div>
<div class="col-md-9">
@if ($assetMaintenance->completion_date)
{{ Helper::getFormattedDateObject($assetMaintenance->completion_date, 'date', false) }}
@else
{{ trans('admin/asset_maintenances/message.asset_maintenance_incomplete') }}
@endif
</div>
</div> <!-- /row -->
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.asset_maintenance_time') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->asset_maintenance_time }}
</div>
</div> <!-- /row -->
@if ($assetMaintenance->cost > 0)
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.cost') }}
</div>
<div class="col-md-9">
{{ \App\Models\Setting::getSettings()->default_currency .' '. Helper::formatCurrencyOutput($assetMaintenance->cost) }}
</div>
</div> <!-- /row -->
@endif
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.is_warranty') }}
</div>
<div class="col-md-9">
{{ $assetMaintenance->is_warranty ? trans('admin/asset_maintenances/message.warranty') : trans('admin/asset_maintenances/message.not_warranty') }}
</div>
</div> <!-- /row -->
@if ($assetMaintenance->notes)
<div class="row">
<div class="col-md-3">
{{ trans('admin/asset_maintenances/form.notes') }}
</div>
<div class="col-md-9">
{!! nl2br(Helper::parseEscapedMarkedownInline($assetMaintenance->notes)) !!}
</div>
</div> <!-- /row -->
@endif
</div>
</div><!-- /row-new-striped -->
<div class="tab-pane" id="files">
<div class="row">
<div class="col-md-12">
<x-filestable object_type="accessories" :object="$assetMaintenance" />
</div>
</div>
</div>
</div><!-- /box-body -->
</div><!-- /box -->
</div> <!-- col-md-9 end -->
<div class="col-md-3">
@if ($assetMaintenance->image!='')
<div class="col-md-12 text-center" style="padding-bottom: 17px;">
<img src="{{ Storage::disk('public')->url(app('asset_maintenances_path').e($assetMaintenance->image)) }}" class="img-responsive img-thumbnail" style="width:100%" alt="{{ $assetMaintenance->name }}">
</div>
@endif
<div class="col-md-12">
<ul class="list-unstyled" style="line-height: 22px; padding-bottom: 20px;">
@if ($assetMaintenance->notes)
<li>
<strong>{{ trans('general.notes') }}</strong>:
{!! nl2br(Helper::parseEscapedMarkedownInline($assetMaintenance->notes)) !!}
</li>
@endif
</ul>
</div>
@can('update', $assetMaintenance)
@@ -218,4 +227,12 @@ use Carbon\Carbon;
</div> <!-- row end -->
@can('assets.files', Asset::class)
@include ('modals.upload-file', ['item_type' => 'maintenance', 'item_id' => $assetMaintenance->id])
@endcan
@stop
@section('moar_scripts')
@include ('partials.bootstrap-table')
@stop

View File

@@ -8,7 +8,7 @@
</div>
<form
method="POST"
action="{{ route('upload/' . $item_type, $item_id) }}"
action="{{ route('ui.files.store', ['object_type' => str_plural($item_type), 'id' => $item_id]) }}"
accept-charset="UTF-8"
class="form-horizontal"
enctype="multipart/form-data"

View File

@@ -1006,7 +1006,7 @@
destination = row.item.type;
}
return '<a href="{{ config('app.url') }}/' + destination + '/' + row.item.id + '/showfile/' + row.id + '/delete" '
return '<a href="{{ config('app.url') }}/' + destination + '/' + row.item.id + '/files/' + row.id + '/delete" '
+ ' data-target="#dataConfirmModal" class="actions btn btn-danger btn-sm delete-asset" data-tooltip="true" '
+ ' data-toggle="modal" data-icon="fa-trash"'
+ ' data-content="{{ trans('general.file_upload_status.confirm_delete') }}: ' + row.filename + '?" '

View File

@@ -1314,7 +1314,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
'index'
]
)->name('api.files.index')
->where(['object_type' => 'assets|hardware|models|users|locations|accessories|consumables|licenses|components']);
->where(['object_type' => 'assets|asset_maintenance|hardware|models|users|locations|accessories|consumables|licenses|components']);
// Get a file
Route::get('{object_type}/{id}/files/{file_id}',
@@ -1323,7 +1323,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
'show'
]
)->name('api.files.show')
->where(['object_type' => 'assets|hardware|models|users|locations|accessories|consumables|licenses|components']);
->where(['object_type' => 'assets|asset_maintenance|hardware|models|users|locations|accessories|consumables|licenses|components']);
// Upload files(s)
Route::post('{object_type}/{id}/files',
@@ -1332,7 +1332,7 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
'store'
]
)->name('api.files.store')
->where(['object_type' => 'assets|hardware|models|users|locations|accessories|consumables|licenses|components']);
->where(['object_type' => 'assets|asset_maintenance|hardware|models|users|locations|accessories|consumables|licenses|components']);
// Delete files(s)
Route::delete('{object_type}/{id}/files/{file_id}/delete',

View File

@@ -13,7 +13,7 @@ use App\Http\Controllers\DepreciationsController;
use App\Http\Controllers\GroupsController;
use App\Http\Controllers\HealthController;
use App\Http\Controllers\LabelsController;
use App\Http\Controllers\LocationsController;
use App\Http\Controllers\UploadedFilesController;
use App\Http\Controllers\ManufacturersController;
use App\Http\Controllers\ModalController;
use App\Http\Controllers\NotesController;
@@ -693,6 +693,39 @@ Route::group(['middleware' => 'web'], function () {
'logout',
[LoginController::class, 'logout']
)->name('logout.post');
/**
* Uploaded files API routes
*/
// Get a file
Route::get('{object_type}/{id}/files/{file_id}',
[
UploadedFilesController::class,
'show'
]
)->name('ui.files.show')
->where(['object_type' => 'assets|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']);
// Upload files(s)
Route::post('{object_type}/{id}/files',
[
UploadedFilesController::class,
'store'
]
)->name('ui.files.store')
->where(['object_type' => 'assets|maintenances|hardware|models|users|locations|accessories|consumables|licenses|components']);
// Delete files(s)
Route::delete('{object_type}/{id}/files/{file_id}/delete',
[
UploadedFilesController::class,
'destroy'
]
)->name('ui.files.destroy')
->where(['object_type' => 'assets|hardware|models|users|locations|accessories|consumables|licenses|components']);
});

View File

@@ -27,21 +27,6 @@ Route::group(['prefix' => 'accessories', 'middleware' => ['auth']], function ()
[Accessories\AccessoryCheckinController::class, 'store']
)->name('accessories.checkin.store');
Route::post(
'{accessoryId}/upload',
[Accessories\AccessoriesFilesController::class, 'store']
)->name('upload/accessory');
Route::delete(
'{accessoryId}/deletefile/{fileId}',
[Accessories\AccessoriesFilesController::class, 'destroy']
)->name('delete/accessoryfile');
Route::get(
'{accessoryId}/showfile/{fileId}/{download?}',
[Accessories\AccessoriesFilesController::class, 'show']
)->name('show.accessoryfile');
Route::get('{accessory}/clone',
[Accessories\AccessoriesController::class, 'getClone']
)->name('clone/accessories');

View File

@@ -25,20 +25,6 @@ Route::group(['prefix' => 'components', 'middleware' => ['auth']], function () {
[Components\ComponentCheckinController::class, 'store']
)->name('components.checkin.store');
Route::post(
'{componentId}/upload',
[Components\ComponentsFilesController::class, 'store']
)->name('upload/component');
Route::delete(
'{componentId}/showfile/{fileId}/delete',
[Components\ComponentsFilesController::class, 'destroy']
)->name('delete/componentfile');
Route::get(
'{componentId}/showfile/{fileId}/{download?}',
[Components\ComponentsFilesController::class, 'show']
)->name('show.componentfile');
});

View File

@@ -16,20 +16,6 @@ Route::group(['prefix' => 'consumables', 'middleware' => ['auth']], function ()
[Consumables\ConsumableCheckoutController::class, 'store']
)->name('consumables.checkout.store');
Route::post(
'{consumableId}/upload',
[Consumables\ConsumablesFilesController::class, 'store']
)->name('upload/consumable');
Route::delete(
'{consumableId}/showfile/{fileId}/delete',
[Consumables\ConsumablesFilesController::class, 'destroy']
)->name('delete/consumablefile');
Route::get(
'{consumableId}/showfile/{fileId}/{download?}',
[Consumables\ConsumablesFilesController::class, 'show']
)->name('show.consumablefile');
Route::get('{consumable}/clone',
[Consumables\ConsumablesController::class, 'clone']

View File

@@ -5,7 +5,6 @@ use App\Http\Controllers\Assets\AssetsController;
use App\Http\Controllers\Assets\BulkAssetsController;
use App\Http\Controllers\Assets\AssetCheckoutController;
use App\Http\Controllers\Assets\AssetCheckinController;
use App\Http\Controllers\Assets\AssetFilesController;
use App\Models\Setting;
use Tabuna\Breadcrumbs\Trail;
use Illuminate\Support\Facades\Route;
@@ -141,19 +140,6 @@ Route::group(
[AssetsController::class, 'getRestore']
)->name('restore/hardware')->withTrashed();
Route::post('{asset}/upload',
[AssetFilesController::class, 'store']
)->name('upload/asset')->withTrashed();
Route::get('{asset}/showfile/{fileId}/{download?}',
[AssetFilesController::class, 'show']
)->name('show/assetfile')->withTrashed();
Route::delete('{asset}/showfile/{fileId}/delete',
[AssetFilesController::class, 'destroy']
)->name('delete/assetfile')->withTrashed();
Route::get('hardware/bulkedit', [BulkAssetsController::class, 'bulkEditForm'])->name('hardware.bulkedit');
Route::post(
'bulkedit',
[BulkAssetsController::class, 'edit']

View File

@@ -47,19 +47,6 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () {
[Licenses\LicenseCheckoutController::class, 'bulkCheckout']
)->name('licenses.bulkcheckout');
Route::post(
'{licenseId}/upload',
[Licenses\LicenseFilesController::class, 'store']
)->name('upload/license');
Route::delete(
'{licenseId}/showfile/{fileId}/delete',
[Licenses\LicenseFilesController::class, 'destroy']
)->name('delete/licensefile');
Route::get(
'{licenseId}/showfile/{fileId}/{download?}',
[Licenses\LicenseFilesController::class, 'show']
)->name('show.licensefile');
Route::get(
'export',
[

View File

@@ -1,24 +1,10 @@
<?php
use App\Http\Controllers\LocationsController;
use App\Http\Controllers\LocationsFilesController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'locations', 'middleware' => ['auth']], function () {
Route::post('{location}/upload',
[LocationsFilesController::class, 'store']
)->name('upload/locations')->withTrashed();
Route::get('{location}/showfile/{fileId}/{download?}',
[LocationsFilesController::class, 'show']
)->name('show/locationsfile')->withTrashed();
Route::delete('{location}/showfile/{fileId}/delete',
[LocationsFilesController::class, 'destroy']
)->name('delete/locationsfile')->withTrashed();
Route::post(
'bulkdelete',
[LocationsController::class, 'postBulkDelete']
@@ -34,7 +20,6 @@ Route::group(['prefix' => 'locations', 'middleware' => ['auth']], function () {
[LocationsController::class, 'postRestore']
)->name('locations.restore');
Route::get('{locationId}/clone',
[LocationsController::class, 'getClone']
)->name('clone/location');

View File

@@ -1,7 +1,6 @@
<?php
use App\Http\Controllers\AssetModelsController;
use App\Http\Controllers\AssetModelsFilesController;
use App\Http\Controllers\BulkAssetModelsController;
use Illuminate\Support\Facades\Route;
use Tabuna\Breadcrumbs\Trail;
@@ -11,18 +10,6 @@ use Tabuna\Breadcrumbs\Trail;
Route::group(['prefix' => 'models', 'middleware' => ['auth']], function () {
Route::post('{model}/upload',
[AssetModelsFilesController::class, 'store']
)->name('upload/models')->withTrashed();
Route::get('{model}/showfile/{fileId}/{download?}',
[AssetModelsFilesController::class, 'show']
)->name('show/modelfile')->withTrashed();
Route::delete('{model}/showfile/{fileId}/delete',
[AssetModelsFilesController::class, 'destroy']
)->name('delete/modelfile')->withTrashed();
Route::get(
'{model}/clone',
[

View File

@@ -1,7 +1,6 @@
<?php
use App\Http\Controllers\Users;
use App\Http\Controllers\Users\UserFilesController;
use Illuminate\Support\Facades\Route;
// User Management
@@ -64,30 +63,6 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
]
)->name('unsuspend/user');
Route::post(
'{user}/upload',
[
Users\UserFilesController::class,
'store'
]
)->name('upload/user')->withTrashed();
Route::delete(
'{userId}/showfile/{fileId}/delete',
[
Users\UserFilesController::class,
'destroy'
]
)->name('userfile.destroy');
Route::get(
'{user}/showfile/{fileId}',
[
Users\UserFilesController::class,
'show'
]
)->name('show/userfile')->withTrashed();
Route::post(
'{userId}/password',
[

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -47,7 +47,6 @@ class CreateAssetMaintenanceTest extends TestCase
->assertOk()
->assertStatus(200);
\Log::error($response->json());
// Since we rename the file in the ImageUploadRequest, we have to fetch the record from the database
$assetMaintenance = AssetMaintenance::where('title', 'Test Maintenance')->first();

View File

@@ -85,7 +85,7 @@ class UpdateLocationsTest extends TestCase
$file = UploadedFile::fake()->image('file.jpg', 100, 100)->size(100);
$this->actingAs(User::factory()->superuser()->create())
->post(route('upload/locations', $location), [
->post(route('ui.files.store', ['object_type' => 'locations', 'id' => $location->id]), [
'file' => [$file],
'notes' => 'Test Upload',
])