Merge branch 'develop' into features/make_eol_sortable

This commit is contained in:
snipe 2023-02-28 18:05:09 -08:00 committed by GitHub
commit 1f7ae08d76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1071 changed files with 9430 additions and 3819 deletions

View file

@ -2837,6 +2837,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "AndrewSav",
"name": "Andrew Savinykh",
"avatar_url": "https://avatars.githubusercontent.com/u/658865?v=4",
"profile": "https://github.com/AndrewSav",
"contributions": [
"code"
]
} }
] ]
} }

View file

@ -20,13 +20,13 @@ PUBLIC_FILESYSTEM_DISK=local_public
# REQUIRED: DATABASE SETTINGS # REQUIRED: DATABASE SETTINGS
# -------------------------------------------- # --------------------------------------------
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=localhost DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=snipeit-local DB_DATABASE=null
DB_USERNAME=snipeit-local DB_USERNAME=null
DB_PASSWORD=snipeit-local DB_PASSWORD=null
DB_PREFIX=null DB_PREFIX=null
DB_DUMP_PATH='/Applications/MAMP/Library/bin' #DB_DUMP_PATH=
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS # OPTIONAL: SSL DATABASE SETTINGS

3
.gitignore vendored
View file

@ -1,6 +1,9 @@
.couscous .couscous
.DS_Store .DS_Store
.env .env
.env.dusk.*
!.env.dusk.example
phpstan.neon
.idea .idea
/bin/ /bin/
/bootstrap/compiled.php /bootstrap/compiled.php

View file

