Compare commits

..

97 Commits

Author SHA1 Message Date
snipe
c651c9f1ed Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-09-05 18:35:43 -07:00
snipe
7c390cee2c Bumped version 2017-09-05 18:35:39 -07:00
Daniel Meltzer
987536930c Assorted fixes (#3923)
* Fix some n+1 problems

* Use route in notification dropdown to make sure we link to correct page

* Work on better UI support for checkout to non-user.  Fix links on index bootstrap table, work towards eliminating assignedUser

* Remove Asset::assigneduser() relationship.  Instead add a checkedOutToUser() method and/or port to using assignedTo()

* Adjust string to fit new reality

* Fix #3780.  Move the consumables getDataView method to the ApiController.  Not entirely RESTful, but it's a weird method that probably doesn't need its own controller and the functionality would be strange to stack on the userscontroller...

* Fix file uploads to assets and restore the delete route.

* Add asset maintence edit action to index.

* Suppliers asset list should link to the related asset, not to the supplier with same ID.

* Asset models page should use polymorphic formatter on assigned to to better handle assorted item types.

* Comment out more assigneduser fallacy until we figure out the query builder approach to searching for location text.
2017-09-05 17:54:58 -07:00
snipe
10f322198f Move audited count to top of table 2017-08-31 21:31:07 -07:00
snipe
761371509d Use notifiables for slack audit notification 2017-08-31 21:30:38 -07:00
snipe
3518ea7e7d Fixes #606 - email notifications for expected checkins 2017-08-31 21:18:05 -07:00
snipe
c92eed2b3e Small HTML email tweaks 2017-08-31 21:17:02 -07:00
snipe
0054ce3071 Fixes #3907 2017-08-31 13:45:48 -07:00
snipe
b0f74466bb Removed dd 2017-08-31 11:15:52 -07:00
snipe
b4a0484295 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-08-31 11:15:03 -07:00
Daniel Meltzer
bb874012d9 Progress towards better email notifications (#3911)
Working mail from notification.  Still requires testing/cleaning

Add tests around checkout notification.

This also removes the ability to check out an asset to a location|asset
that requires acceptance/a Eula.  For 4.1 we may think about how to
support such a thing, but at present it seems to make sense to only alow
such assets to be checked out to users, who can be responsible for the
items.
2017-08-31 11:14:21 -07:00
snipe
bb8583eb14 Remove lower casing for LDAP array re: #3910 2017-08-31 11:00:08 -07:00
snipe
8d2c229bc3 Move LDAP validation into form request 2017-08-31 10:44:00 -07:00
snipe
48e6208214 Fixes #3907 - do not require username on user if LDAP import 2017-08-31 10:43:36 -07:00
snipe
22233e3ba6 Bulk asset audit form (needs more testing) 2017-08-29 16:00:22 -07:00
snipe
e439f15a64 Fixed some date math for auditing 2017-08-28 17:20:20 -07:00
snipe
42175782a5 Only pull logo if there is a value 2017-08-26 17:43:00 -07:00
snipe
c7a21e0e4d Production asset build 2017-08-26 17:39:59 -07:00
snipe
d98ffd94f9 Localized modal titles with correct headers 2017-08-26 16:16:41 -07:00
Brady Wetherington
6ad5da44f3 Formalize modals (#3898)
* Refactor Modal JS into standalone file, remove duplicated JS and HTML

* Finish fixing Bulk-checkout and checkout
2017-08-26 16:06:52 -07:00
snipe
479f422e68 Added default if no audit settings are in place 2017-08-26 15:27:50 -07:00
snipe
e10cdd57a5 Removed old getassetloist method 2017-08-26 15:22:04 -07:00
snipe
bf157773c8 Also related to #3888 2017-08-26 15:21:38 -07:00
snipe
fba3949530 Fixes #3888 - broken preview of existing assets 2017-08-26 15:21:10 -07:00
snipe
abc3dea8ac Fixed wonky datepicker on bulk checkout 2017-08-26 14:16:16 -07:00
snipe
51d74ac06d Auduting improvements 2017-08-25 18:40:20 -07:00
snipe
af835d6efc Additional setting validation for new fields 2017-08-25 17:59:01 -07:00
snipe
a7a10455ae Bumped version 2017-08-25 13:27:58 -07:00
snipe
bd02b9ed62 Audit tweaks 2017-08-25 10:18:18 -07:00
snipe
16f57e16cb Fixes #1190 - added basic audit workflow 2017-08-25 10:04:19 -07:00
snipe
af6f208c43 Reordered settings nav 2017-08-25 10:03:05 -07:00
snipe
52270fa4db Derp 2017-08-25 08:30:48 -07:00
snipe
bf3731d65c Set default min password to 10 2017-08-25 08:23:23 -07:00
snipe
233ebf06ee ANOTHER fix for enum fuckery 2017-08-25 07:36:44 -07:00
snipe
e27f6a483d Updated translations 2017-08-25 07:32:57 -07:00
snipe
19670f9dd8 Remove assigned_to constraint 2017-08-25 06:30:10 -07:00
snipe
1448229cd2 Fixes location user route 2017-08-25 06:30:00 -07:00
snipe
4721cab928 Grr. 2017-08-25 06:08:19 -07:00
snipe
08f3e78d26 Merge branch 'checkout-to-location-v2' of https://github.com/dmeltzer/snipe-it into dmeltzer-checkout-to-location-v2
# Conflicts:
#	app/Http/Controllers/Api/UsersController.php
#	app/Http/Transformers/LocationsTransformer.php
#	resources/views/locations/view.blade.php
#	routes/api.php
#	tests/_data/dump.sql
2017-08-25 06:04:22 -07:00
snipe
62227ec27d Link to location in user view 2017-08-25 05:48:32 -07:00
snipe
10711245ba Fixes #3792 - parent/child locations in API 2017-08-25 05:32:12 -07:00
snipe
29a7c8577d Fixes #3849 - fillable for accessories 2017-08-25 03:48:07 -07:00
snipe
dfb1ff81e6 Fixes settings problem in unit tests 2017-08-25 03:40:56 -07:00
snipe
021e723acf Fixed typo 2017-08-25 03:27:41 -07:00
snipe
14c0c314aa Make sure payload is always passed, even if null 2017-08-25 03:27:31 -07:00
snipe
d23ea70b08 Added auth check back to asset store 2017-08-25 03:26:50 -07:00
snipe
1b047c768b Added fullName() presenter for locations 2017-08-25 03:26:10 -07:00
snipe
e6323e0a1b Version bump for beta 2 2017-08-24 23:32:33 -07:00
snipe
73ce5f98bb Removed some logging to make test debugging less verbose 2017-08-24 23:20:51 -07:00
Brady Wetherington
a37cb616eb Add Error DIV's to all modals (#3886) 2017-08-24 22:43:05 -07:00
snipe
659d953f3f Fixed custom error message for status labels 2017-08-24 22:40:07 -07:00
Brady Wetherington
c1dcc22217 Refactor and improve Modal support for Assets (#3884) 2017-08-24 22:24:02 -07:00
snipe
6a67426140 Create travis user? I don’t know wqhy it’s still looking for a travis user 2017-08-24 17:21:50 -07:00
snipe
4ba474cf73 Fixes asset test 2017-08-24 16:52:27 -07:00
snipe
fb6caa35ff Only increment if settings table has a value
(This should only ever come up in the CI tests)
2017-08-23 14:07:01 -07:00
snipe
a5870c888e Removed incrementer from non-asset event listeners 2017-08-23 13:59:59 -07:00
snipe
f35f8477d3 Maybe the travis user isn’t created automatically anymore? 2017-08-23 13:42:17 -07:00
snipe
d0637d38f3 More travis fixes 2017-08-23 13:32:30 -07:00
snipe
7141968d64 Trying to fix travis. Again. Always. 2017-08-23 13:22:01 -07:00
snipe
0f7b7d8e6a Add @zwerch as a contributor 2017-08-23 13:13:50 -07:00
snipe
ca78b3ed7c Fixes models on create asset 2017-08-23 13:08:42 -07:00
snipe
2d2cae10b9 Fixed wonky “maintained” badge 2017-08-23 12:07:00 -07:00
snipe
5e9331f5ae Fixed typo. (English is hard. Let’s go shopping!) 2017-08-23 04:00:10 -07:00
snipe
6e30fa6922 Fixes custom fields in asset listing where no custom fields were assigned 2017-08-23 03:28:13 -07:00
snipe
58b3f0519d Add empty errors array 2017-08-23 00:31:37 -07:00
snipe
f119c69698 Possible fix for #3852 2017-08-22 22:46:02 -07:00
snipe
57f4c986af Enforce password complexity rules on new account password change 2017-08-22 22:41:35 -07:00
snipe
2958630923 Fixed some settings text 2017-08-22 22:11:26 -07:00
snipe
72dacda4f9 Trying again to resolve doctrine/php7 issue 2017-08-22 21:58:42 -07:00
snipe
9c2b986bb0 Fixes doctrine/etc compatibility issue
See: https://github.com/laravel/framework/issues/20490
2017-08-22 21:51:50 -07:00
snipe
06c5bce3c7 Fixes #3865 - employee number mislabeled in list output 2017-08-22 21:40:35 -07:00
snipe
a0cbca85bf Fixes for API calls for password complexity stuff 2017-08-22 21:15:35 -07:00
snipe
9bda62d295 ADDED: Password rules for complexity, min length, rejecting common passwords 2017-08-22 20:32:39 -07:00
snipe
1d7e243d0a Fixes #3790 - parent display on locations index 2017-08-22 15:02:31 -07:00
snipe
63bc2ec09f Fixes #3802 - make id an (int) in API repsonse 2017-08-22 14:53:48 -07:00
snipe
d5cadeab1a Fixes #3854 - more fillable fields for supplier API 2017-08-22 14:30:39 -07:00
snipe
31516abef9 Fixes #3858 - use transformer for single listing categories 2017-08-22 14:26:08 -07:00
snipe
d2535582f3 Fixes #3853 - added notes/zip to suppliers API response 2017-08-22 14:18:21 -07:00
snipe
eaaea303f4 Fixes #3860 - return JSON instead of redirect in API delete dept call 2017-08-22 14:15:13 -07:00
snipe
8c5312b931 Fixes #3866 - weird API behavior in status label types 2017-08-22 14:10:54 -07:00
snipe
4ef6e292d1 Fixes #3868 - model_number/notes in models API call 2017-08-22 13:56:51 -07:00
snipe
6310670835 Fixes #3869 - asset maintenances API endpoints 2017-08-22 13:52:06 -07:00
snipe
15bb30acd6 Fixes #3873 - show groups correctly on user listing page 2017-08-22 12:29:46 -07:00
snipe
148d41d8dc Removes erroneous else 2017-08-22 12:22:32 -07:00
snipe
71c1c74164 Fixes #3085 - adds “change password” functionality back to user accounts 2017-08-22 12:09:04 -07:00
snipe
9c02526a37 Make fields nullable in settings 2017-08-22 10:41:59 -07:00
snipe
25dc26aac3 Added 30 as page selector value for label printing 2017-08-21 22:31:37 -07:00
snipe
afc763ebac Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-08-21 22:31:04 -07:00
snipe
6a73ec6537 First stab at a recrypter for legacy mcrypt conversion 2017-08-21 22:30:56 -07:00
Ryan McGuire
481143b891 Fixes for Red Hat Enterprise Linux 7 and CentOS 6 (#3846)
* Changes for Red Hat Enterprise Linux.

RHEL uses "rhel" in the ID field in /etc/os-release. We'll leave
"redhat" in the script just in case.

Also, RHEL uses a two digit version number in the VERSION_ID field. So
instead of looking for just "7", look for anything that starts with "7".
This should fix RHEL installations as well as not break anything
with CentOS.

* Fixes issue #3079.

"lsb_release -s -r" returns a two digit version number (at least on recent CentOS releases) while the script is looking for a single digit version. We'll change the script so that it only looks for the version starting with 6. This fixes recent releases of CentOS, while also not breaking previous versions that may have used a single digit.
2017-08-18 15:12:09 -07:00
Brady Wetherington
cef67695cd New Dockerfile fixes to add support for the new barcode library (#3856) 2017-08-16 13:10:25 -07:00
snipe
4576cb6f56 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-08-10 14:38:08 -07:00
snipe
56f88d2c22 Fixes #3836 - Adds supplier to licenses column 2017-08-10 14:38:04 -07:00
gibsonjoshua55
c1d1cb8122 Address #3840 and fixes group transformation in UsersTransformer (#3841)
Removes the incorrect variable access in UsersTransformer of a users's
groups and adds an array of groups' ids and names to the return array.
2017-08-10 13:37:54 -07:00
Daniel Meltzer
54279f22a3 Update DB to fix tests. 2017-06-12 18:24:20 -05:00
Daniel Meltzer
dfea47a272 Fix location view display. Migrate to api controller methods and fix missing bits to make this happen. Show manager on the location view page. 2017-06-12 18:24:20 -05:00
Daniel Meltzer
f0d78091d2 Add a manager field to locations.
This is round one of the rethink of checkout-to-everything.  A location
now has a manager field, and the manager (by default) be responsible for
assets checked out to the location.
2017-06-12 18:23:50 -05:00
523 changed files with 7240 additions and 2512 deletions

View File

@@ -719,6 +719,15 @@
"contributions": [
"code"
]
},
{
"login": "zwerch",
"name": "Robin Temme",
"avatar_url": "https://avatars2.githubusercontent.com/u/2809241?v=4",
"profile": "https://github.com/zwerch",
"contributions": [
"code"
]
}
]
}

View File

@@ -15,7 +15,7 @@ FILESYSTEM_DISK=local
DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=snipeit_unit
DB_USERNAME=travis
DB_USERNAME=root
DB_PASSWORD=null
# --------------------------------------------

View File

@@ -6,6 +6,9 @@ sudo: false
# see http://about.travis-ci.org/docs/user/languages/php/ for more hints
language: php
services:
- mysql
# list any PHP version you want to test against
php:
- 5.6
@@ -15,7 +18,10 @@ php:
before_script:
- phantomjs --webdriver=4444 &
- sleep 4
- mysql -e "create database IF NOT EXISTS snipeit_unit;" -utravis
- mysql -e 'CREATE DATABASE snipeit_unit;'
- mysql -e 'CREATE USER "travis'@'localhost";'
- mysql -e 'GRANT ALL PRIVILEGES ON * . * TO "travis'@'localhost";'
- mysql -e 'FLUSH PRIVILEGES;'
- composer self-update
- composer install -n --prefer-source
- chmod -R 777 storage

View File

@@ -13,6 +13,7 @@ php7.0-gd \
php7.0-xml \
php7.0-mbstring \
php7.0-zip \
php7.0-bcmath \
patch \
curl \
vim \
@@ -23,6 +24,7 @@ mysql-client \
RUN phpenmod mcrypt
RUN phpenmod gd
RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.0/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.0/cli/php.ini

View File

@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=develop)](https://travis-ci.org/snipe/snipe-it) [![Stories in Ready](https://badge.waffle.io/snipe/snipe-it.png?label=ready+for+dev&title=Ready+for+development)](http://waffle.io/snipe/snipe-it) [![Maintenance](https://img.shields.io/maintenance/yes/2016.svg)]() [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.png)](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/snipeyhead.svg?style=social)](https://twitter.com/snipeyhead) [![Zenhub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) [![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-77-orange.svg?style=flat-square)](#contributors)
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=develop)](https://travis-ci.org/snipe/snipe-it) [![Stories in Ready](https://badge.waffle.io/snipe/snipe-it.png?label=ready+for+dev&title=Ready+for+development)](http://waffle.io/snipe/snipe-it) [![Maintenance](https://img.shields.io/maintenance/yes/2017.svg)]() [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.png)](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/snipeyhead.svg?style=social)](https://twitter.com/snipeyhead) [![Zenhub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) [![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-78-orange.svg?style=flat-square)](#contributors)
## Snipe-IT - Open Source Asset Management System
@@ -67,6 +67,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars0.githubusercontent.com/u/8341172?v=3" width="110px;"/><br /><sub>Jay Richards</sub>](http://www.cordeos.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [<img src="https://avatars2.githubusercontent.com/u/7295127?v=3" width="110px;"/><br /><sub>Alexander Innes</sub>](https://necurity.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [<img src="https://avatars2.githubusercontent.com/u/334485?v=3" width="110px;"/><br /><sub>Danny Garcia</sub>](https://buzzedword.codes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [<img src="https://avatars2.githubusercontent.com/u/366855?v=3" width="110px;"/><br /><sub>archpoint</sub>](https://github.com/archpoint)<br />[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [<img src="https://avatars1.githubusercontent.com/u/67991?v=3" width="110px;"/><br /><sub>Jake McGraw</sub>](http://www.jakemcgraw.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [<img src="https://avatars1.githubusercontent.com/u/1714374?v=3" width="110px;"/><br /><sub>FleischKarussel</sub>](https://github.com/FleischKarussel)<br />[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/319644?v=3" width="110px;"/><br /><sub>Dylan Yi</sub>](https://github.com/feeva)<br />[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/857740?v=3" width="110px;"/><br /><sub>Gil Rutkowski</sub>](http://FlashingCursor.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [<img src="https://avatars3.githubusercontent.com/u/129360?v=3" width="110px;"/><br /><sub>Desmond Morris</sub>](http://www.desmondmorris.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [<img src="https://avatars2.githubusercontent.com/u/52936?v=3" width="110px;"/><br /><sub>Nick Peelman</sub>](http://peelman.us)<br />[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [<img src="https://avatars0.githubusercontent.com/u/53161?v=3" width="110px;"/><br /><sub>Abraham Vegh</sub>](https://abrahamvegh.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [<img src="https://avatars0.githubusercontent.com/u/2818680?v=3" width="110px;"/><br /><sub>Mohamed Rashid</sub>](https://github.com/rashivkp)<br />[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1509456?v=3" width="110px;"/><br /><sub>Kasey</sub>](http://hinchk.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [<img src="https://avatars2.githubusercontent.com/u/10522541?v=3" width="110px;"/><br /><sub>Brett</sub>](https://github.com/BrettFagerlund)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "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

@@ -0,0 +1,151 @@
<?php
namespace App\Console\Commands;
use App\Models\CustomField;
use Illuminate\Console\Command;
use App\LegacyEncrypter\McryptEncrypter;
use App\Models\Setting;
use App\Models\Asset;
use Illuminate\Support\Facades\Storage;
class RecryptFromMcrypt extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:legacy-recrypt';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command allows upgrading users to de-encrypt their deprecated mcrypt encrypted fields and re-encrypt them using the current OpenSSL encryption.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Check and see if they have a legacy app key listed in their .env
// If not, we can try to use the current APP_KEY if looks like it's old
$legacy_key = env('LEGACY_APP_KEY');
$errors = array();
if (!$legacy_key) {
$this->error('ERROR: You do not have a LEGACY_APP_KEY set in your .env file. Please locate your old APP_KEY and ADD a line to your .env file like: LEGACY_APP_KEY=YOUR_OLD_APP_KEY');
return false;
}
// Check that the app key is 32 characters
if (strlen($legacy_key) == 32) {
$this->comment('INFO: Your LEGACY_APP_KEY is 32 characters. Okay to continue.');
} else {
$this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.');
return false;
}
$this->error('================================!!!! WARNING !!!!================================');
$this->error('================================!!!! WARNING !!!!================================');
$this->comment("This tool will attempt to decrypt your old Snipe-IT (mcrypt, now deprecated) encrypted data and re-encrypt it using OpenSSL. \n\nYou should only continue if you have backed up any and all old APP_KEYs and have backed up your data.");
if ($this->confirm("Are you SURE you wish to continue?")) {
$backup_file = 'backups/env-backups/'.'app_key-'.date('Y-m-d-gis');
try {
Storage::disk('local')->put($backup_file, 'APP_KEY: '.config('app.key'));
Storage::disk('local')->append($backup_file, 'LEGACY_APP_KEY: '.$legacy_key);
} catch (\Exception $e) {
$this->info('WARNING: Could not backup app keys');
}
$mcrypter = new McryptEncrypter($legacy_key);
$settings = Setting::getSettings();
if ($settings->ldap_password=='') {
$this->comment('INFO: No LDAP password found. Skipping... ');
}
$custom_fields = CustomField::where('field_encrypted','=', 1)->get();
$this->comment('INFO: Retrieving encrypted custom fields...');
$query = Asset::withTrashed();
foreach ($custom_fields as $custom_field) {
$this->comment('FIELD TO RECRYPT: '.$custom_field->name .' ('.$custom_field->db_column.')');
$query->orWhereNotNull($custom_field->db_column);
}
// Get all assets with a value in any of the fields that were encrypted
$assets = $query->get();
$bar = $this->output->createProgressBar(count($assets));
foreach ($custom_fields as $encrypted_field) {
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($encrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
}
$bar->advance();
}
foreach ($assets as $asset) {
foreach ($custom_fields as $encrypted_field) {
// Make sure the value isn't null
if ($asset->{$encrypted_field}!='') {
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($asset->{$encrypted_field});
$asset->{$encrypted_field} = \Crypt::encrypt($decrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
}
}
}
$asset->save();
$bar->advance();
}
$bar->finish();
if (count($errors) > 0) {
$this->comment("\n\n");
$this->error("The decrypter encountered some errors: \n");
foreach ($errors as $error) {
$this->error($error);
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use Illuminate\Console\Command;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
class SendExpectedCheckinAlerts extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'snipeit:expected-checkin';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check for overdue or upcoming expected checkins.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
foreach ($assets as $asset) {
if ($asset->assignedTo && $asset->checkoutOutToUser()) {
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
//$this->info($asset);
}
}
}
}

View File

@@ -17,13 +17,15 @@ class Kernel extends ConsoleKernel
Commands\CreateAdmin::class,
Commands\SendExpirationAlerts::class,
Commands\SendInventoryAlerts::class,
Commands\SendExpectedCheckinAlerts::class,
Commands\ObjectImportCommand::class,
Commands\Versioning::class,
Commands\SystemBackup::class,
Commands\DisableLDAP::class,
Commands\Purge::class,
Commands\LdapSync::class,
Commands\FixDoubleEscape::class
Commands\FixDoubleEscape::class,
Commands\RecryptFromMcrypt::class
];
/**
@@ -37,6 +39,7 @@ class Kernel extends ConsoleKernel
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkins')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Exceptions;
use Exception;
class CheckoutNotAllowed extends Exception
{
public function __toString()
{
"A checkout is not allowed under these circumstances";
}
}

View File

@@ -80,7 +80,7 @@ class Handler extends ExceptionHandler
}
}
// Try to parse 500 Errors ina bit nicer way when debug is enabled.
// 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);
}

View File

@@ -683,12 +683,11 @@ class Helper
public static function formatStandardApiResponse($status, $payload = null, $messages = null) {
$array['status'] = $status;
($payload) ? $array['payload'] = $payload : '';
$array['messages'] = $messages;
if (($messages) && (count($messages) > 0)) {
$array['messages'] = $messages;
}
($payload) ? $array['payload'] = $payload : $array['payload'] = null;
return $array;
}

View File

@@ -260,7 +260,7 @@ class AccessoriesController extends Controller
'assigned_to' => $request->get('assigned_to')
]);
$logaction = $accessory->logCheckout(e(Input::get('note')));
$logaction = $accessory->logCheckout(e(Input::get('note')), $user);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();

View File

@@ -0,0 +1,227 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetMaintenancesTransformer;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Company;
use Auth;
use Carbon\Carbon;
use Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Input;
/**
* This controller handles all actions related to Asset Maintenance for
* the Snipe-IT Asset Management application.
*
* @version v2.0
*/
class AssetMaintenancesController extends Controller
{
/**
* Generates the JSON response for asset maintenances listing view.
*
* @see AssetMaintenancesController::getIndex() method that generates view
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return String JSON
*/
public function index(Request $request)
{
$maintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company', 'admin');
if (Input::has('search')) {
$maintenances = $maintenances->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','title','asset_maintenance_time','asset_maintenance_type','cost','start_date','completion_date','notes','user_id'];
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
case 'user_id':
$maintenances = $maintenances->OrderAdmin($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
}
$maintenances = $maintenances->skip($offset)->take($limit)->get();
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $maintenances->count());
}
/**
* Validates and stores the new asset maintenance
*
* @see AssetMaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return String JSON
*/
public function store(Request $request)
{
// create a new model instance
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = e($request->input('cost'));
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(e($request->input('asset_id')));
if (!Company::isCurrentUserHasAccess($asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot add a maintenance for that asset'));
}
// Save the asset maintenance data
$assetMaintenance->asset_id = $request->input('asset_id');
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->user_id = Auth::id();
if (( $assetMaintenance->completion_date !== null )
&& ( $assetMaintenance->start_date !== "" )
&& ( $assetMaintenance->start_date !== "0000-00-00" )
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
}
/**
* Validates and stores an update to an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $assetMaintenanceId
* @param int $request
* @version v1.0
* @since [v4.0]
* @return String JSON
*/
public function update(Request $request, $assetMaintenanceId = null)
{
// Check if the asset maintenance exists
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
if (!Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot edit a maintenance for that asset'));
}
$assetMaintenance->supplier_id = e($request->input('supplier_id'));
$assetMaintenance->is_warranty = e($request->input('is_warranty'));
$assetMaintenance->cost = Helper::ParseFloat(e($request->input('cost')));
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(request('asset_id'));
if (!Company::isCurrentUserHasAccess($asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot edit a maintenance for that asset'));
}
// Save the asset maintenance data
$assetMaintenance->asset_id = $request->input('asset_id');
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
if (( $assetMaintenance->completion_date == null )
) {
if (( $assetMaintenance->asset_maintenance_time !== 0 )
|| ( !is_null($assetMaintenance->asset_maintenance_time) )
) {
$assetMaintenance->asset_maintenance_time = null;
}
}
if (( $assetMaintenance->completion_date !== null )
&& ( $assetMaintenance->start_date !== "" )
&& ( $assetMaintenance->start_date !== "0000-00-00" )
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.edit.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
}
/**
* Delete an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
* @return String JSON
*/
public function destroy($assetMaintenanceId)
{
// Check if the asset maintenance exists
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
if (!Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset'));
}
$assetMaintenance->delete();
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success')));
}
/**
* View an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v4.0]
* @return String JSON
*/
public function show($assetMaintenanceId)
{
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
if (!Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
}
return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance);
}
}

View File

@@ -76,7 +76,7 @@ class AssetModelsController extends Controller
$assetmodel->fill($request->all());
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/assetmodels/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));

View File

@@ -31,6 +31,7 @@ use TCPDF;
use Validator;
use View;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@@ -83,9 +84,8 @@ class AssetsController extends Controller
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'))->with(
'assetLoc', 'assetstatus', 'defaultLoc', 'assetlog', 'company',
'model.category', 'model.manufacturer', 'model.fieldset', 'assigneduser','supplier');
'assetloc', 'assetstatus', 'defaultLoc', 'assetlog', 'company',
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
// If we should search on everything
if (($request->has('search')) && (count($filter) == 0)) {
$assets->TextSearch($request->input('search'));
@@ -96,7 +96,6 @@ class AssetsController extends Controller
}
}
// These are used by the API to query against specific ID numbers
if ($request->has('status_id')) {
$assets->where('status_id', '=', $request->input('status_id'));
@@ -231,7 +230,8 @@ class AssetsController extends Controller
*/
public function store(AssetRequest $request)
{
// $this->authorize('create', Asset::class);
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
@@ -279,6 +279,7 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
@@ -493,5 +494,52 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
}
/**
* Mark an asset as audited
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
*/
public function audit(Request $request) {
$this->authorize('audit', Asset::class);
$rules = array(
'asset_tag' => 'required',
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable'
);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::where('asset_tag','=', $request->input('asset_tag'))->first();
if ($asset) {
$asset->next_audit_date = $request->input('next_audit_date');
if ($asset->save()) {
$log = $asset->logAudit(request('note'),request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag),
'note'=> e($request->input('note')),
'next_audit_date' => Helper::getFormattedDateObject($log->calcNextAuditDate())
], trans('admin/hardware/message.audit.success')));
}
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found'));
}
}

