mirror of
https://github.com/snipe/snipe-it.git
synced 2025-03-05 20:52:15 -08:00
Improved the error handling in the BulkAssetsController by re-enabling custom field permission checks. Updated the UpdateAssetAction to better handle encrypted custom fields and streamlined user roles in the BulkEditAssetsTest.
409 lines
15 KiB
PHP
409 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Assets;
|
|
|
|
use App\Actions\Assets\StoreAssetAction;
|
|
use App\Actions\Assets\UpdateAssetAction;
|
|
use App\Exceptions\CustomFieldPermissionException;
|
|
use App\Helpers\Helper;
|
|
use App\Http\Controllers\CheckInOutRequest;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\ImageUploadRequest;
|
|
use App\Models\Asset;
|
|
use App\Models\AssetModel;
|
|
use App\Models\Statuslabel;
|
|
use App\Models\Setting;
|
|
use App\View\Label;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Illuminate\Support\Facades\Log;
|
|
use App\Http\Requests\AssetCheckoutRequest;
|
|
use App\Models\CustomField;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Watson\Validating\ValidationException;
|
|
|
|
class BulkAssetsController extends Controller
|
|
{
|
|
use CheckInOutRequest;
|
|
|
|
/**
|
|
* Display the bulk edit page.
|
|
*
|
|
* This method is super weird because it's kinda of like a controller within a controller.
|
|
* It's main function is to determine what the bulk action in, and then return a view with
|
|
* the information that view needs, be it bulk delete, bulk edit, restore, etc.
|
|
*
|
|
* This is something that made sense at the time, but sort of doesn't make sense now. A JS front-end to determine form
|
|
* action would make a lot more sense here and make things a lot more clear.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @internal param int $assetId
|
|
* @since [v2.0]
|
|
*/
|
|
public function edit(Request $request) : View | RedirectResponse
|
|
{
|
|
$this->authorize('view', Asset::class);
|
|
|
|
/**
|
|
* No asset IDs were passed
|
|
*/
|
|
if (! $request->filled('ids')) {
|
|
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
|
}
|
|
|
|
$asset_ids = $request->input('ids');
|
|
if ($request->input('bulk_actions') === 'checkout') {
|
|
$request->session()->flashInput(['selected_assets' => $asset_ids]);
|
|
return redirect()->route('hardware.bulkcheckout.show');
|
|
}
|
|
|
|
// Figure out where we need to send the user after the update is complete, and store that in the session
|
|
$bulk_back_url = request()->headers->get('referer');
|
|
session(['bulk_back_url' => $bulk_back_url]);
|
|
|
|
$allowed_columns = [
|
|
'id',
|
|
'name',
|
|
'asset_tag',
|
|
'serial',
|
|
'model_number',
|
|
'last_checkout',
|
|
'notes',
|
|
'expected_checkin',
|
|
'order_number',
|
|
'image',
|
|
'assigned_to',
|
|
'created_at',
|
|
'updated_at',
|
|
'purchase_date',
|
|
'purchase_cost',
|
|
'last_audit_date',
|
|
'next_audit_date',
|
|
'warranty_months',
|
|
'checkout_counter',
|
|
'checkin_counter',
|
|
'requests_counter',
|
|
'byod',
|
|
'asset_eol_date',
|
|
];
|
|
|
|
|
|
/**
|
|
* Make sure the column is allowed, and if it's a custom field, make sure we strip the custom_fields. prefix
|
|
*/
|
|
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
|
$sort_override = str_replace('custom_fields.', '', $request->input('sort'));
|
|
|
|
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
|
|
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
|
|
|
|
$assets = Asset::with('assignedTo', 'location', 'model')
|
|
->whereIn('assets.id', $asset_ids)
|
|
->withTrashed();
|
|
|
|
$assets = $assets->get();
|
|
|
|
if ($assets->isEmpty()) {
|
|
Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
|
|
return redirect()->back()->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
|
|
}
|
|
|
|
$models = $assets->unique('model_id');
|
|
$modelNames = [];
|
|
foreach($models as $model) {
|
|
$modelNames[] = $model->model->name;
|
|
}
|
|
|
|
if ($request->filled('bulk_actions')) {
|
|
|
|
|
|
switch ($request->input('bulk_actions')) {
|
|
case 'labels':
|
|
$this->authorize('view', Asset::class);
|
|
|
|
return (new Label)
|
|
->with('assets', $assets)
|
|
->with('settings', Setting::getSettings())
|
|
->with('bulkedit', true)
|
|
->with('count', 0);
|
|
|
|
case 'delete':
|
|
$this->authorize('delete', Asset::class);
|
|
$assets->each(function ($assets) {
|
|
$this->authorize('delete', $assets);
|
|
});
|
|
|
|
return view('hardware/bulk-delete')->with('assets', $assets);
|
|
|
|
case 'restore':
|
|
$this->authorize('update', Asset::class);
|
|
$assets = Asset::withTrashed()->find($asset_ids);
|
|
$assets->each(function ($asset) {
|
|
$this->authorize('delete', $asset);
|
|
});
|
|
return view('hardware/bulk-restore')->with('assets', $assets);
|
|
|
|
case 'edit':
|
|
$this->authorize('update', Asset::class);
|
|
|
|
return view('hardware/bulk')
|
|
->with('assets', $asset_ids)
|
|
->with('statuslabel_list', Helper::statusLabelList())
|
|
->with('models', $models->pluck(['model']))
|
|
->with('modelNames', $modelNames);
|
|
}
|
|
}
|
|
|
|
switch ($sort_override) {
|
|
case 'model':
|
|
$assets->OrderModels($order);
|
|
break;
|
|
case 'model_number':
|
|
$assets->OrderModelNumber($order);
|
|
break;
|
|
case 'category':
|
|
$assets->OrderCategory($order);
|
|
break;
|
|
case 'manufacturer':
|
|
$assets->OrderManufacturer($order);
|
|
break;
|
|
case 'company':
|
|
$assets->OrderCompany($order);
|
|
break;
|
|
case 'location':
|
|
$assets->OrderLocation($order);
|
|
case 'rtd_location':
|
|
$assets->OrderRtdLocation($order);
|
|
break;
|
|
case 'status_label':
|
|
$assets->OrderStatus($order);
|
|
break;
|
|
case 'supplier':
|
|
$assets->OrderSupplier($order);
|
|
break;
|
|
case 'assigned_to':
|
|
$assets->OrderAssigned($order);
|
|
break;
|
|
default:
|
|
$assets->orderBy($column_sort, $order);
|
|
break;
|
|
}
|
|
|
|
return redirect()->back()->with('error', 'No action selected');
|
|
}
|
|
|
|
/**
|
|
* Save bulk edits
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @internal param array $assets
|
|
* @since [v2.0]
|
|
*/
|
|
public function update(ImageUploadRequest $request): RedirectResponse
|
|
{
|
|
// this should be in request, but request weird, need to think it through a little
|
|
$this->authorize('update', Asset::class);
|
|
// Get the back url from the session and then destroy the session
|
|
$bulk_back_url = route('hardware.index');
|
|
$custom_field_problem = false;
|
|
// is this necessary?
|
|
if (!$request->filled('ids') || count($request->input('ids')) == 0) {
|
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
|
}
|
|
if ($request->session()->has('bulk_back_url')) {
|
|
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
|
}
|
|
// find and update assets
|
|
$assets = Asset::whereIn('id', $request->input('ids'))->get();
|
|
$errors = [];
|
|
foreach ($assets as $key => $asset) {
|
|
try {
|
|
$updatedAsset = UpdateAssetAction::run(
|
|
asset: $asset,
|
|
request: $request,
|
|
status_id: $request->input('status_id'),
|
|
warranty_months: $request->input('warranty_months'),
|
|
purchase_cost: $request->input('purchase_cost'),
|
|
purchase_date: $request->filled('null_purchase_date') ? null : $request->input('purchase_date'),
|
|
next_audit_date: $request->filled('null_next_audit_date') ? null : $request->input('next_audit_date'),
|
|
supplier_id: $request->input('supplier_id'),
|
|
expected_checkin: $request->filled('null_expected_checkin_date') ? null : $request->input('expected_checkin'),
|
|
requestable: $request->input('requestable'),
|
|
rtd_location_id: $request->input('rtd_location_id'),
|
|
name: $request->filled('null_name') ? null : $request->input('name'),
|
|
company_id: $request->input('company_id'),
|
|
model_id: $request->input('model_id'),
|
|
order_number: $request->input('order_number'),
|
|
isBulk: true,
|
|
);
|
|
// catch exceptions
|
|
} catch (ValidationException $e) {
|
|
$errors[$key] = $e->getMessage();
|
|
} catch (CustomFieldPermissionException $e) {
|
|
//$errors[$key] = $e->getMessage();
|
|
$custom_field_problem = true;
|
|
} catch (\Exception $e) {
|
|
report($e);
|
|
$errors[$key] = trans('general.something_went_wrong');
|
|
}
|
|
}
|
|
if (!empty($errors)) {
|
|
return redirect($bulk_back_url)->with('bulk_asset_errors', $errors);
|
|
}
|
|
if ($custom_field_problem) {
|
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.encrypted_warning'));
|
|
}
|
|
return redirect($bulk_back_url)->with('success', trans('bulk.update.success'));
|
|
}
|
|
|
|
/**
|
|
* Array to store update data per item
|
|
* @var array
|
|
*/
|
|
private $update_array;
|
|
|
|
/**
|
|
* Adds parameter to update array for an item if it exists in request
|
|
* @param string $field field name
|
|
*/
|
|
protected function conditionallyAddItem($field) : BulkAssetsController
|
|
{
|
|
if (request()->filled($field)) {
|
|
$this->update_array[$field] = request()->input($field);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Save bulk deleted.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @param Request $request
|
|
* @internal param array $assets
|
|
* @since [v2.0]
|
|
*/
|
|
public function destroy(Request $request) : RedirectResponse
|
|
{
|
|
$this->authorize('delete', Asset::class);
|
|
|
|
$bulk_back_url = route('hardware.index');
|
|
if ($request->session()->has('bulk_back_url')) {
|
|
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
|
}
|
|
|
|
if ($request->filled('ids')) {
|
|
$assets = Asset::find($request->get('ids'));
|
|
foreach ($assets as $asset) {
|
|
$asset->delete();
|
|
} // endforeach
|
|
|
|
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
|
|
// no values given, nothing to update
|
|
}
|
|
|
|
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
|
|
}
|
|
|
|
/**
|
|
* Show Bulk Checkout Page
|
|
*/
|
|
public function showCheckout() : View
|
|
{
|
|
$this->authorize('checkout', Asset::class);
|
|
return view('hardware/bulk-checkout');
|
|
}
|
|
|
|
/**
|
|
* Process Multiple Checkout Request
|
|
*/
|
|
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
|
|
{
|
|
|
|
$this->authorize('checkout', Asset::class);
|
|
|
|
try {
|
|
$admin = auth()->user();
|
|
|
|
$target = $this->determineCheckoutTarget();
|
|
|
|
if (! is_array($request->get('selected_assets'))) {
|
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
|
|
}
|
|
|
|
$asset_ids = array_filter($request->get('selected_assets'));
|
|
|
|
if (request('checkout_to_type') == 'asset') {
|
|
foreach ($asset_ids as $asset_id) {
|
|
if ($target->id == $asset_id) {
|
|
return redirect()->back()->with('error', 'You cannot check an asset out to itself.');
|
|
}
|
|
}
|
|
}
|
|
$checkout_at = date('Y-m-d H:i:s');
|
|
if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) {
|
|
$checkout_at = e($request->get('checkout_at'));
|
|
}
|
|
|
|
$expected_checkin = '';
|
|
|
|
if ($request->filled('expected_checkin')) {
|
|
$expected_checkin = e($request->get('expected_checkin'));
|
|
}
|
|
|
|
$errors = [];
|
|
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
|
|
foreach ($asset_ids as $asset_id) {
|
|
$asset = Asset::findOrFail($asset_id);
|
|
$this->authorize('checkout', $asset);
|
|
|
|
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
|
|
|
|
//TODO - I think this logic is duplicated in the checkOut method?
|
|
if ($target->location_id != '') {
|
|
$asset->location_id = $target->location_id;
|
|
// TODO - I don't know why this is being saved without events
|
|
$asset::withoutEvents(function () use ($asset) {
|
|
$asset->save();
|
|
});
|
|
}
|
|
|
|
if (!$checkout_success) {
|
|
$errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
|
|
}
|
|
}
|
|
});
|
|
|
|
if (! $errors) {
|
|
// Redirect to the new asset page
|
|
return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
|
|
}
|
|
// Redirect to the asset management page with error
|
|
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
|
|
} catch (ModelNotFoundException $e) {
|
|
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
|
|
}
|
|
|
|
}
|
|
public function restore(Request $request) : RedirectResponse
|
|
{
|
|
$this->authorize('update', Asset::class);
|
|
$assetIds = $request->get('ids');
|
|
|
|
if (empty($assetIds)) {
|
|
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
|
|
} else {
|
|
foreach ($assetIds as $key => $assetId) {
|
|
$asset = Asset::withTrashed()->find($assetId);
|
|
$asset->restore();
|
|
}
|
|
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
|
}
|
|
}
|
|
}
|