@ -1,5 +1,5 @@
![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![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) ![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![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-312-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-313-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 ## Snipe-IT - Open Source Asset Management System
@ -140,7 +140,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | | [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<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/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/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") |
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -43,23 +43,33 @@ you want to run.
## Browser Tests ## Browser Tests
The browser tests use [Dusk](https://laravel.com/docs/8.x/dusk) to run them. Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
When troubleshooting any problems, make sure that your `.env` file is configured
correctly to run the existing application. Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
`cp .env.dusk.example .env.dusk.local`
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
### Test Setup ### Test Setup
Your application needs to be configued and up and running in order for the browser Your application needs to be configured and up and running in order for the browser
tests to actually run. When running the tests locally, you can start the application tests to actually run. When running the tests locally, you can start the application
using the following command: using the following command:
`php artisan serve` `php artisan serve`
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
To run the test suite use the following command from another terminal tab or window:
`php artisan dusk` `php artisan dusk`
To run individual test files, you can pass the path to the test that you want to run. To run individual test files, you can pass the path to the test that you want to run:
`php artisan dusk tests/Browser/LoginTest.php` `php artisan dusk tests/Browser/LoginTest.php`
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
`php artisan dusk:chrome-driver --detect`
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.

View file

@ -56,7 +56,7 @@ class CheckoutLicenseToAllUsers extends Command
return false; return false;
} }
$users = User::whereNull('deleted_at')->with('licenses')->get(); $users = User::whereNull('deleted_at')->where('autoassign_licenses', '==', 1)->with('licenses')->get();
if ($users->count() > $license->getAvailSeatsCountAttribute()) { if ($users->count() > $license->getAvailSeatsCountAttribute()) {
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. '); $this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');

View file

@ -3,14 +3,29 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use \App\Models\User;
class CreateAdmin extends Command class CreateAdmin extends Command
{ {
/** @mixin User **/
/** /**
* The name and signature of the console command. * App\Console\CreateAdmin
* * @property mixed $first_name
* @var string * @property string $last_name
* @property string $username
* @property string $email
* @property string $permissions
* @property string $password
* @property boolean $activated
* @property boolean $show_in_list
* @property \Illuminate\Support\Carbon|null $created_at
* @property mixed $created_by
*/ */
protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?}'; protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?}';
/** /**
@ -30,11 +45,7 @@ class CreateAdmin extends Command
parent::__construct(); parent::__construct();
} }
/**
* Execute the console command.
*
* @return mixed
*/
public function handle() public function handle()
{ {
$first_name = $this->option('first_name'); $first_name = $this->option('first_name');
@ -47,7 +58,7 @@ class CreateAdmin extends Command
if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) { if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) {
$this->info('ERROR: All fields are required.'); $this->info('ERROR: All fields are required.');
} else { } else {
$user = new \App\Models\User; $user = new User;
$user->first_name = $first_name; $user->first_name = $first_name;
$user->last_name = $last_name; $user->last_name = $last_name;
$user->username = $username; $user->username = $username;

View file

@ -44,12 +44,18 @@ class LdapSync extends Command
*/ */
public function handle() public function handle()
{ {
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
if (Setting::getSettings()->ldap_enabled!='1') {
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
exit();
}
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
$ldap_result_username = Setting::getSettings()->ldap_username_field; $ldap_result_username = Setting::getSettings()->ldap_username_field;
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field; $ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field; $ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag; $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num; $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
$ldap_result_email = Setting::getSettings()->ldap_email; $ldap_result_email = Setting::getSettings()->ldap_email;
@ -68,7 +74,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []]; $json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary)); $this->info(json_encode($json_summary));
} }
LOG::info($e); Log::info($e);
return []; return [];
} }
@ -78,7 +84,7 @@ class LdapSync extends Command
try { try {
if ($this->option('base_dn') != '') { if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn'); $search_base = $this->option('base_dn');
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".'); Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else { } else {
$search_base = null; $search_base = null;
} }
@ -92,7 +98,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []]; $json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary)); $this->info(json_encode($json_summary));
} }
LOG::info($e); Log::info($e);
return []; return [];
} }
@ -102,16 +108,16 @@ class LdapSync extends Command
if ($this->option('location') != '') { if ($this->option('location') != '') {
$location = Location::where('name', '=', $this->option('location'))->first(); $location = Location::where('name', '=', $this->option('location'))->first();
LOG::debug('Location name '.$this->option('location').' passed'); Log::debug('Location name '.$this->option('location').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')'); Log::debug('Importing to '.$location->name.' ('.$location->id.')');
} elseif ($this->option('location_id') != '') { } elseif ($this->option('location_id') != '') {
$location = Location::where('id', '=', $this->option('location_id'))->first(); $location = Location::where('id', '=', $this->option('location_id'))->first();
LOG::debug('Location ID '.$this->option('location_id').' passed'); Log::debug('Location ID '.$this->option('location_id').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')'); Log::debug('Importing to '.$location->name.' ('.$location->id.')');
} }
if (! isset($location)) { if (! isset($location)) {
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.'); Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
} }
/* Process locations with explicitly defined OUs, if doing a full import. */ /* Process locations with explicitly defined OUs, if doing a full import. */
@ -127,7 +133,7 @@ class LdapSync extends Command
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations); array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
if (count($ldap_ou_locations) > 0) { if (count($ldap_ou_locations) > 0) {
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.'); Log::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
} }
// Inject location information fields // Inject location information fields
@ -145,7 +151,7 @@ class LdapSync extends Command
$json_summary = ['error' => true, 'error_message' => trans('admin/users/message.error.ldap_could_not_search').' Location: '.$ldap_loc['name'].' (ID: '.$ldap_loc['id'].') cannot connect to "'.$ldap_loc['ldap_ou'].'" - '.$e->getMessage(), 'summary' => []]; $json_summary = ['error' => true, 'error_message' => trans('admin/users/message.error.ldap_could_not_search').' Location: '.$ldap_loc['name'].' (ID: '.$ldap_loc['id'].') cannot connect to "'.$ldap_loc['ldap_ou'].'" - '.$e->getMessage(), 'summary' => []];
$this->info(json_encode($json_summary)); $this->info(json_encode($json_summary));
} }
LOG::info($e); Log::info($e);
return []; return [];
} }
@ -191,18 +197,18 @@ class LdapSync extends Command
for ($i = 0; $i < $results['count']; $i++) { for ($i = 0; $i < $results['count']; $i++) {
$item = []; $item = [];
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : ''; $item['username'] = $results[$i][$ldap_result_username][0] ?? '';
$item['employee_number'] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : ''; $item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? '';
$item['lastname'] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : ''; $item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? '';
$item['firstname'] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : ''; $item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? '';
$item['email'] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : ''; $item['email'] = $results[$i][$ldap_result_email][0] ?? '';
$item['ldap_location_override'] = isset($results[$i]['ldap_location_override']) ? $results[$i]['ldap_location_override'] : ''; $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = isset($results[$i]['location_id']) ? $results[$i]['location_id'] : ''; $item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = isset($results[$i][$ldap_result_phone][0]) ? $results[$i][$ldap_result_phone][0] : ''; $item['telephone'] = $results[$i][$ldap_result_phone][0] ?? '';
$item['jobtitle'] = isset($results[$i][$ldap_result_jobtitle][0]) ? $results[$i][$ldap_result_jobtitle][0] : ''; $item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? '';
$item['country'] = isset($results[$i][$ldap_result_country][0]) ? $results[$i][$ldap_result_country][0] : ''; $item['country'] = $results[$i][$ldap_result_country][0] ?? '';
$item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : ''; $item['department'] = $results[$i][$ldap_result_dept][0] ?? '';
$item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : ''; $item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
$department = Department::firstOrCreate([ $department = Department::firstOrCreate([
@ -303,17 +309,18 @@ class LdapSync extends Command
$user->activated = 0; $user->activated = 0;
} */ } */
$enabled_accounts = [ $enabled_accounts = [
'512', // 0x200 NORMAL_ACCOUNT '512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
'66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD '66080', // 0x10220 NORMAL_ACCOUNT, PASSWD_NOTREQD, DONT_EXPIRE_PASSWORD
'262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED '262656', // 0x40200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED '262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD '328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD '328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH '4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH '4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED '1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
]; ];
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;

View file

@ -149,7 +149,7 @@ class RestoreFromBackup extends Command
$boring_files[] = $raw_path; $boring_files[] = $raw_path;
continue; continue;
} }
if (@pathinfo($raw_path)['extension'] == 'sql') { if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
\Log::debug("Found a sql file!"); \Log::debug("Found a sql file!");
$sqlfiles[] = $raw_path; $sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i; $sqlfile_indices[] = $i;

View file

@ -41,7 +41,9 @@ class Handler extends ExceptionHandler
public function report(Throwable $exception) public function report(Throwable $exception)
{ {
if ($this->shouldReport($exception)) { if ($this->shouldReport($exception)) {
\Log::error($exception); if (class_exists(\Log::class)) {
\Log::error($exception);
}
return parent::report($exception); return parent::report($exception);
} }
} }

View file

@ -63,6 +63,7 @@ class AccessoriesController extends Controller
public function store(ImageUploadRequest $request) public function store(ImageUploadRequest $request)
{ {
$this->authorize(Accessory::class); $this->authorize(Accessory::class);
// create a new model instance // create a new model instance
$accessory = new Accessory(); $accessory = new Accessory();
@ -82,7 +83,6 @@ class AccessoriesController extends Controller
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes'); $accessory->notes = request('notes');
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
// Was the accessory created? // Was the accessory created?
@ -127,45 +127,47 @@ class AccessoriesController extends Controller
*/ */
public function update(ImageUploadRequest $request, $accessoryId = null) public function update(ImageUploadRequest $request, $accessoryId = null)
{ {
if (is_null($accessory = Accessory::find($accessoryId))) { if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->users_count"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
} else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
} }
$min = $accessory->numCheckedOut();
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$min"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors()); return redirect()->back()->withInput()->withErrors($accessory->getErrors());
} }
@ -217,7 +219,7 @@ class AccessoriesController extends Controller
*/ */
public function show($accessoryID = null) public function show($accessoryID = null)
{ {
$accessory = Accessory::find($accessoryID); $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$this->authorize('view', $accessory); $this->authorize('view', $accessory);
if (isset($accessory->id)) { if (isset($accessory->id)) {
return view('accessories/view', compact('accessory')); return view('accessories/view', compact('accessory'));

View file

@ -222,8 +222,8 @@ class AcceptanceController extends Controller
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'eula' => $item->getEula(), 'eula' => $item->getEula(),
'check_out_date' => Carbon::parse($acceptance->created_at)->format($branding_settings->date_display_format), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
'accepted_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), 'accepted_date' => Carbon::parse($acceptance->accepted_at)->format('Y-m-d'),
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name, 'company_name' => $branding_settings->site_name,
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
@ -273,7 +273,7 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag, 'item_tag' => $item->asset_tag,
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'declined_date' => Carbon::parse($acceptance->accepted_at)->format($branding_settings->date_display_format), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'assigned_to' => $assigned_to, 'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name, 'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format, 'date_settings' => $branding_settings->date_display_format,

View file

@ -26,7 +26,10 @@ class AccessoriesController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('view', Accessory::class); if ($request->user()->cannot('reports.view')) {
$this->authorize('view', Accessory::class);
}
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
// Relations will be handled in query scopes a little further down. // Relations will be handled in query scopes a little further down.
@ -41,10 +44,13 @@ class AccessoriesController extends Controller
'min_amt', 'min_amt',
'company_id', 'company_id',
'notes', 'notes',
'users_count',
'qty',
]; ];
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier'); $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
->withCount('users as users_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search')); $accessories = $accessories->TextSearch($request->input('search'));

View file

@ -860,7 +860,8 @@ class AssetsController extends Controller
$checkout_at = request('checkout_at', date('Y-m-d H:i:s')); $checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null); $expected_checkin = request('expected_checkin', null);
$note = request('note', null); $note = request('note', null);
$asset_name = request('name', null); // Using `->has` preserves the asset name if the name parameter was not included in request.
$asset_name = request()->has('name') ? request('name') : $asset->name;
// Set the location ID to the RTD location id if there is one // Set the location ID to the RTD location id if there is one
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense. // Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.

View file

@ -33,7 +33,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function index() public function index()
{ {
$this->authorize('index', CustomFieldset::class); $this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count()); return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
@ -49,7 +49,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function show($id) public function show($id)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) { if ($fieldset = CustomFieldset::find($id)) {
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset); return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
} }
@ -68,7 +68,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
$fieldset->fill($request->all()); $fieldset->fill($request->all());
@ -89,7 +89,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset; $fieldset = new CustomFieldset;
$fieldset->fill($request->all()); $fieldset->fill($request->all());
@ -109,7 +109,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function destroy($id) public function destroy($id)
{ {
$this->authorize('delete', CustomFieldset::class); $this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
$modelsCount = $fieldset->models->count(); $modelsCount = $fieldset->models->count();
@ -136,7 +136,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function fields($id) public function fields($id)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id); $set = CustomFieldset::findOrFail($id);
$fields = $set->fields; $fields = $set->fields;
@ -153,7 +153,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function fieldsWithDefaultValues($fieldsetId, $modelId) public function fieldsWithDefaultValues($fieldsetId, $modelId)
{ {
$this->authorize('view', CustomFieldset::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($fieldsetId); $set = CustomFieldset::findOrFail($fieldsetId);

View file

@ -10,6 +10,7 @@ use App\Models\Asset;
use App\Models\Company; use App\Models\Company;
use App\Models\Import; use App\Models\Import;
use Artisan; use Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -35,7 +36,7 @@ class ImportController extends Controller
* Process and store a CSV upload file. * Process and store a CSV upload file.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\JsonResponse
*/ */
public function store() public function store()
{ {
@ -56,7 +57,7 @@ class ImportController extends Controller
'text/tsv', ])) { 'text/tsv', ])) {
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType(); $results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500); return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
} }
//TODO: is there a lighter way to do this? //TODO: is there a lighter way to do this?
@ -64,7 +65,19 @@ class ImportController extends Controller
ini_set('auto_detect_line_endings', '1'); ini_set('auto_detect_line_endings', '1');
} }
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak? $reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$import->header_row = $reader->fetchOne(0);
try {
$import->header_row = $reader->fetchOne(0);
} catch (JsonEncodingException $e) {
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.header_row_has_malformed_characters')
),
422
);
}
//duplicate headers check //duplicate headers check
$duplicate_headers = []; $duplicate_headers = [];
@ -82,11 +95,22 @@ class ImportController extends Controller
} }
} }
if (count($duplicate_headers) > 0) { if (count($duplicate_headers) > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)), 500); //should this be '4xx'? return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)),422);
} }
// Grab the first row to display via ajax as the user picks fields try {
$import->first_row = $reader->fetchOne(1); // Grab the first row to display via ajax as the user picks fields
$import->first_row = $reader->fetchOne(1);
} catch (JsonEncodingException $e) {
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.content_row_has_malformed_characters')
),
422
);
}
$date = date('Y-m-d-his'); $date = date('Y-m-d-his');
$fixed_filename = str_slug($file->getClientOriginalName()); $fixed_filename = str_slug($file->getClientOriginalName());
@ -108,12 +132,12 @@ class ImportController extends Controller
} }
$results = (new ImportsTransformer)->transformImports($results); $results = (new ImportsTransformer)->transformImports($results);
return [ return response()->json([
'files' => $results, 'files' => $results,
]; ]);
} }
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 500); return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.feature_disabled')), 422);
} }
/** /**
@ -127,7 +151,7 @@ class ImportController extends Controller
$this->authorize('import'); $this->authorize('import');
// Run a backup immediately before processing // Run a backup immediately before processing
if ($request->has('run-backup')) { if ($request->get('run-backup')) {
\Log::debug('Backup manually requested via importer'); \Log::debug('Backup manually requested via importer');
Artisan::call('backup:run'); Artisan::call('backup:run');
} else { } else {

View file

@ -57,8 +57,12 @@ class ReportsController extends Controller
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc'; $order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
$offset = request('offset', 0); $offset = request('offset', 0);
$limit = request('limit', 50);
$total = $actionlogs->count(); $total = $actionlogs->count();
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get(); $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE); return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);

View file

@ -143,47 +143,6 @@ class SettingsController extends Controller
} }
public function slacktest(SlackSettingsRequest $request)
{
$validator = Validator::make($request->all(), [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
]);
if ($validator->fails()) {
return response()->json(['message' => 'Validation failed', 'errors' => $validator->errors()], 422);
}
// If validation passes, continue to the curl request
$slack = new Client([
'base_url' => e($request->input('slack_endpoint')),
'defaults' => [
'exceptions' => false,
],
]);
$payload = json_encode(
[
'channel' => e($request->input('slack_channel')),
'text' => trans('general.slack_test_msg'),
'username' => e($request->input('slack_botname')),
'icon_emoji' => ':heart:',
]);
try {
$slack->post($request->input('slack_endpoint'), ['body' => $payload]);
return response()->json(['message' => 'Success'], 200);
} catch (\Exception $e) {
return response()->json(['message' => 'Please check the channel name and webhook endpoint URL ('.e($request->input('slack_endpoint')).'). Slack responded with: '.$e->getMessage()], 400);
}
//}
return response()->json(['message' => 'Something went wrong :( '], 400);
}
/** /**
* Test the email configuration * Test the email configuration
* *

View file

@ -246,6 +246,7 @@ class UsersController extends Controller
'two_factor_optin', 'two_factor_optin',
'two_factor_enrolled', 'two_factor_enrolled',
'remote', 'remote',
'vip',
'start_date', 'start_date',
'end_date', 'end_date',
]; ];
@ -286,9 +287,11 @@ class UsersController extends Controller
$users = Company::scopeCompanyables($users); $users = Company::scopeCompanyables($users);
if ($request->filled('search')) { if ($request->filled('search')) {
$users = $users->SimpleNameSearch($request->get('search')) $users = $users->where(function ($query) use ($request) {
->orWhere('username', 'LIKE', '%'.$request->get('search').'%') $query->SimpleNameSearch($request->get('search'))
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%'); ->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
});
} }
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc'); $users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
@ -542,9 +545,10 @@ class UsersController extends Controller
if (empty($user->email)) { if (empty($user->email)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
} }
$user->notify((new CurrentInventory($user)));
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success')));
} }
/** /**

View file

@ -27,7 +27,7 @@ class AssetCheckoutController extends Controller
public function create($assetId) public function create($assetId)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find(e($assetId)))) { if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
} }

View file

@ -288,7 +288,8 @@ class BulkAssetsController extends Controller
foreach ($asset_ids as $asset_id) { foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset); $this->authorize('checkout', $asset);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), null);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
if ($target->location_id != '') { if ($target->location_id != '') {
$asset->location_id = $target->location_id; $asset->location_id = $target->location_id;

View file

@ -109,9 +109,9 @@ class CustomFieldsController extends Controller
if ($request->filled('custom_format')) { if ($request->filled('custom_format')) {
$field->format = e($request->get('custom_format')); $field->format = $request->get('custom_format');
} else { } else {
$field->format = e($request->get('format')); $field->format = $request->get('format');
} }
if ($field->save()) { if ($field->save()) {

View file

@ -75,9 +75,9 @@ class CustomFieldsetsController extends Controller
*/ */
public function create() public function create()
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
return view('custom_fields.fieldsets.edit'); return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
} }
/** /**
@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
$this->authorize('create', CustomFieldset::class); $this->authorize('create', CustomField::class);
$cfset = new CustomFieldset([ $cfset = new CustomFieldset([
'name' => e($request->get('name')), 'name' => e($request->get('name')),
@ -110,31 +110,52 @@ class CustomFieldsetsController extends Controller
} }
/** /**
* What the actual fuck, Brady? * Presents edit form for fieldset
* *
* @todo Uhh, build this? * @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @since [v1.8] * @since [v6.0.14]
* @return Fuckall * @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($id) public function edit($id)
{ {
// $this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
} }
/** /**
* GET IN THE SEA BRADY. * Saves updated fieldset data
* *
* @todo Uhh, build this too? * @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @since [v1.8] * @since [v6.0.14]
* @return Fuckall * @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update($id) public function update(Request $request, $id)
{ {
// $this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
$fieldset->name = $request->input('name');
if ($fieldset->save()) {
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
} }
/** /**
@ -202,7 +223,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function makeFieldRequired($fieldset_id, $field_id) public function makeFieldRequired($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id); $fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 1]; $fields[$field->id] = ['required' => 1];
@ -220,7 +241,7 @@ class CustomFieldsetsController extends Controller
*/ */
public function makeFieldOptional($fieldset_id, $field_id) public function makeFieldOptional($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id); $fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 0]; $fields[$field->id] = ['required' => 0];