View File

@@ -22,7 +22,7 @@ class CategoriesController extends Controller
$this->authorize('view', Category::class);
$allowed_columns = ['id', 'name','category_type','use_default_eula','require_acceptance','checkin_email'];
$categories = Category::select(['id', 'name','category_type','use_default_eula','require_acceptance','checkin_email'])
$categories = Category::select(['id', 'created_at', 'updated_at', 'name','category_type','use_default_eula','require_acceptance','checkin_email'])
->withCount('assets', 'accessories', 'consumables', 'components');
if ($request->has('search')) {
@@ -75,7 +75,8 @@ class CategoriesController extends Controller
{
$this->authorize('view', Category::class);
$category = Category::findOrFail($id);
return $category;
return (new CategoriesTransformer)->transformCategory($category);
}

View File

@@ -148,4 +148,47 @@ class ConsumablesController extends Controller
$consumable->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.delete.success')));
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
*/
public function getDataView($consumableId)
{
//$consumable = Consumable::find($consumableID);
$consumable = Consumable::with(array('consumableAssignments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
},
'consumableAssignments.admin'=> function ($query) {
},
'consumableAssignments.user'=> function ($query) {
},
))->find($consumableId);
// $consumable->load('consumableAssignments.admin','consumableAssignments.user');
if (!Company::isCurrentUserHasAccess($consumable)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', Component::class);
$rows = array();
foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [
'name' => $consumable_assignment->user->present()->nameUrl(),
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}
$consumableCount = $consumable->users->count();
$data = array('total' => $consumableCount, 'rows' => $rows);
return $data;
}
}

