From 52140dbe063a1de5f40b332ce11815d4a94159c9 Mon Sep 17 00:00:00 2001 From: snipe Date: Wed, 3 Sep 2025 08:37:42 +0100 Subject: [PATCH] Log upload deletion Signed-off-by: snipe --- app/Helpers/IconHelper.php | 1 + .../Api/UploadedFilesController.php | 8 ++--- .../Controllers/UploadedFilesController.php | 2 +- .../Transformers/ActionlogsTransformer.php | 9 +++-- app/Models/Actionlog.php | 33 +++++++++++-------- app/Models/Traits/HasUploads.php | 9 ++++- app/Presenters/ActionlogPresenter.php | 6 +++- resources/lang/en-US/general.php | 1 + 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/app/Helpers/IconHelper.php b/app/Helpers/IconHelper.php index 668b01a9f6d3..8172f2bbbbde 100644 --- a/app/Helpers/IconHelper.php +++ b/app/Helpers/IconHelper.php @@ -16,6 +16,7 @@ public static function icon($type) { case 'clone': return 'far fa-clone'; case 'delete': + case 'upload deleted': return 'fas fa-trash'; case 'create': return 'fa-solid fa-plus'; diff --git a/app/Http/Controllers/Api/UploadedFilesController.php b/app/Http/Controllers/Api/UploadedFilesController.php index abf35652f9ab..a4a2de05209a 100644 --- a/app/Http/Controllers/Api/UploadedFilesController.php +++ b/app/Http/Controllers/Api/UploadedFilesController.php @@ -51,11 +51,7 @@ public function index(Request $request, $object_type, $id) : JsonResponse | arra ]; - $uploads = Actionlog::select('action_logs.*') - ->whereNotNull('filename') - ->where('item_type', self::$map_object_type[$object_type]) - ->where('item_id', $object->id) - ->where('action_type', '=', 'uploaded') + $uploads = self::$map_object_type[$object_type]::withTrashed()->find($id)->uploads() ->with('adminuser'); $offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset')); @@ -206,7 +202,7 @@ public function destroy($object_type, $id, $file_id) : JsonResponse Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename); } // Delete the record of the file - if ($log->delete()) { + if ($log->logUploadDelete($object, $log->filename)) { return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200); } diff --git a/app/Http/Controllers/UploadedFilesController.php b/app/Http/Controllers/UploadedFilesController.php index d3c5bc08a3a3..8a9f4304adfc 100644 --- a/app/Http/Controllers/UploadedFilesController.php +++ b/app/Http/Controllers/UploadedFilesController.php @@ -148,7 +148,7 @@ public function destroy($object_type, $id, $file_id) : RedirectResponse Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename); } // Delete the record of the file - if ($log->delete()) { + if ($log->logUploadDelete($object, $log->filename)) { return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1)); } diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 5f0007310c5f..c62e3031f557 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -50,17 +50,20 @@ private function clean_field($value) public function transformActionlog (Actionlog $actionlog, $settings = null) { + $icon = $actionlog->present()->icon(); + if (($actionlog->filename!='') && ($actionlog->action_type!='upload deleted')) { + $icon = Helper::filetype_icon($actionlog->filename); + } + static $custom_fields = false; if ($custom_fields === false) { $custom_fields = CustomField::all(); } - if ($actionlog->filename!='') { - $icon = Helper::filetype_icon($actionlog->filename); - } + // This is necessary since we can't escape special characters within a JSON object if (($actionlog->log_meta) && ($actionlog->log_meta!='')) { diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 5a1ad2a9927b..59e7b8244aa4 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -246,19 +246,6 @@ public function targetType() } - /** - * Establishes the actionlog -> uploads relationship - * - * @author [A. Gianotto] [] - * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation - */ - public function uploads() - { - return $this->morphTo('item') - ->where('action_type', '=', 'uploaded') - ->withTrashed(); - } /** * Establishes the actionlog -> userlog relationship @@ -456,6 +443,26 @@ public function determineActionSource(): string } + + /** + * @author Godfrey Martinez + * @since [v8.0.4] + * @return \App\Models\Actionlog + */ + public function logUploadDelete($object, $filename) + { + $log = new Actionlog; + $log->item_type = $object instanceof SnipeModel ? get_class($object) : $object; + $log->item_id = $object->id; + $log->created_by = auth()->id(); + $log->target_id = null; + $log->filename = $filename; + $log->created_at = date('Y-m-d H:i:s'); + $log->logaction('upload deleted'); + + return $log; + } + public function uploads_file_url() { diff --git a/app/Models/Traits/HasUploads.php b/app/Models/Traits/HasUploads.php index 400bb84e1f4d..271da7169026 100644 --- a/app/Models/Traits/HasUploads.php +++ b/app/Models/Traits/HasUploads.php @@ -12,7 +12,14 @@ public function uploads() return $this->hasMany(Actionlog::class, 'item_id') ->where('item_type', self::class) ->where('action_type', '=', 'uploaded') - ->whereNotNull('filename'); + ->whereNotNull('filename') + ->whereNotIn('filename', function ($query) { + $query->select('filename') + ->from('action_logs') + ->where('item_type', '=', self::class) + ->where('action_type', '=', 'upload deleted') + ->where('item_id', $this->id); + }); } diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 4b7aefc87a8e..7ab022138b50 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -62,6 +62,10 @@ public function icon() return 'fa-solid fa-user-minus'; } + if ($this->action_type == 'upload deleted') { + return 'fa-solid fa-trash'; + } + if ($this->action_type == 'update') { return 'fa-solid fa-user-pen'; } @@ -74,7 +78,7 @@ public function icon() return 'fa-solid fa-plus'; } - if ($this->action_type == 'delete') { + if (($this->action_type == 'delete') || ($this->action_type == 'upload deleted')) { return 'fa-solid fa-trash'; } diff --git a/resources/lang/en-US/general.php b/resources/lang/en-US/general.php index b304bc9725e4..bfcad0b713ac 100644 --- a/resources/lang/en-US/general.php +++ b/resources/lang/en-US/general.php @@ -611,6 +611,7 @@ 'use_cloned_no_image_help' => 'This item does not have an associated image and instead inherits from the model or category it belongs to. If you would like to use a specific image for this item, you can upload a new one below.', 'footer_credit' => 'Snipe-IT is open source software, made with love by @snipeitapp.com.', 'set_password' => 'Set a Password', + 'upload_deleted' => 'Upload Deleted', // Add form placeholders here 'placeholders' => [