View file

@ -59,6 +59,12 @@ class LicenseCheckinController extends Controller
} }
$license = License::find($licenseSeat->license_id); $license = License::find($licenseSeat->license_id);
// LicenseSeat is not assigned, it can't be checked in
if (is_null($licenseSeat->assignedTo) && is_null($licenseSeat->asset_id)) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkin.error'));
}
$this->authorize('checkout', $license); $this->authorize('checkout', $license);
if (! $license->reassignable) { if (! $license->reassignable) {

View file

@ -17,7 +17,7 @@ class ModalController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @return View * @return View
*/ */
function show ($type, $itemId = null) { public function show ($type, $itemId = null) {
// These values should correspond to a file in resources/views/modals/ // These values should correspond to a file in resources/views/modals/
$allowed_types = [ $allowed_types = [

View file

@ -67,37 +67,9 @@ class ProfileController extends Controller
$user->location_id = $request->input('location_id'); $user->location_id = $request->input('location_id');
} }
// Handle the avatar upload and/or delete if necessary
app('\App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($request->input('avatar_delete') == 1) {
$user->avatar = null;
}
if ($request->hasFile('avatar')) {
$path = 'avatars';
if (! Storage::disk('public')->exists($path)) {
Storage::disk('public')->makeDirectory($path, 775);
}
$upload = $image = $request->file('avatar');
$ext = $image->getClientOriginalExtension();
$file_name = 'avatar-'.str_random(18).'.'.$ext;
if ($image->getClientOriginalExtension() != 'svg') {
$upload = Image::make($image->getRealPath())->resize(84, 84);
}
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
// Remove Current image if exists
if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) {
Storage::disk('public')->delete($path.'/'.$user->avatar);
}
$user->avatar = $file_name;
}
if ($user->save()) { if ($user->save()) {
return redirect()->route('profile')->with('success', 'Account successfully updated'); return redirect()->route('profile')->with('success', 'Account successfully updated');

View file

@ -51,9 +51,8 @@ class ReportsController extends Controller
public function getAccessoryReport() public function getAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->with('company')->get();
return view('reports/accessories', compact('accessories')); return view('reports/accessories');
} }
/** /**
@ -1120,8 +1119,6 @@ class ReportsController extends Controller
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag)); $row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user'))); $row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
$rows[] = implode(',', $row); $rows[] = implode(',', $row);
} else {
// Log the error maybe?
} }
} }

View file

@ -679,33 +679,6 @@ class SettingsController extends Controller
return view('settings.slack', compact('setting')); return view('settings.slack', compact('setting'));
} }
/**
* Return a form to allow a super admin to update settings.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.0]
*
* @return View
*/
public function postSlack(SlackSettingsRequest $request)
{
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->slack_endpoint = $request->input('slack_endpoint');
$setting->slack_channel = $request->input('slack_channel');
$setting->slack_botname = $request->input('slack_botname');
if ($setting->save()) {
return redirect()->route('settings.index')
->with('success', trans('admin/settings/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($setting->getErrors());
}
/** /**
* Return a form to allow a super admin to update settings. * Return a form to allow a super admin to update settings.
* *
@ -807,7 +780,7 @@ class SettingsController extends Controller
*/ */
public function getPhpInfo() public function getPhpInfo()
{ {
if (true === config('app.debug')) { if (config('app.debug') === true) {
return view('settings.phpinfo'); return view('settings.phpinfo');
} }

View file

@ -121,6 +121,7 @@ class UsersController extends Controller
$user->created_by = Auth::user()->id; $user->created_by = Auth::user()->id;
$user->start_date = $request->input('start_date', null); $user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null); $user->end_date = $request->input('end_date', null);
$user->autoassign_licenses= $request->input('autoassign_licenses', 1);
// Strip out the superuser permission if the user isn't a superadmin // Strip out the superuser permission if the user isn't a superadmin
$permissions_array = $request->input('permission'); $permissions_array = $request->input('permission');
@ -131,7 +132,7 @@ class UsersController extends Controller
$user->permissions = json_encode($permissions_array); $user->permissions = json_encode($permissions_array);
// we have to invoke the // we have to invoke the
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'image', 'avatars', 'avatar'); app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($user->save()) { if ($user->save()) {
if ($request->filled('groups')) { if ($request->filled('groups')) {
@ -271,9 +272,11 @@ class UsersController extends Controller
$user->activated = $request->input('activated', 0); $user->activated = $request->input('activated', 0);
$user->zip = $request->input('zip', null); $user->zip = $request->input('zip', null);
$user->remote = $request->input('remote', 0); $user->remote = $request->input('remote', 0);
$user->vip = $request->input('vip', 0);
$user->website = $request->input('website', null); $user->website = $request->input('website', null);
$user->start_date = $request->input('start_date', null); $user->start_date = $request->input('start_date', null);
$user->end_date = $request->input('end_date', null); $user->end_date = $request->input('end_date', null);
$user->autoassign_licenses = $request->input('autoassign_licenses', 1);
// Update the location of any assets checked out to this user // Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class) Asset::where('assigned_type', User::class)
@ -296,7 +299,7 @@ class UsersController extends Controller
$user->permissions = json_encode($permissions_array); $user->permissions = json_encode($permissions_array);
// Handle uploaded avatar // Handle uploaded avatar
app(\App\Http\Requests\ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
//\Log::debug(print_r($user, true)); //\Log::debug(print_r($user, true));

View file

@ -0,0 +1,95 @@
<?php
namespace App\Http\Livewire;
use GuzzleHttp\Client;
use Livewire\Component;
use App\Models\Setting;
class SlackSettingsForm extends Component
{
public $slack_endpoint;
public $slack_channel;
public $slack_botname;
public $isDisabled ='disabled' ;
public Setting $setting;
protected $rules = [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:https://hooks.slack.com/|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
'slack_botname' => 'string|nullable',
];
public function mount(){
$this->setting = Setting::getSettings();
$this->slack_endpoint = $this->setting->slack_endpoint;
$this->slack_channel = $this->setting->slack_channel;
$this->slack_botname = $this->setting->slack_botname;
}
public function updated($field){
$this->validateOnly($field ,$this->rules);
}
public function render()
{
if(empty($this->slack_channel || $this->slack_endpoint)){
$this->isDisabled= 'disabled';
}
if(empty($this->slack_endpoint && $this->slack_channel)){
$this->isDisabled= '';
}
return view('livewire.slack-settings-form');
}
public function testSlack(){
$slack = new Client([
'base_url' => e($this->slack_endpoint),
'defaults' => [
'exceptions' => false,
],
]);
$payload = json_encode(
[
'channel' => e($this->slack_channel),
'text' => trans('general.slack_test_msg'),
'username' => e($this->slack_botname),
'icon_emoji' => ':heart:',
]);
try {
$slack->post($this->slack_endpoint, ['body' => $payload]);
$this->isDisabled='';
return session()->flash('success' , 'Your Slack Integration works!');
} catch (\Exception $e) {
$this->isDisabled= 'disabled';
return session()->flash('error' , trans('admin/settings/message.slack.error', ['error_message' => $e->getMessage()]));
}
//}
return session()->flash('message' , trans('admin/settings/message.slack.error_misc'));
}
public function submit()
{
$this->validate($this->rules);
$this->setting->slack_endpoint = $this->slack_endpoint;
$this->setting->slack_channel = $this->slack_channel;
$this->setting->slack_botname = $this->slack_botname;
$this->setting->save();
session()->flash('save',trans('admin/settings/message.update.success'));
}
}

View file

@ -11,7 +11,7 @@ class CheckForTwoFactor
/** /**
* Routes to ignore for Two Factor Auth * Routes to ignore for Two Factor Auth
*/ */
const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout']; public const IGNORE_ROUTES = ['two-factor', 'two-factor-enroll', 'setup', 'logout'];
/** /**
* Handle an incoming request. * Handle an incoming request.

View file

@ -63,11 +63,13 @@ class ImageUploadRequest extends Request
* @param string $path location for uploaded images, defaults to uploads/plural of item type. * @param string $path location for uploaded images, defaults to uploads/plural of item type.
* @return SnipeModel Target asset is being checked out to. * @return SnipeModel Target asset is being checked out to.
*/ */
public function handleImages($item, $w = 600, $form_fieldname = null, $path = null, $db_fieldname = 'image') public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
{ {
$type = strtolower(class_basename(get_class($item))); $type = strtolower(class_basename(get_class($item)));
if (is_null($path)) { if (is_null($path)) {
$path = str_plural($type); $path = str_plural($type);
if ($type == 'assetmodel') { if ($type == 'assetmodel') {
@ -79,42 +81,31 @@ class ImageUploadRequest extends Request
} }
} }
if (is_null($form_fieldname)) {
$form_fieldname = 'image';
}
// This is dumb, but we need it for overriding field names for exceptions like avatars and logo uploads
if (is_null($db_fieldname)) {
$use_db_field = $form_fieldname;
} else {
$use_db_field = $db_fieldname;
}
// ConvertBase64ToFiles just changes object type,
// as it cannot currently insert files to $this->files
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) { if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
$image=$this->offsetGet($form_fieldname); $image = $this->offsetGet($form_fieldname);
\Log::debug('Image is an instance of UploadedFile');
} elseif ($this->hasFile($form_fieldname)) {
$image = $this->file($form_fieldname);
\Log::debug('Just use regular upload for '.$form_fieldname);
} else { } else {
if ($this->hasFile($form_fieldname)) { \Log::debug('No image found for form fieldname: '.$form_fieldname);
$image = $this->file($form_fieldname);
}
} }
if (isset($image)) { if (isset($image)) {
\Log::debug($image);
if (!config('app.lock_passwords')) { if (!config('app.lock_passwords')) {
$ext = $image->getClientOriginalExtension(); $ext = $image->getClientOriginalExtension();
$file_name = $type.'-'.$form_fieldname.'-'.str_random(10).'.'.$ext; $file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
\Log::info('File name will be: '.$file_name); \Log::info('File name will be: '.$file_name);
\Log::debug('File extension is: '.$ext); \Log::debug('File extension is: '.$ext);
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) { if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
\Log::debug('Not an SVG or webp - resize'); \Log::debug('Not an SVG or webp - resize');
\Log::debug('Trying to upload to: '.$path.'/'.$file_name); \Log::debug('Trying to upload to: '.$path.'/'.$file_name);
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) { $upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio(); $constraint->aspectRatio();
$constraint->upsize(); $constraint->upsize();
@ -122,6 +113,7 @@ class ImageUploadRequest extends Request
// This requires a string instead of an object, so we use ($string) // This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode()); Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
} else { } else {
// If the file is a webp, we need to just move it since webp support // If the file is a webp, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available // needs to be compiled into gd for resizing to be available
@ -146,30 +138,30 @@ class ImageUploadRequest extends Request
} }
// Remove Current image if exists // Remove Current image if exists
if (($item->{$use_db_field}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field}))) { if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.'); \Log::debug('A file already exists that we are replacing - we should delete the old one.');
try { try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.'); \Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?'); \Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
} }
} }
$item->{$use_db_field} = $file_name; $item->{$db_fieldname} = $file_name;
} }
// If the user isn't uploading anything new but wants to delete their old image, do so // If the user isn't uploading anything new but wants to delete their old image, do so
} else { } elseif ($this->input('image_delete') == '1') {
if ($this->input('image_delete') == '1') { \Log::debug('Deleting image');
\Log::debug('Deleting image'); try {
try { Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname});
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); $item->{$db_fieldname} = null;
$item->{$use_db_field} = null; } catch (\Exception $e) {
} catch (\Exception $e) { \Log::debug($e);
\Log::debug($e);
}
} }
} }
return $item; return $item;

View file

@ -39,14 +39,12 @@ class SaveUserRequest extends FormRequest
// Brand new user // Brand new user
case 'POST': case 'POST':
{
$rules['first_name'] = 'required|string|min:1'; $rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1'; $rules['username'] = 'required_unless:ldap_import,1|string|min:1';
if ($this->request->get('ldap_import') == false) { if ($this->request->get('ldap_import') == false) {
$rules['password'] = Setting::passwordComplexityRulesSaving('store').'|confirmed'; $rules['password'] = Setting::passwordComplexityRulesSaving('store').'|confirmed';
} }
break; break;
}
// Save all fields // Save all fields
case 'PUT': case 'PUT':
@ -57,12 +55,11 @@ class SaveUserRequest extends FormRequest
// Save only what's passed // Save only what's passed
case 'PATCH': case 'PATCH':
{
$rules['password'] = Setting::passwordComplexityRulesSaving('update'); $rules['password'] = Setting::passwordComplexityRulesSaving('update');
break; break;
}
default:break; default:
break;
} }
return $rules; return $rules;