View File

@@ -99,16 +99,14 @@ class DepartmentsController extends Controller
*/
public function destroy($id)
{
if (is_null($department = Department::find($id))) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found'));
}
$department = Department::findOrFail($id);
if ($department->users->count() > 0) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.assoc_users'));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/departments/message.assoc_users')));
}
$department->delete();
return redirect()->to(route('departments.index'))->with('success', trans('admin/departments/message.delete.success'));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/departments/message.delete.success')));
}

View File

@@ -21,7 +21,7 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer'));
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer', 'supplier'));
if ($request->has('search')) {
$licenses = $licenses->TextSearch($request->input('search'));
@@ -59,6 +59,10 @@ class LicensesController extends Controller
$licenses->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
if ($request->has('supplier_id')) {
$licenses->where('supplier_id','=',$request->input('supplier_id'));
}
if ($request->has('depreciation_id')) {
$licenses->where('depreciation_id','=',$request->input('depreciation_id'));
}
@@ -69,22 +73,26 @@ class LicensesController extends Controller
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','manufacturer','company','license_name','license_email'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
switch ($request->input('sort')) {
case 'manufacturer':
$licenses = $licenses->OrderManufacturer($order);
break;
case 'supplier':
$licenses = $licenses->OrderSupplier($order);
break;
case 'company':
$licenses = $licenses->OrderCompany($order);
break;
default:
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','license_name','license_email'];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$licenses = $licenses->orderBy($sort, $order);
break;
}
$total = $licenses->count();
$licenses = $licenses->skip($offset)->take($limit)->get();

View File

@@ -21,7 +21,7 @@ class LocationsController extends Controller
{
$this->authorize('view', Location::class);
$allowed_columns = ['id','name','address','address2','city','state','country','zip','created_at',
'updated_at','parent_id'];
'updated_at','parent_id', 'manager_id'];
$locations = Location::select([
'locations.id',
@@ -33,6 +33,7 @@ class LocationsController extends Controller
'locations.zip',
'locations.country',
'locations.parent_id',
'locations.manager_id',
'locations.created_at',
'locations.updated_at',
'locations.currency'

View File

@@ -19,7 +19,7 @@ class ReportsController extends Controller
public function index(Request $request)
{
$actionlogs = Actionlog::with('item', 'user', 'target');
$actionlogs = Actionlog::with('item', 'user', 'target','location');
if ($request->has('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
@@ -36,6 +36,10 @@ class ReportsController extends Controller
->where('item_type','=',"App\\Models\\".ucwords($request->input('item_type')));
}
if ($request->has('action_type')) {
$actionlogs = $actionlogs->where('action_type','=',$request->input('action_type'))->orderBy('created_at', 'desc');
}
$allowed_columns = [
'id',
'created_at'

View File

@@ -53,9 +53,20 @@ class StatuslabelsController extends Controller
public function store(Request $request)
{
$this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending','archived');
if (!$request->has('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, ["type" => ["Status label type is required."]]));
}
$statuslabel = new Statuslabel;
$statuslabel->fill($request->all());
$statusType = Statuslabel::getStatuslabelTypesForDB($request->input('type'));
$statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived'];
if ($statuslabel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.create.success')));
}
@@ -75,7 +86,7 @@ class StatuslabelsController extends Controller
{
$this->authorize('view', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
return $statuslabel;
return (new StatuslabelsTransformer)->transformStatuslabel($statuslabel);
}
@@ -92,8 +103,20 @@ class StatuslabelsController extends Controller
{
$this->authorize('edit', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
$request->except('deployable', 'pending','archived');
if (!$request->has('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Status label type is required.'));
}
$statuslabel->fill($request->all());
$statusType = Statuslabel::getStatuslabelTypesForDB($request->input('type'));
$statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived'];
if ($statuslabel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.update.success')));
}

View File

@@ -8,6 +8,8 @@ use App\Http\Transformers\UsersTransformer;
use App\Models\Company;
use App\Models\User;
use App\Helpers\Helper;
use App\Http\Requests\SaveUserRequest;
use App\Models\Asset;
class UsersController extends Controller
{
@@ -102,7 +104,7 @@ class UsersController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
public function store(SaveUserRequest $request)
{
$this->authorize('view', User::class);
$user = new User;
@@ -139,7 +141,7 @@ class UsersController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
public function update(SaveUserRequest $request, $id)
{
$this->authorize('edit', User::class);
$user = User::findOrFail($id);
@@ -181,4 +183,19 @@ class UsersController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
}
/**
* Return JSON containing a list of assets assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param $userId
* @return string JSON
*/
public function assets($id)
{
$this->authorize('view', User::class);
$assets = Asset::where('assigned_to', '=', $id)->with('model')->get();
return response()->json($assets);
}
}

View File

@@ -414,7 +414,7 @@ class AssetModelsController extends Controller
$manufacturer_list = $nochange + Helper::manufacturerList();
return view('models/bulk-edit', compact('models'))
return view('models/bulk-edit', compact('models'))
->with('manufacturer_list', $manufacturer_list)
->with('category_list', $category_list)
->with('fieldset_list', $fieldset_list)

View File

@@ -116,9 +116,9 @@ class AssetsController extends Controller
->with('statuslabel_list', Helper::statusLabelList())
->with('location_list', Helper::locationsList())
->with('item', new Asset)
->with('manufacturer', Helper::manufacturerList())
->with('category', Helper::categoryList('asset'))
->with('statuslabel_types', Helper::statusTypeList())
->with('manufacturer', Helper::manufacturerList()) //handled in modal now?
->with('category', Helper::categoryList('asset')) //handled in modal now?
->with('statuslabel_types', Helper::statusTypeList()) //handled in modal now?
->with('users_list', Helper::usersList())
->with('assets_list', Helper::assetsList())
->with('locations_list', Helper::locationsList());
@@ -537,17 +537,18 @@ class AssetsController extends Controller
$this->authorize('checkin', $asset);
$admin = Auth::user();
$user = $asset->assignedUser;
if($asset->assignedType() == Asset::USER) {
$user = $asset->assignedTo;
}
if (is_null($target = $asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
// This is just used for the redirect
$return_to = $asset->assigned_to;
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
$asset->accepted = null;
$asset->name = e(Input::get('name'));
@@ -566,7 +567,7 @@ class AssetsController extends Controller
$data['item_serial'] = $asset->serial;
$data['note'] = $logaction->note;
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!config('app.lock_passwords'))) {
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!empty($user->email)) && (!config('app.lock_passwords'))) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
@@ -575,7 +576,7 @@ class AssetsController extends Controller
}
if ($backto=='user') {
return redirect()->to("admin/users/".$return_to.'/view')->with('success', trans('admin/hardware/message.checkin.success'));
return redirect()->to("admin/users/".$user->id.'/view')->with('success', trans('admin/hardware/message.checkin.success'));
}
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success'));
}
@@ -595,9 +596,12 @@ class AssetsController extends Controller
*/
public function show($assetId = null)
{
$asset = Asset::withTrashed()->find($assetId);
$settings = Setting::getSettings();
$this->authorize('view', $asset);
$settings = Setting::getSettings();
$audit_log = Actionlog::where('action_type','=','audit')->where('item_id','=',$assetId)->where('item_type','=',Asset::class)->orderBy('created_at','DESC')->first();
if (isset($asset)) {
@@ -617,7 +621,8 @@ class AssetsController extends Controller
'url' => route('qr_code/hardware', $asset->id)
);
return view('hardware/view', compact('asset', 'qr_code', 'settings'))->with('use_currency', $use_currency);
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
->with('use_currency', $use_currency)->with('audit_log',$audit_log);
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist', compact('id')));
@@ -953,7 +958,7 @@ class AssetsController extends Controller
* @since [v1.0]
* @return View
*/
public function getDeleteFile($assetId = null, $fileId = null)
public function deleteFile($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
$this->authorize('update', $asset);
@@ -1233,4 +1238,47 @@ class AssetsController extends Controller
// Redirect to the asset management page with error
return redirect()->to("hardware/bulk-checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
}
public function quickScan(Request $request)
{
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths(12)->toDateString();
return view('hardware/quickscan')->with('next_audit_date', $dt)->with('locations_list', Helper::locationsList());
}
public function audit(Request $request, $id)
{
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths(12)->toDateString();
$asset = Asset::findOrFail($id);
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list', Helper::locationsList());
}
public function auditStore(Request $request, $id)
{
$this->authorize('audit', Asset::class);
$rules = array(
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable'
);
$validator = \Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::findOrFail($id);
$asset->next_audit_date = $request->input('next_audit_date');
if ($asset->save()) {
$asset->logAudit(request('note'),request('location_id'));
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
}
}
}

View File

@@ -283,7 +283,7 @@ class ComponentsController extends Controller
'asset_id' => $asset_id
]);
$component->logCheckout(e(Input::get('note')), $asset_id);
$component->logCheckout(e(Input::get('note')), $asset);
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
}

View File

@@ -250,7 +250,7 @@ class ConsumablesController extends Controller
'assigned_to' => e(Input::get('assigned_to'))
]);
$logaction = $consumable->logCheckout(e(Input::get('note')));
$logaction = $consumable->logCheckout(e(Input::get('note')), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
@@ -273,47 +273,4 @@ class ConsumablesController extends Controller
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
*/
public function getDataView($consumableId)
{
//$consumable = Consumable::find($consumableID);
$consumable = Consumable::with(array('consumableAssigments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
},
'consumableAssigments.admin'=> function ($query) {
},
'consumableAssigments.user'=> function ($query) {
},
))->find($consumableId);
// $consumable->load('consumableAssigments.admin','consumableAssigments.user');
if (!Company::isCurrentUserHasAccess($consumable)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', Component::class);
$rows = array();
foreach ($consumable->consumableAssigments as $consumable_assignment) {
$rows[] = [
'name' => $consumable_assignment->user->present()->nameUrl(),
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}
$consumableCount = $consumable->users->count();
$data = array('total' => $consumableCount, 'rows' => $rows);
return $data;
}
}

View File

@@ -291,22 +291,22 @@ class LicensesController extends Controller
// Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator);
}
$target = null;
if ($assigned_to!='') {
// Check if the user exists
if (is_null($is_assigned_to = User::find($assigned_to))) {
if (is_null($target = User::find($assigned_to))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.user_does_not_exist'));
}
}
if ($asset_id!='') {
if (is_null($asset = Asset::find($asset_id))) {
if (is_null($target = Asset::find($asset_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.asset_does_not_exist'));
}
if (($asset->assigned_to!='') && (($asset->assigned_to!=$assigned_to)) && ($assigned_to!='')) {
if (($target->assigned_to!='') && (($target->assigned_to!=$assigned_to)) && ($target!='')) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.owner_doesnt_match_asset'));
}
}
@@ -332,7 +332,7 @@ class LicensesController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckout($request->input('note'));
$licenseSeat->logCheckout($request->input('note'), $target);
$data['license_id'] =$licenseSeat->license_id;
$data['note'] = $request->input('note');

View File

@@ -63,7 +63,8 @@ class LocationsController extends Controller
return view('locations/edit')
->with('location_options', $location_options)
->with('item', new Location);
->with('item', new Location)
->with('manager_list', Helper::managerList());
}
@@ -88,6 +89,7 @@ class LocationsController extends Controller
$location->state = Input::get('state');
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->manager_id = Input::get('manager_id');
$location->user_id = Auth::id();
if ($location->save()) {
@@ -154,7 +156,10 @@ class LocationsController extends Controller
$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'))
->with('location_options', $location_options)
->with('manager_list', Helper::managerList());
}
@@ -185,6 +190,7 @@ class LocationsController extends Controller
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->ldap_ou = Input::get('ldap_ou');
$location->manager_id = Input::get('manager_id');
// Was the location updated?
if ($location->save()) {
@@ -232,8 +238,6 @@ class LocationsController extends Controller
* the content for the locations detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getDataViewUsers() method that returns JSON for location users
* @see LocationsController::getDataViewAssets() method that returns JSON for location assets
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
@@ -252,78 +256,4 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', $error);
}
/**
* Returns a JSON response that contains the users association with the
* selected location, to be used by the location detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getView() method that creates the display view
* @param $locationID
* @return array
* @internal param int $locationId
* @since [v1.8]
*/
public function getDataViewUsers($locationID)
{
$location = Location::find($locationID);
$users = User::where('location_id', '=', $location->id);
if (Input::has('search')) {
$users = $users->TextSearch(e(Input::get('search')));
}
$users = $users->get();
$rows = array();
foreach ($users as $user) {
$rows[] = array(
'name' => (string)link_to_route('users.show', e($user->present()->fullName()), ['user'=>$user->id])
);
}
$data = array('total' => $users->count(), 'rows' => $rows);
return $data;
}
/**
* Returns a JSON response that contains the assets association with the
* selected location, to be used by the location detail view.
*
* @todo This is broken for accessories and consumables.
* @todo This is a very naive implementation. Should clean this up with query scopes.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getView() method that creates the display view
* @param int $locationID
* @since [v1.8]
* @return array
*/
public function getDataViewAssets($locationID)
{
$location = Location::find($locationID)->load('assignedassets.model');
$assets = Asset::AssetsByLocation($location);
if (Input::has('search')) {
$assets = $assets->TextSearch(e(Input::get('search')));
}
$assets = $assets->get();
$rows = array();
foreach ($assets as $asset) {
$rows[] = [
'name' => (string)link_to_route('hardware.show', e($asset->present()->name()), ['hardware' => $asset->id]),
'asset_tag' => e($asset->asset_tag),
'serial' => e($asset->serial),
'model' => e($asset->model->name),
];
}
$data = array('total' => $assets->count(), 'rows' => $rows);
return $data;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Helpers\Helper;
class ModalController extends Controller
{
function location() {
return view('modals.location');
}
function model() {
return view('modals.model')
->with('manufacturer', Helper::manufacturerList())
->with('category', Helper::categoryList('asset'));
}
function statuslabel() {
return view('modals.statuslabel')->with('statuslabel_types', Helper::statusTypeList());
}
function supplier() {
return view('modals.supplier');
}
function user() {
return view('modals.user');
}
}

View File

@@ -4,12 +4,13 @@ namespace App\Http\Controllers;
use Image;
use Input;
use Redirect;
use App\Models\Location;
use View;
use Auth;
use App\Helpers\Helper;
use App\Models\Setting;
use Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
/**
* This controller handles all actions related to User Profiles for
@@ -87,4 +88,60 @@ class ProfileController extends Controller
public function api() {
return view('account/api');
}
/**
* User change email page.
*
* @return View
*/
public function password()
{
$user = Auth::user();
return view('account/change-password', compact('user'));
}
/**
* Users change password form processing page.
*
* @return Redirect
*/
public function passwordSave(Request $request)
{
if (config('app.lock_passwords')) {
return redirect()->route('account.password.index')->with('error', Lang::get('admin/users/table.lock_passwords'));
}
$user = Auth::user();
if ($user->ldap_import=='1') {
return redirect()->route('account.password.index')->with('error', Lang::get('admin/users/message.error.password_ldap'));
}
$rules = array(
'current_password' => 'required',
'password' => Setting::passwordComplexityRulesSaving('store'),
'password_confirm' => 'required|same:password',
);
$validator = \Validator::make($request->all(), $rules);
$validator->after(function($validator) use ($request, $user) {
if (!Hash::check($request->input('current_password'), $user->password)) {
$validator->errors()->add('current_password', trans('validation.hashed_pass'));
}
});
if (!$validator->fails()) {
$user->password = Hash::make($request->input('password'));
$user->save();
return redirect()->route('account.password.index')->with('success', 'Password updated!');
}
return redirect()->back()->withInput()->withErrors($validator);
}
}

View File

@@ -186,7 +186,7 @@ class ReportsController extends Controller
{
// Grab all the assets
$assets = Asset::with('model', 'assignedTo', 'assetstatus', 'defaultLoc', 'assetlog', 'company')
$assets = Asset::with( 'assignedTo', 'assetstatus', 'defaultLoc', 'assetloc', 'assetlog', 'company', 'model.category', 'model.depreciation')
->orderBy('created_at', 'DESC')->get();
return view('reports/depreciation', compact('assets'));
@@ -271,6 +271,20 @@ class ReportsController extends Controller
}
/**
* Displays audit report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return View
*/
public function audit()
{
return view('reports/audit');
}
/**
* Displays activity report.
*
@@ -376,7 +390,7 @@ class ReportsController extends Controller
*/
public function postCustom()
{
$assets = Asset::orderBy('created_at', 'DESC')->with('company', 'assigneduser', 'assetloc', 'defaultLoc', 'assigneduser.userloc', 'model', 'supplier', 'assetstatus', 'model.manufacturer')->get();
$assets = Asset::orderBy('created_at', 'DESC')->with('company', 'assignedTo', 'assetloc', 'defaultLoc', 'model', 'supplier', 'assetstatus', 'model.manufacturer')->get();
$customfields = CustomField::get();
$rows = [ ];
@@ -538,8 +552,8 @@ class ReportsController extends Controller
if (e(Input::get('username')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->assigneduser) {
$row[] = '"' .e($asset->assigneduser->username). '"';
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->username). '"';
} else {
$row[] = ''; // Empty string if unassigned
}
@@ -547,8 +561,8 @@ class ReportsController extends Controller
if (e(Input::get('employee_num')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->assigneduser) {
$row[] = '"' .e($asset->assigneduser->employee_num). '"';
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->employee_num). '"';
} else {
$row[] = ''; // Empty string if unassigned
}

View File

@@ -20,6 +20,7 @@ use Auth;
use App\Models\User;
use App\Http\Requests\SetupUserRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsLdapRequest;
/**
* This controller handles all actions related to Settings for
@@ -184,6 +185,7 @@ class SettingsController extends Controller
$settings->site_name = e(Input::get('site_name'));
$settings->alert_email = e(Input::get('email'));
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
$settings->locale = 'en';
$settings->default_currency = 'USD';
@@ -259,6 +261,13 @@ class SettingsController extends Controller
Artisan::call('migrate', ['--force' => true]);
$output = Artisan::output();
if ((!file_exists(storage_path().'/oauth-private.key')) || (!file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('passport:install');
Artisan::call('migrate', ['--force' => true]);
}
return view('setup/migrate')
->with('output', $output)
->with('step', 2)
@@ -462,6 +471,15 @@ class SettingsController extends Controller
}
$setting->pwd_secure_uncommon = (int) $request->input('pwd_secure_uncommon');
$setting->pwd_secure_min = (int) $request->input('pwd_secure_min');
$setting->pwd_secure_complexity = '';
if ($request->has('pwd_secure_complexity')) {
$setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity'));
}
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -545,10 +563,12 @@ class SettingsController extends Controller
$alert_email = rtrim($request->input('alert_email'), ',');
$alert_email = trim($alert_email);
$setting->alert_email = e($alert_email);
$setting->alert_email = $alert_email;
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
$setting->audit_interval = $request->input('audit_interval');
$setting->audit_warning_days = $request->input('audit_warning_days');
if ($setting->save()) {
return redirect()->route('settings.index')

View File

@@ -12,9 +12,7 @@ use App\Models\Company;
use App\Models\Location;
use App\Models\License;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Http\Requests\SaveUserRequest;
use App\Http\Requests\UpdateUserRequest;
use Symfony\Component\HttpFoundation\StreamedResponse;
use App\Models\User;
use App\Models\Ldap;
@@ -23,7 +21,6 @@ use Config;
use Crypt;
use DB;
use HTML;
use Illuminate\Support\Facades\Log;
use Input;
use Lang;
use League\Csv\Reader;
@@ -169,7 +166,7 @@ class UsersController extends Controller
* @since [v1.8]
* @return string JSON
*/
public function apiStore(Request $request)
public function apiStore(SaveUserRequest $request)
{
$this->authorize('create', User::class);
@@ -270,7 +267,7 @@ class UsersController extends Controller
* @param int $id
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateUserRequest $request, $id = null)
public function update(SaveUserRequest $request, $id = null)
{
// We need to reverse the UI specific logic for our
// permissions here before we update the user.
@@ -309,14 +306,11 @@ class UsersController extends Controller
}
}
// Do we want to update the user password?
if ($request->has('password')) {
$user->password = bcrypt($request->input('password'));
}
if ($request->has('username')) {
$user->username = e($request->input('username'));
$user->username = $request->input('username');
}
$user->email = e($request->input('email'));
$user->email = $request->input('email');
// Update the user
@@ -334,6 +328,12 @@ class UsersController extends Controller
$user->notes = $request->input('notes');
$user->department_id = $request->input('department_id', null);
// Do we want to update the user password?
if ($request->has('password')) {
$user->password = bcrypt($request->input('password'));
}
// Strip out the superuser permission if the user isn't a superadmin
$permissions_array = $request->input('permission');
@@ -376,7 +376,6 @@ class UsersController extends Controller
}
if ($user->licenses()->count() > 0) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->licenses()->count() . ' licenses associated with them.');
}
@@ -386,6 +385,11 @@ class UsersController extends Controller
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->accessories()->count() . ' accessories associated with them.');
}
if ($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.');
}
// Delete the user
$user->delete();
@@ -1147,21 +1151,7 @@ class UsersController extends Controller
}
return redirect()->route('ldap/user')->with('success', "LDAP Import successful.")->with('summary', $summary);
}
/**
* Return JSON containing a list of assets assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param $userId
* @return string JSON
*/
public function getAssetList($userId)
{
$this->authorize('view', User::class);
$assets = Asset::where('assigned_to', '=', $userId)->with('model')->get();
return response()->json($assets);
}
/**
* Exports users to CSV

View File

@@ -68,8 +68,8 @@ class ViewAssetsController extends Controller
public function getRequestableIndex()
{
$assets = Asset::with('model', 'defaultLoc', 'assetloc', 'assignedTo')->Hardware()->RequestableAssets()->get();
$models = AssetModel::with('category')->RequestableModels()->get();
$assets = Asset::with('model', 'defaultLoc', 'assetloc', 'assignedTo', 'requests')->Hardware()->RequestableAssets()->get();
$models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get();
return view('account/requestable-assets', compact('user', 'assets', 'models'));
}

View File

@@ -3,6 +3,7 @@
namespace App\Http\Requests;
use App\Http\Requests\Request;
use App\Models\Setting;
class SaveUserRequest extends Request
{
@@ -23,12 +24,38 @@ class SaveUserRequest extends Request
*/
public function rules()
{
return [
'first_name' => 'required|string|min:1',
'email' => 'email',
'password' => 'required|min:6',
'password_confirm' => 'sometimes|required_with:password',
'username' => 'required|string|min:2|unique:users,username,NULL,deleted_at',
];
$rules = [];
switch($this->method())
{
// Brand new asset
case 'POST':
{
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('store');
}
// Save all fields
case 'PUT':
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
// Save only what's passed
case 'PATCH':
{
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
}
default:break;
}
$rules['password_confirm'] = 'sometimes|required_with:password';
return $rules;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Session;
class SettingsLdapRequest 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()
{
$rules = [
"ldap_server" => 'sometimes|required_if:ldap_enabled,1|url|nullable',
"ldap_uname" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_basedn" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_filter" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_username_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_fname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_lname_field" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_auth_filter_query" => 'sometimes|required_if:ldap_enabled,1|nullable',
"ldap_version" => 'sometimes|required_if:ldap_enabled,1|nullable',
];
return $rules;
}
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);
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class UpdateUserRequest 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 [
'first_name' => 'required|string|min:1',
'email' => 'email',
'password_confirm' => 'sometimes|required_with:password',
];
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Transformers;
use App\Models\Actionlog;
use App\Models\Setting;
use Gate;
use Illuminate\Database\Eloquent\Collection;
use App\Helpers\Helper;
@@ -12,23 +13,32 @@ class ActionlogsTransformer
public function transformActionlogs (Collection $actionlogs, $total)
{
$array = array();
$settings = Setting::getSettings();
foreach ($actionlogs as $actionlog) {
$array[] = self::transformActionlog($actionlog);
$array[] = self::transformActionlog($actionlog, $settings);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformActionlog (Actionlog $actionlog)
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$array = [
'id' => (int) $actionlog->id,
'icon' => $actionlog->present()->icon(),
'image' => (method_exists($actionlog->item, 'getImageUrl')) ? $actionlog->item->getImageUrl() : null,
'item' => ($actionlog->item) ? [
'id' => (int) $actionlog->item->id,
'name' => e($actionlog->item->getDisplayNameAttribute()),
'type' => e($actionlog->itemType()),
] : null,
'location' => ($actionlog->location) ? [
'id' => (int) $actionlog->location->id,
'name' => e($actionlog->location->name)
] : null,
'created_at' => Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($actionlog->updated_at, 'datetime'),
'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(), 'date'): null,
'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item),
'action_type' => $actionlog->present()->actionType(),
'admin' => ($actionlog->user) ? [
'id' => (int) $actionlog->user->id,

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Transformers;
use App\Models\AssetMaintenance;
use Gate;
use Illuminate\Database\Eloquent\Collection;
use App\Helpers\Helper;
class AssetMaintenancesTransformer
{
public function transformAssetMaintenances (Collection $assetmaintenances, $total)
{
$array = array();
foreach ($assetmaintenances as $assetmaintenance) {
$array[] = self::transformAssetMaintenance($assetmaintenance);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformAssetMaintenance (AssetMaintenance $assetmaintenance)
{
$array = [
'id' => (int) $assetmaintenance->id,
'asset_name' => ($assetmaintenance->asset) ? ['id' => $assetmaintenance->asset->id,'name'=> e($assetmaintenance->asset->name)] : null,
'title' => ($assetmaintenance->title) ? e($assetmaintenance->title) : null,
'notes' => ($assetmaintenance->notes) ? e($assetmaintenance->notes) : null,
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id,'name'=> e($assetmaintenance->supplier->name)] : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'datetime'),
'asset_maintenance_time' => $assetmaintenance->asset_maintenance_time,
'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'datetime'),
'user_id' => ($assetmaintenance->admin) ? ['id' => $assetmaintenance->admin->id,'name'=> e($assetmaintenance->admin->getFullNameAttribute())] : null,
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),
];
$permissions_array['available_actions'] = [
'update' => (bool) Gate::allows('update', Asset::class),
'delete' => (bool) Gate::allows('delete', Asset::class),
];
$array += $permissions_array;
return $array;
}
}

View File

@@ -62,14 +62,7 @@ class AssetsTransformer
'name'=> e($asset->defaultLoc->name)
] : null,
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
'assigned_to' => ($asset->assigneduser) ? [
'id' => (int) $asset->assigneduser->id,
'username' => e($asset->assigneduser->username),
'name' => e($asset->assigneduser->getFullNameAttribute()),
'first_name'=> e($asset->assigneduser->first_name),
'last_name'=> e($asset->assigneduser->last_name),
'employee_number' => e($asset->assigneduser->employee_num),
] : null,
'assigned_to' => $this->transformAssignedTo($asset),
'warranty' => ($asset->warranty_months > 0) ? e($asset->warranty_months . ' ' . trans('admin/hardware/form.months')) : null,
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
@@ -110,6 +103,8 @@ class AssetsTransformer
//array += $fields_array;
$array['custom_fields'] = $fields_array;
}
} else {
$array['custom_fields'] = array();
}
$permissions_array['available_actions'] = [
@@ -128,4 +123,24 @@ class AssetsTransformer
{
return (new DatatablesTransformer)->transformDatatables($assets);
}
public function transformAssignedTo($asset)
{
if ($asset->checkedOutToUser()) {
return $asset->assignedTo ? [
'id' => (int) $asset->assignedTo->id,
'username' => e($asset->assignedTo->username),
'name' => e($asset->assignedTo->getFullNameAttribute()),
'first_name'=> e($asset->assignedTo->first_name),
'last_name'=> e($asset->assignedTo->last_name),
'employee_number' => e($asset->assignedTo->employee_num),
'type' => 'user'
] : null;
}
return $asset->assignedTo ? [
'id' => $asset->assignedTo->id,
'name' => $asset->assignedTo->display_name,
'type' => $asset->assignedType()
] : null;
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Transformers;
use App\Models\Category;
use Illuminate\Database\Eloquent\Collection;
use Gate;
use App\Helpers\Helper;
class CategoriesTransformer
{
@@ -22,15 +23,18 @@ class CategoriesTransformer
if ($category) {
$array = [
'id' => e($category->id),
'id' => (int) $category->id,
'name' => e($category->name),
'type' => e($category->category_type),
'use_default_eula' => ($category->use_default_eula =='1') ? true : false,
'checkin_email' => ($category->checkin_email =='1') ? true : false,
'require_acceptance' => ($category->require_acceptance =='1') ? true : false,
'assets_count' => $category->assets_count,
'accessories_count' => $category->accessories_count,
'consumables_count' => $category->consumables_count,
'components_count' => $category->components_count,
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($category->updated_at, 'datetime'),
];
$permissions_array['available_actions'] = [

View File

@@ -23,7 +23,7 @@ class CompaniesTransformer
if ($company) {
$array = [
'id' => e($company->id),
'id' => (int) $company->id,
'name' => e($company->name),
"created_at" => Helper::getFormattedDateObject($company->created_at, 'datetime'),
"updated_at" => Helper::getFormattedDateObject($company->updated_at, 'datetime'),

View File

@@ -9,7 +9,7 @@ use App\Helpers\Helper;
class LocationsTransformer
{
public function transformLocations (Collection $locations, $total)
public function transformLocations(Collection $locations, $total)
{
$array = array();
foreach ($locations as $location) {
@@ -18,18 +18,16 @@ class LocationsTransformer
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformLocation (Location $location = null)
public function transformLocation(Location $location = null)
{
if ($location) {
$assets_arr = [];
foreach($location->assets() as $asset) {
$assets_arr = ['id' => $asset->id];
}
$children_arr = [];
foreach($location->childLocations() as $child) {
$children_arr = ['id' => $child->id];
foreach($location->childLocations as $child) {
$children_arr[] = [
'id' => (int) $child->id,
'name' => $child->name
];
}
$array = [
@@ -41,10 +39,15 @@ class LocationsTransformer
'assets_checkedout' => $location->assets()->count(),
'assets_default' => $location->assignedassets()->count(),
'country' => e($location->country),
'assets' => $assets_arr,
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'),
'parent_id' => ($location->parent_id) ? (int) $location->parent_id : null,
'parent' => ($location->parent) ? [
'id' => (int) $location->parent->id,
'name'=> e($location->parent->name)
] : null,
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
'children' => $children_arr,
];

View File

@@ -30,6 +30,7 @@ class SuppliersTransformer
'city' => ($supplier->city) ? e($supplier->city) : null,
'state' => ($supplier->state) ? e($supplier->state) : null,
'country' => ($supplier->country) ? e($supplier->country) : null,
'zip' => ($supplier->zip) ? e($supplier->zip) : null,
'fax' => ($supplier->fax) ? e($supplier->fax) : null,
'phone' => ($supplier->phone) ? e($supplier->phone) : null,
'email' => ($supplier->email) ? e($supplier->email) : null,
@@ -37,6 +38,7 @@ class SuppliersTransformer
'assets_count' => (int) $supplier->assets_count,
'licenses_count' => (int) $supplier->licenses_count,
'image' => ($supplier->image) ? e($supplier->image) : null,
'notes' => ($supplier->notes) ? e($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'),

View File

@@ -32,11 +32,6 @@ class UsersTransformer
'id' => (int) $user->manager->id,
'name'=> e($user->manager->username)
] : null,
'groups' => ($user->groups) ? [
'id' => (int) $user->userloc->id,
'name'=> e($user->userloc->name)
] : null,
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
'email' => e($user->email),
'department' => ($user->department) ? [
@@ -68,6 +63,24 @@ class UsersTransformer
$array += $permissions_array;
$numGroups = count($user->groups);
if($numGroups > 0)
{
$groups["total"] = $numGroups;
foreach($user->groups as $group)
{
$groups["rows"][] = [
'id' => (int) $group->id,
'name' => e($group->name)
];
}
$array["groups"] = $groups;
}
else {
$array["groups"] = null;
}
return $array;
}

View File

@@ -136,14 +136,12 @@ abstract class Importer
* @param $default string
* @return string
*/
public function findCsvMatch(array $array, $key, $default = '')
public function findCsvMatch(array $array, $key, $default = null)
{
$val = $default;
if ($customKey = $this->lookupCustomKey($key)) {
$key = $customKey;
}
$key = $this->lookupCustomKey($key);
$this->log("Custom Key: ${key}");
if (array_key_exists($key, $array)) {
@@ -169,7 +167,8 @@ abstract class Importer
$this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]);
return $this->fieldMap[$key];
}
return null;
// Otherwise no custom key, return original.
return $key;
}
/**

View File

@@ -90,7 +90,6 @@ class ItemImporter extends Importer
$item = collect($this->item);
// First Filter the item down to the model's fillable fields
$item = $item->only($model->getFillable());
// Then iterate through the item and, if we are updating, remove any blank values.
if ($updating) {
$item = $item->reject(function ($value) {

View File

@@ -0,0 +1,81 @@
<?php
namespace App\LegacyEncrypter;
use Illuminate\Contracts\Encryption\DecryptException;
abstract class BaseEncrypter
{
/**
* The encryption key.
*
* @var string
*/
protected $key;
/**
* Create a MAC for the given value.
*
* @param string $iv
* @param string $value
* @return string
*/
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
/**
* Get the JSON array from the given payload.
*
* @param string $payload
* @return array
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function getJsonPayload($payload)
{
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if (! $payload || $this->invalidPayload($payload)) {
throw new DecryptException('The payload is invalid.');
}
if (! $this->validMac($payload)) {
throw new DecryptException('The MAC is invalid.');
}
return $payload;
}
/**
* Verify that the encryption payload is valid.
*
* @param array|mixed $data
* @return bool
*/
protected function invalidPayload($data)
{
return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']);
}
/**
* Determine if the MAC for the given payload is valid.
*
* @param array $payload
* @return bool
*
* @throws \RuntimeException
*/
protected function validMac(array $payload)
{
$bytes = random_bytes(16);
$calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true);
return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac);
}
}

View File

@@ -0,0 +1,214 @@
<?php
namespace App\LegacyEncrypter;
use Exception;
use RuntimeException;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
/**
* @deprecated since version 5.1. Use Illuminate\Encryption\Encrypter.
*/
class McryptEncrypter extends BaseEncrypter implements EncrypterContract
{
/**
* The algorithm used for encryption.
*
* @var string
*/
protected $cipher;
/**
* The block size of the cipher.
*
* @var int
*/
protected $block;
/**
* Create a new encrypter instance.
*
* @param string $key
* @param string $cipher
* @return void
*
* @throws \RuntimeException
*/
public function __construct($key, $cipher = MCRYPT_RIJNDAEL_128)
{
$key = (string) $key;
if (static::supported($key, $cipher)) {
$this->key = $key;
$this->cipher = $cipher;
$this->block = mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC);
} else {
throw new RuntimeException('The only supported ciphers are MCRYPT_RIJNDAEL_128 and MCRYPT_RIJNDAEL_256.');
}
}
/**
* Determine if the given key and cipher combination is valid.
*
* @param string $key
* @param string $cipher
* @return bool
*/
public static function supported($key, $cipher)
{
return defined('MCRYPT_RIJNDAEL_128') &&
($cipher === MCRYPT_RIJNDAEL_128 || $cipher === MCRYPT_RIJNDAEL_256);
}
/**
* Encrypt the given value.
*
* @param string $value
* @return string
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function encrypt($value, $serialize = true)
{
$iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer());
$value = base64_encode($this->padAndMcrypt($value, $iv));
// Once we have the encrypted value we will go ahead base64_encode the input
// vector and create the MAC for the encrypted value so we can verify its
// authenticity. Then, we'll JSON encode the data in a "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
/**
* Pad and use mcrypt on the given value and input vector.
*
* @param string $value
* @param string $iv
* @return string
*/
protected function padAndMcrypt($value, $iv)
{
$value = $this->addPadding(serialize($value));
return mcrypt_encrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv);
}
/**
* Decrypt the given value.
*
* @param string $payload
* @return string
*/
public function decrypt($payload, $unserialize = true)
{
$payload = $this->getJsonPayload($payload);
// We'll go ahead and remove the PKCS7 padding from the encrypted value before
// we decrypt it. Once we have the de-padded value, we will grab the vector
// and decrypt the data, passing back the unserialized from of the value.
$value = base64_decode($payload['value']);
$iv = base64_decode($payload['iv']);
return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv)));
}
/**
* Run the mcrypt decryption routine for the value.
*
* @param string $value
* @param string $iv
* @return string
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function mcryptDecrypt($value, $iv)
{
try {
return mcrypt_decrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv);
} catch (Exception $e) {
throw new DecryptException($e->getMessage());
}
}
/**
* Add PKCS7 padding to a given value.
*
* @param string $value
* @return string
*/
protected function addPadding($value)
{
$pad = $this->block - (strlen($value) % $this->block);
return $value.str_repeat(chr($pad), $pad);
}
/**
* Remove the padding from the given value.
*
* @param string $value
* @return string
*/
protected function stripPadding($value)
{
$pad = ord($value[($len = strlen($value)) - 1]);
return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value;
}
/**
* Determine if the given padding for a value is valid.
*
* @param string $pad
* @param string $value
* @return bool
*/
protected function paddingIsValid($pad, $value)
{
$beforePad = strlen($value) - $pad;
return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad);
}
/**
* Get the IV size for the cipher.
*
* @return int
*/
protected function getIvSize()
{
return mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC);
}
/**
* Get the random data source available for the OS.
*
* @return int
*/
protected function getRandomizer()
{
if (defined('MCRYPT_DEV_URANDOM')) {
return MCRYPT_DEV_URANDOM;
}
if (defined('MCRYPT_DEV_RANDOM')) {
return MCRYPT_DEV_RANDOM;
}
mt_srand();
return MCRYPT_RAND;
}
}

View File

@@ -60,6 +60,8 @@ class Accessory extends SnipeModel
'purchase_cost',
'purchase_date',
'model_number',
'manufacturer_id',
'notes',
'qty',
'requestable'
];

