Added option to clone original images

Signed-off-by: snipe <snipe@snipe.net>
This commit is contained in:
snipe
2025-08-04 18:47:26 +01:00
parent 06b040a337
commit f1dd84edba
10 changed files with 126 additions and 36 deletions

View File

@@ -77,7 +77,18 @@ class AccessoriesController extends Controller
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'accessories/'.$new_image_name;
Storage::disk('public')->copy('accessories/'.$cloned_model_img->image, $new_image);
$accessory->image = $new_image_name;
}
} else {
$accessory = $request->handleImages($accessory);
}
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created?
@@ -114,11 +125,12 @@ class AccessoriesController extends Controller
$this->authorize('create', Accessory::class);
$cloned = clone $accessory;
$accessory_to_clone = $accessory;
$cloned->id = null;
$cloned->deleted_at = '';
$cloned->location_id = null;
return view('accessories/edit')
->with('cloned_model', $accessory_to_clone)
->with('item', $cloned);
}

View File

@@ -87,7 +87,20 @@ class AssetModelsController extends Controller
$model->fieldset_id = $request->input('fieldset_id');
}
$model = $request->handleImages($model);
if ($request->has('use_cloned_image')) {
$cloned_model_img = AssetModel::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'models/'.$new_image_name;
Storage::disk('public')->copy('models/'.$cloned_model_img->image, $new_image);
$model->image = $new_image_name;
}
} else {
$model = $request->handleImages($model);
}
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
@@ -271,7 +284,7 @@ class AssetModelsController extends Controller
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
->with('model_id', $model->id)
->with('clone_model', $cloned_model);
->with('cloned_model', $cloned_model);
}

View File

@@ -157,8 +157,16 @@ class AssetsController extends Controller
$asset->location_id = $request->input('rtd_location_id', null);
}
// Create the image (if one was chosen.)
if ($request->has('image')) {
if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'assets/'.$new_image_name;
Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
}
} else {
$asset = $request->handleImages($asset);
}

View File

@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Consumable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
@@ -81,11 +81,23 @@ class ConsumablesController extends Controller
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = $request->input('qty');
$consumable->created_by = auth()->id();
$consumable->created_by = auth()->id();
$consumable->notes = $request->input('notes');
$consumable = $request->handleImages($consumable);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Consumable::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
\Log::error($new_image_name);
$new_image = 'consumables/'.$new_image_name;
Storage::disk('public')->copy('consumables/'.$cloned_model_img->image, $new_image);
$consumable->image = $new_image_name;
}
} else {
$consumable = $request->handleImages($consumable);
}
session()->put(['redirect_option' => $request->get('redirect_option')]);
@@ -213,9 +225,10 @@ class ConsumablesController extends Controller
$consumable_to_close = $consumable;
$consumable = clone $consumable_to_close;
$consumable->id = null;
$consumable->image = null;
$consumable->created_by = null;
return view('consumables/edit')->with('item', $consumable);
return view('consumables/edit')
->with('cloned_model', $consumable_to_close)
->with('item', $consumable);
}
}

View File

@@ -96,7 +96,18 @@ class LocationsController extends Controller
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Location::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'locations/'.$new_image_name;
Storage::disk('public')->copy('locations/'.$cloned_model_img->image, $new_image);
$location->image = $new_image_name;
}
} else {
$location = $request->handleImages($location);
}
if ($location->save()) {
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success'));
@@ -275,9 +286,9 @@ class LocationsController extends Controller
// unset these values
$location->id = null;
$location->image = null;
return view('locations/edit')
->with('cloned_model', $location_to_clone)
->with('item', $location);
}

View File

@@ -432,7 +432,7 @@ class UsersController extends Controller
app('request')->request->set('permissions', $permissions);
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
$user_to_clone = User::with('userloc')->withTrashed()->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user_to_clone);
@@ -447,6 +447,8 @@ class UsersController extends Controller
$user->last_name = '';
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
$user->id = null;
$user->username = null;
$user->avatar = null;
// Get this user's groups
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
@@ -462,7 +464,7 @@ class UsersController extends Controller
->with('user', $user)
->with('groups', Group::pluck('name', 'id'))
->with('userGroups', $userGroups)
->with('clone_user', $user_to_clone)
->with('cloned_model', $user_to_clone)
->with('item', $user);
}

View File

