Compare commits

..

64 Commits

Author SHA1 Message Date
snipe
70c8ad9797 Bumped minor version 2019-10-28 13:55:21 -07:00
snipe
0290257734 Limit license seats to 999 to prevent latency 2019-10-28 13:48:18 -07:00
snipe
4fe689dc5d Merge branch 'master' of https://github.com/snipe/snipe-it 2019-10-21 15:45:17 -07:00
snipe
0769f585ea Disallow locations from being their own parents 2019-10-21 15:45:05 -07:00
snipe
04562e6d4a Added 4260352 to ldapsync enabled account constraint 2019-10-18 17:48:50 -07:00
snipe
22d2ad9248 Fixes nested location selectlist (#7483)
* Rename child locations method

* Use Ajax dropdown for locations selectlist for edit/create

* Removed locations database call on edit/create blades for faster loading

* Updated locations controller to use the new iterator

* Increase pagination on locations controller to 500

We’re already loading all of that data up beforehand anyway, so no point in keeping the query smaller.

* Fixed the else to make codacy happy

* Improve the design and performance of the nested location selectlist (#7484)

* Improve the design and performance of the nested location selectlist

* Fixed parse errors

* Removed debugging code/comments
2019-10-02 03:56:56 -07:00
snipe
6deb26fafe Remove unused variable 2019-09-30 19:37:52 -07:00
snipe
6c1de7ff05 Apply fix for #6642 to master 2019-09-30 19:21:57 -07:00
snipe
7f5f4a1297 Added softwarew support and hardware support to maintenance types 2019-09-24 01:34:23 -07:00
snipe
c68c0e1208 Account for limit if none is passed in the request 2019-09-03 20:28:49 -07:00
snipe
c256536d21 Math is hard 2019-09-03 14:29:58 -07:00
snipe
4159a0effa Bumped version 2019-09-03 14:12:13 -07:00
snipe
b8f7cd81eb Limit API request results per page (#7405) 2019-09-03 14:02:08 -07:00
snipe
b381528668 Added console rekey tool (#7330)
* Removed console command specifications, since they’re pulled dynamically now

* Added rekey console command

* Removed unused code

* Comment clarifiction

* Handle LDAP password
2019-09-03 11:03:32 -07:00
snipe
6d66d7e215 Removed withErrors on JSON response 2019-08-22 21:36:47 -07:00
snipe
b5bf8e9a37 Smaller chunking for custom report, add max_execution_time 2019-08-15 06:14:25 -07:00
snipe
ba197c8857 Fixed #7259 - upgraded phpdocumentor/reflection-docblock to v4 2019-08-15 03:02:24 -07:00
snipe
124b249df4 Fixed #7289 - git fetch before checkout in upgrade.php 2019-08-15 01:32:20 -07:00
snipe
2a6919c438 Fixed #7321 - added link to Helm Chart repo 2019-08-15 01:07:30 -07:00
snipe
8b4a9aa382 Fixes to history importer 2019-08-13 18:15:42 -07:00
snipe
99cd552d5c History importer fixes 2019-08-13 18:00:21 -07:00
snipe
6c7e5cb9cf Merge branch 'master' of https://github.com/snipe/snipe-it 2019-08-10 17:49:23 -07:00
snipe
6ebb01a081 Group related variables in .env 2019-08-10 17:48:03 -07:00
Greg Stamper
5591c861b9 Update README.md (#7334)
Add reference to CSV importer.
2019-08-07 23:12:46 -07:00
snipe
d37280567d Fixed CVE-2019-10742
https://nvd.nist.gov/vuln/detail/CVE-2019-10742
2019-08-06 21:11:38 -07:00
snipe
e7b0ee2539 Added accessories checkout/checkin API endpoint 2019-08-02 15:08:26 -07:00
snipe
c593b3645c Added comments to the ByFilter query scope for clarity 2019-07-31 14:24:01 -07:00
Ivan Nieto
28ae90fa8a Added condition to deal with fieldname 'rtd_location' which can be tried to be queried in some places and doesn't exist in database (#7317) 2019-07-31 13:55:21 -07:00
snipe
c7be25078e Bumped version 2019-07-26 13:02:47 -07:00
snipe
3dc2cc9f22 CORS for api (#7292)
* Added CORS support to API

* Changed order so CORS will still work if throttle hit

* Added APP_CORS_ALLOWED_ORIGINS env option

* Fixed typo

* Clarified header comments

* More clarification

* DIsable CORS allowed origins by default to replicate existing behavior

* Change variable name to be clearer
2019-07-26 12:38:31 -07:00
snipe
ab86e42b2e Fixed #7270 - Checking-in Assets via API Removes the Item's Asset Name 2019-07-26 12:37:38 -07:00
snipe
9af9ed9eb9 Add @mskrip as a contributor 2019-07-24 11:01:28 -07:00
snipe
250a797339 Fixed #7250 - permission issue for API fieldsets and fields endpoints
This applies the change from #7294 to master
2019-07-24 11:00:42 -07:00
snipe
a0f3fc6d76 Bumped hash 2019-07-18 14:37:54 -07:00
snipe
74e647fea7 Apply fix from PR #7273 to master 2019-07-18 14:37:48 -07:00
snipe
55ee90b25d Fixes #7252 form request changes (#7272)
* Fixes for #7252 - custom fields not validating / no validaton messages in API w/form requests

* Removed debug info

* More fixes for #7252

This is mostly working as intended, if not yet the way Laravel wants us to do it.

Right now, the API returns correctly, and the form UI will return highlighted errors, with the input filled in ~sometimes~. I’m not sure why it’s only sometimes yet, but this is potentially progress.

* Removed experimental method

* Check for digits_between:0,240 for warranty

* Removed debug code
2019-07-18 14:32:23 -07:00
snipe
eec445fcf5 Command to fix custom field unicode conversion differences between PHP versions (#7263) 2019-07-18 14:30:18 -07:00
snipe
cef22c3158 Caps asset warranty to 20 years 2019-07-18 09:49:58 -07:00
snipe
61fb38087e Bumped hash 2019-07-17 17:55:53 -07:00
snipe
0e93495ca2 Check that the user has assets and that the aset model is valid 2019-07-17 17:51:35 -07:00
snipe
444e250609 Fixed countable() strings on user destroy 2019-07-17 17:51:13 -07:00
snipe
77a6f6f400 Cap warranty months to 3 on the frontend blade 2019-07-17 12:15:15 -07:00
snipe
15bfd07f30 Cap warranty months to three characters
Filles rollbar 209
2019-07-17 12:13:15 -07:00
snipe
fecf8015a1 Added space between footer and custom message 2019-07-17 12:09:32 -07:00
snipe
79ab0d8dc2 Check for valid seat on hardware view 2019-07-17 12:09:18 -07:00
snipe
b4b6d6b571 Comment clarification on #7186 2019-07-15 15:31:09 -07:00
snipe
8c73a47afb Fixed #7186 - has vs filled in User’s API blanking out groups if no group_ids are passed 2019-07-15 15:27:02 -07:00
snipe
f82ffe378c Merge branch 'master' of https://github.com/snipe/snipe-it 2019-07-15 14:11:18 -07:00
snipe
984c2a8fd4 Better log message for bad LDAP connection 2019-07-15 14:10:57 -07:00
snipe
6736b1c4e7 Addresses #7238 - add PWA code to layout
Needs additional UX testing
2019-07-15 13:49:56 -07:00
Ivan Nieto
d409be6d43 Fix #6910: Add logic to manipulate the eloquent query. (#7006)
* Added company_id to consumables_users table

* Added logic to manage when a pivot table doesn't have the column company_id trough a join with users

* Remove a migration that tries to fix this problem, but is not longer necessary
2019-07-15 13:02:44 -07:00
Thomas Misilo
e1b33f3087 Spelling Correction (#7206)
Fixed Spelling for the word reqrite, to be rewrite.
2019-06-27 18:33:13 -07:00
snipe
740d5a6846 Downgrading rollbar for Laravel 5.5 2019-06-25 18:07:21 -07:00
snipe
d19df4ded8 Bumped version 2019-06-24 14:43:44 -07:00
Kasey
03a4512406 fixing previous commit's actual wiping of password (#7183)
replaced Input::fille('ldap_pword') with _filled_.   Should be good to go.  

https://github.com/snipe/snipe-it/issues/7179

https://github.com/snipe/snipe-it/issues/7169
2019-06-19 14:21:53 -07:00
snipe
de992e4df3 Fixed LDAP password blanking on save 2019-06-14 17:20:37 -07:00
snipe
a85251aa83 Fixed #7164 - change table name to permission_groups 2019-06-14 10:37:20 -07:00
snipe
26a1181765 Possible fix for reporting/admin migration back in time 2019-06-12 18:53:53 -07:00
snipe
cef030cf55 Fixed permission insert
//TODO

Handle this via model
2019-06-12 18:39:55 -07:00
snipe
2bfa05fd2d Back in time fix FOR #7145 for new installs on MySQL 8+ 2019-06-12 16:24:55 -07:00
snipe
30904dd019 Reduce minimum group name length to 2 (from 3)
eg: IT
2019-06-12 15:56:19 -07:00
snipe
1d0d25db37 Fixed #7145 - rename groups table to permissions_group for mysql 8 reserved word compatibility 2019-06-12 15:51:47 -07:00
snipe
cbff66c9db Improved error checking in locations importer 2019-06-10 18:50:41 -07:00
snipe
27231d49ea Small fixes for phpleague CSB reader v9 2019-06-03 22:05:16 -07:00
67 changed files with 1462 additions and 619 deletions

View File

@@ -1659,6 +1659,15 @@
"contributions": [
"code"
]
},
{
"login": "mskrip",
"name": "Marián Skrip",
"avatar_url": "https://avatars0.githubusercontent.com/u/17459600?v=4",
"profile": "https://github.com/mskrip",
"contributions": [
"code"
]
}
]
}

View File

@@ -7,6 +7,7 @@ APP_KEY=ChangeMe
APP_URL=null
APP_TIMEZONE='UTC'
APP_LOCALE=en
MAX_RESULTS=500
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
@@ -64,8 +65,11 @@ SECURE_COOKIES=false
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
# --------------------------------------------
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
CORS_ALLOWED_ORIGINS=null
# --------------------------------------------
# OPTIONAL: CACHE SETTINGS
@@ -109,8 +113,6 @@ APP_LOG=single
APP_LOG_MAX_FILES=10
APP_LOCKED=false
FILESYSTEM_DISK=local
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
APP_CIPHER=AES-256-CBC
GOOGLE_MAPS_API=
BACKUP_ENV=true

View File

@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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-181-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![All Contributors](https://img.shields.io/badge/all_contributors-182-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
## Snipe-IT - Open Source Asset Management System
@@ -61,6 +61,8 @@ Since the release of the JSON REST API, several third-party developers have been
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
@@ -103,7 +105,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -48,60 +48,90 @@ class ImportLocations extends Command
$filename = $this->argument('filename');
$csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r');
$this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename);
$csv->setOffset(1); //because we don't want to insert the header
$csv->setHeaderOffset(0); //because we don't want to insert the header
$results = $csv->getRecords();
// Import parent location names first if they don't exist
foreach ($results as $parent_index => $parent_row) {
$parent_name = trim($parent_row['Parent Name']);
// First create any parents if they don't exist
if ($parent_name!='') {
if (array_key_exists('Parent Name', $parent_row)) {
$parent_name = trim($parent_row['Parent Name']);
if (array_key_exists('Name', $parent_row)) {
$this->info('- Parent: ' . $parent_name . ' in row as: ' . trim($parent_row['Parent Name']));
}
// Save parent location name
// This creates a sort of name-stub that we'll update later on in this script
$parent_location = Location::firstOrCreate(array('name' => $parent_name));
$this->info('Parent for '.$parent_row['Name'].' is '.$parent_name.'. Attempting to save '.$parent_name.'.');
if (array_key_exists('Name', $parent_row)) {
$this->info('Parent for ' . $parent_row['Name'] . ' is ' . $parent_name . '. Attempting to save ' . $parent_name . '.');
}
// Check if the record was updated or created.
// This is mostly for clearer debugging.
if ($parent_location->exists) {
$this->info('- Parent location '.$parent_name.' already exists.');
$this->info('- Parent location '.$parent_name.' already exists.');
} else {
$this->info('- Parent location '.$parent_name.' was created.');
}
} else {
$this->info('- No parent location for '.$parent_row['Name'].' provided.');
$this->info('- No Parent Name provided, so no parent location will be created.');
}
}
$this->info('----- Parents Created.... backfilling additional details... --------');
// Loop through ALL records and add/update them if there are additional fields
// besides name
foreach ($results as $index => $row) {
if (array_key_exists('Parent Name', $row)) {
$parent_name = trim($row['Parent Name']);
}
// Set the location attributes to save
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
$location->name = trim($row['Name']);
$location->currency = trim($row['Currency']);
$location->address = trim($row['Address 1']);
$location->address2 = trim($row['Address 2']);
$location->city = trim($row['City']);
$location->state = trim($row['State']);
$location->zip = trim($row['Zip']);
$location->country = trim($row['Country']);
$location->ldap_ou = trim($row['OU']);
if (array_key_exists('Name', $row)) {
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
$location->name = trim($row['Name']);
$this->info('Checking location: '.$location->name);
} else {
$this->error('Location name is required and is missing from at least one row in this dataset. Check your CSV for extra trailing rows and try again.');
return false;
}
if (array_key_exists('Currency', $row)) {
$location->currency = trim($row['Currency']);
}
if (array_key_exists('Address 1', $row)) {
$location->address = trim($row['Address 1']);
}
if (array_key_exists('Address 2', $row)) {
$location->address2 = trim($row['Address 2']);
}
if (array_key_exists('City', $row)) {
$location->city = trim($row['City']);
}
if (array_key_exists('State', $row)) {
$location->state = trim($row['State']);
}
if (array_key_exists('Zip', $row)) {
$location->zip = trim($row['Zip']);
}
if (array_key_exists('Country', $row)) {
$location->country = trim($row['Country']);
}
if (array_key_exists('Country', $row)) {
$location->ldap_ou = trim($row['OU']);
}
$this->info('Checking location: '.$location->name);
// If a parent name nis provided, we created it earlier in the script,
// If a parent name is provided, we created it earlier in the script,
// so let's grab that ID
if ($parent_name) {
$this->info('-- Searching for Parent Name: '.$parent_name);
$parent = Location::where('name', '=', $parent_name)->first();
$location->parent_id = $parent->id;
$this->info('Parent ID: '.$parent->id);
$this->info('Parent: '.$parent_name.' - ID: '.$parent->id);
}
// Make sure the more advanced (non-name) fields pass validation

View File

@@ -189,7 +189,7 @@ class LdapSync extends Command
// Sync activated state for Active Directory.
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
$enabled_accounts = [
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224'
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224', '4260352'
];
$user->activated = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
}

View File

@@ -33,7 +33,7 @@ class Purge extends Command
*
* @var string
*/
protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also reqrite history for users associated with deleted items.';
protected $description = 'Purge all soft-deleted deleted records in the database. This will rewrite history for items that have been edited, or checked in or out. It will also rewrite history for users associated with deleted items.';
/**
* Create a new command instance.

View File

@@ -0,0 +1,126 @@
<?php
namespace App\Console\Commands;
use App\Models\CustomField;
use Illuminate\Console\Command;
class ReEncodeCustomFieldNames extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:regenerate-fieldnames';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This utility will regenerate the column names for custom fields. It should typically only be needed when a PHP upgrade changed the behavior of the unicode conversion between versions.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* All three of these things must match for the custom fields system to work as expected:
*
* - what the system thinks the output of $field->convertUnicodeDbSlug() is
* - the actual db_column name in the customfields table
* - the physical column name that was created on the assets table
*
* For some people who upgraded their version of PHP, the unicode converter now behaves
* differently in than it did when their custom fields were first created, specifically as it
* relates to handling slashes, ampersands, etc. This can result in the field names no longer
* matching up, as an older version of the PHP extension simply dropped slashes, etc, while the
* newer version of the PHP extension will convert them to underscores.
*
* @return mixed
*/
public function handle()
{
if ($this->confirm('This will regenerate all of the custom field database fieldnames in your database. THIS WILL CHANGE YOUR SCHEMA AND SHOULD NOT BE DONE WITHOUT MAKING A BACKUP FIRST. Do you wish to continue?'))
{
/** Get all of the custom fields */
$fields = CustomField::get();
$asset_columns = \DB::getSchemaBuilder()->getColumnListing('assets');
$custom_field_columns = array();
/** Loop through the columns on the assets table */
foreach ($asset_columns as $asset_column) {
/** Add ones that start with _snipeit_ to an array for handling */
if (strpos($asset_column, '_snipeit_') === 0) {
/**
* Get the ID of the custom field based on the fieldname.
* For example, in _snipeit_mac_address_1, we grab the 1 because we know
* that's the ID of the custom field that created the column.
* Then use that ID as the array key for use comparing the actual assets field name
* and the db_column value from the custom fields table.
*/
$last_part = substr(strrchr($asset_column, "_snipeit_"), 1);
$custom_field_columns[$last_part] = $asset_column;
}
}
foreach ($fields as $field) {
$this->info($field->name .' ('.$field->id.') column should be '. $field->convertUnicodeDbSlug().'');
/** The assets table has the column it should have, all is well */
if (\Schema::hasColumn('assets', $field->convertUnicodeDbSlug()))
{
$this->info('-- ✓ This field exists - all good');
/**
* There is a mismatch between the fieldname on the assets table and
* what $field->convertUnicodeDbSlug() is *now* expecting.
*/
} else {
$this->warn('-- X Field mismatch: updating... ');
/** Make sure the custom_field_columns array has the ID */
if (array_key_exists($field->id, $custom_field_columns)) {
/**
* Update the asset schema to the corrected fieldname that will be recognized by the
* system elsewhere that we use $field->convertUnicodeDbSlug()
*/
\Schema::table('assets', function($table) use ($custom_field_columns, $field) {
$table->renameColumn($custom_field_columns[$field->id], $field->convertUnicodeDbSlug());
});
$this->warn('-- ✓ Field updated from '.$custom_field_columns[$field->id].' to '.$field->convertUnicodeDbSlug());
} else {
$this->warn('-- X WARNING: There is no field on the assets table ending in '.$field->id.'. This may require more in-depth investigation and may mean the schema was altered manually.');
}
}
/** Update the db_column property in the custom fields table, just in case it doesn't match the other
* things.
*/
$field->db_column = $field->convertUnicodeDbSlug();
$field->save();
}
}
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Artisan;
use App\Models\CustomField;
use App\Models\Asset;
use App\Models\Setting;
use \Illuminate\Encryption\Encrypter;
class RotateAppKey extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:rotate-key';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if ($this->confirm("\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ")) {
// Get the existing app_key and ciphers
// We put them in a variable since we clear the cache partway through here.
$old_app_key = config('app.key');
$cipher = config('app.cipher');
// Generate a new one
Artisan::call('key:generate', ['--show' => true]);
$new_app_key = Artisan::output();
// Clear the config cache
Artisan::call('config:clear');
$this->warn('Your app cipher is: '.$cipher);
$this->warn('Your old APP_KEY is: '.$old_app_key);
$this->warn('Your new APP_KEY is: '.$new_app_key);
// Write the new app key to the .env file
$this->writeNewEnvironmentFileWith($new_app_key);
// Manually create an old encrypter instance using the old app key
// and also create a new encrypter instance so we can re-crypt the field
// using the newly generated app key
$oldEncrypter = new Encrypter(base64_decode(substr($old_app_key, 7)), $cipher);
$newEncrypter = new Encrypter(base64_decode(substr($new_app_key, 7)), $cipher);
$fields = CustomField::where('field_encrypted', '1')->get();
foreach ($fields as $field) {
$assets = Asset::whereNotNull($field->db_column)->get();
foreach ($assets as $asset) {
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
$this->line('DECRYPTED: '. $field->db_column);
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
$this->line('ENCRYPTED: '.$field->db_column);
$asset->save();
}
}
// Handle the LDAP password if one is provided
$setting = Setting::first();
if ($setting->ldap_pword!='') {
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
$setting->save();
$this->warn('LDAP password has been re-encrypted.');
}
} else {
$this->info('This operation has been canceled. No changes have been made.');
}
}
/**
* Write a new environment file with the given key.
*
* @param string $key
* @return void
*/
protected function writeNewEnvironmentFileWith($key)
{
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
$this->keyReplacementPattern(),
'APP_KEY='.$key,
file_get_contents($this->laravel->environmentFilePath())
));
}
/**
* Get a regex pattern that will match env APP_KEY with any random key.
*
* @return string
*/
protected function keyReplacementPattern()
{
$escaped = preg_quote('='.$this->laravel['config']['app.key'], '/');
return "/^APP_KEY{$escaped}/m";
}
}

View File

@@ -2,39 +2,14 @@
namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
Commands\PaveIt::class,
Commands\CreateAdmin::class,
Commands\SendExpirationAlerts::class,
Commands\SendInventoryAlerts::class,
Commands\SendExpectedCheckinAlerts::class,
Commands\ObjectImportCommand::class,
Commands\Version::class,
Commands\SystemBackup::class,
Commands\DisableLDAP::class,
Commands\Purge::class,
Commands\LdapSync::class,
Commands\FixDoubleEscape::class,
Commands\RecryptFromMcrypt::class,
Commands\ResetDemoSettings::class,
Commands\SyncAssetLocations::class,
Commands\RegenerateAssetTags::class,
Commands\SyncAssetCounters::class,
Commands\RestoreDeletedUsers::class,
Commands\SendUpcomingAuditReport::class,
Commands\ImportLocations::class,
];
/**
* Define the application's command schedule.

View File

@@ -67,10 +67,6 @@ class Handler extends ExceptionHandler
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
}
if ($e instanceof \Illuminate\Validation\ValidationException) {
return response()->json(Helper::formatStandardApiResponse('error', null, $e->response['messages'], 400));
}
if ($this->isHttpException($e)) {
$statusCode = $e->getStatusCode();
@@ -85,11 +81,6 @@ class Handler extends ExceptionHandler
}
}
// Try to parse 500 Errors in a bit nicer way when debug is enabled.
if (config('app.debug')) {
return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500);
}
}
@@ -128,6 +119,6 @@ class Handler extends ExceptionHandler
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json($exception->errors(), $exception->status);
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors(), 400));
}
}

View File

@@ -8,7 +8,10 @@ use App\Helpers\Helper;
use App\Models\Accessory;
use App\Http\Transformers\AccessoriesTransformer;
use App\Models\Company;
use App\Models\User;
use Carbon\Carbon;
use Auth;
use DB;
class AccessoriesController extends Controller
{
@@ -47,7 +50,11 @@ class AccessoriesController extends Controller
}
$offset = (($accessories) && (request('offset') > $accessories->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -191,4 +198,94 @@ class AccessoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success')));
}
/**
* Save the Accessory checkout information.
*
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
*/
public function checkout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$this->authorize('checkout', $accessory);
if ($accessory->numRemaining() > 0) {
if (!$user = User::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
}
// Update the accessory data
$accessory->assigned_to = $request->input('assigned_to');
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to')
]);
$accessory->logCheckout($request->input('note'), $user);
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
}
/**
* Check in the item so that it can be checked out again to someone else
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param integer $accessoryUserId
* @param string $backto
* @return Redirect
* @internal param int $accessoryId
*/
public function checkin(Request $request, $accessoryUserId = null)
{
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
$logaction = $accessory->logCheckin(User::find($accessoryUserId), $request->input('note'));
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
if (!is_null($accessory_user->assigned_to)) {
$user = User::find($accessory_user->assigned_to);
}
$data['log_id'] = $logaction->id;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['item_name'] = $accessory->name;
$data['checkin_date'] = $logaction->created_at;
$data['item_tag'] = '';
$data['note'] = $logaction->note;
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
}
}

