] */ class AssetsController extends Controller { protected $qrCodeDimensions = ['height' => 3.5, 'width' => 3.5]; protected $barCodeDimensions = ['height' => 2, 'width' => 22]; public function __construct() { $this->middleware('auth'); parent::__construct(); } /** * Returns a view that invokes the ajax tables which actually contains * the content for the assets listing, which is generated in getDatatable. * * @author [A. Gianotto] [] * * @see AssetController::getDatatable() method that generates the JSON response * @since [v1.0] */ public function index(Request $request): View { $this->authorize('index', Asset::class); $companyId = $request->input('company_id'); $company = is_scalar($companyId) ? Company::find($companyId) : null; return view('hardware/index')->with('company', $company); } /** * Returns a view that presents a form to create a new asset. * * @author [A. Gianotto] [] * * @since [v1.0] * * @internal param int $model_id */ public function create(Request $request): View { $this->authorize('create', Asset::class); $view = view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('item', new Asset) ->with('statuslabel_types', Helper::statusTypeList()); if ($request->filled('model_id')) { $selected_model = AssetModel::find($request->input('model_id')); $view->with('selected_model', $selected_model); } return $view; } /** * Validate and process new asset form data. * * @author [A. Gianotto] [] * * @since [v1.0] */ public function store(CreateMultipleAssetRequest $request): RedirectResponse { $this->authorize(Asset::class); // There are a lot more rules to add here but prevents // errors around `asset_tags` not being present below. $this->validate($request, ['asset_tags' => ['required', 'array']]); // Handle asset tags - there could be one, or potentially many. // This is only necessary on create, not update, since bulk editing is handled // differently $asset_tags = $request->input('asset_tags'); $model = AssetModel::find($request->input('model_id')); $serial_errors = []; $serials = $request->input('serials'); $settings = Setting::getSettings(); // Validate required serial based on model setting for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { if ($model && $model->require_serial === 1 && empty($serials[$a])) { $serial_errors["serials.$a"] = trans('admin/hardware/form.serial_required', ['number' => $a]); } } if (! empty($serial_errors)) { return redirect()->back() ->withInput() ->withErrors($serial_errors); } $asset = null; $companyId = Company::getIdForCurrentUser($request->input('company_id')); $successes = []; $failures = []; try { DB::beginTransaction(); for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) { $asset = new Asset; $asset->model()->associate($model); $asset->name = $request->input('name'); // Check for a corresponding serial if (($serials) && (array_key_exists($a, $serials))) { $asset->serial = $serials[$a]; } if (($asset_tags) && (array_key_exists($a, $asset_tags))) { $asset->asset_tag = $asset_tags[$a]; } $asset->company_id = $companyId; $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); $asset->created_by = auth()->id(); $asset->status_id = request('status_id'); $asset->warranty_months = request('warranty_months', null); $asset->purchase_cost = request('purchase_cost'); $asset->purchase_date = request('purchase_date', null); $asset->asset_eol_date = request('asset_eol_date', null); $asset->assigned_to = request('assigned_to', null); $asset->supplier_id = request('supplier_id', null); $asset->requestable = request('requestable', 0); $asset->rtd_location_id = request('rtd_location_id', null); $asset->byod = request('byod', 0); if (! empty($settings->audit_interval)) { $asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString(); } // Set location_id to rtd_location_id ONLY if the asset isn't being checked out if (! request('assigned_user') && ! request('assigned_asset') && ! request('assigned_location')) { $asset->location_id = $request->input('rtd_location_id', null); } 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); } // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { if ($field->field_encrypted == '1') { if (Gate::allows('assets.view.encrypted_custom_fields')) { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); } else { $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); } } } else { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); } else { $asset->{$field->db_column} = $request->input($field->db_column); } } } } // Validate the asset before saving // Note - it can be tempting to instead want to call saveOrFail(), to automatically throw when an object // is invalid (and can't save). But this won't work, because Custom Fields _overrides_ the save() method // to inject the Custom Field Rules into the $rules property right before invoking the _real_ save. // so, instead, we have to catch failures on the 'else' clause and throw there. if ($asset->isValid() && $asset->save()) { $target = null; $location = null; if ($userId = request('assigned_user')) { $target = User::find($userId); if (! $target) { return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user')); } $location = $target->location_id; } elseif ($assetId = request('assigned_asset')) { $target = Asset::find($assetId); if (! $target) { return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset')); } $location = $target->location_id; } elseif ($locationId = request('assigned_location')) { $target = Location::find($locationId); if (! $target) { return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location')); } $location = $target->id; } if (isset($target)) { $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->input('name'), $location); } $successes[] = "".e($asset->asset_tag).''; } else { $asset->throwValidationException(); // we have to do this for the reason listed above - can't use saveOrFail() $failures[] = implode(',', $asset->getErrors()->all()); // TODO - this can probably go away soon } } } catch (\Throwable $e) { \Log::debug('Caught exception in multi-create - rolling back: '.$e->getMessage()); DB::rollBack(); throw $e; } DB::commit(); if ($request->input('redirect_option') === 'back') { session()->put(['redirect_option' => 'index']); } else { session()->put(['redirect_option' => $request->input('redirect_option')]); } session()->put(['checkout_to_type' => $request->input('checkout_to_type'), 'other_redirect' => 'model']); if ($successes) { if ($failures) { // some succeeded, some failed return Helper::getRedirectOption($request, $asset->id, 'Assets') // FIXME - not tested ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => implode(', ', $successes)])) ->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => implode('; ', $failures)])); } else { if (count($successes) == 1) { // the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed // and re-translated return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)])); } else { // multi-success return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => implode(', ', $successes)])); } } } return redirect()->back()->withInput()->withErrors($asset->getErrors()); } /** * Returns a view that presents a form to edit an existing asset. * * @author [A. Gianotto] [] * * @since [v1.0] * * @return View */ public function edit(Asset $asset): View|RedirectResponse { $this->authorize($asset); if ($safeReferer = Helper::sameOriginUrl(url()->previous())) { session()->put('url.intended', $safeReferer); } return view('hardware/edit') ->with('item', $asset) ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()); } /** * Returns a view that presents information about an asset for detail view. * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] * * @return View */ public function show(Asset $asset): View|RedirectResponse { $this->authorize('view', $asset); $settings = Setting::getSettings(); if (isset($asset)) { $audit_log = Actionlog::where('action_type', '=', 'audit') ->where('item_id', '=', $asset->id) ->where('item_type', '=', Asset::class) ->orderBy('created_at', 'DESC')->first(); if ($asset->location) { $use_currency = $asset->location->currency; } else { if ($settings->default_currency != '') { $use_currency = $settings->default_currency; } else { $use_currency = trans('general.currency'); } } $qr_code = (object) [ 'display' => $settings->qr_code == '1', 'url' => route('qr_code/common', ['object_type' => 'hardware', 'id' => $asset->id]), ]; $total_maintenance_cost = $asset->maintenances?->sum('cost'); $total_asset_cost = ($asset->assignedAssets()?->AssetsForShow()) ? $asset->assignedAssets()?->AssetsForShow()?->sum('purchase_cost') : 0; $total_license_cost = ($asset->licenses) ? $asset->licenses->sum('purchase_cost') : 0; $total_accessory_cost = ($asset->accessories) ? $asset->accessories()->sum('purchase_cost') : 0; $total_component_cost = ($asset->components) ? $asset->components->sum('calculated_purchase_cost') : 0; $total_cost_for_asset = $asset->purchase_cost + $total_maintenance_cost + $total_asset_cost + $total_license_cost + $total_accessory_cost + $total_component_cost; $audit_custom_field_columns = []; if ($asset->model && $asset->model->fieldset) { $audit_custom_field_columns = $asset->model->fieldset->fields ->where('display_audit', '1') ->map(fn ($field) => [ 'field' => $field->db_column, 'searchable' => false, 'sortable' => false, 'switchable' => true, 'title' => e($field->name), 'visible' => true, ]) ->values() ->all(); } return view('hardware/view', compact('asset', 'qr_code', 'settings')) ->with('total_maintenance_cost', $total_maintenance_cost) ->with('total_asset_cost', $total_asset_cost) ->with('total_license_cost', $total_license_cost) ->with('total_accessory_cost', $total_accessory_cost) ->with('total_component_cost', $total_component_cost) ->with('total_cost_for_asset', $total_cost_for_asset) ->with('use_currency', $use_currency) ->with('audit_log', $audit_log) ->with('audit_custom_field_columns', $audit_custom_field_columns); } return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } /** * Validate and process asset edit form. * * @param int $assetId * * @since [v1.0] * * @author [A. Gianotto] [] */ public function update(ImageUploadRequest $request, Asset $asset): RedirectResponse { $this->authorize($asset); $asset->status_id = $request->input('status_id', null); $asset->warranty_months = $request->input('warranty_months', null); $asset->purchase_cost = $request->input('purchase_cost', null); $asset->purchase_date = $request->input('purchase_date', null); $asset->next_audit_date = $request->input('next_audit_date', null); if ($request->filled('purchase_date') && ! $request->filled('asset_eol_date') && ($asset->model?->eol > 0)) { $asset->purchase_date = $request->input('purchase_date', null); $asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); $asset->eol_explicit = false; } elseif ($request->filled('asset_eol_date')) { $asset->asset_eol_date = $request->input('asset_eol_date', null); $months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true); if ($asset->model->eol) { if ($months != $asset->model->eol > 0) { $asset->eol_explicit = true; } else { $asset->eol_explicit = false; } } else { $asset->eol_explicit = true; } } elseif (! $request->filled('asset_eol_date') && (($asset->model?->eol) == 0)) { $asset->asset_eol_date = null; $asset->eol_explicit = false; } $asset->supplier_id = $request->input('supplier_id', null); $asset->expected_checkin = $request->input('expected_checkin', null); $asset->requestable = $request->input('requestable', 0); $asset->rtd_location_id = $request->input('rtd_location_id', null); $asset->byod = $request->input('byod', 0); $status = Statuslabel::find($request->input('status_id')); // This is an archived or undeployable - we should check the asset back in. // Pending is allowed here if (($status) && (($status->getStatuslabelType() != 'pending') && ($status->getStatuslabelType() != 'deployable')) && ($target = $asset->assignedTo)) { $originalValues = $asset->getRawOriginal(); $asset->assigned_to = null; $asset->assigned_type = null; $asset->accepted = null; $asset->last_checkin = now(); event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues)); } if ($request->filled('image_delete')) { try { unlink(public_path().'/uploads/assets/'.basename($asset->image)); $asset->image = ''; } catch (\Exception $e) { Log::info($e); } } // Update the asset data $serial = $request->input('serials'); $asset->serial = $request->input('serials'); if (is_array($request->input('serials'))) { $asset->serial = $serial[1]; } $asset->name = $request->input('name'); $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset_tags = $request->input('asset_tags'); $asset->asset_tag = $request->input('asset_tags'); if (is_array($request->input('asset_tags'))) { $asset->asset_tag = $asset_tags[1]; } $asset->notes = $request->input('notes'); $asset = $request->handleImages($asset); // Update custom fields in the database. // FIXME: No idea why this is returning a Builder error on db_column_name. // Need to investigate and fix. Using static method for now. $model = AssetModel::find($request->input('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { if ($field->element == 'checkbox' && ! $request->has($field->db_column)) { $asset->{$field->db_column} = null; } if ($request->has($field->db_column)) { if ($field->field_encrypted == '1') { if (Gate::allows('assets.view.encrypted_custom_fields')) { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); } else { $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); } } } else { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); } else { $asset->{$field->db_column} = $request->input($field->db_column); } } } } } session()->put([ 'redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type'), 'other_redirect' => $request->input('redirect_option') === 'other_redirect' ? 'model' : null, ]); // Validate required serial based on model setting if ($model && $model->require_serial === 1 && empty($serial[1])) { return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('warning', trans('admin/hardware/form.serial_required_post_model_update', [ 'asset_model' => $model->name, ])); } if ($asset->save()) { return Helper::getRedirectOption($request, $asset->id, 'Assets') ->with('success', trans('admin/hardware/message.update.success')); } return redirect()->back()->withInput()->withErrors($asset->getErrors()); } /** * Delete a given asset (mark as deleted). * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] */ public function destroy(Request $request, Asset $asset): RedirectResponse { $this->authorize('delete', $asset); if ($asset->assignedTo) { $target = $asset->assignedTo; $checkin_at = date('Y-m-d H:i:s'); $originalValues = $asset->getRawOriginal(); event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); DB::table('assets') ->where('id', $asset->id) ->update(['assigned_to' => null, 'assigned_type' => null]); } // Note: the image file is deliberately preserved across this // soft-delete. Snipe-IT's `snipeit:purge` command permanently // removes it later when the row is force-deleted. Keeping the // file here means a restored soft-deleted row still has its // image. $asset->delete(); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success')); } /** * Searches the assets table by serial, and redirects if it finds one * * @author [A. Gianotto] [] * * @since [v3.0] */ public function getAssetBySerial(Request $request, $serial = null): RedirectResponse { $serial = $serial ?: $request->input('serial'); $topsearch = ($request->input('topsearch') == 'true'); if (! $asset = Asset::where('serial', '=', $serial)->first()) { return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } $this->authorize('view', $asset); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); } /** * Searches the assets table by asset tag, and redirects if it finds one * * @author [A. Gianotto] [] * * @since [v3.0] */ public function getAssetByTag(Request $request, $tag = null): RedirectResponse { $tag = $tag ? $tag : $request->input('assetTag'); $topsearch = ($request->input('topsearch') == 'true'); // Search for an exact and unique asset tag match $assets = Asset::where('asset_tag', '=', $tag); // If not a unique result, redirect to the index view if ($assets->count() != 1) { return redirect()->route('hardware.index') ->with('search', $tag) ->with('warning', trans('admin/hardware/message.does_not_exist_var', ['asset_tag' => $tag])); } $asset = $assets->first(); $this->authorize('view', $asset); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); } /** * Return a QR code for the asset * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] */ public function getQrCode(Asset $asset): Response|BinaryFileResponse|string|bool { $settings = Setting::getSettings(); if ($settings->label2_2d_type !== 'none') { if ($asset) { $size = Helper::barcodeDimensions($settings->label2_2d_type); $qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png'; if (isset($asset->id, $asset->asset_tag)) { if (file_exists($qr_file)) { $header = ['Content-type' => 'image/png']; return response()->file($qr_file, $header); } else { $barcode = new Barcode; $barcode_obj = $barcode->getBarcodeObj($settings->label2_2d_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', [-2, -2, -2, -2]); file_put_contents($qr_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); } } } return 'That asset is invalid'; } return false; } /** * Return a 2D barcode for the asset * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] * * @return Response */ public function getBarCode($assetId = null) { $settings = Setting::getSettings(); if ($asset = Asset::withTrashed()->find($assetId)) { $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->label2_1d_type).'-'.str_slug($asset->asset_tag).'.png'; if (isset($asset->id, $asset->asset_tag)) { if (file_exists($barcode_file)) { $header = ['Content-type' => 'image/png']; return response()->file($barcode_file, $header); } else { // Calculate barcode width in pixel based on label width (inch) $barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001; $barcode = new Barcode; try { $barcode_obj = $barcode->getBarcodeObj($settings->label2_1d_type, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50); file_put_contents($barcode_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); } catch (\Exception|TypeError $e) { Log::debug('The barcode format is invalid.'); return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif'); } } } } return null; } /** * Return a label for an individual asset. * * @author [L. Swartzendruber] [ * * @param int $assetId * @return View */ public function getLabel($assetId = null) { if (isset($assetId)) { $asset = Asset::find($assetId); $this->authorize('view', $asset); return (new Label) ->with('assets', collect([$asset])) ->with('settings', Setting::getSettings()) ->with('template', request()->input('template')) ->with('offset', request()->input('offset')) ->with('bulkedit', false) ->with('count', 0); } } /** * Returns a view that presents a form to clone an asset. * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] * * @return View */ public function getClone(Asset $asset) { $this->authorize('create', Asset::class); $cloned = clone $asset; $cloned_model = $asset; $cloned->id = null; $cloned->asset_tag = ''; $cloned->serial = ''; $cloned->assigned_to = ''; $cloned->deleted_at = ''; return view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()) ->with('cloned_model', $cloned_model) ->with('item', $cloned); } public function sortByName(array $recordA, array $recordB): int { return strcmp($recordB['Full Name'], $recordA['Full Name']); } /** * Restore a deleted asset. * * @author [A. Gianotto] [] * * @param int $assetId * * @since [v1.0] * * @return View */ public function getRestore($assetId = null) { if ($asset = Asset::withTrashed()->find($assetId)) { $this->authorize('delete', $asset); if ($asset->deleted_at == '') { return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset')])); } if ($asset->restore()) { // Redirect them to the deleted page if there are more, otherwise the section index $deleted_assets = Asset::onlyTrashed()->count(); if ($deleted_assets > 0) { return redirect()->back()->with('success', trans('admin/hardware/message.restore.success')); } return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); } // Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()])); } return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); } public function quickScan() { $this->authorize('audit', Asset::class); $settings = Setting::getSettings(); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); return view('hardware/quickscan')->with('next_audit_date', $dt); } public function quickScanCheckin() { $this->authorize('checkin', Asset::class); return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList()); } public function dueForAudit() { $this->authorize('audit', Asset::class); return view('hardware/audit-due'); } public function dueForCheckin() { $this->authorize('checkin', Asset::class); return view('hardware/checkin-due'); } public function audit(Asset $asset): View|RedirectResponse { $this->authorize('audit', Asset::class); $settings = Setting::getSettings(); // Invoke the validation to see if the audit will complete successfully $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); if ($asset->isInvalid()) { return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); } $dt = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString(); return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list'); } public function auditStore(UploadFileRequest $request, Asset $asset) { $this->authorize('audit', Asset::class); session()->put('redirect_option', $request->input('redirect_option')); session()->put('other_redirect', 'audit'); $originalValues = $asset->getRawOriginal(); $asset->next_audit_date = $request->input('next_audit_date'); $asset->last_audit_date = date('Y-m-d H:i:s'); // Check to see if they checked the box to update the physical location, // not just note it in the audit notes if ($request->input('update_location') == '1') { $asset->location_id = $request->input('location_id'); } // Update custom fields in the database if (($asset->model) && ($asset->model->fieldset)) { foreach ($asset->model->fieldset->fields as $field) { if (($field->display_audit == '1') && ($request->has($field->db_column))) { if ($field->field_encrypted == '1') { if (Gate::allows('assets.view.encrypted_custom_fields')) { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); } else { $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); } } } else { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); } else { $asset->{$field->db_column} = $request->input($field->db_column); } } } } } // Invoke the validation to see if the audit will complete successfully $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); // Validate the rest of the data before we turn off the event dispatcher if ($asset->isInvalid()) { return redirect()->back()->withInput()->withErrors($asset->getErrors()); } /** * Even though we do a save() further down, we don't want to log this as a "normal" asset update, * which would trigger the Asset Observer and would log an asset *update* log entry (because the * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to * the audit log entry we're creating through this controller. * * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() * will bypass normal model-level validation that's usually handled at the observer ) * * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, * which manually invokes Watson Validating to make sure the asset's model is valid. * * @see AssetObserver::updating() * @see Asset::save() */ $asset->unsetEventDispatcher(); /** * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. * We have to invoke this manually because of the unsetEventDispatcher() above.) */ if ($asset->isValid() && $asset->save()) { $file_name = null; // Create the image (if one was chosen.) if ($request->hasFile('image')) { $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image')); } $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues); return Helper::getRedirectOption($request, $asset->id, 'Assets')->with('success', trans('admin/hardware/message.audit.success')); } return redirect()->back()->withInput()->withErrors($asset->getErrors()); } public function getRequestedIndex($user_id = null) { $this->authorize('index', Asset::class); $requestedItems = CheckoutRequest::with('user', 'requestedItem')->whereNull('canceled_at'); if ($user_id) { $requestedItems->where('user_id', $user_id); } $requestedItems = $requestedItems->orderBy('created_at', 'desc')->get(); if (Company::isFullMultipleCompanySupportEnabled() && ! auth()->user()->isSuperUser()) { $requestedItems = $requestedItems->filter( fn (CheckoutRequest $request) => $request->requestable && Company::isCurrentUserHasAccess($request->requestable) )->values(); } return view('hardware/requested', compact('requestedItems')); } }