@@ -598,6 +598,8 @@ return [
'by' => 'By',
'version' => 'Version',
'build' => 'build',
'use_cloned_image' => 'Clone image from original',
'use_cloned_image_help' => 'You may clone the original image or you can upload a new one using the upload field below.',
'footer_credit' => '<a target="_blank" href="https://snipeitapp.com" rel="noopener">Snipe-IT</a> is open source software, made with <i class="fa fa-heart" aria-hidden="true" style="color: #a94442; font-size: 10px" /></i><span class="sr-only">love</span> by <a href="https://bsky.app/profile/snipeitapp.com" rel="noopener">@snipeitapp.com</a>.',
'set_password' => 'Set a Password',

View File

@@ -1154,6 +1154,19 @@ dir="{{ Helper::determineLanguageDirection() }}">
$(function () {
// This handles the show/hide for cloned items
$('#use_cloned_image').click(function() {
if ($('#use_cloned_image').is(':checked')) {
$('#image_delete').prop('checked', false);
$('#image-upload').hide();
$('#existing-image').show();
} else {
$('#image-upload').show();
$('#existing-image').hide();
}
//$('#image-upload').hide();
});
// Invoke Bootstrap 3's tooltip
$('[data-tooltip="true"]').tooltip({
container: 'body',

View File

@@ -9,7 +9,6 @@
{{-- Page content --}}
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/models/table.name'), 'required' => 'true'])
@include ('partials.forms.edit.category-select', ['translated_name' => trans('admin/categories/general.category_name'), 'fieldname' => 'category_id', 'required' => 'true', 'category_type' => 'asset'])
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
@@ -43,4 +42,4 @@
@include ('partials.forms.edit.image-upload', ['image_path' => app('models_upload_path')])
@stop
@stop

View File

@@ -1,27 +1,45 @@
<!-- Image stuff - kept in /resources/views/partials/forms/edit/image-upload.blade.php -->
<!-- Image Delete -->
@if (isset($image_path))
@if (isset($item) && ($item->{($fieldname ?? 'image')}))
<div class="form-group{{ $errors->has('image_delete') ? ' has-error' : '' }}">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="image_delete" value="1" @checked(old('image_delete')) aria-label="image_delete">
{{ trans('general.image_delete') }}
{!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!}
</label>
@if ((isset($cloned_model)) && ($cloned_model->image!=''))
<!-- We are cloning a model. Use the cloned image if the user has checked that box -->
<input type="hidden" name="clone_image_from_id" value="{{ $cloned_model->id }}" />
<label class="form-control">
<input type="checkbox" name="use_cloned_image" value="1" @checked(old('use_cloned_image')) aria-label="use_cloned_image" id="use_cloned_image">
{{ trans('general.use_cloned_image') }}
</label>
<p class="help-block">
{{ trans('general.use_cloned_image_help') }}
</p>
{!! $errors->first('use_cloned_image', '<span class="alert-msg">:message</span>') !!}
@else
<!-- Image Delete -->
<label class="form-control">
<input type="checkbox" name="image_delete" value="1" @checked(old('image_delete')) aria-label="image_delete" id="image_delete">
{{ trans('general.image_delete') }}
{!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!}
</label>
@endif
</div>
</div>
<!-- existing image -->
<div class="form-group" id="existing-image">
<div class="col-md-8 col-md-offset-3">
<img src="{{ Storage::disk('public')->url($image_path.e($item->{($fieldname ?? 'image')})) }}" class="img-responsive">
{!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!}
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-3">
<img src="{{ Storage::disk('public')->url($image_path.e($item->{($fieldname ?? 'image')})) }}" class="img-responsive">
{!! $errors->first('image_delete', '<span class="alert-msg">:message</span>') !!}
</div>
</div>
@endif
</div>
@endif
@endif
<!-- Image Upload and preview -->
<div class="form-group {{ $errors->has((isset($fieldname) ? $fieldname : 'image')) ? 'has-error' : '' }}">
<div class="form-group {{ $errors->has((isset($fieldname) ? $fieldname : 'image')) ? 'has-error' : '' }}" id="image-upload">
<label class="col-md-3 control-label" for="{{ (isset($fieldname) ? $fieldname : 'image') }}">{{ trans('general.image_upload') }}</label>
<div class="col-md-8">
@@ -31,16 +49,15 @@
{{ trans('button.select_file') }}
<input type="file" name="{{ (isset($fieldname) ? $fieldname : 'image') }}" class="js-uploadFile" id="uploadFile" data-maxsize="{{ Helper::file_upload_max_size() }}" accept="image/gif,image/jpeg,image/webp,image/png,image/svg,image/svg+xml,image/avif" style="display:none; max-width: 90%" aria-label="{{ (isset($fieldname) ? $fieldname : 'image') }}" aria-hidden="true">
</label>
<span class='label label-default' id="uploadFile-info"></span>
<p class="help-block" id="uploadFile-status">{{ trans('general.image_filetypes_help', ['size' => Helper::file_upload_max_size_readable()]) }} {{ $help_text ?? '' }}</p>
{!! $errors->first('image', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
<div class="col-md-4 col-md-offset-3" aria-hidden="true">
<img id="uploadFile-imagePreview" style="max-width: 300px; display: none;" alt="{{ trans('general.alt_uploaded_image_thumbnail') }}">
</div>
<div class="col-md-4 col-md-offset-3" aria-hidden="true">
<img id="uploadFile-imagePreview" style="max-width: 300px; display: none;" alt="{{ trans('general.alt_uploaded_image_thumbnail') }}">
</div>
</div>