View File

@@ -45,7 +45,10 @@ class AssetMaintenancesController extends Controller
}
$offset = (($maintenances) && (request('offset') > $maintenances->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
// 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');
$allowed_columns = [
'id',

View File

@@ -61,7 +61,10 @@ class AssetModelsController extends Controller
}
$offset = (($assetmodels) && (request('offset') > $assetmodels->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';

View File

@@ -145,7 +145,10 @@ class AssetsController extends Controller
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
$offset = (($assets) && (request('offset') > $assets->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
// This is used by the audit reporting routes
@@ -645,7 +648,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')))->withErrors($asset->getErrors());
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
}
@@ -674,7 +677,11 @@ class AssetsController extends Controller
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = Input::get('name');
if ($request->filled('name')) {
$asset->name = $request->input('name');
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {

View File

@@ -31,7 +31,10 @@ class CategoriesController extends Controller
}
$offset = (($categories) && (request('offset') > $categories->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
$categories->orderBy($sort, $order);

View File

@@ -42,7 +42,10 @@ class CompaniesController extends Controller
}
$offset = (($companies) && (request('offset') > $companies->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$companies->orderBy($sort, $order);

View File

@@ -44,7 +44,9 @@ class ComponentsController extends Controller
}
$offset = (($components) && (request('offset') > $components->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
// 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');
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -45,7 +45,10 @@ class ConsumablesController extends Controller
$offset = (($consumables) && (request('offset') > $consumables->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
// 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');
$allowed_columns = ['id','name','order_number','min_amt','purchase_date','purchase_cost','company','category','model_number', 'item_no', 'manufacturer','location','qty','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -165,7 +168,7 @@ class ConsumablesController extends Controller
{
$consumable = Consumable::with(array('consumableAssignments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
},
'consumableAssignments.admin'=> function ($query) {
},

View File

@@ -24,7 +24,7 @@ class CustomFieldsController extends Controller
public function index()
{
$this->authorize('index', CustomFields::class);
$this->authorize('index', CustomField::class);
$fields = CustomField::get();
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
}
@@ -38,7 +38,7 @@ class CustomFieldsController extends Controller
*/
public function show($id)
{
$this->authorize('show', CustomField::class);
$this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) {
return (new CustomFieldsTransformer)->transformCustomField($field);
}

View File

@@ -58,7 +58,7 @@ class CustomFieldsetsController extends Controller
*/
public function show($id)
{
$this->authorize('show', CustomFieldset::class);
$this->authorize('view', CustomFieldset::class);
if ($fieldset = CustomFieldset::find($id)) {
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
}

View File

@@ -40,7 +40,10 @@ class DepartmentsController extends Controller
}
$offset = (($departments) && (request('offset') > $departments->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View File

@@ -29,7 +29,10 @@ class DepreciationsController extends Controller
}
$offset = (($depreciations) && (request('offset') > $depreciations->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$depreciations->orderBy($sort, $order);

View File

@@ -29,7 +29,10 @@ class GroupsController extends Controller
}
$offset = (($groups) && (request('offset') > $groups->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$groups->orderBy($sort, $order);

View File

@@ -83,7 +83,10 @@ class LicensesController extends Controller
$offset = (($licenses) && (request('offset') > $licenses->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -8,6 +8,8 @@ use App\Helpers\Helper;
use App\Models\Location;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class LocationsController extends Controller
{
@@ -26,7 +28,7 @@ class LocationsController extends Controller
'updated_at','manager_id','image',
'assigned_assets_count','users_count','assets_count','currency'];
$locations = Location::with('parent', 'manager', 'childLocations')->select([
$locations = Location::with('parent', 'manager', 'children')->select([
'locations.id',
'locations.name',
'locations.address',
@@ -52,7 +54,10 @@ class LocationsController extends Controller
$offset = (($locations) && (request('offset') > $locations->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -106,7 +111,7 @@ class LocationsController extends Controller
public function show($id)
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'childLocations')
$location = Location::with('parent', 'manager', 'children')
->select([
'locations.id',
'locations.name',
@@ -143,6 +148,13 @@ class LocationsController extends Controller
{
$this->authorize('update', Location::class);
$location = Location::findOrFail($id);
if ($request->input('parent_id') == $id) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'A location cannot be its own parent. Please select a different parent ID.'));
}
$location->fill($request->all());
if ($location->save()) {
@@ -178,6 +190,27 @@ class LocationsController extends Controller
/**
* Gets a paginated collection for the select2 menus
*
* This is handled slightly differently as of ~4.7.8-pre, as
* we have to do some recursive magic to get the hierarchy to display
* properly when looking at the parent/child relationship in the
* rich menus.
*
* This means we can't use the normal pagination that we use elsewhere
* in our selectlists, since we have to get the full set before we can
* determine which location is parent/child/grandchild, etc.
*
* This also means that hierarchy display gets a little funky when people
* use the Select2 search functionality, but there's not much we can do about
* that right now.
*
* As a result, instead of paginating as part of the query, we have to grab
* the entire data set, and then invoke a paginator manually and pass that
* through to the SelectListTransformer.
*
* Many thanks to @uberbrady for the help getting this working better.
* Recursion still sucks, but I guess he doesn't have to get in the
* sea... this time.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
@@ -189,25 +222,39 @@ class LocationsController extends Controller
$locations = Location::select([
'locations.id',
'locations.name',
'locations.parent_id',
'locations.image',
]);
$page = 1;
if ($request->filled('page')) {
$page = $request->input('page');
}
if ($request->filled('search')) {
$locations = $locations->where('locations.name', 'LIKE', '%'.$request->get('search').'%');
\Log::debug('Searching... ');
$locations = $locations->where('locations.name', 'LIKE', '%'.$request->input('search').'%');
}
$locations = $locations->orderBy('name', 'ASC')->paginate(50);
$locations = $locations->orderBy('name', 'ASC')->get();
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
$locations_with_children = [];
foreach ($locations as $location) {
$location->use_text = $location->name;
$location->use_image = ($location->image) ? url('/').'/uploads/locations/'.$location->image : null;
if(!array_key_exists($location->parent_id, $locations_with_children)) {
$locations_with_children[$location->parent_id] = [];
}
$locations_with_children[$location->parent_id][] = $location;
}
return (new SelectlistTransformer)->transformSelectlist($locations);
$location_options = Location::indenter($locations_with_children);
$locations_formatted = new Collection($location_options);
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
//return [];
return (new SelectlistTransformer)->transformSelectlist($paginated_results);
}
}

View File

@@ -40,7 +40,10 @@ class ManufacturersController extends Controller
$offset = (($manufacturers) && (request('offset') > $manufacturers->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$manufacturers->orderBy($sort, $order);

View File

@@ -38,7 +38,7 @@ class SettingsController extends Controller
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
\Log::debug('Connection failed');
\Log::debug('Connection failed but we cannot debug it any further on our end.');
return response()->json(['message' => $e->getMessage()], 600);
}

View File

@@ -31,7 +31,10 @@ class StatuslabelsController extends Controller
}
$offset = (($statuslabels) && (request('offset') > $statuslabels->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$statuslabels->orderBy($sort, $order);

View File

@@ -34,7 +34,10 @@ class SuppliersController extends Controller
}
$offset = (($suppliers) && (request('offset') > $suppliers->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
// 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');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$suppliers->orderBy($sort, $order);

View File

@@ -88,7 +88,10 @@ class UsersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
$limit = request('limit', 20);
// 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');
switch ($request->input('sort')) {
case 'manager':
@@ -260,12 +263,21 @@ class UsersController extends Controller
if ($user->save()) {
// Sync group memberships:
// This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5
// which changes the behavior of has vs filled.
// The $request->has method will now return true even if the input value is an empty string or null.
// A new $request->filled method has was added that provides the previous behavior of the has method.
// Check if the request has groups passed and has a value
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
} else {
// The groups field has been passed but it is null, so we should blank it out
} elseif ($request->has('groups')) {
$user->groups()->sync(array());
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
@@ -287,10 +299,23 @@ class UsersController extends Controller
$this->authorize('delete', $user);
if ($user->assets()->count() > 0) {
if (($user->assets) && ($user->assets->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
}
if (($user->licenses) && ($user->licenses->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.'));
}
if (($user->accessories) && ($user->accessories->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.'));
}
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.'));
}
if ($user->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
}

View File

@@ -570,15 +570,17 @@ class AssetsController extends Controller
*/
public function postImportHistory(Request $request)
{
if (!$request->hasFile('user_import_csv')) {
return back()->with('error', 'No file provided. Please select a file for import and try again. ');
}
if (!ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$csv = Reader::createFromPath(Input::file('user_import_csv'));
$csv->setNewline("\r\n");
//get the first row, usually the CSV header
//$headers = $csv->fetchOne();
$csv->setHeaderOffset(0);
$results = $csv->getRecords();
$item = array();
$status = array();
@@ -595,7 +597,9 @@ class AssetsController extends Controller
}
$batch_counter = count($item[$asset_tag]);
$item[$asset_tag][$batch_counter]['checkout_date'] = Carbon::parse(Helper::array_smart_fetch($row, "date"))->format('Y-m-d H:i:s');
$item[$asset_tag][$batch_counter]['checkout_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkout date"))->format('Y-m-d H:i:s');
$item[$asset_tag][$batch_counter]['checkin_date'] = Carbon::parse(Helper::array_smart_fetch($row, "checkin date"))->format('Y-m-d H:i:s');
\Log::debug($item[$asset_tag][$batch_counter]['checkin_date']);
$item[$asset_tag][$batch_counter]['asset_tag'] = Helper::array_smart_fetch($row, "asset tag");
$item[$asset_tag][$batch_counter]['name'] = Helper::array_smart_fetch($row, "name");
@@ -678,9 +682,11 @@ class AssetsController extends Controller
// Only do this if a matching user was found
if ((array_key_exists('checkedout_to', $asset_batch[$x])) && ($asset_batch[$x]['checkedout_to']!='')) {
if (($total_in_batch > 1) && ($x < $total_in_batch) && (array_key_exists($next, $asset_batch))) {
$checkin_date = Carbon::parse($asset_batch[$next]['checkout_date'])->subDay(1)->format('Y-m-d H:i:s');
$checkin_date = Carbon::parse($asset_batch[$next]['checkin_date'])->format('Y-m-d H:i:s');
$asset_batch[$x]['real_checkin'] = $checkin_date;
\Log::debug($asset_batch[$next]['checkin_date']);
\Log::debug($checkin_date);
Actionlog::firstOrCreate(array(
'item_id' => $asset_batch[$x]['asset_id'],
'item_type' => Asset::class,

View File

@@ -41,8 +41,6 @@ class LocationsController extends Controller
{
// Grab all the locations
$this->authorize('view', Location::class);
$locations = Location::orderBy('created_at', 'DESC')->with('parent', 'assets', 'assignedassets')->get();
// Show the page
return view('locations/index');
}
@@ -59,14 +57,7 @@ class LocationsController extends Controller
public function create()
{
$this->authorize('create', Location::class);
$locations = Location::orderBy('name', 'ASC')->get();
$location_options_array = Location::getLocationHierarchy($locations);
$location_options = Location::flattenLocationsArray($location_options_array);
$location_options = array('' => 'Top Level') + $location_options;
return view('locations/edit')
->with('location_options', $location_options)
->with('item', new Location);
}
@@ -132,14 +123,8 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
// Show the page
$locations = Location::orderBy('name', 'ASC')->get();
$location_options_array = Location::getLocationHierarchy($locations);
$location_options = Location::flattenLocationsArray($location_options_array);
$location_options = array('' => 'Top Level') + $location_options;
return view('locations/edit', compact('item'))
->with('location_options', $location_options);
return view('locations/edit', compact('item'));
}
@@ -160,6 +145,11 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($request->input('parent_id') == $locationId) {
return redirect()->back()->withInput()->with('error', 'A location cannot be its own parent. Please select a different parent location.');
}
// Update the location data
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
@@ -229,7 +219,7 @@ class LocationsController extends Controller
if ($location->users->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
} elseif ($location->childLocations->count() > 0) {
} elseif ($location->children->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
} elseif ($location->assets->count() > 0) {

View File

@@ -307,12 +307,18 @@ class ReportsController extends Controller
public function postCustom(Request $request)
{
ini_set('max_execution_time', 12000);
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
\Log::debug('Starting streamed response');
// Open output stream
$handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
@@ -464,8 +470,12 @@ class ReportsController extends Controller
}
}
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Starting headers: '.$executionTime);
fputcsv($handle, $header);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Added headers: '.$executionTime);
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
@@ -520,9 +530,13 @@ class ReportsController extends Controller
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
$assets->orderBy('assets.created_at', 'ASC')->chunk(500, function($assets) use($handle, $customfields, $request) {
$assets->orderBy('assets.created_at', 'ASC')->chunk(20, function($assets) use($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Walking results: '.$executionTime);
$count = 0;
foreach ($assets as $asset) {
$count++;
$row = [];
if ($request->filled('company')) {
@@ -695,17 +709,24 @@ class ReportsController extends Controller
}
}
fputcsv($handle, $row);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('-- Record '.$count.' Asset ID:' .$asset->id. ' in '. $executionTime);
}
});
// Close the output stream
fclose($handle);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('-- SCRIPT COMPLETED IN '. $executionTime);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition'
=> 'attachment; filename="custom-assets-report-'.date('Y-m-d-his').'.csv"',
]);
return $response;

View File

@@ -865,7 +865,7 @@ class SettingsController extends Controller
$setting->ldap_server = $request->input('ldap_server');
$setting->ldap_server_cert_ignore = $request->input('ldap_server_cert_ignore', false);
$setting->ldap_uname = $request->input('ldap_uname');
if (Input::has('ldap_pword')) {
if (Input::filled('ldap_pword')) {
$setting->ldap_pword = Crypt::encrypt($request->input('ldap_pword'));
}
$setting->ldap_basedn = $request->input('ldap_basedn');

View File

@@ -340,25 +340,25 @@ class UsersController extends Controller
// Check if we are not trying to delete ourselves
if ($user->id === Auth::user()->id) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->assets()->count() . ' assets associated with them.');
return redirect()->route('users.index')->with('error', 'You cannot delete yourself.');
}
if ($user->assets->count() > 0) {
if (($user->assets) && ($user->assets->count() > 0)) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . count($user->assets->count()) . ' assets associated with them.');
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->assets->count() . ' assets associated with them. Use the Checkin and Delete button on the user profile to check these items back in and delete this user.');
}
if ($user->licenses()->count() > 0) {
if (($user->licenses) && ($user->licenses->count() > 0)) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->assets()->count() . ' assets associated with them.');
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->licenses->count() . ' license(s associated with them. Use the Checkin and Delete button on the user profile to check these items back in and delete this user.');
}
if ($user->accessories()->count() > 0) {
if (($user->accessories) && ($user->accessories->count() > 0)) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->accessories()->count() . ' accessories associated with them.');
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->accessories->count() . ' accessories associated with them. Use the Checkin and Delete button on the user profile to check these items back in and delete this user.');
}
if ($user->managedLocations()->count() > 0) {
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.');
}

View File

@@ -44,6 +44,7 @@ class Kernel extends HttpKernel
],
'api' => [
\Barryvdh\Cors\HandleCors::class,
'throttle:120,1',
'auth:api',
],

View File

@@ -4,7 +4,7 @@ namespace App\Http\Requests;
use App\Models\AssetModel;
use Session;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;
@@ -32,7 +32,7 @@ class AssetRequest extends Request
'model_id' => 'required|integer|exists:models,id',
'status_id' => 'required|integer|exists:status_labels,id',
'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable',
'warranty_months' => 'numeric|nullable|digits_between:0,240',
'physical' => 'integer|nullable',
'checkout_date' => 'date',
'checkin_date' => 'date',
@@ -48,7 +48,7 @@ class AssetRequest extends Request
$rules['asset_tag'] = ($settings->auto_increment_assets == '1') ? 'max:255' : 'required';
if($this->request->get('model_id') != '') {
if ($this->request->get('model_id') != '') {
$model = AssetModel::find($this->request->get('model_id'));
if (($model) && ($model->fieldset)) {
@@ -60,13 +60,6 @@ class AssetRequest extends Request
}
public function response(array $errors)
{
$this->session()->flash('errors', Session::get('errors', new \Illuminate\Support\ViewErrorBag)
->put('default', new \Illuminate\Support\MessageBag($errors)));
\Input::flash();
return parent::response($errors);
}
/**
* Handle a failed validation attempt.
@@ -76,13 +69,17 @@ class AssetRequest extends Request
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
protected function failedValidation(Validator $validator)
{
return response()->json([
'message' => 'The given data is invalid',
'errors' => $validator->errors()
], 422);
$this->session()->flash('errors', Session::get('errors', new \Illuminate\Support\ViewErrorBag)
->put('default', new \Illuminate\Support\MessageBag($validator->errors()->toArray())));
\Input::flash();
throw new HttpResponseException(response()->json([
'status' => 'error',
'messages' => $validator->errors(),
'payload' => null
], 422));
}
}

View File

@@ -2,10 +2,12 @@
namespace App\Http\Requests;
use App\Http\Requests\Request;
use App\Models\Setting;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
class SaveUserRequest extends Request
class SaveUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
@@ -62,4 +64,5 @@ class SaveUserRequest extends Request
return $rules;
}
}

View File

@@ -32,7 +32,15 @@ class ActionlogsTransformer
$meta_array = json_decode($actionlog->log_meta);
foreach ($meta_array as $key => $value) {
foreach ($value as $meta_key => $meta_value) {
$clean_meta[$key][$meta_key] = e($meta_value);
if (is_array($meta_value)) {
foreach ($meta_value as $meta_value_key => $meta_value_value) {
$clean_meta[$key][$meta_value_key] = e($meta_value_value);
}
} else {
$clean_meta[$key][$meta_key] = e($meta_value);
}
}
}
}

View File

@@ -23,7 +23,7 @@ class LocationsTransformer
if ($location) {
$children_arr = [];
foreach($location->childLocations as $child) {
foreach($location->children as $child) {
$children_arr[] = [
'id' => (int) $child->id,
'name' => $child->name

View File

@@ -27,9 +27,17 @@ class AssetImporter extends ItemImporter
foreach ($this->customFields as $customField) {
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
if ($customFieldValue) {
$this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue;
$this->log('Custom Field '. $customField->name.': '.$customFieldValue);
if ($customField->field_encrypted == 1) {
$this->item['custom_fields'][$customField->db_column_name()] = \Crypt::encrypt($customFieldValue);
$this->log('Custom Field '. $customField->name.': '.\Crypt::encrypt($customFieldValue));
} else {
$this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue;
$this->log('Custom Field '. $customField->name.': '.$customFieldValue);
}
} else {
// Clear out previous data.
$this->item['custom_fields'][$customField->db_column_name()] = null;

View File

@@ -75,7 +75,7 @@ class Asset extends Depreciable
'model_id' => 'required|integer|exists:models,id',
'status_id' => 'required|integer|exists:status_labels,id',
'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable',
'warranty_months' => 'numeric|nullable|digits_between:0,240',
'physical' => 'numeric|max:1|nullable',
'checkout_date' => 'date|max:10|min:10|nullable',
'checkin_date' => 'date|max:10|min:10|nullable',
@@ -866,8 +866,10 @@ class Asset extends Depreciable
public function scopeDueOrOverdueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
return $query->whereNotNull('assets.next_audit_date')
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $settings->audit_warning_days DAY) <= '".Carbon::now()."'")
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
->where('assets.archived', '=', 0)
->NotArchived();
}
@@ -1165,7 +1167,29 @@ class Asset extends Depreciable
}
}
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='location') && ($fieldname!='supplier')
/**
* THIS CLUNKY BIT IS VERY IMPORTANT
*
* Although inelegant, this section matters a lot when querying against fields that do not
* exist on the asset table. There's probably a better way to do this moving forward, for
* example using the Schema:: methods to determine whether or not a column actually exists,
* or even just using the $searchableRelations variable earlier in this file.
*
* In short, this set of statements tells the query builder to ONLY query against an
* actual field that's being passed if it doesn't meet known relational fields. This
* allows us to query custom fields directly in the assetsv table
* (regardless of their name) and *skip* any fields that we already know can only be
* searched through relational searches that we do earlier in this method.
*
* For example, we do not store "location" as a field on the assets table, we store
* that relationship through location_id on the assets table, therefore querying
* assets.location would fail, as that field doesn't exist -- plus we're already searching
* against those relationships earlier in this method.
*
* - snipe
*
*/
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
&& ($fieldname!='status_label') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) {
$query->orWhere('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
}

View File

@@ -73,7 +73,8 @@ class AssetMaintenance extends Model implements ICompanyableChild
trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
'PAT test' => 'PAT test',
trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'),
'PAT test' => 'PAT test',
'Software Support' => trans('admin/asset_maintenances/general.software_support'),
'Hardware Support' => trans('admin/asset_maintenances/general.hardware_support'),
];
}

View File

@@ -80,7 +80,13 @@ final class Company extends SnipeModel
}
$table = ($table_name) ? DB::getTablePrefix().$table_name."." : '';
return $query->where($table.$column, '=', $company_id);
if(\Schema::hasColumn($query->getModel()->getTable(), $column)){
return $query->where($table.$column, '=', $company_id);
} else {
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
}
}
public static function getIdFromInput($unescaped_input)

View File

@@ -7,10 +7,10 @@ use Watson\Validating\ValidatingTrait;
class Group extends SnipeModel
{
protected $table = 'groups';
protected $table = 'permission_groups';
public $rules = array(
'name' => 'required|min:3|max:255',
'name' => 'required|min:2|max:255',
);
/**

View File

@@ -48,7 +48,7 @@ class License extends Depreciable
protected $table = 'licenses';
protected $rules = array(
'name' => 'required|string|min:3|max:255',
'seats' => 'required|min:1|max:1000000|integer',
'seats' => 'required|min:1|max:999|integer',
'license_email' => 'email|nullable|max:120',
'license_name' => 'string|nullable|max:100',
'notes' => 'string|nullable',

View File

@@ -113,7 +113,8 @@ class Location extends SnipeModel
public function parent()
{
return $this->belongsTo('\App\Models\Location', 'parent_id','id');
return $this->belongsTo('\App\Models\Location', 'parent_id','id')
->with('parent');
}
public function manager()
@@ -121,9 +122,9 @@ class Location extends SnipeModel
return $this->belongsTo('\App\Models\User', 'manager_id');
}
public function childLocations()
{
return $this->hasMany('\App\Models\Location', 'parent_id');
public function children() {
return $this->hasMany('\App\Models\Location','parent_id')
->with('children');
}
// I don't think we need this anymore since we de-normed location_id in assets?
@@ -137,59 +138,37 @@ class Location extends SnipeModel
return $this->attributes['ldap_ou'] = empty($ldap_ou) ? null : $ldap_ou;
}
public static function getLocationHierarchy($locations, $parent_id = null)
{
/**
* Query builder scope to order on parent
*
* @param Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return Illuminate\Database\Query\Builder Modified query builder
*/
$op = array();
foreach ($locations as $location) {
if ($location['parent_id'] == $parent_id) {
$op[$location['id']] =
array(
'name' => $location['name'],
'parent_id' => $location['parent_id']
);
// Using recursion
$children = Location::getLocationHierarchy($locations, $location['id']);
if ($children) {
$op[$location['id']]['children'] = $children;
}
}
public static function indenter($locations_with_children, $parent_id = null, $prefix = '') {
$results = Array();
if (!array_key_exists($parent_id, $locations_with_children)) {
return [];
}
return $op;
foreach ($locations_with_children[$parent_id] as $location) {
$location->use_text = $prefix.' '.$location->name;
$location->use_image = ($location->image) ? url('/').'/uploads/locations/'.$location->image : null;
$results[] = $location;
//now append the children. (if we have any)
if (array_key_exists($location->id, $locations_with_children)) {
$results = array_merge($results, Location::indenter($locations_with_children, $location->id,$prefix.'--'));
}
}
return $results;
}
public static function flattenLocationsArray($location_options_array = null)
{
$location_options = array();
foreach ($location_options_array as $id => $value) {
// get the top level key value
$location_options[$id] = $value['name'];
// If there is a key named children, it has child locations and we have to walk it
if (array_key_exists('children', $value)) {
foreach ($value['children'] as $child_id => $child_location_array) {
$child_location_options = Location::flattenLocationsArray($value['children']);
foreach ($child_location_options as $child_id => $child_name) {
$location_options[$child_id] = '--'.$child_name;
}
}
}
}
return $location_options;
}
/**
* Query builder scope to order on parent

View File

@@ -491,7 +491,7 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
public function scopeByGroup($query, $id) {
return $query->whereHas('groups', function ($query) use ($id) {
$query->where('groups.id', '=', $id);
$query->where('permission_groups.id', '=', $id);
});
}

View File

@@ -6,6 +6,7 @@
"type": "project",
"require": {
"php": ">=7.1.2",
"barryvdh/laravel-cors": "^0.11.3",
"barryvdh/laravel-debugbar": "^3.2",
"doctrine/cache": "^1.8",
"doctrine/common": "^2.10",
@@ -26,12 +27,12 @@
"maknz/slack": "^1.7",
"neitanod/forceutf8": "^2.0",
"patchwork/utf8": "^1.3",
"phpdocumentor/reflection-docblock": "3.2.2",
"phpdocumentor/reflection-docblock": "^4.0",
"phpspec/prophecy": "^1.8",
"pragmarx/google2fa": "^5.0",
"pragmarx/google2fa-laravel": "^1.0",
"predis/predis": "^1.1",
"rollbar/rollbar-laravel": "^4.0",
"rollbar/rollbar-laravel": "2.*",
"schuppo/password-strength": "~1.5",
"spatie/laravel-backup": "^5.12",
"tecnickcom/tc-lib-barcode": "^1.15",

857
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,19 @@ return [
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Result Limit
|--------------------------------------------------------------------------
|
| This value determines the max number of results to return, even if a higher limit
| is passed in the API request. This is done to prevent server timeouts when
| custom scripts are requesting 100k assets at a time.
|
*/
'max_results' => env('MAX_RESULTS', 500),
/*
|--------------------------------------------------------------------------
| Application Debug Mode

48
config/cors.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
/**
* ---------------------------------------------------------------------
* THIS IS $allowed_origins code IS NOT PART OF THE ORIGINAL CORS PACKAGE.
* IT IS A MODIFICATION BY SNIPE-IT TO ALLOW ADDING ALLOWED ORIGINS VIA THE ENV.
* ---------------------------------------------------------------------
*
* Since we don't really want people editing config files (lest they get
* overwritten later), this enables the person managing the Snipe-IT
* installation to modify these values without modifying the code.
*
* If APP_CORS_ALLOWED_ORIGINS is not set in the .env (for example if no one added it
* after an upgrade from a previous version that didn't include it in the .env.example) or is null,
* set it to * to allow all. If there is a value, either a single url or a comma-delimited
* list of urls, explode that out into an array to whitelist just those urls.
*/
$allowed_origins = env('CORS_ALLOWED_ORIGINS') !== null ?
explode(',', env('CORS_ALLOWED_ORIGINS')) : [];
/**
* Original Laravel CORS package config file modifications end here
*
*/
return [
/*
|--------------------------------------------------------------------------
| Laravel CORS
|--------------------------------------------------------------------------
|
| allowedOrigins, allowedHeaders and allowedMethods can be set to array('*')
| to accept any value.
|
*/
'supportsCredentials' => false,
'allowedOrigins' => $allowed_origins,
'allowedOriginsPatterns' => [],
'allowedHeaders' => ['*'],
'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'exposedHeaders' => [],
'maxAge' => 0,
];

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v4.7.4',
'full_app_version' => 'v4.7.4 - build 4107-g49a255c8f',
'build_version' => '4107',
'app_version' => 'v4.7.8',
'full_app_version' => 'v4.7.8 - build 4170-g4fe689dc5',
'build_version' => '4170',
'prerelease_version' => '',
'hash_version' => 'g49a255c8f',
'full_hash' => 'v4.7.4-8-g49a255c8f',
'hash_version' => 'g4fe689dc5',
'full_hash' => 'v4.7.8-7-g4fe689dc5',
'branch' => 'master',
);

View File

@@ -29,7 +29,7 @@ class MigrationCartalystSentryInstallGroups extends Migration {
*/
public function up()
{
Schema::create('groups', function($table)
Schema::create('permission_groups', function($table)
{
$table->increments('id');
$table->string('name');
@@ -46,7 +46,7 @@ class MigrationCartalystSentryInstallGroups extends Migration {
*/
public function down()
{
Schema::drop('groups');
Schema::drop('permission_groups');
}
}

View File

@@ -2,39 +2,44 @@
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Models\Group;
class UpdateGroupFieldForReporting extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
// Schema::table('groups', function(Blueprint $table)
// {
// //
// });
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin":1,"users":1,"reports":1}', 1]);
// This is janky because we had to do a back in time change to handle a MySQL 8+
// compatibility issue.
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users":1,"reports":1}', 2]);
// Ideally we'd be using the model here, but since we can't really know whether this is an upgrade
// or a fresh install, we have to check which table is being used.
if (Schema::hasTable('permission_groups')) {
// DB::statement('UPDATE '.$prefix.'groups SET permissions="{\"admin\":1,\"users\":1,\"reports\":1}" where id=1');
// DB::statement('UPDATE '.$prefix.'groups SET permissions="{\"users\":1,\"reports\":1}" where id=2');
Group::where('id', 1)->update(['permissions' => '{"users-poop":1,"reports":1}']);
Group::where('id', 2)->update(['permissions' => '{"users-pop":1,"reports":1}']);
}
} elseif (Schema::hasTable('groups')) {
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"admin-farts":1,"users":1,"reports":1}', 1]);
DB::update('update '.DB::getTablePrefix().'groups set permissions = ? where id = ?', ['{"users-farts":1,"reports":1}', 2]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@@ -0,0 +1,44 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RenameGroupsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// We check to see if this table exists before attempting the migration since
// upgraded installs would have this table, but new installs wouldn't.
// We had to change the name of the table in the older migrations
// to handle a MySQl 8+ compatibility issue related to reserved words.
// Without going back in time in migrations, this would fail since the groups table
// would never be allowed to be created in the first place on MySql 8+.
//
// So... if an upgrade, let's rename that table.
// If a new install, the migration was already changed, so the table isn't
// called that anymore and we can skip this migration.
if (Schema::hasTable('groups')) {
Schema::rename('groups', 'permission_groups');
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (Schema::hasTable('permission_groups')) {
Schema::rename('permission_groups', 'groups');
}
}
}

View File

@@ -10,7 +10,7 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js "
},
"devDependencies": {
"axios": "^0.16.2",
"axios": ">=0.18.1",
"babel-preset-latest": "^6.24.1",
"cross-env": "^5.0.5",
"jquery": "^3.1.1",

View File

@@ -8,5 +8,7 @@
'repair' => 'Repair',
'maintenance' => 'Maintenance',
'upgrade' => 'Upgrade',
'calibration' => 'Calibration'
'calibration' => 'Calibration',
'software_support' => 'Software Support',
'hardware_support' => 'Hardware Support',
];

View File

@@ -7,5 +7,7 @@
'view' => 'Voir les détails de la maintenance d\'actif',
'repair' => 'Réparation',
'maintenance' => 'Entretien',
'upgrade' => 'Mise à niveau'
'upgrade' => 'Mise à niveau',
'software_support' => 'Support logiciel',
'hardware_support' => 'Support matériel',
];

View File

@@ -64,9 +64,8 @@
Upload a CSV that contains asset history. The assets and users MUST already exist in the system, or they will be skipped. Matching assets for history import happens against the asset tag. We will try to find a matching user based on the user's name you provide, and the criteria you select below. If you do not select any criteria below, it will simply try to match on the username format you configured in the Admin &gt; General Settings.
</p>
<p>Fields included in the CSV must match the headers: <strong>Date, Tag, Name</strong>. Any additional fields will be ignored. </p>
<p>Fields included in the CSV must match the headers: <strong>Asset Tag, Checkout Date, Checkin Date, Name</strong>. Any additional fields will be ignored. </p>
<p><strong>Date</strong> should be the checkout date. <strong>Tag</strong> should be the asset tag. <strong>Name</strong> should be the user's name (firstname lastname).</p>
<p><strong>History should be ordered by date in ascending order.</strong></p>

View File

@@ -569,19 +569,21 @@
</thead>
<tbody>
@foreach ($asset->licenseseats as $seat)
<tr>
<td><a href="{{ route('licenses.show', $seat->license->id) }}">{{ $seat->license->name }}</a></td>
<td>
@can('viewKeys', $seat->license)
{!! nl2br(e($seat->license->serial)) !!}
@else
------------
@endcan
</td>
<td>
<a href="{{ route('licenses.checkin', $seat->id) }}" class="btn btn-sm bg-purple" data-tooltip="true">{{ trans('general.checkin') }}</a>
</td>
</tr>
@if ($seat->license)
<tr>
<td><a href="{{ route('licenses.show', $seat->license->id) }}">{{ $seat->license->name }}</a></td>
<td>
@can('viewKeys', $seat->license)
{!! nl2br(e($seat->license->serial)) !!}
@else
------------
@endcan
</td>
<td>
<a href="{{ route('licenses.checkin', $seat->id) }}" class="btn btn-sm bg-purple" data-tooltip="true">{{ trans('general.checkin') }}</a>
</td>
</tr>
@endif
@endforeach
</tbody>
</table>

View File

@@ -11,6 +11,11 @@
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}">
<link rel="apple-touch-startup-image" href="{{ url('/') }}/uploads/{{ $snipeSettings->logo }}">
<!-- Select2 -->
<link rel="stylesheet" href="{{ url(asset('js/plugins/select2/select2.min.css')) }}">
@@ -761,7 +766,7 @@
<div class="pull-right hidden-xs">
@if ($snipeSettings->version_footer!='off')
@if (($snipeSettings->version_footer=='on') || (($snipeSettings->version_footer=='admin') && (Auth::user()->isSuperUser()=='1')))
<b>Version</b> {{ config('version.app_version') }} - build {{ config('version.build_version') }} ({{ config('version.branch') }})
&nbsp; <b>Version</b> {{ config('version.app_version') }} - build {{ config('version.build_version') }} ({{ config('version.branch') }})
@endif
@endif

View File

@@ -10,16 +10,8 @@
@section('inputFields')
@include ('partials.forms.edit.name', ['translated_name' => trans('admin/locations/table.name')])
<!-- Parent-->
<div class="form-group {{ $errors->has('parent_id') ? ' has-error' : '' }}">
<label for="parent_id" class="col-md-3 control-label">
{{ trans('admin/locations/table.parent') }}
</label>
<div class="col-md-9{{ (\App\Helpers\Helper::checkIfRequired($item, 'parent_id')) ? ' required' : '' }}">
{!! Form::select('parent_id', $location_options , Input::old('parent_id', $item->parent_id), array('class'=>'select2 parent', 'style'=>'width:350px')) !!}
{!! $errors->first('parent_id', '<span class="alert-msg"><i class="fa fa-times"></i> :message</span>') !!}
</div>
</div>
<!-- parent -->
@include ('partials.forms.edit.location-select', ['translated_name' => trans('admin/locations/table.parent'), 'fieldname' => 'parent_id'])
<!-- Manager-->
@include ('partials.forms.edit.user-select', ['translated_name' => trans('admin/users/table.manager'), 'fieldname' => 'manager_id'])

View File

@@ -3,11 +3,11 @@
<label for="warranty_months" class="col-md-3 control-label">{{ trans('admin/hardware/form.warranty') }}</label>
<div class="col-md-9">
<div class="input-group col-md-3" style="padding-left: 0px;">
<input class="form-control" type="text" name="warranty_months" id="warranty_months" value="{{ Input::old('warranty_months', $item->warranty_months) }}" />
<input class="form-control" type="text" name="warranty_months" id="warranty_months" value="{{ Input::old('warranty_months', $item->warranty_months) }}" maxlength="3" />
<span class="input-group-addon">{{ trans('admin/hardware/form.months') }}</span>
</div>
<div class="col-md-9" style="padding-left: 0px;">
{!! $errors->first('warranty_months', '<span class="alert-msg"><i class="fa fa-times"></i> :message</span>') !!}
</div>
</div>
</div>
</div>

View File

@@ -340,10 +340,11 @@
</tr>
</thead>
<tbody>
@if ($user->assets)
@foreach ($user->assets as $asset)
<tr>
<td>
@if ($asset->physical=='1')
@if (($asset->model) && ($asset->physical=='1'))
<a href="{{ route('models.show', $asset->model->id) }}">{{ $asset->model->name }}</a>
@endif
</td>
@@ -360,6 +361,7 @@
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>

View File

@@ -56,6 +56,21 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'api'], fun
'uses' => 'AccessoriesController@checkedout'
]
);
Route::post('{accessory}/checkout',
[
'as' => 'api.accessories.checkout',
'uses' => 'AccessoriesController@checkout'
]
);
Route::post('{accessory}/checkin',
[
'as' => 'api.accessories.checkin',
'uses' => 'AccessoriesController@checkin'
]
);
}); // Accessories group

View File

@@ -49,9 +49,11 @@ $git_version = shell_exec('git --version');
if ((strpos('git version', $git_version)) === false) {
echo "Git is installed. \n";
$git_fetch = shell_exec('git fetch');
$git_checkout = shell_exec('git checkout '.$branch);
$git_stash = shell_exec('git stash');
$git_pull = shell_exec('git pull');
echo '-- '.$git_fetch;
echo '-- '.$git_stash;
echo '-- '.$git_checkout;
echo '-- '.$git_pull;