View File

@@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Auth;
use Response;
use Carbon;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -123,6 +124,10 @@ class Actionlog extends SnipeModel
return $this->belongsTo('\App\Models\ActionLog', 'thread_id');
}
public function location() {
return $this->belongsTo('\App\Models\Location', 'location_id' )->withTrashed();
}
/**
* Check if the file exists, and if it does, force a download
**/
@@ -149,6 +154,33 @@ class Actionlog extends SnipeModel
}
}
public function daysUntilNextAudit($monthInterval = 12, $asset = null) {
$now = Carbon::now();
$last_audit_date = $this->created_at;
$next_audit = $last_audit_date->addMonth($monthInterval);
$next_audit_days = $now->diffInDays($next_audit);
// Override the default setting for interval if the asset has its own next audit date
if (($asset) && ($asset->next_audit_date)) {
$override_default_next = \Carbon::parse($asset->next_audit_date);
$next_audit_days = $override_default_next->diffInDays($now);
}
return $next_audit_days;
}
public function calcNextAuditDate($monthInterval = 12, $asset = null) {
$last_audit_date = Carbon::parse($this->created_at);
// If there is an asset-specific next date already given,
if (($asset) && ($asset->next_audit_date)) {
return \Carbon::parse($asset->next_audit_date);
}
return \Carbon::parse($last_audit_date)->addMonths($monthInterval)->toDateString();
}
/**
* getListingOfActionLogsChronologicalOrder
*

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Models;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Presenters\Presentable;
use AssetPresenter;
@@ -10,6 +11,7 @@ use Config;
use Illuminate\Database\Eloquent\SoftDeletes;
use Log;
use Watson\Validating\ValidatingTrait;
use Illuminate\Notifications\Notifiable;
/**
* Model for Assets.
@@ -19,7 +21,7 @@ use Watson\Validating\ValidatingTrait;
class Asset extends Depreciable
{
protected $presenter = 'App\Presenters\AssetPresenter';
use Loggable, Requestable, Presentable;
use Loggable, Requestable, Presentable, Notifiable;
use SoftDeletes;
const LOCATION = 'location';
@@ -66,6 +68,7 @@ class Asset extends Depreciable
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'status' => 'integer',
'purchase_cost' => 'numeric|nullable',
'next_audit_date' => 'date|nullable',
];
/**
@@ -140,7 +143,7 @@ class Asset extends Depreciable
* @return bool
*/
//FIXME: The admin parameter is never used. Can probably be removed.
public function checkOut($target, $admin, $checkout_at = null, $expected_checkin = null, $note = null, $name = null)
public function checkOut($target, $admin = null, $checkout_at = null, $expected_checkin = null, $note = null, $name = null)
{
if (!$target) {
return false;
@@ -160,41 +163,19 @@ class Asset extends Depreciable
}
if ($this->requireAcceptance()) {
if(get_class($target) != User::class) {
throw new CheckoutNotAllowed;
}
$this->accepted="pending";
}
if ($this->save()) {
$this->logCheckout($note, $target);
// if ((($this->requireAcceptance()=='1') || ($this->getEula())) && ($user->email!='')) {
// $this->checkOutNotifyMail($log->id, $user, $checkout_at, $expected_checkin, $note);
// }
return true;
}
return false;
}
public function checkOutNotifyMail($log_id, $user, $checkout_at, $expected_checkin, $note)
{
$data['log_id'] = $log_id;
$data['eula'] = $this->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $this->present()->name();
$data['checkout_date'] = $checkout_at;
$data['expected_checkin'] = $expected_checkin;
$data['item_tag'] = $this->asset_tag;
$data['note'] = $note;
$data['item_serial'] = $this->serial;
$data['require_acceptance'] = $this->requireAcceptance();
if ((($this->requireAcceptance()=='1') || ($this->getEula())) && (!config('app.lock_passwords'))) {
\Mail::send('emails.accept-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_asset_delivery'));
});
}
}
public function getDetailedNameAttribute()
{
if ($this->assignedTo) {
@@ -246,16 +227,14 @@ class Asset extends Depreciable
->orderBy('created_at', 'desc');
}
/**
* Even though we allow allow for checkout to things beyond users
* this method is an easy way of seeing if we are checked out to a user.
* @return mixed
*/
public function assigneduser()
public function checkedOutToUser()
{
return $this->belongsTo('\App\Models\User', 'assigned_to')
->withTrashed();
return $this->assignedType() === self::USER;
}
public function assignedTo()
@@ -276,13 +255,16 @@ class Asset extends Depreciable
if (!empty($this->assignedType())) {
if ($this->assignedType() == self::ASSET) {
return $this->assignedTo->assetloc(); // Recurse until we have a final location
} elseif ($this->assignedType() == self::LOCATION) {
}
if ($this->assignedType() == self::LOCATION) {
return $this->assignedTo();
} elseif (!$this->assignedTo) {
return $this->defaultLoc();
} elseif ($this->assignedType() == self::USER) {
}
if ($this->assignedType() == self::USER) {
return $this->assignedTo->userLoc();
}
if (!$this->assignedTo) {
return $this->defaultLoc();
}
}
return $this->defaultLoc();
}
@@ -544,7 +526,7 @@ class Asset extends Depreciable
/**
* Query builder scope for pending assets
* Query builder scope for searching location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
@@ -554,8 +536,17 @@ class Asset extends Depreciable
public function scopeAssetsByLocation($query, $location)
{
return $query->where(function ($query) use ($location) {
$query->whereHas('assigneduser', function ($query) use ($location) {
$query->where('users.location_id', '=', $location->id);
$query->whereHas('assignedTo', function ($query) use ($location) {
$query->where([
['users.location_id', '=', $location->id],
['assets.assigned_type', '=', User::class]
])->orWhere([
['locations.id', '=', $location->id],
['assets.assigned_type', '=', Location::class]
])->orWhere([
['assets.rtd_location_id', '=', $location->id],
['assets.assigned_type', '=', Asset::class]
]);
})->orWhere(function ($query) use ($location) {
$query->where('assets.rtd_location_id', '=', $location->id);
$query->whereNull('assets.assigned_to');
@@ -775,18 +766,26 @@ class Asset extends Depreciable
$query->whereHas('defaultLoc', function ($query) use ($search) {
$query->where('locations.name', 'LIKE', '%'.$search.'%');
});
})->orWhere(function ($query) use ($search) {
$query->whereHas('assigneduser', function ($query) use ($search) {
$query->where(function ($query) use ($search) {
$query->where('users.first_name', 'LIKE', '%'.$search.'%')
->orWhere('users.last_name', 'LIKE', '%'.$search.'%')
->orWhere(function ($query) use ($search) {
$query->whereHas('userloc', function ($query) use ($search) {
$query->where('locations.name', 'LIKE', '%'.$search.'%');
});
});
});
});
//FIXME: This needs attention to work with checkout to not-users.
// })->orWhere(function ($query) use ($search) {
// $query->whereHas('assignedTo', function ($query) use ($search) {
// $query->where(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', User::class)
// ->join('users', 'users.id', '=', 'assets.assigned_to')
// ->where(function($query) use ($search) {
// $query->where('users.first_name', 'LIKE', '%'.$search.'%')
// ->orWhere('users.last_name', 'LIKE', '%'.$search.'%');
// });
// })->orWhere(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', Location::class)
// ->join('locations', 'locations.id', '=', 'assets.assigned_to')
// ->where('locations.name', 'LIKE', '%'.$search.'%');
// })->orWhere(function ($query) use ($search) {
// $query->where('assets.assigned_type', '=', Asset::class)
// ->join('assets as assigned_asset', 'assigned_assets.id', '=', 'assets.assigned_to')
// ->where('assigned_assets.name', 'LIKE', '%'.$search.'%');
// });
// });
})->orWhere('assets.name', 'LIKE', '%'.$search.'%')
->orWhere('assets.asset_tag', 'LIKE', '%'.$search.'%')
->orWhere('assets.serial', 'LIKE', '%'.$search.'%')
@@ -1088,14 +1087,16 @@ class Asset extends Depreciable
return $query->where(function ($query) use ($search) {
$query->whereHas('defaultLoc', function ($query) use ($search) {
$query->where('locations.id', '=', $search);
})->whereNull('assigned_to');
})->orWhere(function ($query) use ($search) {
$query->whereHas('assigneduser', function ($query) use ($search) {
$query->whereHas('userloc', function ($query) use ($search) {
$query->where('locations.id', '=', $search);
});
});
});
// FIXME: This needs porting to checkout to non-user.
// ->orWhere(function ($query) use ($search) {
// $query->whereHas('assigneduser', function ($query) use ($search) {
// $query->whereHas('userloc', function ($query) use ($search) {
// $query->where('locations.id', '=', $search);
// });
// });
// });
}

View File

@@ -55,7 +55,7 @@ class AssetModel extends SnipeModel
*
* @var array
*/
protected $fillable = ['name','manufacturer_id','category_id','eol', 'user_id', 'fieldset_id'];
protected $fillable = ['name','manufacturer_id','category_id','eol', 'user_id', 'fieldset_id', 'model_number', 'notes'];
public function assets()
{

View File

@@ -73,7 +73,7 @@ class Consumable extends SnipeModel
return $this->belongsTo('\App\Models\User', 'user_id');
}
public function consumableAssigments()
public function consumableAssignments()
{
return $this->hasMany('\App\Models\ConsumableAssignment');
}