View file

@ -1,33 +0,0 @@
<?php
namespace App\Http\Requests;
class SlackSettingsRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'slack_endpoint' => 'url|required_with:slack_channel|starts_with:"https://hooks.slack.com"|nullable',
'slack_channel' => 'required_with:slack_endpoint|starts_with:#|nullable',
'slack_botname' => 'string|nullable',
];
}
}

View file

@ -38,7 +38,8 @@ class AccessoriesTransformer
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => $accessory->numRemaining(), 'remaining_qty' => (int) $accessory->numRemaining(),
'users_count' => $accessory->users_count,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),

View file

@ -60,12 +60,14 @@ class ActionlogsTransformer
if ($actionlog->action_type == 'accepted') { if ($actionlog->action_type == 'accepted') {
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]); $file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
} else { } else {
if ($actionlog->itemType() == 'asset') { if ($actionlog->item) {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]); if ($actionlog->itemType() == 'asset') {
} elseif ($actionlog->itemType() == 'license') { $file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'license') {
} elseif ($actionlog->itemType() == 'user') { $file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'user') {
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
}
} }
} }
} }

View file

@ -4,7 +4,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\AssetModel; use App\Models\AssetModel;
use Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View file

@ -74,7 +74,7 @@ class ItemImporter extends Importer
$this->item['purchase_date'] = null; $this->item['purchase_date'] = null;
if ($this->findCsvMatch($row, 'purchase_date') != '') { if ($this->findCsvMatch($row, 'purchase_date') != '') {
$this->item['purchase_date'] = date('Y-m-d 00:00:01', strtotime($this->findCsvMatch($row, 'purchase_date'))); $this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date')));
} }
$this->item['last_audit_date'] = null; $this->item['last_audit_date'] = null;

View file

@ -80,6 +80,11 @@ class LicenseImporter extends ItemImporter
$checkout_target = $this->item['checkout_target']; $checkout_target = $this->item['checkout_target'];
$asset = Asset::where('asset_tag', $asset_tag)->first(); $asset = Asset::where('asset_tag', $asset_tag)->first();
$targetLicense = $license->freeSeat(); $targetLicense = $license->freeSeat();
if (is_null($targetLicense)){
return;
}
if ($checkout_target) { if ($checkout_target) {
$targetLicense->assigned_to = $checkout_target->id; $targetLicense->assigned_to = $checkout_target->id;
$targetLicense->user_id = Auth::id(); $targetLicense->user_id = Auth::id();

View file

@ -63,6 +63,7 @@ class Accessory extends SnipeModel
'company_id' => 'integer|nullable', 'company_id' => 'integer|nullable',
'min_amt' => 'integer|min:0|nullable', 'min_amt' => 'integer|min:0|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
'purchase_date' => 'date_format:Y-m-d|nullable',
]; ];
@ -327,20 +328,6 @@ class Accessory extends SnipeModel
return null; return null;
} }
/**
* Check how many items within an accessory are checked out
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
* @return int
*/
public function numCheckedOut()
{
$checkedout = 0;
$checkedout = $this->users->count();
return $checkedout;
}
/** /**
* Check how many items of an accessory remain * Check how many items of an accessory remain
@ -351,11 +338,11 @@ class Accessory extends SnipeModel
*/ */
public function numRemaining() public function numRemaining()
{ {
$checkedout = $this->users->count(); $checkedout = $this->users_count;
$total = $this->qty; $total = $this->qty;
$remaining = $total - $checkedout; $remaining = $total - $checkedout;
return $remaining; return (int) $remaining;
} }
/** /**

View file

@ -34,9 +34,9 @@ class Asset extends Depreciable
use CompanyableTrait; use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait; use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
const LOCATION = 'location'; public const LOCATION = 'location';
const ASSET = 'asset'; public const ASSET = 'asset';
const USER = 'user'; public const USER = 'user';
use Acceptable; use Acceptable;
@ -84,11 +84,11 @@ class Asset extends Depreciable
protected $casts = [ protected $casts = [
'purchase_date' => 'datetime', 'purchase_date' => 'date',
'last_checkout' => 'datetime', 'last_checkout' => 'datetime',
'expected_checkin' => 'datetime', 'expected_checkin' => 'date',
'last_audit_date' => 'datetime', 'last_audit_date' => 'datetime',
'next_audit_date' => 'datetime', 'next_audit_date' => 'date',
'model_id' => 'integer', 'model_id' => 'integer',
'status_id' => 'integer', 'status_id' => 'integer',
'company_id' => 'integer', 'company_id' => 'integer',
@ -105,16 +105,14 @@ class Asset extends Depreciable
'company_id' => 'integer|nullable', 'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable|digits_between:0,240', 'warranty_months' => 'numeric|nullable|digits_between:0,240',
'physical' => 'numeric|max:1|nullable', 'physical' => 'numeric|max:1|nullable',
'checkout_date' => 'date|max:10|min:10|nullable', 'last_checkout' => 'date_format:Y-m-d H:i:s|nullable',
'checkin_date' => 'date|max:10|min:10|nullable', 'expected_checkin' => 'date|nullable',
'location_id' => 'exists:locations,id|nullable', 'location_id' => 'exists:locations,id|nullable',
'rtd_location_id' => 'exists:locations,id|nullable', 'rtd_location_id' => 'exists:locations,id|nullable',
'asset_tag' => 'required|min:1|max:255|unique_undeleted', 'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'status' => 'integer', 'purchase_date' => 'date|date_format:Y-m-d|nullable',
'serial' => 'unique_serial|nullable', 'serial' => 'unique_serial|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
'next_audit_date' => 'date|nullable',
'last_audit_date' => 'date|nullable',
'supplier_id' => 'exists:suppliers,id|nullable', 'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|max:10|min:10|nullable', 'asset_eol_date' => 'date|max:10|min:10|nullable',
]; ];
@ -147,6 +145,8 @@ class Asset extends Depreciable
'expected_checkin', 'expected_checkin',
'byod', 'byod',
'asset_eol_date', 'asset_eol_date',
'last_audit_date',
'next_audit_date',
]; ];
use Searchable; use Searchable;
@ -188,6 +188,14 @@ class Asset extends Depreciable
'model.manufacturer' => ['name'], 'model.manufacturer' => ['name'],
]; ];
// To properly set the expected checkin as Y-m-d
public function setExpectedCheckinAttribute($value)
{
if ($value == '') {
$value = null;
}
$this->attributes['expected_checkin'] = $value;
}
/** /**
* This handles the custom field validation for assets * This handles the custom field validation for assets
@ -1635,9 +1643,9 @@ class Asset extends Depreciable
*/ */
public function scopeOrderManufacturer($query, $order) public function scopeOrderManufacturer($query, $order)
{ {
return $query->join('models', 'assets.model_id', '=', 'models.id') return $query->join('models as order_asset_model', 'assets.model_id', '=', 'order_asset_model.id')
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id') ->join('manufacturers as manufacturer_order', 'order_asset_model.manufacturer_id', '=', 'manufacturer_order.id')
->orderBy('manufacturers.name', $order); ->orderBy('manufacturer_order.name', $order);
} }
/** /**

View file

@ -16,12 +16,19 @@ class CheckoutAcceptance extends Model
'declined_at' => 'datetime', 'declined_at' => 'datetime',
]; ];
// Get the mail recipient from the config /**
public function routeNotificationForMail(): string * Get the mail recipient from the config
*
* @return mixed|string|null
*/
public function routeNotificationForMail()
{ {
// At this point the endpoint is the same for everything. // At this point the endpoint is the same for everything.
// In the future this may want to be adapted for individual notifications. // In the future this may want to be adapted for individual notifications.
return (config('mail.reply_to.address')) ? config('mail.reply_to.address') : '' ; $recipients_string = explode(',', Setting::getSettings()->alert_email);
$recipients = array_map('trim', $recipients_string);
return array_filter($recipients);
} }
/** /**

View file

@ -35,7 +35,7 @@ class Component extends SnipeModel
'category_id' => 'required|integer|exists:categories,id', 'category_id' => 'required|integer|exists:categories,id',
'company_id' => 'integer|nullable', 'company_id' => 'integer|nullable',
'min_amt' => 'integer|min:0|nullable', 'min_amt' => 'integer|min:0|nullable',
'purchase_date' => 'date|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
]; ];

View file

@ -41,6 +41,7 @@ class Consumable extends SnipeModel
'company_id' => 'integer|nullable', 'company_id' => 'integer|nullable',
'min_amt' => 'integer|min:0|nullable', 'min_amt' => 'integer|min:0|nullable',
'purchase_cost' => 'numeric|nullable|gte:0', 'purchase_cost' => 'numeric|nullable|gte:0',
'purchase_date' => 'date_format:Y-m-d|nullable',
]; ];
/** /**

View file

@ -21,7 +21,7 @@ class CustomField extends Model
* *
* @var array * @var array
*/ */
const PREDEFINED_FORMATS = [ public const PREDEFINED_FORMATS = [
'ANY' => '', 'ANY' => '',
'CUSTOM REGEX' => '', 'CUSTOM REGEX' => '',
'ALPHA' => 'alpha', 'ALPHA' => 'alpha',

View file

@ -217,16 +217,16 @@ class Ldap extends Model
$ldap_result_manager = Setting::getSettings()->ldap_manager; $ldap_result_manager = Setting::getSettings()->ldap_manager;
// Get LDAP user data // Get LDAP user data
$item = []; $item = [];
$item['username'] = isset($ldapattributes[$ldap_result_username][0]) ? $ldapattributes[$ldap_result_username][0] : ''; $item['username'] = $ldapattributes[$ldap_result_username][0] ?? '';
$item['employee_number'] = isset($ldapattributes[$ldap_result_emp_num][0]) ? $ldapattributes[$ldap_result_emp_num][0] : ''; $item['employee_number'] = $ldapattributes[$ldap_result_emp_num][0] ?? '';
$item['lastname'] = isset($ldapattributes[$ldap_result_last_name][0]) ? $ldapattributes[$ldap_result_last_name][0] : ''; $item['lastname'] = $ldapattributes[$ldap_result_last_name][0] ?? '';
$item['firstname'] = isset($ldapattributes[$ldap_result_first_name][0]) ? $ldapattributes[$ldap_result_first_name][0] : ''; $item['firstname'] = $ldapattributes[$ldap_result_first_name][0] ?? '';
$item['email'] = isset($ldapattributes[$ldap_result_email][0]) ? $ldapattributes[$ldap_result_email][0] : ''; $item['email'] = $ldapattributes[$ldap_result_email][0] ?? '';
$item['telephone'] = isset($ldapattributes[$ldap_result_phone][0]) ? $ldapattributes[$ldap_result_phone][0] : ''; $item['telephone'] = $ldapattributes[$ldap_result_phone][0] ?? '';
$item['jobtitle'] = isset($ldapattributes[$ldap_result_jobtitle][0]) ? $ldapattributes[$ldap_result_jobtitle][0] : ''; $item['jobtitle'] = $ldapattributes[$ldap_result_jobtitle][0] ?? '';
$item['country'] = isset($ldapattributes[$ldap_result_country][0]) ? $ldapattributes[$ldap_result_country][0] : ''; $item['country'] = $ldapattributes[$ldap_result_country][0] ?? '';
$item['department'] = isset($ldapattributes[$ldap_result_dept][0]) ? $ldapattributes[$ldap_result_dept][0] : ''; $item['department'] = $ldapattributes[$ldap_result_dept][0] ?? '';
$item['manager'] = isset($ldapattributes[$ldap_result_manager][0]) ? $ldapattributes[$ldap_result_manager][0] : ''; $item['manager'] = $ldapattributes[$ldap_result_manager][0] ?? '';
return $item; return $item;
} }

