mirror of
https://github.com/snipe/snipe-it.git
synced 2025-02-02 08:21:09 -08:00
Merge branch 'develop' into snipeit_v7_laravel10
This commit is contained in:
commit
6210716199
|
@ -2979,6 +2979,24 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Azooz2014",
|
||||
"name": "Abdelaziz Faki",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7429229?v=4",
|
||||
"profile": "https://azooz2014.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bilias",
|
||||
"name": "bilias",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/47315739?v=4",
|
||||
"profile": "https://github.com/bilias",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-328-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-330-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
|
||||
|
||||
## Snipe-IT - Open Source Asset Management System
|
||||
|
||||
|
@ -145,7 +145,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
|
|||
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
@ -91,6 +91,7 @@ class LdapSync extends Command
|
|||
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
|
||||
}
|
||||
}
|
||||
|
||||
else if ($this->option('base_dn') != '') {
|
||||
$search_base = $this->option('base_dn');
|
||||
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
|
||||
|
|
|
@ -18,31 +18,36 @@ class AccessoryCheckoutController extends Controller
|
|||
* Return the form to checkout an Accessory to a user.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param int $accessoryId
|
||||
* @param int $id
|
||||
* @return View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($accessoryId)
|
||||
public function create($id)
|
||||
{
|
||||
// Check if the accessory exists
|
||||
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
}
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($accessory->numRemaining() <= 0){
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
|
||||
}
|
||||
|
||||
if ($accessory->category) {
|
||||
if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
|
||||
|
||||
$this->authorize('checkout', $accessory);
|
||||
|
||||
// Get the dropdown of users and then pass it to the checkout view
|
||||
return view('accessories/checkout', compact('accessory'));
|
||||
if ($accessory->category) {
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($accessory->numRemaining() <= 0){
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('accessories/checkout', compact('accessory'));
|
||||
}
|
||||
|
||||
// Invalid category
|
||||
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
|
||||
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
|
||||
// Not found
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Events\CheckoutableCheckedIn;
|
||||
use App\Http\Requests\StoreAssetRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
@ -534,8 +533,10 @@ class AssetsController extends Controller
|
|||
* @since [v4.0]
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function store(StoreAssetRequest $request)
|
||||
public function store(ImageUploadRequest $request)
|
||||
{
|
||||
$this->authorize('create', Asset::class);
|
||||
|
||||
$asset = new Asset();
|
||||
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
|
||||
|
||||
|
@ -545,8 +546,7 @@ class AssetsController extends Controller
|
|||
$asset->model_id = $request->get('model_id');
|
||||
$asset->order_number = $request->get('order_number');
|
||||
$asset->notes = $request->get('notes');
|
||||
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset()); //yup, problem :/
|
||||
// NO IT IS NOT!!! This is never firing; we SHOW the asset_tag you're going to get, so it *will* be filled in!
|
||||
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
|
||||
$asset->user_id = Auth::id();
|
||||
$asset->archived = '0';
|
||||
$asset->physical = '1';
|
||||
|
@ -754,34 +754,24 @@ class AssetsController extends Controller
|
|||
*/
|
||||
public function restore(Request $request, $assetId = null)
|
||||
{
|
||||
// Get asset information
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
$this->authorize('delete', $asset);
|
||||
|
||||
if (isset($asset->id)) {
|
||||
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||
$this->authorize('delete', $asset);
|
||||
|
||||
if ($asset->deleted_at=='') {
|
||||
$message = 'Asset was not deleted. No data was changed.';
|
||||
|
||||
} else {
|
||||
|
||||
$message = trans('admin/hardware/message.restore.success');
|
||||
// Restore the asset
|
||||
Asset::withTrashed()->where('id', $assetId)->restore();
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Asset::class;
|
||||
$logaction->item_id = $asset->id;
|
||||
$logaction->created_at = date("Y-m-d H:i:s");
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restored');
|
||||
if ($asset->deleted_at == '') {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.asset')])), 200);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset, $request), $message));
|
||||
|
||||
if ($asset->restore()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/hardware/message.restore.success')), 200);
|
||||
}
|
||||
|
||||
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()])), 200);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -263,9 +263,14 @@ class ConsumablesController extends Controller
|
|||
// Make sure there is at least one available to checkout
|
||||
if ($consumable->numRemaining() <= 0) {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
|
||||
\Log::debug('No enough remaining');
|
||||
}
|
||||
|
||||
// Make sure there is a valid category
|
||||
if (!$consumable->category){
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
|
||||
}
|
||||
|
||||
|
||||
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
|
||||
if (!$user = User::find($request->input('assigned_to'))) {
|
||||
// Return error message
|
||||
|
|
|
@ -6,9 +6,11 @@ use App\Helpers\Helper;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Transformers\ManufacturersTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Manufacturer;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ManufacturersController extends Controller
|
||||
|
@ -159,6 +161,44 @@ class ManufacturersController extends Controller
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a given Manufacturer (mark as un-deleted)
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.3.4]
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function restore($id)
|
||||
{
|
||||
$this->authorize('delete', Manufacturer::class);
|
||||
|
||||
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
|
||||
|
||||
if ($manufacturer->deleted_at == '') {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')])), 200);
|
||||
}
|
||||
|
||||
if ($manufacturer->restore()) {
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Manufacturer::class;
|
||||
$logaction->item_id = $manufacturer->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restore');
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
|
||||
}
|
||||
|
||||
// Check validation to make sure we're not restoring an item with the same unique attributes as a non-deleted one
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()])), 200);
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a paginated collection for the select2 menus
|
||||
*
|
||||
|
|
|
@ -11,6 +11,7 @@ use App\Http\Transformers\ConsumablesTransformer;
|
|||
use App\Http\Transformers\LicensesTransformer;
|
||||
use App\Http\Transformers\SelectlistTransformer;
|
||||
use App\Http\Transformers\UsersTransformer;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\License;
|
||||
|
@ -691,17 +692,31 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function restore($userId = null)
|
||||
{
|
||||
// Get asset information
|
||||
$user = User::withTrashed()->find($userId);
|
||||
$this->authorize('delete', $user);
|
||||
if (isset($user->id)) {
|
||||
// Restore the user
|
||||
User::withTrashed()->where('id', $userId)->restore();
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
|
||||
if ($user = User::withTrashed()->find($userId)) {
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if ($user->deleted_at == '') {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.user')])), 200);
|
||||
}
|
||||
|
||||
if ($user->restore()) {
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->item_id = $user->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restore');
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/users/message.restore.success')), 200);
|
||||
}
|
||||
|
||||
// Check validation to make sure we're not restoring a user with the same username as an existing user
|
||||
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()])), 200);
|
||||
}
|
||||
|
||||
$id = $userId;
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))), 200);
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ namespace App\Http\Controllers;
|
|||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
|
@ -209,7 +212,7 @@ class AssetModelsController extends Controller
|
|||
$this->authorize('delete', AssetModel::class);
|
||||
// Check if the model exists
|
||||
if (is_null($model = AssetModel::find($modelId))) {
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.not_found'));
|
||||
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
|
||||
}
|
||||
|
||||
if ($model->assets()->count() > 0) {
|
||||
|
@ -237,22 +240,42 @@ class AssetModelsController extends Controller
|
|||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param int $modelId
|
||||
* @param int $id
|
||||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function getRestore($modelId = null)
|
||||
public function getRestore($id)
|
||||
{
|
||||
$this->authorize('create', AssetModel::class);
|
||||
// Get user information
|
||||
$model = AssetModel::withTrashed()->find($modelId);
|
||||
|
||||
if (isset($model->id)) {
|
||||
$model->restore();
|
||||
if ($model = AssetModel::withTrashed()->find($id)) {
|
||||
|
||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
||||
if ($model->deleted_at == '') {
|
||||
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset_model')]));
|
||||
}
|
||||
|
||||
if ($model->restore()) {
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->item_id = $model->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restore');
|
||||
|
||||
|
||||
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||
$deleted_models = AssetModel::onlyTrashed()->count();
|
||||
if ($deleted_models > 0) {
|
||||
return redirect()->back()->with('success', trans('admin/models/message.restore.success'));
|
||||
}
|
||||
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
|
||||
}
|
||||
|
||||
// Check validation
|
||||
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset_model'), 'error' => $model->getErrors()->first()]));
|
||||
}
|
||||
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Helpers\Helper;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Manufacturer;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Models\Asset;
|
||||
use App\Models\AssetModel;
|
||||
|
@ -204,8 +205,9 @@ class AssetsController extends Controller
|
|||
}
|
||||
|
||||
if ($success) {
|
||||
\Log::debug(e($asset->asset_tag));
|
||||
return redirect()->route('hardware.index')
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => $asset->asset_tag]));
|
||||
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
|
||||
|
||||
|
||||
}
|
||||
|
@ -794,21 +796,24 @@ class AssetsController extends Controller
|
|||
*/
|
||||
public function getRestore($assetId = null)
|
||||
{
|
||||
// Get asset information
|
||||
$asset = Asset::withTrashed()->find($assetId);
|
||||
$this->authorize('delete', $asset);
|
||||
if (isset($asset->id)) {
|
||||
// Restore the asset
|
||||
Asset::withTrashed()->where('id', $assetId)->restore();
|
||||
if ($asset = Asset::withTrashed()->find($assetId)) {
|
||||
$this->authorize('delete', $asset);
|
||||
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Asset::class;
|
||||
$logaction->item_id = $asset->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restored');
|
||||
if ($asset->deleted_at == '') {
|
||||
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset')]));
|
||||
}
|
||||
|
||||
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
|
||||
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'));
|
||||
|
|
|
@ -56,7 +56,6 @@ class LoginController extends Controller
|
|||
parent::__construct();
|
||||
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
|
||||
Session::put('backUrl', \URL::previous());
|
||||
// $this->ldap = $ldap;
|
||||
$this->saml = $saml;
|
||||
}
|
||||
|
||||
|
@ -82,7 +81,6 @@ class LoginController extends Controller
|
|||
}
|
||||
|
||||
if (Setting::getSettings()->login_common_disabled == '1') {
|
||||
\Log::debug('login_common_disabled is set to 1 - return a 403');
|
||||
return view('errors.403');
|
||||
}
|
||||
|
||||
|
@ -123,7 +121,7 @@ class LoginController extends Controller
|
|||
|
||||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->save();
|
||||
$user->saveQuietly();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
@ -199,7 +197,7 @@ class LoginController extends Controller
|
|||
$user->email = $ldap_attr['email'];
|
||||
$user->first_name = $ldap_attr['firstname'];
|
||||
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
|
||||
$user->save();
|
||||
$user->saveQuietly();
|
||||
} // End if(!user)
|
||||
return $user;
|
||||
}
|
||||
|
@ -319,7 +317,7 @@ class LoginController extends Controller
|
|||
if ($user = Auth::user()) {
|
||||
$user->last_login = \Carbon::now();
|
||||
$user->activated = 1;
|
||||
$user->save();
|
||||
$user->saveQuietly();
|
||||
}
|
||||
// Redirect to the users page
|
||||
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
|
||||
|
@ -371,7 +369,7 @@ class LoginController extends Controller
|
|||
[-2, -2, -2, -2]
|
||||
);
|
||||
|
||||
$user->save(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
|
||||
$user->saveQuietly(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
|
||||
|
||||
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
|
||||
}
|
||||
|
@ -426,7 +424,7 @@ class LoginController extends Controller
|
|||
|
||||
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
|
||||
$user->two_factor_enrolled = 1;
|
||||
$user->save();
|
||||
$user->saveQuietly();
|
||||
$request->session()->put('2fa_authed', $user->id);
|
||||
|
||||
return redirect()->route('home')->with('success', 'You are logged in!');
|
||||
|
|
|
@ -20,25 +20,38 @@ class ComponentCheckoutController extends Controller
|
|||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ComponentCheckoutController::store() method that stores the data.
|
||||
* @since [v3.0]
|
||||
* @param int $componentId
|
||||
* @param int $id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($componentId)
|
||||
public function create($id)
|
||||
{
|
||||
// Check if the component exists
|
||||
if (is_null($component = Component::find($componentId))) {
|
||||
// Redirect to the component management page with error
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
}
|
||||
$this->authorize('checkout', $component);
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($component->numRemaining() <= 0){
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.checkout.unavailable'));
|
||||
if ($component = Component::find($id)) {
|
||||
|
||||
$this->authorize('checkout', $component);
|
||||
|
||||
// Make sure the category is valid
|
||||
if ($component->category) {
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($component->numRemaining() <= 0){
|
||||
return redirect()->route('components.index')
|
||||
->with('error', trans('admin/components/message.checkout.unavailable'));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('components/checkout', compact('component'));
|
||||
}
|
||||
|
||||
// Invalid category
|
||||
return redirect()->route('components.edit', ['component' => $component->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.component')]));
|
||||
}
|
||||
|
||||
return view('components/checkout', compact('component'));
|
||||
// Not found
|
||||
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Consumables;
|
|||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -18,25 +19,38 @@ class ConsumableCheckoutController extends Controller
|
|||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @see ConsumableCheckoutController::store() method that stores the data.
|
||||
* @since [v1.0]
|
||||
* @param int $consumableId
|
||||
* @param int $id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($consumableId)
|
||||
public function create($id)
|
||||
{
|
||||
|
||||
if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
||||
if ($consumable = Consumable::with('users')->find($id)) {
|
||||
|
||||
$this->authorize('checkout', $consumable);
|
||||
|
||||
// Make sure the category is valid
|
||||
if ($consumable->category) {
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($consumable->numRemaining() <= 0){
|
||||
return redirect()->route('consumables.index')
|
||||
->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('consumables/checkout', compact('consumable'));
|
||||
}
|
||||
|
||||
// Invalid category
|
||||
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
|
||||
}
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($consumable->numRemaining() <= 0){
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
|
||||
}
|
||||
// Not found
|
||||
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
|
||||
|
||||
$this->authorize('checkout', $consumable);
|
||||
|
||||
return view('consumables/checkout', compact('consumable'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Licenses;
|
|||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\LicenseCheckoutRequest;
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Asset;
|
||||
use App\Models\License;
|
||||
use App\Models\LicenseSeat;
|
||||
|
@ -21,23 +22,35 @@ class LicenseCheckoutController extends Controller
|
|||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @param $licenseId
|
||||
* @param $id
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function create($licenseId)
|
||||
public function create($id)
|
||||
{
|
||||
// Check that the license is valid
|
||||
if ($license = License::find($licenseId)) {
|
||||
|
||||
if ($license = License::find($id)) {
|
||||
|
||||
$this->authorize('checkout', $license);
|
||||
// If the license is valid, check that there is an available seat
|
||||
if ($license->avail_seats_count < 1) {
|
||||
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
|
||||
|
||||
if ($license->category) {
|
||||
|
||||
// Make sure there is at least one available to checkout
|
||||
if ($license->availCount()->count() < 1){
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
|
||||
}
|
||||
|
||||
// Return the checkout view
|
||||
return view('licenses/checkout', compact('license'));
|
||||
}
|
||||
return view('licenses/checkout', compact('license'));
|
||||
|
||||
// Invalid category
|
||||
return redirect()->route('licenses.edit', ['license' => $license->id])
|
||||
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
|
||||
|
||||
}
|
||||
|
||||
// Not found
|
||||
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
|
||||
|
||||
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
@ -218,22 +222,37 @@ class ManufacturersController extends Controller
|
|||
* @return Redirect
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function restore($manufacturers_id)
|
||||
public function restore($id)
|
||||
{
|
||||
$this->authorize('create', Manufacturer::class);
|
||||
$manufacturer = Manufacturer::onlyTrashed()->where('id', $manufacturers_id)->first();
|
||||
$this->authorize('delete', Manufacturer::class);
|
||||
|
||||
if ($manufacturer) {
|
||||
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
|
||||
|
||||
if ($manufacturer->deleted_at == '') {
|
||||
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')]));
|
||||
}
|
||||
|
||||
// Not sure why this is necessary - it shouldn't fail validation here, but it fails without this, so....
|
||||
$manufacturer->setValidating(false);
|
||||
if ($manufacturer->restore()) {
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = Manufacturer::class;
|
||||
$logaction->item_id = $manufacturer->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restore');
|
||||
|
||||
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||
$deleted_manufacturers = Manufacturer::onlyTrashed()->count();
|
||||
if ($deleted_manufacturers > 0) {
|
||||
return redirect()->back()->with('success', trans('admin/manufacturers/message.success.restored'));
|
||||
}
|
||||
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Could not restore.');
|
||||
// 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.manufacturer'), 'error' => $manufacturer->getErrors()->first()]));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/manufacturers/message.does_not_exist'));
|
||||
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ class ProfileController extends Controller
|
|||
];
|
||||
|
||||
$validator = \Validator::make($request->all(), $rules);
|
||||
|
||||
$validator->after(function ($validator) use ($request, $user) {
|
||||
if (! Hash::check($request->input('current_password'), $user->password)) {
|
||||
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
|
||||
|
@ -159,12 +160,14 @@ class ProfileController extends Controller
|
|||
});
|
||||
|
||||
if (! $validator->fails()) {
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->save();
|
||||
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
// We have to use saveQuietly here because for some reason this method was calling the User Oserver twice :(
|
||||
$user->saveQuietly();
|
||||
|
||||
// Log the user out of other devices
|
||||
Auth::logoutOtherDevices($request->input('password'));
|
||||
return redirect()->route('account.password.index')->with('success', 'Password updated!');
|
||||
return redirect()->route('account')->with('success', trans('passwords.password_change'));
|
||||
|
||||
}
|
||||
return redirect()->back()->withInput()->withErrors($validator);
|
||||
|
|
|
@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
|
|||
use App\Http\Controllers\UserNotFoundException;
|
||||
use App\Http\Requests\ImageUploadRequest;
|
||||
use App\Http\Requests\SaveUserRequest;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Company;
|
||||
use App\Models\Group;
|
||||
use App\Models\Ldap;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\WelcomeNotification;
|
||||
|
@ -385,18 +385,35 @@ class UsersController extends Controller
|
|||
*/
|
||||
public function getRestore($id = null)
|
||||
{
|
||||
$this->authorize('update', User::class);
|
||||
// Get user information
|
||||
if (! User::onlyTrashed()->find($id)) {
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/messages.user_not_found'));
|
||||
if ($user = User::withTrashed()->find($id)) {
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if ($user->deleted_at == '') {
|
||||
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
|
||||
}
|
||||
|
||||
if ($user->restore()) {
|
||||
$logaction = new Actionlog();
|
||||
$logaction->item_type = User::class;
|
||||
$logaction->item_id = $user->id;
|
||||
$logaction->created_at = date('Y-m-d H:i:s');
|
||||
$logaction->user_id = Auth::user()->id;
|
||||
$logaction->logaction('restore');
|
||||
|
||||
// Redirect them to the deleted page if there are more, otherwise the section index
|
||||
$deleted_users = User::onlyTrashed()->count();
|
||||
if ($deleted_users > 0) {
|
||||
return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
|
||||
}
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
|
||||
|
||||
}
|
||||
|
||||
// Check validation to make sure we're not restoring a user with the same username as an existing user
|
||||
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
|
||||
}
|
||||
|
||||
// Restore the user
|
||||
if (User::withTrashed()->where('id', $id)->restore()) {
|
||||
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->with('error', 'User could not be restored.');
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.does_not_exist'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Traits;
|
||||
|
||||
use App\Models\Setting;
|
||||
|
||||
trait UniqueSerialTrait
|
||||
{
|
||||
/**
|
||||
* Prepare a unique_ids rule, adding a model identifier if required.
|
||||
*
|
||||
* @param array $parameters
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function prepareUniqueSerialRule($parameters, $field)
|
||||
{
|
||||
if ($settings = Setting::getSettings()) {
|
||||
if ($settings->unique_serial == '1') {
|
||||
return 'unique_undeleted:'.$this->table.','.$this->getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ class AssetModelsTransformer
|
|||
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
||||
'delete' => (Gate::allows('delete', AssetModel::class) && ($assetmodel->assets_count == 0)),
|
||||
'delete' => $assetmodel->isDeletable(),
|
||||
'clone' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at == '')),
|
||||
'restore' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at != '')),
|
||||
];
|
||||
|
|
|
@ -147,7 +147,7 @@ class AssetsTransformer
|
|||
'clone' => Gate::allows('create', Asset::class) ? true : false,
|
||||
'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false,
|
||||
'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false,
|
||||
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class)) ? true : false,
|
||||
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false,
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ class UsersTransformer
|
|||
|
||||
$permissions_array['available_actions'] = [
|
||||
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
|
||||
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
|
||||
'delete' => $user->isDeletable(),
|
||||
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
|
||||
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
|
||||
];
|
||||
|
|
|
@ -61,7 +61,7 @@ class UserImporter extends ItemImporter
|
|||
$this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0;
|
||||
$this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num'));
|
||||
$this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department'))));
|
||||
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
|
||||
$this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name')));
|
||||
$this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0;
|
||||
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
|
||||
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
|
||||
|
|
|
@ -69,7 +69,6 @@ class LogListener
|
|||
$logaction->item()->associate($event->acceptance->checkoutable->license);
|
||||
}
|
||||
|
||||
\Log::debug('New onCheckoutAccepted Listener fired. logaction: '.print_r($logaction, true));
|
||||
$logaction->save();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ use App\Events\AssetCheckedOut;
|
|||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Exceptions\CheckoutNotAllowed;
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Traits\UniqueSerialTrait;
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
|
@ -32,7 +31,7 @@ class Asset extends Depreciable
|
|||
protected $presenter = \App\Presenters\AssetPresenter::class;
|
||||
|
||||
use CompanyableTrait;
|
||||
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
|
||||
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
|
||||
|
||||
public const LOCATION = 'location';
|
||||
public const ASSET = 'asset';
|
||||
|
@ -100,9 +99,9 @@ class Asset extends Depreciable
|
|||
'expected_checkin' => 'date|nullable',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'rtd_location_id' => 'exists:locations,id|nullable',
|
||||
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag',
|
||||
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array',
|
||||
'purchase_date' => 'date|date_format:Y-m-d|nullable',
|
||||
'serial' => 'unique_serial|nullable',
|
||||
'serial' => 'unique_undeleted:assets,serial|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'supplier_id' => 'exists:suppliers,id|nullable',
|
||||
'asset_eol_date' => 'date|nullable',
|
||||
|
@ -110,6 +109,7 @@ class Asset extends Depreciable
|
|||
'byod' => 'boolean',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Models\Traits\Searchable;
|
|||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
|
@ -188,6 +189,21 @@ class AssetModel extends SnipeModel
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the model is deletable
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets_count == 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uploads for this model
|
||||
*
|
||||
|
|
|
@ -100,7 +100,8 @@ class Category extends SnipeModel
|
|||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->itemCount() == 0);
|
||||
&& ($this->itemCount() == 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,6 +248,26 @@ class Category extends SnipeModel
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN MUTATORS
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* This sets the checkin_value to a boolean 0 or 1. This accounts for forms or API calls that
|
||||
* explicitly pass the checkin_email field but it has a null or empty value.
|
||||
*
|
||||
* This will also correctly parse a 1/0 if "true"/"false" is passed.
|
||||
*
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setCheckinEmailAttribute($value)
|
||||
{
|
||||
$this->attributes['checkin_email'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
|
|
|
@ -77,7 +77,8 @@ class Manufacturer extends SnipeModel
|
|||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->licenses()->count() === 0)
|
||||
&& ($this->consumables()->count() === 0)
|
||||
&& ($this->accessories()->count() === 0);
|
||||
&& ($this->accessories()->count() === 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
public function assets()
|
||||
|
|
|
@ -129,8 +129,20 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
|
|||
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
|
||||
'locale' => null, // see RFC5646
|
||||
'timezone' => null, // see RFC6557
|
||||
'active' => AttributeMapping::eloquent('activated'),
|
||||
|
||||
'active' => (new AttributeMapping())->setAdd(
|
||||
function ($value, &$object) {
|
||||
$object->activated = $value;
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$object->activated = $value;
|
||||
}
|
||||
)->setRead(
|
||||
// this works as specified.
|
||||
function (&$object) {
|
||||
return (bool)$object->activated;
|
||||
}
|
||||
),
|
||||
'password' => AttributeMapping::eloquent('password')->disableRead(),
|
||||
|
||||
// Multi-Valued Attributes
|
||||
|
|
|
@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Passport\HasApiTokens;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
|
@ -201,6 +202,23 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
|||
return $this->checkPermissionSection('superuser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is deletable
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->licenses()->count() === 0)
|
||||
&& ($this->consumables()->count() === 0)
|
||||
&& ($this->accessories()->count() === 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the user -> company relationship
|
||||
|
|
|
@ -22,6 +22,13 @@ class AssetObserver
|
|||
$attributesOriginal = $asset->getRawOriginal();
|
||||
$same_checkout_counter = false;
|
||||
$same_checkin_counter = false;
|
||||
$restoring_or_deleting = false;
|
||||
|
||||
|
||||
// This is a gross hack to prevent the double logging when restoring an asset
|
||||
if (array_key_exists('deleted_at', $attributes) && array_key_exists('deleted_at', $attributesOriginal)){
|
||||
$restoring_or_deleting = (($attributes['deleted_at'] != $attributesOriginal['deleted_at']));
|
||||
}
|
||||
|
||||
if (array_key_exists('checkout_counter', $attributes) && array_key_exists('checkout_counter', $attributesOriginal)){
|
||||
$same_checkout_counter = (($attributes['checkout_counter'] == $attributesOriginal['checkout_counter']));
|
||||
|
@ -33,10 +40,10 @@ class AssetObserver
|
|||
|
||||
// If the asset isn't being checked out or audited, log the update.
|
||||
// (Those other actions already create log entries.)
|
||||
if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
|
||||
if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
|
||||
&& ($same_checkout_counter) && ($same_checkin_counter)
|
||||
&& ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null))
|
||||
&& ($attributes['last_checkout'] == $attributesOriginal['last_checkout']))
|
||||
&& ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting))
|
||||
{
|
||||
$changed = [];
|
||||
|
||||
|
@ -121,6 +128,22 @@ class AssetObserver
|
|||
$logAction->logaction('delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the Asset deleting event.
|
||||
*
|
||||
* @param Asset $asset
|
||||
* @return void
|
||||
*/
|
||||
public function restoring(Asset $asset)
|
||||
{
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = Asset::class;
|
||||
$logAction->item_id = $asset->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('restore');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes every time an asset is saved.
|
||||
*
|
||||
|
|
149
app/Observers/UserObserver.php
Normal file
149
app/Observers/UserObserver.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\User;
|
||||
use Auth;
|
||||
|
||||
class UserObserver
|
||||
{
|
||||
/**
|
||||
* Listen to the User updating event. This fires automatically every time an existing asset is saved.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function updating(User $user)
|
||||
{
|
||||
|
||||
// ONLY allow these fields to be stored
|
||||
$allowed_fields = [
|
||||
'email',
|
||||
'activated',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'website',
|
||||
'country',
|
||||
'gravatar',
|
||||
'location_id',
|
||||
'phone',
|
||||
'jobtitle',
|
||||
'manager_id',
|
||||
'employee_num',
|
||||
'username',
|
||||
'notes',
|
||||
'company_id',
|
||||
'ldap_import',
|
||||
'locale',
|
||||
'two_factor_enrolled',
|
||||
'two_factor_optin',
|
||||
'department_id',
|
||||
'address',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'zip',
|
||||
'remote',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'autoassign_licenses',
|
||||
'vip',
|
||||
'password'
|
||||
];
|
||||
|
||||
$changed = [];
|
||||
|
||||
foreach ($user->getRawOriginal() as $key => $value) {
|
||||
|
||||
// Make sure the info is in the allow fields array
|
||||
if (in_array($key, $allowed_fields)) {
|
||||
|
||||
// Check and see if the value changed
|
||||
if ($user->getRawOriginal()[$key] != $user->getAttributes()[$key]) {
|
||||
|
||||
$changed[$key]['old'] = $user->getRawOriginal()[$key];
|
||||
$changed[$key]['new'] = $user->getAttributes()[$key];
|
||||
|
||||
// Do not store the hashed password in changes
|
||||
if ($key == 'password') {
|
||||
$changed['password']['old'] = '*************';
|
||||
$changed['password']['new'] = '*************';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (count($changed) > 0) {
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
|
||||
$logAction->target_id = $user->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->log_meta = json_encode($changed);
|
||||
$logAction->logaction('update');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User created event, and increment
|
||||
* the next_auto_tag_base value in the settings table when i
|
||||
* a new asset is created.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function created(User $user)
|
||||
{
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = User::class; // can we instead say $logAction->item = $asset ?
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User deleting event.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function deleting(User $user)
|
||||
{
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
|
||||
$logAction->target_id = $user->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the User deleting event.
|
||||
*
|
||||
* @param User $user
|
||||
* @return void
|
||||
*/
|
||||
public function restoring(User $user)
|
||||
{
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
|
||||
$logAction->target_id = $user->id;
|
||||
$logAction->created_at = date('Y-m-d H:i:s');
|
||||
$logAction->user_id = Auth::id();
|
||||
$logAction->logaction('restore');
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -38,22 +38,63 @@ class ActionlogPresenter extends Presenter
|
|||
|
||||
public function icon()
|
||||
{
|
||||
$itemicon = 'fas fa-paperclip';
|
||||
|
||||
// User related icons
|
||||
if ($this->itemType() == 'user') {
|
||||
|
||||
if ($this->itemType() == 'asset') {
|
||||
return 'fas fa-barcode';
|
||||
} elseif ($this->itemType() == 'accessory') {
|
||||
return 'far fa-keyboard';
|
||||
} elseif ($this->itemType() == 'consumable') {
|
||||
return 'fas fa-tint';
|
||||
} elseif ($this->itemType() == 'license') {
|
||||
return 'far fa-save';
|
||||
} elseif ($this->itemType() == 'component') {
|
||||
return 'far fa-hdd';
|
||||
} elseif ($this->itemType() == 'user') {
|
||||
return 'fa-solid fa-people-arrows';
|
||||
if ($this->actionType()=='create new') {
|
||||
return 'fa-solid fa-user-plus';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='merged') {
|
||||
return 'fa-solid fa-people-arrows';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='delete') {
|
||||
return 'fa-solid fa-user-minus';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='delete') {
|
||||
return 'fa-solid fa-user-minus';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='update') {
|
||||
return 'fa-solid fa-user-pen';
|
||||
}
|
||||
return 'fa-solid fa-user';
|
||||
}
|
||||
|
||||
// Everything else
|
||||
if ($this->actionType()=='create new') {
|
||||
return 'fa-solid fa-plus';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='delete') {
|
||||
return 'fa-solid fa-user-xmark';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='update') {
|
||||
return 'fa-solid fa-pen';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='restore') {
|
||||
return 'fa-solid fa-trash-arrow-up';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='upload') {
|
||||
return 'fas fa-paperclip';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='checkout') {
|
||||
return 'fa-solid fa-rotate-left';
|
||||
}
|
||||
|
||||
if ($this->actionType()=='checkin from') {
|
||||
return 'fa-solid fa-rotate-right';
|
||||
}
|
||||
|
||||
return 'fa-solid fa-rotate-right';
|
||||
|
||||
}
|
||||
|
||||
public function actionType()
|
||||
|
|
|
@ -7,10 +7,12 @@ use App\Models\Asset;
|
|||
use App\Models\Component;
|
||||
use App\Models\Consumable;
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use App\Models\Setting;
|
||||
use App\Models\SnipeSCIMConfig;
|
||||
use App\Observers\AccessoryObserver;
|
||||
use App\Observers\AssetObserver;
|
||||
use App\Observers\UserObserver;
|
||||
use App\Observers\ComponentObserver;
|
||||
use App\Observers\ConsumableObserver;
|
||||
use App\Observers\LicenseObserver;
|
||||
|
@ -58,6 +60,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
|
||||
Schema::defaultStringLength(191);
|
||||
Asset::observe(AssetObserver::class);
|
||||
User::observe(UserObserver::class);
|
||||
Accessory::observe(AccessoryObserver::class);
|
||||
Component::observe(ComponentObserver::class);
|
||||
Consumable::observe(ConsumableObserver::class);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Setting;
|
||||
use DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
@ -45,32 +46,87 @@ class ValidationServiceProvider extends ServiceProvider
|
|||
return $validator->passes();
|
||||
});
|
||||
|
||||
// Unique only if undeleted
|
||||
// This works around the use case where multiple deleted items have the same unique attribute.
|
||||
// (I think this is a bug in Laravel's validator?)
|
||||
// $parameters is the rule parameters, like `unique_undeleted:users,id` - $parameters[0] is users, $parameters[1] is id
|
||||
// the UniqueUndeletedTrait prefills these so you can just use `unique_undeleted` in your rules (but this would only work directly in the model)
|
||||
|
||||
/**
|
||||
* Unique only if undeleted.
|
||||
*
|
||||
* This works around the use case where multiple deleted items have the same unique attribute.
|
||||
* (I think this is a bug in Laravel's validator?)
|
||||
*
|
||||
* $attribute is the FIELDNAME you're checking against
|
||||
* $value is the VALUE of the item you're checking against the existing values in the fieldname
|
||||
* $parameters[0] is the TABLE NAME you're querying
|
||||
* $parameters[1] is the ID of the item you're querying - this makes it work on saving, checkout, etc,
|
||||
* since it defaults to 0 if there is no item created yet (new item), but populates the ID if editing
|
||||
*
|
||||
* The UniqueUndeletedTrait prefills these parameters, so you can just use
|
||||
* `unique_undeleted:table,fieldname` in your rules out of the box
|
||||
*/
|
||||
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
|
||||
|
||||
if (count($parameters)) {
|
||||
$count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
|
||||
|
||||
// This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable
|
||||
if (($parameters[0]=='assets') && ($attribute == 'serial') && (Setting::getSettings()->unique_serial != '1')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$count = DB::table($parameters[0])
|
||||
->select('id')
|
||||
->where($attribute, '=', $value)
|
||||
->whereNull('deleted_at')
|
||||
->where('id', '!=', $parameters[1])->count();
|
||||
|
||||
return $count < 1;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Unique if undeleted for two columns
|
||||
*
|
||||
* Same as unique_undeleted but taking the combination of two columns as unique constrain.
|
||||
* This uses the Validator::replacer('two_column_unique_undeleted') below for nicer translations.
|
||||
*
|
||||
* $parameters[0] - the name of the first table we're looking at
|
||||
* $parameters[1] - the ID (this will be 0 on new creations)
|
||||
* $parameters[2] - the name of the second table we're looking at
|
||||
* $parameters[3] - the value that the request is passing for the second table we're
|
||||
* checking for uniqueness across
|
||||
*
|
||||
*/
|
||||
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
|
||||
if (count($parameters)) {
|
||||
$count = DB::table($parameters[0])
|
||||
->select('id')->where($attribute, '=', $value)
|
||||
->whereNull('deleted_at')
|
||||
->where('id', '!=', $parameters[1])
|
||||
->where($parameters[2], $parameters[3])->count();
|
||||
|
||||
return $count < 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Unique if undeleted for two columns
|
||||
// Same as unique_undeleted but taking the combination of two columns as unique constrain.
|
||||
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
|
||||
if (count($parameters)) {
|
||||
$count = DB::table($parameters[0])
|
||||
->select('id')->where($attribute, '=', $value)
|
||||
->whereNull('deleted_at')
|
||||
->where('id', '!=', $parameters[1])
|
||||
->where($parameters[2], $parameters[3])->count();
|
||||
|
||||
return $count < 1;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* This is the validator replace static method that allows us to pass the $parameters of the table names
|
||||
* into the translation string in validation.two_column_unique_undeleted for two_column_unique_undeleted
|
||||
* validation messages.
|
||||
*
|
||||
* This is invoked automatically by Validator::extend('two_column_unique_undeleted') above and
|
||||
* produces a translation like: "The name value must be unique across categories and category type."
|
||||
*
|
||||
* The $parameters passed coincide with the ones the two_column_unique_undeleted custom validator above
|
||||
* uses, so $parameter[0] is the first table and so $parameter[2] is the second table.
|
||||
*/
|
||||
Validator::replacer('two_column_unique_undeleted', function($message, $attribute, $rule, $parameters) {
|
||||
$message = str_replace(':table1', $parameters[0], $message);
|
||||
$message = str_replace(':table2', $parameters[2], $message);
|
||||
|
||||
// Change underscores to spaces for a friendlier display
|
||||
$message = str_replace('_', ' ', $message);
|
||||
return $message;
|
||||
});
|
||||
|
||||
|
||||
// Prevent circular references
|
||||
//
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?php
|
||||
return array (
|
||||
'app_version' => 'v6.2.3',
|
||||
'full_app_version' => 'v6.2.3 - build 11936-gb47e734b3',
|
||||
'build_version' => '11936',
|
||||
'app_version' => 'v6.2.4-pre',
|
||||
'full_app_version' => 'v6.2.4-pre - build 12090-g776b16d37',
|
||||
'build_version' => '12090',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'gb47e734b3',
|
||||
'full_hash' => 'v6.2.3-175-gb47e734b3',
|
||||
'hash_version' => 'g776b16d37',
|
||||
'full_hash' => 'v6.2.4-pre-329-g776b16d37',
|
||||
'branch' => 'develop',
|
||||
);
|
4292
package-lock.json
generated
4292
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -25,11 +25,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"acorn": "^8.9.0",
|
||||
"acorn": "^8.11.2",
|
||||
"acorn-import-assertions": "^1.9.0",
|
||||
"admin-lte": "^2.4.18",
|
||||
"ajv": "^6.12.6",
|
||||
"alpinejs": "^3.10.5",
|
||||
"alpinejs": "^3.13.2",
|
||||
"blueimp-file-upload": "^9.34.0",
|
||||
"bootstrap": "^3.4.1",
|
||||
"bootstrap-colorpicker": "^2.5.3",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"jquery-ui": "^1.13.2",
|
||||
"jquery-ui-bundle": "^1.12.1",
|
||||
"jquery.iframe-transport": "^1.0.0",
|
||||
"jspdf-autotable": "^3.5.30",
|
||||
"jspdf-autotable": "^3.7.1",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^6.0",
|
||||
"list.js": "^1.5.0",
|
||||
|
@ -56,6 +56,6 @@
|
|||
"tableexport.jquery.plugin": "1.28.0",
|
||||
"tether": "^1.4.0",
|
||||
"vue-resource": "^1.5.2",
|
||||
"webpack": "^5.88.2"
|
||||
"webpack": "^5.89.0"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/css/dist/skins/skin-green-dark.css
vendored
BIN
public/css/dist/skins/skin-green-dark.css
vendored
Binary file not shown.
BIN
public/css/dist/skins/skin-green-dark.min.css
vendored
BIN
public/css/dist/skins/skin-green-dark.min.css
vendored
Binary file not shown.
BIN
public/js/dist/all-defer.js
vendored
BIN
public/js/dist/all-defer.js
vendored
Binary file not shown.
|
@ -14,7 +14,7 @@
|
|||
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=7f0eb9e355b36b41c61c3af3b4d41143",
|
||||
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=f4d95ad9d0944587549e35b6929b4b04",
|
||||
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
|
||||
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=44f9320d0739f419c9246f7f39395b02",
|
||||
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=0ed42b67f9b02a74815e885bfd9e3f66",
|
||||
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
|
||||
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
|
||||
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
|
||||
|
@ -33,9 +33,9 @@
|
|||
"/js/build/vendor.js": "/js/build/vendor.js?id=ede02ee2aad89fe10c64a87f6c76838a",
|
||||
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=1f678160a05960c3087fb8263168ff41",
|
||||
"/js/dist/all.js": "/js/dist/all.js?id=b9dbb598e7c52f20fa595893cfa1199d",
|
||||
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=55eff1c09b1f966c0c4f16d898f89c86",
|
||||
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7f9a130eda6916eaa32a0a57e81918f3",
|
||||
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=44f9320d0739f419c9246f7f39395b02",
|
||||
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=0ed42b67f9b02a74815e885bfd9e3f66",
|
||||
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
|
||||
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=f4d95ad9d0944587549e35b6929b4b04",
|
||||
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=392cc93cfc0be0349bab9697669dd091",
|
||||
|
|
|
@ -361,7 +361,7 @@ input[type=text], input[type=search] {
|
|||
background-color: var(--back-sub);
|
||||
}
|
||||
.table-striped>tbody>tr:nth-of-type(even){
|
||||
background-color: var(--back-sub-alt);
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {
|
||||
color: var(--text-main);
|
||||
|
|
|
@ -9,6 +9,7 @@ return array(
|
|||
'assoc_users' => 'This license is currently checked out to a user and cannot be deleted. Please check the license in first, and then try deleting again. ',
|
||||
'select_asset_or_person' => 'You must select an asset or a user, but not both.',
|
||||
'not_found' => 'License not found',
|
||||
'seats_available' => ':seat_count seats available',
|
||||
|
||||
|
||||
'create' => array(
|
||||
|
@ -41,7 +42,8 @@ return array(
|
|||
|
||||
'checkout' => array(
|
||||
'error' => 'There was an issue checking out the license. Please try again.',
|
||||
'success' => 'The license was checked out successfully'
|
||||
'success' => 'The license was checked out successfully',
|
||||
'not_enough_seats' => 'Not enough license seats available for checkout',
|
||||
),
|
||||
|
||||
'checkin' => array(
|
||||
|
|
|
@ -72,6 +72,8 @@ return [
|
|||
'consumable' => 'Consumable',
|
||||
'consumables' => 'Consumables',
|
||||
'country' => 'Country',
|
||||
'could_not_restore' => 'Error restoring :item_type: :error',
|
||||
'not_deleted' => 'The :item_type is not deleted so it cannot be restored',
|
||||
'create' => 'Create New',
|
||||
'created' => 'Item Created',
|
||||
'created_asset' => 'created asset',
|
||||
|
@ -353,7 +355,8 @@ return [
|
|||
'synchronize' => 'Synchronize',
|
||||
'sync_results' => 'Synchronization Results',
|
||||
'license_serial' => 'Serial/Product Key',
|
||||
'invalid_category' => 'Invalid category',
|
||||
'invalid_category' => 'Invalid or missing category',
|
||||
'invalid_item_category_single' => 'Invalid or missing :type category. Please update the category of this :type to include a valid category before checking out.',
|
||||
'dashboard_info' => 'This is your dashboard. There are many like it, but this one is yours.',
|
||||
'60_percent_warning' => '60% Complete (warning)',
|
||||
'dashboard_empty' => 'It looks like you have not added anything yet, so we do not have anything awesome to display. Get started by adding some assets, accessories, consumables, or licenses now!',
|
||||
|
@ -485,7 +488,7 @@ return [
|
|||
],
|
||||
'percent_complete' => '% complete',
|
||||
'uploading' => 'Uploading... ',
|
||||
'upload_error' => 'Error uploading file. Please check that there are no empty trailing rows.',
|
||||
'upload_error' => 'Error uploading file. Please check that there are no empty rows and that no column names are duplicated.',
|
||||
'copy_to_clipboard' => 'Copy to Clipboard',
|
||||
'copied' => 'Copied!',
|
||||
|
||||
|
|
|
@ -5,4 +5,5 @@ return [
|
|||
'user' => 'If a matching user with a valid email address exists in our system, a password recovery email has been sent.',
|
||||
'token' => 'This password reset token is invalid or expired, or does not match the username provided.',
|
||||
'reset' => 'Your password has been reset!',
|
||||
'password_change' => 'Your password has been updated!',
|
||||
];
|
||||
|
|
|
@ -90,12 +90,14 @@ return [
|
|||
],
|
||||
'string' => 'The :attribute must be a string.',
|
||||
'timezone' => 'The :attribute must be a valid zone.',
|
||||
'two_column_unique_undeleted' => 'The :attribute must be unique across :table1 and :table2. ',
|
||||
'unique' => 'The :attribute has already been taken.',
|
||||
'uploaded' => 'The :attribute failed to upload.',
|
||||
'url' => 'The :attribute format is invalid.',
|
||||
'unique_undeleted' => 'The :attribute must be unique.',
|
||||
'non_circular' => 'The :attribute must not create a circular reference.',
|
||||
'not_array' => 'The :attribute field cannot be an array.',
|
||||
'unique_serial' => 'The :attribute must be unique.',
|
||||
'disallow_same_pwd_as_user_fields' => 'Password cannot be the same as the username.',
|
||||
'letters' => 'Password must contain at least one letter.',
|
||||
'numbers' => 'Password must contain at least one number.',
|
||||
|
|
|
@ -10,15 +10,17 @@
|
|||
@section('content')
|
||||
|
||||
@if ($acceptances = \App\Models\CheckoutAcceptance::forUser(Auth::user())->pending()->count())
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert alert-warning fade in">
|
||||
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert alert-warning fade in">
|
||||
<i class="fas fa-exclamation-triangle faa-pulse animated"></i>
|
||||
|
||||
<strong>
|
||||
<a href="{{ route('account.accept') }}" style="color: white;">
|
||||
{{ trans('general.unaccepted_profile_warning', array('count' => $acceptances)) }}
|
||||
</a>
|
||||
</strong>
|
||||
<strong>
|
||||
<a href="{{ route('account.accept') }}" style="color: white;">
|
||||
{{ trans('general.unaccepted_profile_warning', array('count' => $acceptances)) }}
|
||||
</a>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
@ -388,7 +390,6 @@
|
|||
data-show-columns="true"
|
||||
data-show-export="true"
|
||||
data-show-footer="true"
|
||||
data-show-refresh="false"
|
||||
data-sort-order="asc"
|
||||
id="userAssets"
|
||||
class="table table-striped snipe-table"
|
||||
|
@ -405,6 +406,7 @@
|
|||
<th class="col-md-2" data-switchable="true" data-visible="true">{{ trans('general.name') }}</th>
|
||||
<th class="col-md-2" data-switchable="true" data-visible="true">{{ trans('admin/hardware/table.asset_model') }}</th>
|
||||
<th class="col-md-3" data-switchable="true" data-visible="true">{{ trans('admin/hardware/table.serial') }}</th>
|
||||
<th class="col-md-2" data-switchable="true" data-visible="false">{{ trans('admin/hardware/form.default_location') }}</th>
|
||||
@can('self.view_purchase_cost')
|
||||
<th class="col-md-6" data-footer-formatter="sumFormatter" data-fieldname="purchase_cost">{{ trans('general.purchase_cost') }}</th>
|
||||
@endcan
|
||||
|
@ -442,7 +444,7 @@
|
|||
@endif
|
||||
</td>
|
||||
<td>{{ $asset->serial }}</td>
|
||||
|
||||
<td>{{ ($asset->defaultLoc) ? $asset->defaultLoc->name : '' }}</td>
|
||||
@can('self.view_purchase_cost')
|
||||
<td>
|
||||
{!! Helper::formatCurrencyOutput($asset->purchase_cost) !!}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
@can('update', $custom_fieldset)
|
||||
<th class="col-md-1"><span class="sr-only">{{ trans('admin/custom_fields/general.reorder') }}</span></th>
|
||||
@endcan
|
||||
<th class="col-md-1">{{ trans('admin/custom_fields/general.order') }}</th>
|
||||
<th class="col-md-1" style="display: none;">{{ trans('admin/custom_fields/general.order') }}</th>
|
||||
<th class="col-md-3">{{ trans('admin/custom_fields/general.field_name') }}</th>
|
||||
<th class="col-md-2">{{ trans('admin/custom_fields/general.field_format') }}</th>
|
||||
<th class="col-md-2">{{ trans('admin/custom_fields/general.field_element') }}</th>
|
||||
|
@ -51,7 +51,7 @@
|
|||
</span>
|
||||
</td>
|
||||
@endcan
|
||||
<td class="index">{{$field->pivot->order + 1}}</td>
|
||||
<td class="index" style="display: none;">{{$field->pivot->order + 1}}</td> {{--this +1 needs to exist to keep the first row from reverting to 0 if you edit/delete fields in/from a fielset--}}
|
||||
<td>{{$field->name}}</td>
|
||||
<td>{{$field->format}}</td>
|
||||
<td>{{$field->element}}</td>
|
||||
|
@ -110,7 +110,7 @@
|
|||
</label>
|
||||
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<div class="form-group col-md-2" style="display: none;">
|
||||
|
||||
{{ Form::text('order', $maxid, array('class' => 'form-control col-sm-1 col-md-1', 'style'=> 'width: 80px; padding-;right: 10px;', 'aria-label'=>'order', 'maxlength'=>'3', 'size'=>'3')) }}
|
||||
<label for="order">{{ trans('admin/custom_fields/general.order') }}</label>
|
||||
|
|
|
@ -21,17 +21,25 @@
|
|||
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h2 class="box-title"> {{ $license->name }}</h2>
|
||||
<h2 class="box-title"> {{ $license->name }} ({{ trans('admin/licenses/message.seats_available', ['seat_count' => $license->availCount()->count()]) }})</h2>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
|
||||
<!-- Asset name -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{{ trans('admin/hardware/form.name') }}</label>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ $license->name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Category -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">{{ trans('general.category') }}</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ $license->category->name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Serial -->
|
||||
<div class="form-group">
|
||||
|
@ -57,8 +65,8 @@
|
|||
<!-- Note -->
|
||||
<div class="form-group {{ $errors->has('notes') ? 'error' : '' }}">
|
||||
<label for="note" class="col-md-3 control-label">{{ trans('admin/hardware/form.notes') }}</label>
|
||||
<div class="col-md-7">
|
||||
<textarea class="col-md-6 form-control" id="notes" name="notes">{{ old('note') }}</textarea>
|
||||
<div class="col-md-8">
|
||||
<textarea class="col-md-6 form-control" id="notes" name="notes" style="width: 100%">{{ old('note') }}</textarea>
|
||||
{!! $errors->first('note', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -235,7 +235,7 @@
|
|||
])
|
||||
}}
|
||||
</div>
|
||||
@if ($activeFile->first_row)
|
||||
@if (($activeFile->first_row) && (array_key_exists($index, $activeFile->first_row)))
|
||||
<div class="col-md-5">
|
||||
<p class="form-control-static">{{ str_limit($activeFile->first_row[$index], 50, '...') }}</p>
|
||||
</div>
|
||||
|
|
|
@ -279,7 +279,11 @@
|
|||
+ ' data-title="{{ trans('general.delete') }}" onClick="return false;">'
|
||||
+ '<i class="fas fa-trash" aria-hidden="true"></i><span class="sr-only">{{ trans('general.delete') }}</span></a> ';
|
||||
} else {
|
||||
actions += '<span data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}"><a class="btn btn-danger btn-sm delete-asset disabled" onClick="return false;"><i class="fas fa-trash"></i></a></span> ';
|
||||
// Do not show the delete button on things that are already deleted
|
||||
if ((row.available_actions) && (row.available_actions.restore != true)) {
|
||||
actions += '<span data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}"><a class="btn btn-danger btn-sm delete-asset disabled" onClick="return false;"><i class="fas fa-trash"></i></a></span> ';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -706,6 +706,13 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
]
|
||||
)->name('api.manufacturers.selectlist');
|
||||
|
||||
Route::post('{id}/restore',
|
||||
[
|
||||
Api\ManufacturersController::class,
|
||||
'restore'
|
||||
]
|
||||
)->name('api.manufacturers.restore');
|
||||
|
||||
});
|
||||
|
||||
Route::resource('manufacturers',
|
||||
|
@ -742,6 +749,13 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
|
|||
]
|
||||
)->name('api.models.assets');
|
||||
|
||||
Route::post('{id}/restore',
|
||||
[
|
||||
Api\AssetModelsController::class,
|
||||
'restore'
|
||||
]
|
||||
)->name('api.models.restore');
|
||||
|
||||
});
|
||||
|
||||
Route::resource('models',
|
||||
|
|
Loading…
Reference in a new issue