View File

@@ -38,12 +38,10 @@ class CustomField extends Model
public static function boot()
{
self::created(function ($custom_field) {
\Log::debug("\n\nCreating Original Name: ".$custom_field->name);
\Log::debug('Creating Column Name: '.$custom_field->convertUnicodeDbSlug());
// column exists - nothing to do here
if (Schema::hasColumn(CustomField::$table_name, $custom_field->convertUnicodeDbSlug())) {
\Log::debug('Column exists. Nothing to do here.');
return false;
}
@@ -57,18 +55,13 @@ class CustomField extends Model
self::updating(function ($custom_field) {
\Log::debug('Updating column name');
\Log::debug('Updating Original Name: '.$custom_field->getOriginal("name"));
\Log::debug('Updating New Column Name: '.$custom_field->convertUnicodeDbSlug());
// Column already exists. Nothing to update.
if ($custom_field->isDirty("name")) {
if (Schema::hasColumn(CustomField::$table_name, $custom_field->convertUnicodeDbSlug())) {
\Log::debug('Column already exists. Nothing to update.');
return true;
}
\Log::debug('Updating column name to.'.$custom_field->convertUnicodeDbSlug());
return Schema::table(CustomField::$table_name, function ($table) use ($custom_field) {
$table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal("name")), $custom_field->convertUnicodeDbSlug());
});
@@ -85,7 +78,7 @@ class CustomField extends Model
public function fieldset()
{
return $this->belongsToMany('\App\Models\CustomFieldset'); //?!?!?!?!?!?
return $this->belongsToMany('\App\Models\CustomFieldset');
}
public function user()
@@ -102,10 +95,9 @@ class CustomField extends Model
public function db_column_name()
{
return $this->db_column;
// return self::convertUnicodeDbSlug();
}
//mutators for 'format' attribute
// mutators for 'format' attribute
public function getFormatAttribute($value)
{
foreach (self::$PredefinedFormats as $name => $pattern) {
@@ -116,6 +108,13 @@ class CustomField extends Model
return $value;
}
/**
* Format a value string as an array for select boxes and checkboxes.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.4]
* @return Array
*/
public function setFormatAttribute($value)
{
if (isset(self::$PredefinedFormats[$value])) {

View File

@@ -110,7 +110,7 @@ class Ldap extends Model
return false;
}
if (!$user = array_change_key_case(ldap_get_attributes($connection, $entry), CASE_LOWER)) {
if (!$user = ldap_get_attributes($connection, $entry)) {
return false;
}

View File

@@ -401,6 +401,20 @@ class License extends Depreciable
->orderBy('manufacturers.name', $order);
}
/**
* Query builder scope to order on supplier
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderSupplier($query, $order)
{
return $query->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->select('licenses.*')
->orderBy('suppliers.name', $order);
}
/**
* Query builder scope to order on company
*

View File

@@ -24,6 +24,7 @@ class Location extends SnipeModel
'address' => 'max:80|nullable',
'address2' => 'max:80|nullable',
'zip' => 'min:3|max:10|nullable',
// 'manager_id' => 'exists:users'
);
/**
@@ -63,7 +64,12 @@ class Location extends SnipeModel
public function parent()
{
return $this->belongsTo('\App\Models\Location', 'parent_id');
return $this->belongsTo('\App\Models\Location', 'parent_id','id');
}
public function manager()
{
return $this->belongsTo('\App\Models\User', 'manager_id');
}
public function childLocations()

View File

@@ -7,6 +7,7 @@ use App\Models\Asset;
use App\Models\CheckoutRequest;
use App\Models\User;
use App\Notifications\CheckinNotification;
use App\Notifications\AuditNotification;
use App\Notifications\CheckoutNotification;
use Illuminate\Support\Facades\Auth;
@@ -29,11 +30,56 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckout($note, $target = null /*target is overridable for components*/)
public function logCheckout($note, $target /* What are we checking out to? */)
{
$log = new Actionlog;
$log = $this->determineLogItemType($log);
$log->user_id = Auth::user()->id;
// We need to special case licenses because of license_seat vs license. So much for clean polymorphism :)
if (!isset($target)) {
throw new Exception('All checkout logs require a target');
return;
}
$log->target_type = get_class($target);
$log->target_id = $target->id;
$class = get_class($target);
if ($class == Location::class) {
// We can checkout to a location
$log->location_id = $target->id;
} else if ($class== Asset::class) {
$log->location_id = $target->rtd_location_id;
} else {
$log->location_id = $target->location_id;
}
$log->note = $note;
$log->logaction('checkout');
$params = [
'item' => $this,
'target' => $target,
'admin' => $log->user,
'note' => $note,
'log_id' => $log->id
];
if ($settings = Setting::getSettings()) {
$settings->notify(new CheckoutNotification($params));
}
if (method_exists($target, 'notify')) {
$target->notify(new CheckoutNotification($params));
}
return $log;
}
/**
* Helper method to determine the log item type
*/
private function determineLogItemType($log)
{
// We need to special case licenses because of license_seat vs license. So much for clean polymorphism :
if (static::class == LicenseSeat::class) {
$log->item_type = License::class;
$log->item_id = $this->license_id;
@@ -42,49 +88,8 @@ trait Loggable
$log->item_id = $this->id;
}
$log->user_id = Auth::user()->id;
// @FIXME This needs to be generalized with new asset checkout.
if(isset($target)) {
$log->target_type = get_class($target);
$log->target_id = $target->id;
} else {
if (!is_null($this->asset_id)) {
$log->target_type = Asset::class;
$log->target_id = $this->asset_id;
} elseif (!is_null($this->assigned_to)) {
$log->target_type = User::class;
$log->target_id = $this->assigned_to;
}
}
$item = call_user_func(array($log->target_type, 'find'), $log->target_id);
if($this->assignedTo) {
$item = $this->assignedTo;
}
$class = get_class($item);
if($class == Location::class) {
// We can checkout to a location
$log->location_id = $item->id;
} else if ($class== Asset::class) {
$log->location_id = $item->rtd_location_id;
} else {
$log->location_id = $item->location_id;
}
$log->note = $note;
$log->logaction('checkout');
$params = [
'item' => $log->item,
'target' => $log->target,
'admin' => $log->user,
'note' => $note
];
Setting::getSettings()->notify(new CheckoutNotification($params));
return $log;
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @since [v3.4]
@@ -117,6 +122,41 @@ trait Loggable
return $log;
}
/**
* @author A. Gianotto <snipe@snipe.net>
* @since [v4.0]
* @return \App\Models\Actionlog
*/
public function logAudit($note, $location_id)
{
$log = new Actionlog;
$location = Location::find($location_id);
if (static::class == LicenseSeat::class) {
$log->item_type = License::class;
$log->item_id = $this->license_id;
} else {
$log->item_type = static::class;
$log->item_id = $this->id;
}
$log->location_id = ($location_id) ? $location_id : null;
$log->note = $note;
$log->user_id = Auth::user()->id;
$log->logaction('audit');
$params = [
'item' => $log->item,
'admin' => $log->user,
'location' => ($location) ? $location->name : '',
'note' => $note
];
Setting::getSettings()->notify(new AuditNotification($params));
return $log;
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @since [v3.5]

View File

@@ -19,9 +19,9 @@ trait Requestable
public function isRequestedBy(User $user)
{
return $this->requests()
->where('user_id', $user->id)
->exists();
$requests = $this->requests->where('user_id', $user->id);
return $requests->count() > 0;
}
public function scopeRequestedBy($query, User $user)

View File

@@ -14,36 +14,30 @@ class Setting extends Model
protected $rules = [
"brand" => 'required|min:1|numeric',
"qr_text" => 'max:31',
"qr_text" => 'max:31|nullable',
"logo_img" => 'mimes:jpeg,bmp,png,gif',
"alert_email" => 'email_array',
"alert_email" => 'email_array|nullable',
"default_currency" => 'required',
"locale" => 'required',
"slack_endpoint" => 'url|required_with:slack_channel',
"slack_channel" => 'regex:/(?<!\w)#\w+/|required_with:slack_endpoint',
"slack_endpoint" => 'url|required_with:slack_channel|nullable',
"slack_channel" => 'regex:/(?<!\w)#\w+/|required_with:slack_endpoint|nullable',
"slack_botname" => 'string|nullable',
'labels_per_page' => 'numeric',
'labels_width' => 'numeric',
'labels_height' => 'numeric',
'labels_pmargin_left' => 'numeric',
'labels_pmargin_right' => 'numeric',
'labels_pmargin_top' => 'numeric',
'labels_pmargin_bottom' => 'numeric',
'labels_display_bgutter' => 'numeric',
'labels_display_sgutter' => 'numeric',
'labels_pmargin_left' => 'numeric|nullable',
'labels_pmargin_right' => 'numeric|nullable',
'labels_pmargin_top' => 'numeric|nullable',
'labels_pmargin_bottom' => 'numeric|nullable',
'labels_display_bgutter' => 'numeric|nullable',
'labels_display_sgutter' => 'numeric|nullable',
'labels_fontsize' => 'numeric|min:5',
'labels_pagewidth' => 'numeric',
'labels_pageheight' => 'numeric',
"ldap_server" => 'sometimes|required_if:ldap_enabled,1|url',
"ldap_uname" => 'sometimes|required_if:ldap_enabled,1',
"ldap_basedn" => 'sometimes|required_if:ldap_enabled,1',
"ldap_filter" => 'sometimes|required_if:ldap_enabled,1',
"ldap_username_field" => 'sometimes|required_if:ldap_enabled,1',
"ldap_fname_field" => 'sometimes|required_if:ldap_enabled,1',
"ldap_lname_field" => 'sometimes|required_if:ldap_enabled,1',
"ldap_auth_filter_query" => 'sometimes|required_if:ldap_enabled,1',
"ldap_version" => 'sometimes|required_if:ldap_enabled,1',
'labels_pagewidth' => 'numeric|nullable',
'labels_pageheight' => 'numeric|nullable',
"thumbnail_max_h" => 'numeric|max:500|min:25',
"pwd_secure_min" => "numeric|required|min:5",
"audit_warning_days" => "numeric|nullable",
"audit_interval" => "numeric|nullable",
];
protected $fillable = ['site_name','email_domain','email_format','username_format'];
@@ -158,4 +152,30 @@ class Setting extends Model
// In the future this may want to be adapted for individual notifications.
return $this->slack_endpoint;
}
public static function passwordComplexityRulesSaving($action = 'update')
{
$security_rules = '';
$settings = Setting::getSettings();
// Check if they have uncommon password enforcement selected in settings
if ($settings->pwd_secure_uncommon == 1) {
$security_rules .= '|dumbpwd';
}
// Check for any secure password complexity rules that may have been selected
if ($settings->pwd_secure_complexity!='') {
$security_rules .= '|'.$settings->pwd_secure_complexity;
}
if ($action == 'update') {
return 'nullable|min:'.$settings->pwd_secure_min.$security_rules;
}
return 'required|min:'.$settings->pwd_secure_min.$security_rules;
}
}

View File

@@ -82,4 +82,8 @@ class SnipeModel extends Model
{
return $this->name;
}
public function name() {
return $this->name;
}
}

View File

@@ -45,7 +45,7 @@ class Supplier extends SnipeModel
*
* @var array
*/
protected $fillable = ['name'];
protected $fillable = ['name','address','address2','city','state','country','zip','phone','fax','email','contact','url','notes'];
// Eager load counts.

View File

@@ -51,9 +51,9 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
protected $rules = [
'first_name' => 'required|string|min:1',
'username' => 'required|string|min:1|unique_undeleted',
'email' => 'email',
'email' => 'email|nullable',
'password' => 'required|min:6',
'locale' => 'max:10'
'locale' => 'max:10|nullable'
];
@@ -205,6 +205,14 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
return $this->belongsTo('\App\Models\User', 'manager_id')->withTrashed();
}
/**
* Get any locations the user manages.
**/
public function managedLocations()
{
return $this->hasMany('\App\Models\Location', 'manager_id')->withTrashed();
}
/**
* Get user groups
*/

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class AuditNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
//
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint) {
$notifyBy[] = 'slack';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->success()
->content(class_basename(get_class($this->params['item'])) . " Audited")
->attachment(function ($attachment) use ($notifiable) {
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$fields = [
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>'
];
array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note'];
array_key_exists('location', $this->params) && $fields['Location'] = $this->params['location'];
$attachment->title($item->present()->name, $item->present()->viewUrl())
->fields($fields);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -5,10 +5,11 @@ namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutNotification extends Notification
{
@@ -43,11 +44,12 @@ class CheckoutNotification extends Notification
}
$item = $this->params['item'];
if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance()=='1'))
|| (method_exists($item, 'getEula') && ($item->getEula()))
) {
$notifyBy[] = 'mail';
}
$notifyBy[]='mail';
// if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance()=='1'))
// || (method_exists($item, 'getEula') && ($item->getEula()))
// ) {
// $notifyBy[] = 'mail';
// }
return $notifyBy;
}
@@ -79,10 +81,30 @@ class CheckoutNotification extends Notification
*/
public function toMail($notifiable)
{
//TODO: Expand for non assets.
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$target = $this->params['target'];
$data = [
'eula' => method_exists($item, 'getEula') ? $item->getEula() : '',
'first_name' => $target->present()->fullName(),
'item_name' => $item->present()->name(),
'checkout_date' => $item->last_checkout,
'expected_checkin' => $item->expected_checkin,
'item_tag' => $item->asset_tag,
'note' => $this->params['note'],
'item_serial' => $item->serial,
'require_acceptance' => $item->requireAcceptance(),
'log_id' => $this->params['log_id'],
];
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', 'https://laravel.com')
->line('Thank you for using our application!');
->view('emails.accept-asset', $data)
->subject(trans('mail.Confirm_asset_delivery'));
// \Mail::send('emails.accept-asset', $data, function ($m) use ($target) {
// $m->to($target->email, $target->first_name . ' ' . $target->last_name);
// $m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
// $m->subject(trans('mail.Confirm_asset_delivery'));
// });
}
/**

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;
class ExpectedCheckinNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
$item = $this->params['item'];
$notifyBy[]='mail';
return $notifyBy;
}
public function toSlack($notifiable)
{
}
/**
* Get the mail representation of the notification.
*
* @param mixed $asset
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($params)
{
$formatted_due = Carbon::parse($this->params->expected_checkin)->format('D, M j, Y');
return (new MailMessage)
->error()
->subject('Reminder: '.$this->params->present()->name().' checkin deadline approaching')
->line('Hi, '.$this->params->assignedto->first_name)
->greeting('An asset checked out to you is due to be checked back in on '.$formatted_due.'.')
->line('Asset: '.$this->params->present()->name())
->line('Serial: '.$this->params->serial)
->line('Asset Tag: '.$this->params->asset_tag)
->action('View Your Assets', route('view-assets'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -28,8 +28,7 @@ class AccessoryObserver
/**
* Listen to the Accessory created event, and increment
* the next_auto_tag_base value in the settings table when i
* Listen to the Accessory created event when
* a new accessory is created.
*
* @param Accessory $accessory
@@ -37,9 +36,6 @@ class AccessoryObserver
*/
public function created(Accessory $accessory)
{
$settings = Setting::first();
$settings->increment('next_auto_tag_base');
$logAction = new Actionlog();
$logAction->item_type = Accessory::class;
$logAction->item_id = $accessory->id;

View File

@@ -18,7 +18,9 @@ class AssetObserver
public function updating(Asset $asset)
{
if (($asset->getAttributes()['assigned_to'] == $asset->getOriginal()['assigned_to'])
if ((isset($asset->getOriginal()['assigned_to'])) && ($asset->getAttributes()['assigned_to'] == $asset->getOriginal()['assigned_to'])
&& ($asset->getAttributes()['next_audit_date'] == $asset->getOriginal()['next_audit_date'])
&& ($asset->getAttributes()['last_checkout'] == $asset->getOriginal()['last_checkout'])
&& ($asset->getAttributes()['status_id'] == $asset->getOriginal()['status_id']))
{
@@ -43,8 +45,9 @@ class AssetObserver
*/
public function created(Asset $asset)
{
$settings = Setting::first();
$settings->increment('next_auto_tag_base');
if ($settings = Setting::first()) {
$settings->increment('next_auto_tag_base');
}
$logAction = new Actionlog();
$logAction->item_type = Asset::class;

View File

@@ -28,8 +28,7 @@ class ComponentObserver
/**
* Listen to the Component created event, and increment
* the next_auto_tag_base value in the settings table when i
* Listen to the Component created event when
* a new component is created.
*
* @param Component $component
@@ -37,9 +36,6 @@ class ComponentObserver
*/
public function created(Component $component)
{
$settings = Setting::first();
$settings->increment('next_auto_tag_base');
$logAction = new Actionlog();
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;

View File

@@ -28,8 +28,7 @@ class ConsumableObserver
/**
* Listen to the Consumable created event, and increment
* the next_auto_tag_base value in the settings table when i
* Listen to the Consumable created event when
* a new consumable is created.
*
* @param Consumable $consumable
@@ -37,8 +36,6 @@ class ConsumableObserver
*/
public function created(Consumable $consumable)
{
$settings = Setting::first();
$settings->increment('next_auto_tag_base');
$logAction = new Actionlog();
$logAction->item_type = Consumable::class;

View File

@@ -28,8 +28,7 @@ class LicenseObserver
/**
* Listen to the License created event, and increment
* the next_auto_tag_base value in the settings table when i
* Listen to the License created event when
* a new license is created.
*
* @param License $license
@@ -37,8 +36,6 @@ class LicenseObserver
*/
public function created(License $license)
{
$settings = Setting::first();
$settings->increment('next_auto_tag_base');
$logAction = new Actionlog();
$logAction->item_type = License::class;

View File

@@ -98,15 +98,14 @@ class AssetPresenter extends Presenter
"sortable" => true,
"title" => trans('admin/hardware/form.checkedout_to'),
"visible" => true,
"formatter" => "usersLinkObjFormatter"
"formatter" => "polymorphicItemFormatter"
], [
"field" => "assigned_to",
"field" => "employee_number",
"searchable" => false,
"sortable" => false,
"title" => trans('admin/users/table.employee_num'),
"visible" => false,
"formatter" => "employeeNumFormatter"
],[
"field" => "location",
"searchable" => true,

View File

@@ -61,6 +61,14 @@ class LicensePresenter extends Presenter
"searchable" => true,
"sortable" => true,
"title" => trans('admin/licenses/form.to_name'),
], [
"field" => "supplier",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.supplier'),
"visible" => false,
"formatter" => "suppliersLinkObjFormatter"
], [
"field" => "manufacturer",
"searchable" => true,

View File

@@ -42,4 +42,8 @@ class LocationPresenter extends Presenter
{
return '<i class="fa fa-globe"></i>';
}
public function fullName() {
return $this->name;
}
}

View File

@@ -67,11 +67,15 @@ class AppServiceProvider extends ServiceProvider
// This works around the use case where multiple deleted items have the same unique attribute.
// (I think this is a bug in Laravel's validator?)
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
$count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
return $count < 1;
if (count($parameters)) {
$count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
return $count < 1;
}
});
// Share common variables with all views.
// Share common setting variables with all views.
view()->composer('*', function ($view) {
$view->with('snipeSettings', \App\Models\Setting::getSettings());
});

View File

@@ -15,7 +15,7 @@
"fideloper/proxy": "^3.1",
"intervention/image": "^2.3",
"javiereguiluz/easyslugger": "^1.0",
"laravel/framework": "5.4.27",
"laravel/framework": "5.4.20",
"laravel/passport": "^1.0",
"laravel/tinker": "^1.0",
"laravelcollective/html": "^5.3",
@@ -24,9 +24,13 @@
"neitanod/forceutf8": "^2.0",
"patchwork/utf8": "~1.2",
"pragmarx/google2fa": "^1.0",
"schuppo/password-strength": "~1.5",
"spatie/laravel-backup": "^3.0.0",
"tecnickcom/tc-lib-barcode": "^1.15",
"watson/validating": "^3.0"
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^3.0",
"doctrine/instantiator": "1.0.5",
"doctrine/inflector": "1.0.*"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
@@ -34,7 +38,8 @@
"symfony/css-selector": "3.1.*",
"symfony/dom-crawler": "3.1.*",
"codeception/codeception": "2.2.9",
"squizlabs/php_codesniffer": "*"
"squizlabs/php_codesniffer": "*",
"phpunit/php-token-stream": "1.4.11"
},
"autoload": {

731
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -220,6 +220,8 @@ return [
PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class,
Laravel\Passport\PassportServiceProvider::class,
Laravel\Tinker\TinkerServiceProvider::class,
Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class,
Schuppo\PasswordStrength\PasswordStrengthServiceProvider::class,
/*

View File

@@ -85,8 +85,8 @@ return array(
array(
'permission' => 'assets.audit',
'label' => 'Audit ',
'note' => '',
'display' => false,
'note' => 'Allows the user to mark an asset as physically inventoried.',
'display' => true,
),

View File

@@ -1,7 +1,7 @@
<?php
return array (
'app_version' => 'v4.0-beta',
'build_version' => '1',
'hash_version' => 'gcb1e3b7',
'full_hash' => 'v4.0-beta-244-gcb1e3b7',
'app_version' => 'v4',
'build_version' => 'beta4',
'hash_version' => '24',
'full_hash' => 'v4-beta4-24-g10f3221',
);

View File

@@ -20,7 +20,7 @@ $factory->defineAs(Actionlog::class, 'asset-checkout', function (Faker\Generator
$user = factory(App\Models\User::class)->create(['company_id' => $company->id]);
$target = factory(App\Models\User::class)->create(['company_id' => $company->id]);
// $item = factory(App\Models\Asset::class)->create(['company_id' => $company->id]);
// dd($item);
return [
'user_id' => $user->id,
'action_type' => 'checkout',

View File

@@ -1,6 +1,8 @@
<?php
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
/*
|--------------------------------------------------------------------------
@@ -69,6 +71,13 @@ $factory->state(Asset::class, 'assigned-to-asset', function ($faker) {
];
});
$factory->state(Asset::class, 'requires-acceptance', function ($faker) {
$cat = factory(Category::class)->states('asset-category', 'requires-acceptance')->create();
$model = factory(AssetModel::class)->create(['category_id' => $cat->id]);
return [
'model_id' => $model->id
];
});
$factory->define(App\Models\AssetModel::class, function (Faker\Generator $faker) {
return [

View File

@@ -15,7 +15,7 @@ $factory->define(App\Models\Category::class, function (Faker\Generator $faker) {
'name' => $faker->text(20),
'category_type' => $faker->randomElement(['asset', 'accessory', 'component', 'consumable']),
'eula_text' => $faker->paragraph(),
'require_acceptance' => $faker->boolean(),
'require_acceptance' => false,
'use_default_eula' => $faker->boolean(),
'checkin_email' => $faker->boolean()
];
@@ -44,3 +44,9 @@ $factory->state(App\Models\Category::class, 'consumable-category', function ($fa
'category_type' => 'consumable',
];
});
$factory->state(App\Models\Category::class, 'requires-acceptance', function ($faker) {
return [
'require_acceptance' => true,
];
});

View File

@@ -210,5 +210,6 @@ $factory->define(App\Models\Setting::class, function ($faker) {
'brand' => 1,
'default_currency' => $faker->currencyCode,
'locale' => $faker->locale,
'pwd_secure_min' => 5,
];
});

View File

@@ -21,7 +21,7 @@ use Illuminate\Database\Schema\Blueprint;
function updateLegacyColumnName($customfield) {
$name_to_db_name = CustomField::name_to_db_name($customfield->name);
\Log::debug('Trying to rename '.$name_to_db_name." to ".$customfield->convertUnicodeDbSlug()."...\n");
//\Log::debug('Trying to rename '.$name_to_db_name." to ".$customfield->convertUnicodeDbSlug()."...\n");
if (Schema::hasColumn(CustomField::$table_name, $name_to_db_name)) {
@@ -32,7 +32,7 @@ function updateLegacyColumnName($customfield) {
);
} else {
\Log::debug('Legacy DB column '.$name_to_db_name.' was not found on the assets table.');
//\Log::debug('Legacy DB column '.$name_to_db_name.' was not found on the assets table.');
}
}

View File

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

View File

@@ -25,7 +25,7 @@ class AddNextAutoincrementToSettings extends Migration
$table->bigInteger('next_auto_tag_base')->default('1');
});
\Log::debug('Setting '.$next.' as default auto-increment');
//\Log::debug('Setting '.$next.' as default auto-increment');
if ($settings = App\Models\Setting::first()) {
$settings->next_auto_tag_base = $next;

View File

@@ -13,6 +13,9 @@ class SetAssetArchivedToZeroDefault extends Migration
*/
public function up()
{
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
Schema::table('assets', function (Blueprint $table) {
$table->boolean('archived')->default(0)->change();
});

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddSecurePasswordOptions extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->boolean('pwd_secure_uncommon')->default('0');
$table->string('pwd_secure_complexity')->nullable()->default(NULL);
$table->integer('pwd_secure_min')->default('8');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('pwd_secure_uncommon');
$table->dropColumn('pwd_secure_complexity');
$table->dropColumn('pwd_secure_min');
});
}
}

View File

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

View File

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

View File

@@ -1,80 +0,0 @@
var elixir = require('laravel-elixir');
require('laravel-elixir-codeception-standalone');
require('laravel-elixir-phpcs');
require('laravel-elixir-vue-2');
var bowerPath = './bower_components';
/*
|--------------------------------------------------------------------------
| Elixir Asset Management
|--------------------------------------------------------------------------
|
| Elixir provides a clean, fluent API for defining some basic Gulp tasks
| for your Laravel application. By default, we are compiling the Sass
| file for our application, as well as publishing vendor resources.
|
*/
elixir(function(mix) {
mix.less(
[
'app.less',
bowerPath + '/iCheck/skins/minimal/*',
'AdminLTE.less',
'skins/skin-blue.less',
'overrides.less'
], 'public/assets/css/')
.copy(bowerPath + '/bootstrap-less/assets/fonts/bootstrap/**', 'public/assets/fonts')
.copy(bowerPath + '/font-awesome/fonts/*', 'public/build/fonts');
mix.webpack(
// jQuery is loaded from vue.js webpack process
'./resources/assets/js/vue.js',
'./resources/assets/js/vue-dist.js'
);
mix.scripts([
bowerPath + '/tether/dist/js/tether.js',
'vue-dist.js',
bowerPath + '/jquery-ui/jquery-ui.js',
bowerPath + '/jquery-slimscroll/jquery.slimscroll.js',
bowerPath + '/jquery.iframe-transport/jquery.iframe-transport.js',
bowerPath + '/blueimp-file-upload/js/jquery.fileupload.js',
bowerPath + '/fastclick/lib/fastclick.js',
bowerPath + '/bootstrap-colorpicker/dist/js/bootstrap-colorpicker.js',
bowerPath + '/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
bowerPath + '/iCheck/icheck.js',
bowerPath + '/select2/dist/js/select2.full.js',
bowerPath + '/ekko-lightbox/dist/ekko-lightbox.js',
'snipeit.js',
'app.js'
],'public/assets/js');
mix.version(['assets/css/app.css','assets/js/all.js']);
// mix.codeception(null, { flags: '--report' });
// mix.phpcs([
// 'app/**/*.php',
// 'tests/unit/*.php',
// 'tests/functional/*.php',
// 'tests/acceptance/*.php'
// ], {
// bin: 'vendor/bin/phpcs',
// standard: 'PSR2'
// });
});

28
public/js/dist/all.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,127 @@
/*
*
* Snipe-IT Universal Modal support
*
* Enables modal dialogs to create sub-resources throughout Snipe-IT
*
*/
/*
HOW TO USE
Create a Button looking like this:
<a href='{{ route('modal.user') }}' data-toggle="modal" data-target="#createModal" data-dependency="user" data-select='assigned_to' class="btn btn-sm btn-default">New</a>
If you don't have access to Blade commands (like {{ and }}, etc), you can hard-code a URL as the 'href'
data-toggle="modal" - required for Bootstrap Modals
data-target="#createModal" - fixed ID for the modal, do not change
data-dependency="user" - which Snipe-IT model you're going to be creating.
data-select="assigned_to" - What is the *ID* of the select-dropdown that you're going to be adding to, if the modal-create was a
success? Be on the lookout for duplicate ID's, it will confuse this library!
class="btn btn-sm btn-default" - makes it look button-ey, feel free to change :)
*/
$(function () {
console.warn("Loading up Modal functionality.");
//handle modal-add-interstitial calls
var model, select;
if($('#createModal').length == 0) {
$('body').append('<div class="modal fade" id="createModal"></div><!-- /.modal -->');
}
$('#createModal').on("show.bs.modal", function (event) {
var link = $(event.relatedTarget);
model = link.data("dependency");
select = link.data("select");
// console.warn("Uh, href is: "+link.attr('href'));
// console.dir(link);
$('#createModal').load(link.attr('href'),function () {
//do we need to re-select2 this, after load? Probably.
$('#createModal').find('select.select2').select2();
});
});
$('#createModal').on('click','#modal-save', function () {
console.warn("MODAL SAVE CALLED FOR MODAL!");
var data = {};
console.warn("We are about to SAVE!!! for model: "+model+" and select ID: "+select);
$('.modal-body input:visible').each(function (index, elem) {
console.warn("["+index+"]: "+elem.id+" = "+$(elem).val());
var bits = elem.id.split("-");
if (bits[0] === "modal") {
data[bits[1]] = $(elem).val();
}
});
//this can probably get replaced with a normal 'serialize' instead
$('.modal-body select:visible').each(function (index, elem) {
var bits = elem.id.split("-");
data[bits[1]] = $(elem).val();
});
data._token = Laravel.csrfToken;
console.log(data);
$.ajax({
type: 'POST',
url: "/api/v1/" + model + "s",
headers: {
"X-Requested-With": 'XMLHttpRequest',
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
},
data: data,
success: function (result) {
// {"status":"error","messages":{"name":["The name field is required."]}}
if(result.status == "error") {
var error_message="";
for(var field in result.messages) {
error_message+="Problem(s) with field '"+field+"': "+result.messages[field].join(", ");
}
//window.alert("Error adding "+model+": "+error_message);
$('#modal_error_msg').html(error_message).show();
return false;
}
var id = result.payload.id;
var name = result.payload.name || (result.payload.first_name + " " + result.payload.last_name);
if(!id || !name) {
console.error("Could not find resulting name or ID from modal-create. Name: "+name+", id: "+id);
return false;
}
$('#createModal').modal('hide');
$('#createModal').html("");
// "select" is the original drop-down menu that someone
// clicked 'add' on to add a new 'thing'
// this code adds the newly created object to that select
var selector = document.getElementById(select);
if(!selector) {
console.error("Could not find original <select> element with an id of: "+select);
return false;
}
//console.log(document.getElementById(select));
console.dir(selector);
selector.options[selector.length] = new Option(name, id);
selector.selectedIndex = selector.length - 1;
$(selector).trigger("change");
if(fetchCustomFields) {
fetchCustomFields();
}
},
error: function (result) {
// console.log('Error: ' + result.responseJSON.error.message );
msg = result.responseJSON.messages || result.responseJSON.error;
$('#modal_error_msg').html("Server Error: "+msg).show();
//window.alert("Unable to add new " + model + " - error: " + msg);
}
});
});
});

View File

@@ -12,8 +12,8 @@
'success' => 'Asset Maintenance created successfully.'
],
'edit' => [
'error' => 'Asset Maintenance was not edited, please try again.',
'success' => 'Asset Maintenance edited successfully.'
'error' => 'Asset Maintenance was not created, please try again.',
'success' => 'Asset Maintenance created successfully.'
],
'asset_maintenance_incomplete' => 'Not Completed Yet',
'warranty' => 'Warranty',

View File

@@ -28,7 +28,7 @@ return array(
'fieldset' => array(
'does_not_exist' => 'Fieldset does not exist',
'create' => array(
'error' => 'Fieldset was not created, please try again.',

View File

@@ -63,6 +63,8 @@ return array(
'ldap_email' => 'LDAP Email',
'load_remote_text' => 'Remote Scripts',
'load_remote_help_text' => 'This Snipe-IT install can load scripts from the outside world.',
'login_note' => 'Login Note',
'login_note_help' => 'Optionally include a few sentences on your login screen, for example to assist people who have found a lost or stolen device. This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>',
'logo' => 'Logo',
'full_multiple_companies_support_help_text' => 'Restricting users (including admins) assigned to companies to their company\'s assets.',
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
@@ -71,6 +73,12 @@ return array(
'php' => 'PHP Version',
'php_gd_info' => 'You must install php-gd to display QR codes, see install instructions.',
'php_gd_warning' => 'PHP Image Processing and GD plugin is NOT installed.',
'pwd_secure_complexity' => 'Password Complexity',
'pwd_secure_complexity_help' => 'Select whichever password complexity rules you wish to enforce.',
'pwd_secure_min' => 'Password minimum characters',
'pwd_secure_min_help' => 'Minimum permitted value is 5',
'pwd_secure_uncommon' => 'Prevent common passwords',
'pwd_secure_uncommon_help' => 'This will disallow users from using common passwords from the top 10,000 passwords reported in breaches.',
'qr_help' => 'Enable QR Codes first to set this',
'qr_text' => 'QR Code Text',
'setting' => 'Setting',
@@ -105,6 +113,8 @@ return array(
'width_w' => 'w',
'height_h' => 'h',
'text_pt' => 'pt',
'thumbnail_max_h' => 'Max thumbnail height',
'thumbnail_max_h_help' => 'Maximum height in pixels that thumbnails may display in the listing view. Min 25, max 500.',
'two_factor' => 'Two Factor Authentication',
'two_factor_secret' => 'Two-Factor Code',
'two_factor_enrollment' => 'Two-Factor Enrollment',

View File

@@ -31,6 +31,7 @@ return array(
'create' => 'There was an issue creating the user. Please try again.',
'update' => 'There was an issue updating the user. Please try again.',
'delete' => 'There was an issue deleting the user. Please try again.',
'delete_has_assets' => 'This user has items assigned and could not be deleted.',
'unsuspend' => 'There was an issue unsuspending the user. Please try again.',
'import' => 'There was an issue importing users. Please try again.',
'asset_already_accepted' => 'This asset has already been accepted.',
@@ -40,6 +41,7 @@ return array(
'ldap_could_not_bind' => 'Could not bind to the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server: ',
'ldap_could_not_search' => 'Could not search the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'ldap_could_not_get_entries' => 'Could not get entries from the LDAP server. Please check your LDAP server configuration in the LDAP config file. <br>Error from LDAP Server:',
'password_ldap' => 'The password for this account is managed by LDAP/Active Directory. Please contact your IT department to change your password. ',
),
'deletefile' => array(

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