View file

@ -36,7 +36,6 @@ class License extends Depreciable
'purchase_date' => 'datetime', 'purchase_date' => 'datetime',
'expiration_date' => 'datetime', 'expiration_date' => 'datetime',
'termination_date' => 'datetime', 'termination_date' => 'datetime',
'seats' => 'integer',
'category_id' => 'integer', 'category_id' => 'integer',
'company_id' => 'integer', 'company_id' => 'integer',
]; ];
@ -50,6 +49,9 @@ class License extends Depreciable
'category_id' => 'required|exists:categories,id', 'category_id' => 'required|exists:categories,id',
'company_id' => 'integer|nullable', 'company_id' => 'integer|nullable',
'purchase_cost'=> 'numeric|nullable|gte:0', 'purchase_cost'=> 'numeric|nullable|gte:0',
'purchase_date' => 'date_format:Y-m-d|nullable',
'expiration_date' => 'date_format:Y-m-d|nullable',
'termination_date' => 'date_format:Y-m-d|nullable',
]; ];
/** /**
@ -365,7 +367,7 @@ class License extends Depreciable
*/ */
public function assignedusers() public function assignedusers()
{ {
return $this->belongsToMany(\App\Models\User::class, 'license_seats', 'assigned_to', 'license_id'); return $this->belongsToMany(\App\Models\User::class, 'license_seats', 'license_id', 'assigned_to');
} }
/** /**

View file

@ -93,8 +93,12 @@ trait Loggable
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$log = new Actionlog; $log = new Actionlog;
$log->target_type = get_class($target);
$log->target_id = $target->id; if($target != null){
$log->target_type = get_class($target);
$log->target_id = $target->id;
}
if (static::class == LicenseSeat::class) { if (static::class == LicenseSeat::class) {
$log->item_type = License::class; $log->item_type = License::class;

View file

@ -31,7 +31,7 @@ class Setting extends Model
* *
* @var string * @var string
*/ */
const SETUP_CHECK_KEY = 'snipeit_setup_check'; public const SETUP_CHECK_KEY = 'snipeit_setup_check';
/** /**
* Whether the model should inject it's identifier to the unique * Whether the model should inject it's identifier to the unique
@ -83,6 +83,9 @@ class Setting extends Model
'email_domain', 'email_domain',
'email_format', 'email_format',
'username_format', 'username_format',
'slack_endpoint',
'slack_channel',
'slack_botname',
]; ];
/** /**

View file

@ -12,94 +12,9 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
{ {
public function getUserConfig() public function getUserConfig()
{ {
$config = parent::getUserConfig();
// Much of this is copied verbatim from the library, then adjusted for our needs // Much of this is copied verbatim from the library, then adjusted for our needs
$config['class'] = SCIMUser::class;
unset($config['mapping']['example:name:space']); /*
$config['map_unmapped'] = false; // anything we don't explicitly map will _not_ show up.
$core_namespace = 'urn:ietf:params:scim:schemas:core:2.0:User';
$core = $core_namespace.':';
$mappings =& $config['mapping'][$core_namespace]; //grab this entire key, we don't want to be repeating ourselves
//username - *REQUIRED*
$config['validations'][$core.'userName'] = 'required';
$mappings['userName'] = AttributeMapping::eloquent('username');
//human name - *FIRST NAME REQUIRED*
$config['validations'][$core.'name.givenName'] = 'required';
$config['validations'][$core.'name.familyName'] = 'string'; //not required
$mappings['name']['familyName'] = AttributeMapping::eloquent("last_name");
$mappings['name']['givenName'] = AttributeMapping::eloquent("first_name");
$mappings['name']['formatted'] = (new AttributeMapping())->ignoreWrite()->setRead(
function (&$object) {
return $object->getFullNameAttribute();
}
);
// externalId support
$config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta
// note that the mapping is *not* namespaced like the other $mappings
$config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid');
$config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT...
$config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here)
$mappings['emails'] = [[
"value" => AttributeMapping::eloquent("email"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]];
//active
$config['validations'][$core.'active'] = 'boolean';
$mappings['active'] = AttributeMapping::eloquent('activated');
//phone
$config['validations'][$core.'phoneNumbers'] = 'nullable|array';
$config['validations'][$core.'phoneNumbers.*.value'] = 'string'; // another one where want to say 'we don't _need_ a phone number, but if you have one it better have a value.
$mappings['phoneNumbers'] = [[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]];
//address
$config['validations'][$core.'addresses'] = 'nullable|array';
$config['validations'][$core.'addresses.*.streetAddress'] = 'string';
$config['validations'][$core.'addresses.*.locality'] = 'string';
$config['validations'][$core.'addresses.*.region'] = 'nullable|string';
$config['validations'][$core.'addresses.*.postalCode'] = 'nullable|string';
$config['validations'][$core.'addresses.*.country'] = 'string';
$mappings['addresses'] = [[
'type' => AttributeMapping::constant("work")->ignoreWrite(),
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
'streetAddress' => AttributeMapping::eloquent("address"),
'locality' => AttributeMapping::eloquent("city"),
'region' => AttributeMapping::eloquent("state"),
'postalCode' => AttributeMapping::eloquent("zip"),
'country' => AttributeMapping::eloquent("country"),
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
]];
//title
$config['validations'][$core.'title'] = 'string';
$mappings['title'] = AttributeMapping::eloquent('jobtitle');
//Preferred Language
$config['validations'][$core.'preferredLanguage'] = 'string';
$mappings['preferredLanguage'] = AttributeMapping::eloquent('locale');
/*
more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?): more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?):
- website - website
- notes? - notes?
@ -108,66 +23,213 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
- company_id to "organization?" - company_id to "organization?"
*/ */
$enterprise_namespace = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User';
$ent = $enterprise_namespace.':';
// we remove the 'example' namespace and add the Enterprise one $user_prefix = 'urn:ietf:params:scim:schemas:core:2.0:User:';
$config['mapping']['schemas'] = AttributeMapping::constant( [$core_namespace, $enterprise_namespace] )->ignoreWrite(); $enterprise_prefix = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:';
$config['validations'][$ent.'employeeNumber'] = 'string'; return [
$config['validations'][$ent.'department'] = 'string';
$config['validations'][$ent.'manager'] = 'nullable';
$config['validations'][$ent.'manager.value'] = 'string';
$config['mapping'][$enterprise_namespace] = [ // Set to 'null' to make use of auth.providers.users.model (App\User::class)
'employeeNumber' => AttributeMapping::eloquent('employee_num'), 'class' => SCIMUser::class,
'department' =>(new AttributeMapping())->setAdd( // FIXME parent?
function ($value, &$object) { 'validations' => [
$department = Department::where("name", $value)->first(); $user_prefix . 'userName' => 'required',
if ($department) { $user_prefix . 'name.givenName' => 'required',
$object->department_id = $department->id; $user_prefix . 'name.familyName' => 'nullable|string',
} $user_prefix . 'externalId' => 'nullable|string',
} $user_prefix . 'emails' => 'nullable|array',
)->setReplace( $user_prefix . 'emails.*.value' => 'nullable|email',
function ($value, &$object) { $user_prefix . 'active' => 'boolean',
$department = Department::where("name", $value)->first(); $user_prefix . 'phoneNumbers' => 'nullable|array',
if ($department) { $user_prefix . 'phoneNumbers.*.value' => 'nullable|string',
$object->department_id = $department->id; $user_prefix . 'addresses' => 'nullable|array',
$user_prefix . 'addresses.*.streetAddress' => 'nullable|string',
$user_prefix . 'addresses.*.locality' => 'nullable|string',
$user_prefix . 'addresses.*.region' => 'nullable|string',
$user_prefix . 'addresses.*.postalCode' => 'nullable|string',
$user_prefix . 'addresses.*.country' => 'nullable|string',
$user_prefix . 'title' => 'nullable|string',
$user_prefix . 'preferredLanguage' => 'nullable|string',
// Enterprise validations:
$enterprise_prefix . 'employeeNumber' => 'nullable|string',
$enterprise_prefix . 'department' => 'nullable|string',
$enterprise_prefix . 'manager' => 'nullable',
$enterprise_prefix . 'manager.value' => 'nullable|string'
],
'singular' => 'User',
'schema' => [Schema::SCHEMA_USER],
//eager loading
'withRelations' => [],
'map_unmapped' => false,
// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped',
'description' => 'User Account',
// Map a SCIM attribute to an attribute of the object.
'mapping' => [
'id' => AttributeMapping::eloquent("id")->disableWrite(),
'externalId' => AttributeMapping::eloquent('scim_externalid'), // FIXME - I have a PR that changes a lot of this.
'meta' => [
'created' => AttributeMapping::eloquent("created_at")->disableWrite(),
'lastModified' => AttributeMapping::eloquent("updated_at")->disableWrite(),
'location' => (new AttributeMapping())->setRead(
function ($object) {
return route(
'scim.resource',
[
'resourceType' => 'Users',
'resourceObject' => $object->id
]
);
} }
} )->disableWrite(),
)->setRead(
function (&$object) { 'resourceType' => AttributeMapping::constant("User")
return $object->department ? $object->department->name : null; ],
}
), 'schemas' => AttributeMapping::constant(
'manager' => [ [
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool. 'urn:ietf:params:scim:schemas:core:2.0:User',
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(), ]
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing )->ignoreWrite(),
'value' => (new AttributeMapping())->setAdd(
function ($value, &$object) { 'urn:ietf:params:scim:schemas:core:2.0:User' => [
$manager = User::find($value);
if ($manager) { 'userName' => AttributeMapping::eloquent("username"),
$object->manager_id = $manager->id;
'name' => [
'formatted' => (new AttributeMapping())->ignoreWrite()->setRead(
function (&$object) {
return $object->getFullNameAttribute();
}
),
'familyName' => AttributeMapping::eloquent("last_name"),
'givenName' => AttributeMapping::eloquent("first_name"),
'middleName' => null,
'honorificPrefix' => null,
'honorificSuffix' => null
],
'displayName' => null,
'nickName' => null,
'profileUrl' => null,
'title' => AttributeMapping::eloquent('jobtitle'),
'userType' => null,
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
'locale' => null, // see RFC5646
'timezone' => null, // see RFC6557
'active' => AttributeMapping::eloquent('activated'),
'password' => AttributeMapping::eloquent('password')->disableRead(),
// Multi-Valued Attributes
'emails' => [[
"value" => AttributeMapping::eloquent("email"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
'phoneNumbers' => [[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
'ims' => [[
"value" => null,
"display" => null,
"type" => null,
"primary" => null
]], // Instant messaging addresses for the User
'photos' => [[
"value" => null,
"display" => null,
"type" => null,
"primary" => null
]],
'addresses' => [[
'type' => AttributeMapping::constant("work")->ignoreWrite(),
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
'streetAddress' => AttributeMapping::eloquent("address"),
'locality' => AttributeMapping::eloquent("city"),
'region' => AttributeMapping::eloquent("state"),
'postalCode' => AttributeMapping::eloquent("zip"),
'country' => AttributeMapping::eloquent("country"),
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
]],
'groups' => [[
'value' => null,
'$ref' => null,
'display' => null,
'type' => null,
'type' => null
]],
'entitlements' => null,
'roles' => null,
'x509Certificates' => null
],
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => [
'employeeNumber' => AttributeMapping::eloquent('employee_num'),
'department' => (new AttributeMapping())->setAdd( // FIXME parent?
function ($value, &$object) {
$department = Department::where("name", $value)->first();
if ($department) {
$object->department_id = $department->id;
}
} }
}
)->setReplace( )->setReplace(
function ($value, &$object) { function ($value, &$object) {
$manager = User::find($value); $department = Department::where("name", $value)->first();
if ($manager) { if ($department) {
$object->manager_id = $manager->id; $object->department_id = $department->id;
} }
} }
)->setRead( )->setRead(
function (&$object) { function (&$object) {
return $object->manager_id; return $object->department ? $object->department->name : null;
} }
), ),
'manager' => [
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool.
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing
'value' => (new AttributeMapping())->setAdd(
function ($value, &$object) {
$manager = User::find($value);
if ($manager) {
$object->manager_id = $manager->id;
}
}
)->setReplace(
function ($value, &$object) {
$manager = User::find($value);
if ($manager) {
$object->manager_id = $manager->id;
}
}
)->setRead(
function (&$object) {
return $object->manager_id;
}
),
]
]
] ]
]; ];
return $config;
} }
} }

View file

@ -164,7 +164,7 @@ trait Searchable
} }
// I put this here because I only want to add the concat one time in the end of the user relation search // I put this here because I only want to add the concat one time in the end of the user relation search
if($relation == 'user') { if($relation == 'user') {
$query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%$term%"]); $query->orWhereRaw('CONCAT (users.first_name, " ", users.last_name) LIKE ?', ["%{$term}%"]);
} }
}); });
} }
@ -195,7 +195,7 @@ trait Searchable
*/ */
private function getSearchableAttributes() private function getSearchableAttributes()
{ {
return isset($this->searchableAttributes) ? $this->searchableAttributes : []; return $this->searchableAttributes ?? [];
} }
/** /**
@ -205,7 +205,7 @@ trait Searchable
*/ */
private function getSearchableRelations() private function getSearchableRelations()
{ {
return isset($this->searchableRelations) ? $this->searchableRelations : []; return $this->searchableRelations ?? [];
} }
/** /**

22
app/Models/User.php Executable file → Normal file
View file

@ -61,7 +61,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'remote', 'remote',
'start_date', 'start_date',
'end_date', 'end_date',
'scim_externalid' 'scim_externalid',
'avatar',
'gravatar',
'vip',
]; ];
protected $casts = [ protected $casts = [
@ -69,6 +72,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'manager_id' => 'integer', 'manager_id' => 'integer',
'location_id' => 'integer', 'location_id' => 'integer',
'company_id' => 'integer', 'company_id' => 'integer',
'vip' => 'boolean',
]; ];
@ -76,8 +80,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'created_at', 'created_at',
'updated_at', 'updated_at',
'deleted_at', 'deleted_at',
'start_date', 'start_date' => 'date_format:Y-m-d',
'end_date', 'end_date' => 'date_format:Y-m-d',
]; ];
@ -96,8 +100,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'website' => 'url|nullable|max:191', 'website' => 'url|nullable|max:191',
'manager_id' => 'nullable|exists:users,id|cant_manage_self', 'manager_id' => 'nullable|exists:users,id|cant_manage_self',
'location_id' => 'exists:locations,id|nullable', 'location_id' => 'exists:locations,id|nullable',
'start_date' => 'nullable|date', 'start_date' => 'nullable|date_format:Y-m-d',
'end_date' => 'nullable|date|after_or_equal:start_date', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
]; ];
/** /**
@ -283,7 +287,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/ */
public function assets() public function assets()
{ {
return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed(); return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed()->orderBy('id');
} }
/** /**
@ -311,7 +315,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
public function accessories() public function accessories()
{ {
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id') return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id')
->withPivot('id', 'created_at', 'note')->withTrashed(); ->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id');
} }
/** /**
@ -655,7 +659,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{ {
$query = $query->where('first_name', 'LIKE', '%'.$search.'%') $query = $query->where('first_name', 'LIKE', '%'.$search.'%')
->orWhere('last_name', 'LIKE', '%'.$search.'%') ->orWhere('last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$search%"]); ->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%{$search}%"]);
return $query; return $query;
} }
@ -671,7 +675,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
public function advancedTextSearch(Builder $query, array $terms) { public function advancedTextSearch(Builder $query, array $terms) {
foreach($terms as $term) { foreach($terms as $term) {
$query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$term%"]); $query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%{$term}%"]);
} }
return $query; return $query;

View file

@ -40,12 +40,17 @@ class AcceptanceAssetAcceptedNotification extends Notification
public function via() public function via()
{ {
$notifyBy[] = 'mail'; $notifyBy = ['mail'];
return $notifyBy; return $notifyBy;
} }
public function shouldSend($notifiable, $channel)
{
return $this->settings->alerts_enabled && ! empty($this->settings->alert_email);
}
/** /**
* Get the mail representation of the notification. * Get the mail representation of the notification.
* *

View file

@ -38,12 +38,17 @@ class AcceptanceAssetDeclinedNotification extends Notification
*/ */
public function via($notifiable) public function via($notifiable)
{ {
$notifyBy[] = 'mail'; $notifyBy = ['mail'];
return $notifyBy; return $notifyBy;
} }
public function shouldSend($notifiable, $channel)
{
return $this->settings->alerts_enabled && ! empty($this->settings->alert_email);
}
/** /**
* Get the mail representation of the notification. * Get the mail representation of the notification.
* *

View file

@ -44,7 +44,7 @@ class CurrentInventory extends Notification
'accessories' => $this->user->accessories, 'accessories' => $this->user->accessories,
'licenses' => $this->user->licenses, 'licenses' => $this->user->licenses,
]) ])
->subject('Inventory Report'); ->subject(trans('mail.inventory_report'));
return $message; return $message;
} }

View file

@ -32,7 +32,7 @@ class InventoryAlert extends Notification
*/ */
public function via() public function via()
{ {
$notifyBy[] = 'mail'; $notifyBy = ['mail'];
return $notifyBy; return $notifyBy;
} }

View file

@ -40,7 +40,7 @@ class SendUpcomingAuditNotification extends Notification
*/ */
public function toMail() public function toMail()
{ {
$message = (new MailMessage)->markdown('notifications.markdown.upcoming-audits', $message = (new MailMessage())->markdown('notifications.markdown.upcoming-audits',
[ [
'assets' => $this->assets, 'assets' => $this->assets,
'threshold' => $this->threshold, 'threshold' => $this->threshold,

View file

@ -44,7 +44,7 @@ class WelcomeNotification extends Notification
*/ */
public function toMail() public function toMail()
{ {
return (new MailMessage) return (new MailMessage())
->subject(trans('mail.welcome', ['name' => $this->_data['first_name'].' '.$this->_data['last_name']])) ->subject(trans('mail.welcome', ['name' => $this->_data['first_name'].' '.$this->_data['last_name']]))
->markdown('notifications.Welcome', $this->_data); ->markdown('notifications.Welcome', $this->_data);
} }

View file

@ -80,19 +80,25 @@ class AccessoryPresenter extends Presenter
], [ ], [
'field' => 'qty', 'field' => 'qty',
'searchable' => false, 'searchable' => false,
'sortable' => false,
'title' => trans('admin/accessories/general.total'),
], [
'field' => 'min_qty',
'searchable' => false,
'sortable' => true, 'sortable' => true,
'title' => trans('general.min_amt'), 'title' => trans('admin/accessories/general.total'),
], [ ], [
'field' => 'remaining_qty', 'field' => 'remaining_qty',
'searchable' => false, 'searchable' => false,
'sortable' => false, 'sortable' => false,
'visible' => false, 'visible' => false,
'title' => trans('admin/accessories/general.remaining'), 'title' => trans('admin/accessories/general.remaining'),
],[
'field' => 'users_count',
'searchable' => false,
'sortable' => true,
'visible' => true,
'title' => trans('general.checked_out'),
], [
'field' => 'min_qty',
'searchable' => false,
'sortable' => true,
'title' => trans('general.min_amt'),
], [ ], [
'field' => 'purchase_date', 'field' => 'purchase_date',
'searchable' => true, 'searchable' => true,

View file

@ -85,6 +85,15 @@ class UserPresenter extends Presenter
'visible' => true, 'visible' => true,
'formatter' => 'usersLinkFormatter', 'formatter' => 'usersLinkFormatter',
], ],
[
'field' => 'vip',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/users/general.vip_label'),
'visible' => false,
'formatter' => 'trueFalseFormatter',
],
[ [
'field' => 'remote', 'field' => 'remote',
'searchable' => false, 'searchable' => false,
@ -346,8 +355,7 @@ class UserPresenter extends Presenter
public function emailLink() public function emailLink()
{ {
if ($this->email) { if ($this->email) {
return '<a href="mailto:'.$this->email.'">'.$this->email.'</a>' return '<a href="mailto:'.$this->email.'">'.$this->email.'</a><a href="mailto:'.$this->email.'" class="hidden-xs hidden-sm"><i class="far fa-envelope"></i></a>';
.'<a href="mailto:'.$this->email.'" class="hidden-xs hidden-sm"><i class="far fa-envelope"></i></a>';
} }
return ''; return '';

View file

@ -22,7 +22,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
*/ */
class Saml class Saml
{ {
const DATA_SESSION_KEY = '_samlData'; public const DATA_SESSION_KEY = '_samlData';
/** /**
* OneLogin_Saml2_Auth instance. * OneLogin_Saml2_Auth instance.
@ -308,12 +308,9 @@ class Saml
*/ */
public function samlLogin($data) public function samlLogin($data)
{ {
$setting = Setting::getSettings();
$this->saveDataToSession($data); $this->saveDataToSession($data);
$this->loadDataFromSession(); $this->loadDataFromSession();
$username = $this->getUsername(); $username = $this->getUsername();
return User::where('username', '=', $username)->whereNull('deleted_at')->where('activated', '=', '1')->first(); return User::where('username', '=', $username)->whereNull('deleted_at')->where('activated', '=', '1')->first();
} }

View file

@ -61,25 +61,29 @@
"nunomaduro/collision": "^5.4", "nunomaduro/collision": "^5.4",
"onelogin/php-saml": "^3.4", "onelogin/php-saml": "^3.4",
"paragonie/constant_time_encoding": "^2.3", "paragonie/constant_time_encoding": "^2.3",
"symfony/polyfill-mbstring": "^1.22", "paragonie/sodium_compat": "^1.19",
"phpdocumentor/reflection-docblock": "^5.1", "phpdocumentor/reflection-docblock": "^5.1",
"phpspec/prophecy": "^1.10", "phpspec/prophecy": "^1.10",
"pragmarx/google2fa-laravel": "^1.3", "pragmarx/google2fa-laravel": "^1.3",
"rollbar/rollbar-laravel": "^7.0", "rollbar/rollbar-laravel": "^7.0",
"spatie/laravel-backup": "^6.16", "spatie/laravel-backup": "^6.16",
"symfony/polyfill-mbstring": "^1.22",
"tecnickcom/tc-lib-barcode": "^1.15", "tecnickcom/tc-lib-barcode": "^1.15",
"unicodeveloper/laravel-password": "^1.0", "unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^6.1" "watson/validating": "^6.1"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.16", "fakerphp/faker": "^1.16",
"laravel/dusk": "^6.19", "laravel/dusk": "^6.25",
"mockery/mockery": "^1.4", "mockery/mockery": "^1.4",
"nunomaduro/larastan": "^1.0",
"nunomaduro/phpinsights": "^2.7",
"phpunit/php-token-stream": "^3.1", "phpunit/php-token-stream": "^3.1",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5", "squizlabs/php_codesniffer": "^3.5",
"symfony/css-selector": "^4.4", "symfony/css-selector": "^4.4",
"symfony/dom-crawler": "^4.4" "symfony/dom-crawler": "^4.4",
"vimeo/psalm": "^5.6"
}, },
"extra": { "extra": {
"laravel": { "laravel": {

2978
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -102,6 +102,7 @@ return [
'pgsql' => [ 'pgsql' => [
'driver' => 'pgsql', 'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),

128
config/insights.php Normal file
View file

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenDefineFunctions;
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenFinalClasses;
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenNormalClasses;
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenPrivateMethods;
use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenTraits;
use NunoMaduro\PhpInsights\Domain\Metrics\Architecture\Classes;
use SlevomatCodingStandard\Sniffs\Commenting\UselessFunctionDocCommentSniff;
use SlevomatCodingStandard\Sniffs\Namespaces\AlphabeticallySortedUsesSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\DisallowMixedTypeHintSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\ParameterTypeHintSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\PropertyTypeHintSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\ReturnTypeHintSniff;
return [
/*
|--------------------------------------------------------------------------
| Default Preset
|--------------------------------------------------------------------------
|
| This option controls the default preset that will be used by PHP Insights
| to make your code reliable, simple, and clean. However, you can always
| adjust the `Metrics` and `Insights` below in this configuration file.
|
| Supported: "default", "laravel", "symfony", "magento2", "drupal"
|
*/
'preset' => 'laravel',
/*
|--------------------------------------------------------------------------
| IDE
|--------------------------------------------------------------------------
|
| This options allow to add hyperlinks in your terminal to quickly open
| files in your favorite IDE while browsing your PhpInsights report.
|
| Supported: "textmate", "macvim", "emacs", "sublime", "phpstorm",
| "atom", "vscode".
|
| If you have another IDE that is not in this list but which provide an
| url-handler, you could fill this config with a pattern like this:
|
| myide://open?url=file://%f&line=%l
|
*/
'ide' => null,
/*
|--------------------------------------------------------------------------
| Configuration
|--------------------------------------------------------------------------
|
| Here you may adjust all the various `Insights` that will be used by PHP
| Insights. You can either add, remove or configure `Insights`. Keep in
| mind that all added `Insights` must belong to a specific `Metric`.
|
*/
'exclude' => [
// 'path/to/directory-or-file'
],
'add' => [
Classes::class => [
ForbiddenFinalClasses::class,
],
],
'remove' => [
AlphabeticallySortedUsesSniff::class,
DeclareStrictTypesSniff::class,
DisallowMixedTypeHintSniff::class,
ForbiddenDefineFunctions::class,
ForbiddenNormalClasses::class,
ForbiddenTraits::class,
ParameterTypeHintSniff::class,
PropertyTypeHintSniff::class,
ReturnTypeHintSniff::class,
UselessFunctionDocCommentSniff::class,
],
'config' => [
ForbiddenPrivateMethods::class => [
'title' => 'The usage of private methods is not idiomatic in Laravel.',
],
],
/*
|--------------------------------------------------------------------------
| Requirements
|--------------------------------------------------------------------------
|
| Here you may define a level you want to reach per `Insights` category.
| When a score is lower than the minimum level defined, then an error
| code will be returned. This is optional and individually defined.
|
*/
'requirements' => [
// 'min-quality' => 0,
// 'min-complexity' => 0,
// 'min-architecture' => 0,
// 'min-style' => 0,
// 'disable-security-check' => false,
],
/*
|--------------------------------------------------------------------------
| Threads
|--------------------------------------------------------------------------
|
| Here you may adjust how many threads (core) PHPInsights can use to perform
| the analyse. This is optional, don't provide it and the tool will guess
| the max core number available. It accepts null value or integer > 0.
|
*/
'threads' => null,
];

View file

@ -1,5 +1,4 @@
<?php <?php
use Monolog\Handler\NullHandler; use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler; use Monolog\Handler\SyslogUdpHandler;
@ -113,6 +112,13 @@ $config = [
'handler' => \Rollbar\Laravel\MonologHandler::class, 'handler' => \Rollbar\Laravel\MonologHandler::class,
'access_token' => env('ROLLBAR_TOKEN'), 'access_token' => env('ROLLBAR_TOKEN'),
'level' => env('ROLLBAR_LEVEL', 'error'), 'level' => env('ROLLBAR_LEVEL', 'error'),
'check_ignore' => function($isUncaught, $args, $payload) {
if (App::environment('production') && is_object($args) && get_class($args) == Rollbar\ErrorWrapper::class && $args->errorLevel == E_WARNING ) {
\Log::info("IGNORING E_WARNING in production mode: ".$args->getMessage());
return true; // "TRUE - you should ignore it!"
}
return false;
},
], ],
], ],

View file

@ -3,6 +3,6 @@
return [ return [
"trace" => env("SCIM_TRACE",false), "trace" => env("SCIM_TRACE",false),
// below, if we ever get 'sure' that we can change this default to 'true' we should // below, if we ever get 'sure' that we can change this default to 'true' we should
"omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', false), "omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', true),
"publish_routes" => false, "publish_routes" => false,
]; ];

View file

@ -1,10 +1,10 @@
<?php <?php
return array ( return array (
'app_version' => 'v6.0.14', 'app_version' => 'v6.0.14',
'full_app_version' => 'v6.0.14 - build 9161-g799c9c910', 'full_app_version' => 'v6.0.14 - build 9599-g1415c8c6e',
'build_version' => '9161', 'build_version' => '9599',
'prerelease_version' => '', 'prerelease_version' => '',
'hash_version' => 'g799c9c910', 'hash_version' => 'g1415c8c6e',
'full_hash' => 'v6.0.14-117-g799c9c910', 'full_hash' => 'v6.0.14-555-g1415c8c6e',
'branch' => 'master', 'branch' => 'develop',
); );

View file

@ -44,7 +44,7 @@ class AssetFactory extends Factory
'user_id' => 1, 'user_id' => 1,
'asset_tag' => $this->faker->unixTime('now'), 'asset_tag' => $this->faker->unixTime('now'),
'notes' => 'Created by DB seeder', 'notes' => 'Created by DB seeder',
'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'),
'purchase_cost' => $this->faker->randomFloat(2, '299.99', '2999.99'), 'purchase_cost' => $this->faker->randomFloat(2, '299.99', '2999.99'),
'order_number' => $this->faker->numberBetween(1000000, 50000000), 'order_number' => $this->faker->numberBetween(1000000, 50000000),
'supplier_id' => Supplier::all()->random()->id, 'supplier_id' => Supplier::all()->random()->id,

View file

@ -46,7 +46,7 @@ class LicenseFactory extends Factory
'serial' => $this->faker->uuid, 'serial' => $this->faker->uuid,
'notes' => 'Created by DB seeder', 'notes' => 'Created by DB seeder',
'seats' => $this->faker->numberBetween(1, 10), 'seats' => $this->faker->numberBetween(1, 10),
'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'),
'order_number' => $this->faker->numberBetween(1000000, 50000000), 'order_number' => $this->faker->numberBetween(1000000, 50000000),
'expiration_date' => $this->faker->dateTimeBetween('now', '+3 years', date_default_timezone_get())->format('Y-m-d H:i:s'), 'expiration_date' => $this->faker->dateTimeBetween('now', '+3 years', date_default_timezone_get())->format('Y-m-d H:i:s'),
'reassignable' => $this->faker->boolean(), 'reassignable' => $this->faker->boolean(),

View file

@ -52,4 +52,13 @@ class SettingFactory extends Factory
'email_domain' => 'test.com', 'email_domain' => 'test.com',
]; ];
} }
public function withMultipleFullCompanySupport()
{
return $this->state(function () {
return [
'full_multiple_companies_support' => 1,
];
});
}
} }

View file

@ -44,6 +44,8 @@ class MigrationCartalystSentryInstallGroups extends Migration
*/ */
public function down() public function down()
{ {
Schema::drop('permission_groups'); // See 2014_11_04_231416_update_group_field_for_reporting.php and 2019_06_12_184327_rename_groups_table.php
Schema::dropIfExists('permission_groups');
Schema::dropIfExists('groups');
} }
} }

View file

@ -26,6 +26,6 @@ class AddPhysicalToAssets extends Migration
*/ */
public function down() public function down()
{ {
$table->dropColumn('physical'); // $table->dropColumn('physical');
} }
} }

View file

@ -37,7 +37,7 @@ class ReCreateLicensesTable extends Migration
*/ */
public function down() public function down()
{ {
// // This was most likely handled in 2013_11_17_054359_drop_licenses_table.php
Schema::drop('licenses'); Schema::dropIfExists('licenses');
} }
} }

View file

@ -31,6 +31,6 @@ class CreateLicenseSeatsTable extends Migration
*/ */
public function down() public function down()
{ {
// Schema::dropIfExists('license_seats');
} }
} }

View file

@ -23,6 +23,8 @@ class AlterWarrantyColumnOnAssets extends Migration
*/ */
public function down() public function down()
{ {
// Schema::table('assets', function ($table) {
$table->renameColumn('warranty_months', 'warrantee_months');
});
} }
} }

View file

@ -24,7 +24,7 @@ class AddEolOnModelsTable extends Migration
public function down() public function down()
{ {
Schema::table('models', function ($table) { Schema::table('models', function ($table) {
$table->dropColumn('old'); $table->dropColumn('eol');
}); });
} }
} }

View file

@ -105,10 +105,10 @@
*/ */
public function down() public function down()
{ {
Schema::table('asset_logs', function (Blueprint $table) { // Schema::table('asset_logs', function (Blueprint $table) {
$table->dropIndex('thread_id'); // $table->dropIndex('thread_id');
$table->dropColumn('thread_id'); // $table->dropColumn('thread_id');
}); // });
} }
/** /**

View file

@ -48,13 +48,19 @@ class MigrateMacAddress extends Migration
*/ */
public function down() public function down()
{ {
//
$f = \App\Models\CustomFieldset::where(['name' => 'Asset with MAC Address'])->first(); $f = \App\Models\CustomFieldset::where(['name' => 'Asset with MAC Address'])->first();
$f->fields()->delete();
$f->delete(); if ($f) {
$f->fields()->delete();
$f->delete();
}
Schema::table('models', function (Blueprint $table) { Schema::table('models', function (Blueprint $table) {
$table->renameColumn('deprecated_mac_address', 'show_mac_address'); $table->renameColumn('deprecated_mac_address', 'show_mac_address');
}); });
DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)');
if (Schema::hasColumn('assets', '_snipeit_mac_address')) {
DB::statement('ALTER TABLE assets CHANGE _snipeit_mac_address mac_address varchar(255)');
}
} }
} }

View file

@ -25,7 +25,7 @@ class AddShowInNavToStatusLabels extends Migration
public function down() public function down()
{ {
Schema::table('status_labels', function (Blueprint $table) { Schema::table('status_labels', function (Blueprint $table) {
$table->dropColumn('show_in_nav', 'field_encrypted'); $table->dropColumn('show_in_nav');
}); });
} }
} }

View file

@ -4,6 +4,7 @@ use App\Models\CustomField;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
/** /**
* Fixes issue #2551 where columns got donked if the field name in non-ascii * Fixes issue #2551 where columns got donked if the field name in non-ascii
@ -71,6 +72,25 @@ class FixUtf8CustomFieldColumnNames extends Migration
*/ */
public function down() public function down()
{ {
// In the up method above, updateLegacyColumnName is called and custom fields in the assets table are prefixed
// with "_snipe_it_", suffixed with "_{id of the CustomField}", and stored in custom_fields.db_column.
// The following reverses those changes.
foreach (CustomField::all() as $field) {
$currentColumnName = $field->db_column;
// "_snipeit_imei_1" becomes "_snipeit_imei"
$legacyColumnName = (string) Str::of($currentColumnName)->replaceMatches('/_(\d)+$/', '');
if (Schema::hasColumn(CustomField::$table_name, $currentColumnName)) {
Schema::table(CustomField::$table_name, function (Blueprint $table) use ($currentColumnName, $legacyColumnName) {
$table->renameColumn(
$currentColumnName,
$legacyColumnName
);
});
}
}
Schema::table('custom_fields', function ($table) { Schema::table('custom_fields', function ($table) {
$table->dropColumn('db_column'); $table->dropColumn('db_column');
$table->dropColumn('help_text'); $table->dropColumn('help_text');

View file

@ -28,7 +28,7 @@ class AddFieldsToManufacturer extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('settings', function ($table) { Schema::table('manufacturers', function ($table) {
$table->dropColumn('url'); $table->dropColumn('url');
$table->dropColumn('support_url'); $table->dropColumn('support_url');
$table->dropColumn('support_phone'); $table->dropColumn('support_phone');

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddsShouldAutoassignBoolToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('autoassign_licenses')->nullable(false)->default(1);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('autoassign_licenses');
});
}
}

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddVipToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('vip')->nullable()->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'vip')) {
$table->dropColumn('vip');
}
});
}
}

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CustomField;
class FixUnescapedCustomfieldsFormat extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$customfields = CustomField::where('format', 'LIKE', '%&%')->get();
foreach($customfields as $customfield){
$customfield->update(['format' => html_entity_decode($customfield->format)]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

6
package-lock.json generated
View file

@ -2033,9 +2033,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "8.8.0", "version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
}, },
"acorn-import-assertions": { "acorn-import-assertions": {
"version": "1.8.0", "version": "1.8.0",

View file

@ -25,7 +25,7 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.2.1", "@fortawesome/fontawesome-free": "^6.2.1",
"acorn": "^8.8.0", "acorn": "^8.8.2",
"acorn-import-assertions": "^1.8.0", "acorn-import-assertions": "^1.8.0",
"admin-lte": "^2.4.18", "admin-lte": "^2.4.18",
"ajv": "^6.12.6", "ajv": "^6.12.6",

12
phpstan.neon.dist Normal file
View file

@ -0,0 +1,12 @@
includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app
- config
- database/migrations
- resources/lang
- resources/views
level: 4

8
phpstan.neon.example Normal file
View file

@ -0,0 +1,8 @@
# Copy this file to "phpstan.neon" and update the parameters section as needed
includes:
- phpstan.neon.dist
parameters:
# https://phpstan.org/user-guide/output-format#opening-file-in-an-editor
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'

18
psalm.xml Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
>
<projectFiles>
<directory name="app" />
<directory name="database/factories" />
<directory name="database/seeders" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -16,7 +16,7 @@
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=c0d21166315b7c2cdd4819fa4a5e4d1e", "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=c0d21166315b7c2cdd4819fa4a5e4d1e",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=8e538625ebd4b8096e150d1aa483547b", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/all.css": "/css/dist/all.css?id=ef030b613d45620b907cf0184a14e868", "/css/dist/all.css": "/css/dist/all.css?id=ef030b613d45620b907cf0184a14e868",
"/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f2122815b2e4b150", "/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f2122815b2e4b150",
@ -49,5 +49,5 @@
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=713b1205aa2d7c9db282f8cd5754c0e4", "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=713b1205aa2d7c9db282f8cd5754c0e4",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=f343f659ca1d45534d2c2c3cc30fb619", "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=f343f659ca1d45534d2c2c3cc30fb619",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=8e538625ebd4b8096e150d1aa483547b" "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da"
} }

View file

@ -84,7 +84,13 @@
color: #fff; color: #fff;
} }
} }
a.btn.btn-link.text-left{
color:@navy;
border: 1px solid #000;
}
a.btn.btn-link.text-left:hover{
color:@navy;
}
a { a {
color: @navy; color: @navy;
@ -102,10 +108,6 @@ a.btn {
&:hover { &:hover {
color: #fff; color: #fff;
text-decoration: underline; text-decoration: underline;
}
&:visited {
color: #fff;
} }
} }

View file

@ -13,7 +13,8 @@ return array(
'update' => array( 'update' => array(
'error' => 'Kategorie is nie opgedateer nie, probeer asseblief weer', 'error' => 'Kategorie is nie opgedateer nie, probeer asseblief weer',
'success' => 'Kategorie suksesvol opgedateer.' 'success' => 'Kategorie suksesvol opgedateer.',
'cannot_change_category_type' => 'You cannot change the category type once it has been created',
), ),
'delete' => array( 'delete' => array(

View file

@ -12,4 +12,5 @@ return array(
'remaining' => 'oorblywende', 'remaining' => 'oorblywende',
'total' => 'totale', 'total' => 'totale',
'update' => 'Opdateer komponent', 'update' => 'Opdateer komponent',
'checkin_limit' => 'Amount checked in must be equal to or less than :assigned_qty'
); );

View file

@ -27,6 +27,9 @@ return [
'used_by_models' => 'Gebruik deur modelle', 'used_by_models' => 'Gebruik deur modelle',
'order' => 'Orde', 'order' => 'Orde',
'create_fieldset' => 'Nuwe Fieldset', 'create_fieldset' => 'Nuwe Fieldset',
'update_fieldset' => 'Update Fieldset',
'fieldset_does_not_exist' => 'Fieldset :id does not exist',
'fieldset_updated' => 'Fieldset updated',
'create_fieldset_title' => 'Create a new fieldset', 'create_fieldset_title' => 'Create a new fieldset',
'create_field' => 'Nuwe aangepaste veld', 'create_field' => 'Nuwe aangepaste veld',
'create_field_title' => 'Create a new custom field', 'create_field_title' => 'Create a new custom field',

View file

@ -14,6 +14,8 @@ return [
'deleted' => 'This asset has been deleted.', 'deleted' => 'This asset has been deleted.',
'edit' => 'Wysig bate', 'edit' => 'Wysig bate',
'model_deleted' => 'This Assets model has been deleted. You must restore the model before you can restore the Asset.', 'model_deleted' => 'This Assets model has been deleted. You must restore the model before you can restore the Asset.',
'model_invalid' => 'The Model of this Asset is invalid.',
'model_invalid_fix' => 'The Asset should be edited to correct this before attempting to check it in or out.',
'requestable' => 'Requestable', 'requestable' => 'Requestable',
'requested' => 'versoek', 'requested' => 'versoek',
'not_requestable' => 'Not Requestable', 'not_requestable' => 'Not Requestable',

View file

@ -48,6 +48,8 @@ return [
'success' => 'Jou lêer is ingevoer', 'success' => 'Jou lêer is ingevoer',
'file_delete_success' => 'Jou lêer is suksesvol verwyder', 'file_delete_success' => 'Jou lêer is suksesvol verwyder',
'file_delete_error' => 'Die lêer kon nie uitgevee word nie', 'file_delete_error' => 'Die lêer kon nie uitgevee word nie',
'header_row_has_malformed_characters' => 'One or more attributes in the header row contain malformed UTF-8 characters',
'content_row_has_malformed_characters' => 'One or more attributes in the first row of content contain malformed UTF-8 characters',
], ],

Some files were not shown because too many files have changed in this diff Show more