Compare commits

..

360 Commits

Author SHA1 Message Date
snipe
9247dc592b Removed debugging statements 2019-02-13 07:09:49 -08:00
snipe
35898e4ca0 Added asset tag to maintenances report 2019-02-13 07:09:16 -08:00
snipe
cd333fa93b Fixed wonky merge :( 2019-02-13 06:52:36 -08:00
snipe
533649f24e Merge branch 'develop' into dev-master-integration
# Conflicts:
#	.gitignore
#	.travis.yml
#	app/Console/Commands/LdapSync.php
#	app/Console/Commands/SendExpectedCheckinAlerts.php
#	app/Console/Commands/SendExpirationAlerts.php
#	app/Console/Commands/SendInventoryAlerts.php
#	app/Console/Kernel.php
#	app/Http/Controllers/Api/AssetsController.php
#	app/Http/Controllers/Api/ManufacturersController.php
#	app/Http/Controllers/Api/StatuslabelsController.php
#	app/Http/Controllers/Api/UsersController.php
#	app/Http/Controllers/AssetMaintenancesController.php
#	app/Http/Controllers/Assets/AssetsController.php
#	app/Http/Controllers/Auth/ForgotPasswordController.php
#	app/Http/Controllers/Auth/LoginController.php
#	app/Http/Controllers/Auth/ResetPasswordController.php
#	app/Http/Controllers/ReportsController.php
#	app/Http/Controllers/SettingsController.php
#	app/Http/Controllers/UsersController.php
#	app/Http/Transformers/AssetMaintenancesTransformer.php
#	app/Importer/Importer.php
#	app/Importer/ItemImporter.php
#	app/Importer/UserImporter.php
#	app/Importer/import_mappings.md
#	app/Models/Ldap.php
#	app/Models/License.php
#	app/Models/Location.php
#	app/Models/Recipients/AlertRecipient.php
#	app/Models/User.php
#	app/Providers/AppServiceProvider.php
#	composer.json
#	composer.lock
#	config/trustedproxy.php
#	config/version.php
#	public/js/build/all.js
#	public/js/build/vue.js
#	public/js/build/vue.js.map
#	public/js/dist/all.js
#	public/mix-manifest.json
#	resources/assets/js/components/importer/importer-file.vue
#	resources/lang/ar/admin/settings/general.php
#	resources/lang/bg/admin/settings/general.php
#	resources/lang/en-ID/admin/settings/general.php
#	resources/lang/en-ID/passwords.php
#	resources/lang/en/passwords.php
#	resources/lang/es-CO/passwords.php
#	resources/lang/es-ES/passwords.php
#	resources/lang/es-MX/passwords.php
#	resources/lang/es-VE/passwords.php
#	resources/lang/fi/admin/settings/general.php
#	resources/lang/id/admin/settings/general.php
#	resources/lang/id/passwords.php
#	resources/lang/ja/passwords.php
#	resources/lang/nl/passwords.php
#	resources/lang/pl/admin/settings/general.php
#	resources/lang/pl/passwords.php
#	resources/lang/pt-BR/admin/settings/general.php
#	resources/lang/pt-BR/passwords.php
#	resources/lang/ru/admin/settings/general.php
#	resources/lang/ru/admin/statuslabels/table.php
#	resources/lang/ru/passwords.php
#	resources/lang/sr-CS/general.php
#	resources/lang/sr-CS/mail.php
#	resources/lang/sv-SE/admin/settings/general.php
#	resources/lang/tr/admin/settings/general.php
#	resources/lang/tr/passwords.php
#	resources/lang/vi/admin/models/message.php
#	resources/lang/vi/admin/users/general.php
#	resources/lang/zh-CN/admin/settings/general.php
#	resources/views/importer/import.blade.php
#	resources/views/partials/bootstrap-table.blade.php
#	resources/views/partials/forms/edit/image-upload.blade.php
#	resources/views/users/edit.blade.php
#	resources/views/users/view.blade.php
#	tests/unit/ImporterTest.php
2019-02-13 06:42:52 -08:00
snipe
3b525f0009 Fixed #6705 - undefined is not a function (near '...$.validate...')
Not sure what happened here, but re-running webpack seems to have resolved it.
2019-02-13 05:16:12 -08:00
snipe
f1fa5bdaa9 Fixed (develop) #6704 - don’t apply gate to $arrays collection, just check that they can view assets 2019-02-13 04:56:13 -08:00
snipe
2768ea692e Removed extraneous js call 2019-02-13 04:24:37 -08:00
snipe
83c511e1c8 Removed gh-changelog - we don’t use it anymore 2019-02-13 04:24:23 -08:00
snipe
35e60fd151 Updated languages 2019-02-13 03:56:18 -08:00
snipe
471fcd1272 Set beta version tag 2019-02-13 02:27:56 -08:00
snipe
1e1d32dc85 Make user notes field editable via API 2019-02-13 01:32:00 -08:00
snipe
b317fb8d83 Fixed #6367 - pass table name and column_id to scopeCompanyables 2019-02-13 01:29:53 -08:00
snipe
245b3ca09f Fixed #6061 - Assigned user group cannot be removed 2019-02-12 23:49:42 -08:00
snipe
da34b82b3e Do not count deleted locations in managedLocation check on user delete 2019-02-12 23:33:01 -08:00
snipe
8e358faebc Fixed #6113 - use $asset->fill vs filled() to allow blanking values via API 2019-02-12 22:15:32 -08:00
herroworrd
3f7d2aebc7 Fixed #6634: Asset Import History fixes and optimizations (#6657)
* Starting work on asset history importer.

* Starting work on asset history importer.

* Added checkin target.

* Last change... importing history should also probably be an admin only task.

* Added caching for user and asset queries.

* Updated cache keepalive time to DateTimeInterface

* Updated cache keepalive time to DateTimeInterface
2019-02-08 16:05:56 -08:00
Steffen
5624ea14e7 Fix accessories edit error (#6698)
* Add accessories_upload_path singleton (used in accessories/edit)

* Fix indent
2019-02-08 15:43:11 -08:00
Steffen
3530603797 Simplify slack channel regexp and allow private (no # prefix) channels and direct messages (@ prefix) (#6699) 2019-02-08 15:41:54 -08:00
snipe
4fd469e07b Prevent editing special users demo mode 2019-02-04 19:13:55 -08:00
snipe
59cb1e561e Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2019-02-04 18:59:53 -08:00
snipe
4fe63d2966 Fixed #6633 - return 200 status code on asset API 2019-02-04 18:59:49 -08:00
Ivan Nieto
9ac40f705c Valo/ch37/cannot log in with generated password from (#6655)
* Cannot log in with generated password from [ch37]

* Cannot log in with generated password from [ch37]. Light cleanup

* Added the fetchHumanBoolean() method.

* Cleaning up the ternary
2019-01-31 19:58:54 -08:00
Colin Campbell
ef8e20f66b Alpine linux docker image (#6645)
* docker-alpine: Alpine linux container + apache 2.4 support

* docker-alpine: Force passport migrations (production mode Exception)

* docker-alpine: Copy default env in Dockerfile
2019-01-30 14:45:36 -08:00
snipe
9d21fc85bc Fixed #6644 - asset name not linked in Reports > Asset Maintenance Report 2019-01-25 20:59:54 -08:00
Ivan Nieto
79b41ee662 Importing with CSV into Encrypted Custom [ch86] (#6642) 2019-01-25 14:26:59 -08:00
Colin Campbell
f8d9301bd4 fix-docker-pecl: Fix Dockerfile after pear.php.net hack (#6641) 2019-01-25 13:41:39 -08:00
snipe
c1a4fbee16 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2019-01-25 13:34:57 -08:00
Colin Campbell
8c632f63b0 Misc ldap fixes (#6631)
* misc_ldap_fixes: Map location from OU, not group

* misc_ldap_fixes: Ensure ldap is initialised for import

* misc_ldap_fixes: Import Job Title

* misc_ldap_fixes: Import telephone, fix field select from schema

* misc_ldap_fixes: Cleanup login error-handling, fix boolean tests
2019-01-24 16:15:44 -08:00
Ivan Nieto
7d982c9ea6 Import page is blank [ch90] (#6636) 2019-01-24 15:34:50 -08:00
snipe
ec4161a959 Fixed typo 2019-01-24 15:21:38 -08:00
snipe
b326d8593b Fixed bad groups route on error 2019-01-24 14:56:47 -08:00
snipe
d1fe7abb18 Replaced custom deleted query scopes with onlyTrashed() 2019-01-24 14:47:44 -08:00
snipe
ffc51d6db6 Specify table name in deleted user display 2019-01-24 14:33:54 -08:00
snipe
394e51029e Only try to upload the audit file if one is provided 2019-01-24 14:04:06 -08:00
snipe
4b8f9d810b Fixed #6625 - include fully depreciated date in custom asset report 2019-01-24 12:38:17 -08:00
snipe
89a2ce1c6c Add @jackka as a contributor 2019-01-23 14:12:15 -08:00
snipe
c6ad7f80a8 Add @omyno as a contributor 2019-01-23 14:12:03 -08:00
snipe
7e6a59bdcc Add @KeenRivals as a contributor 2019-01-23 14:11:37 -08:00
snipe
8ebd9afd15 Add @reuser as a contributor 2019-01-23 14:11:23 -08:00
snipe
d1b9eddb34 Add @shinayoshi as a contributor 2019-01-23 14:11:11 -08:00
snipe
0394dd7ee5 Add @andreybolonin as a contributor 2019-01-23 14:10:59 -08:00
snipe
53c6c74a43 Add @fanta8897 as a contributor 2019-01-23 14:10:46 -08:00
snipe
65c5378a10 Add @Sxderp as a contributor 2019-01-23 14:10:35 -08:00
snipe
1c6cbbf8ee Add @smb as a contributor 2019-01-23 14:10:21 -08:00
snipe
10cb4ec401 Add @NMathar as a contributor 2019-01-23 14:10:06 -08:00
bricelabelle
5281713fd9 Added #6617: View licenses checked out to an asset with the hardware API (#6621) 2019-01-22 14:47:40 -08:00
snipe
7982b3f237 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2019-01-22 14:13:39 -08:00
snipe
07eead2dbf Fixed bug where assets could be deleted without being checked back in [ch38] 2019-01-22 14:13:30 -08:00
Ivan Nieto
3fa5976315 Language of mail doesn't show as intended by the settings. [ch87] (#6619)
* Fixes #5554. Language of mail doesn't show as intended by the settings.
2019-01-22 14:02:08 -08:00
NMathar
66f557d436 add user license api endpoint fix #6241 (#6616) 2019-01-22 13:48:15 -08:00
snipe
fdd6ddf61b Fixed favicon upload
[ch309]
2019-01-18 14:05:52 -08:00
snipe
4129463923 Added filesstem cache 2019-01-18 14:05:10 -08:00
Ivan Nieto
310ed0f1d3 Fix an issue when trying to checkin a license seat. [ch46] (#6599)
* Fix an issue when trying to checkin a license seat.

* Minor changes in the checkin view, so it can returns to the expected place where the checkin is launched.
2019-01-17 20:36:40 -08:00
snipe
1eace04ad9 Handle asset maintenances that do not have a valid asset associated 2019-01-16 02:26:42 -08:00
Steffen
74c099f0b3 fix LDAP/AD sync: function calls for password creation (#6581)
* - change generatePassword to be more secure (allow duplicate chars)
- move generatePassword from trait to helper
- fix summary output for sync command

* - Don't treat ldap_active_flag as boolean - fixes sync not working at all when ldap field is set
- Sync non activated users (But set activated=0)

* - Read user first before checking against user settings

* Fix failed logins to not throw exceptions
2019-01-15 14:05:47 -08:00
omyno
f0500e9797 Show user's signature in print view (#6471) 2019-01-15 14:03:40 -08:00
Wes Hulette
3e19509a49 Fixed: #6395 - Adjusted graph size (#6518)
* Fixed missing oauth tables during setup.

* Merge remote-tracking branch 'snipe-it-upstream/develop' into develop

* Merge remote-tracking branch 'snipe-it-upstream/develop' into develop



Merge remote-tracking branch 'origin/develop' into develop

* Fixed Pie Graph sizing on dashboard

Updated chart.js to 2.7.3
Added height to canvas
2019-01-15 14:02:49 -08:00
snipe
125938762b Patched #6565 to develop 2019-01-15 14:02:10 -08:00
Steffen
61c619660d Initialize customFormat to prevent Blade error when creating new fields (#6596) 2019-01-15 13:56:56 -08:00
Steffen
1de9087427 LDAP fixes (#6533)
* Add iCheck png files to webpack config (inconsistency for css <> png) and blue.png to public folder

* php 7.3 collect() fix (undefined variable)

* Fix travis ci

* Add iCheck png files to webpack config (inconsistency for css <> png) and blue.png to public folder

* php 7.3 collect() fix (undefined variable)

* change LDAP implementation from model to (singleton) service

* Re-apply check for content in ldap_server variable before parsing

* Update LDAP implementation

* Switch iCheck to minimal as referenced in js

* Don't init on load but on first access via init (returns ldap enabled status)

* Re-Enable notifications

* Re-add missing test target php versions

* Only init() once (singleton class, so ldap variable is already set)
2019-01-10 13:20:43 -08:00
shinayoshi
c23cdb0e31 Support Raspbian 9.x installs. (#6554)
* Updated code related to Raspbian.

Tested on Raspbian 9.6.
2019-01-10 13:18:30 -08:00
Kasey
5e06d53d8c buildable dockerfile (#6525)
Awesome, thanks!
2018-12-17 15:29:05 -08:00
Brady Wetherington
89a325c0ea Handle missing Flysystem env vars; let Debugbar autodiscover (#6514) 2018-12-17 14:04:56 -08:00
Brady Wetherington
1fdb057199 New LDAP system tries to load LDAP configuration before determining (#6512)
whether or not LDAP is actually enabled
2018-12-17 14:04:35 -08:00
Ivan Nieto
876ff2ef72 Fixes #6341. Some style changes in this fix. (#6492) 2018-12-14 18:08:33 -08:00
KeenRivals
f5ba2106cd Fix snipeit:unescape not unescaping quotes (#6506) 2018-12-14 18:08:00 -08:00
snipe
67f6df2be3 Fixed (develop) #4568 - escaping values in custom report 2018-12-12 19:40:15 -08:00
snipe
71f2440ddf Moved contributors to the bottom of the README 2018-12-12 18:52:45 -08:00
snipe
426dae0310 Fixed methods for PHP 7.3 compact() 2018-12-12 18:16:39 -08:00
snipe
f91fd3c91f Updated language string to include XML 2018-12-12 17:46:19 -08:00
snipe
86cc1a228d Fixed typo 2018-12-12 16:45:49 -08:00
snipe
394d265b96 Added XML to uploadable file type - re: #6099 2018-12-12 16:45:09 -08:00
snipe
6a6923b1d8 Fixed parse_url() expects parameter 1 to be string, null given 2018-12-12 16:37:27 -08:00
Steffen
28edf13457 WIP develop ldap fixes (errors, check if disabled, parsing in one place) (#6500)
* Fix errors and exception when ldap settings are empty (even with ldap disabled)

* Re-add newline at the end of file
2018-12-11 21:01:11 -08:00
snipe
93947b09c5 Small fix for #6484 - corrected LDAP sync command name 2018-12-11 15:18:30 -08:00
snipe
8b8ce256f5 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-12-11 13:07:48 -08:00
snipe
4cc82a808f Updated composer 2018-12-11 13:07:23 -08:00
snipe
8313a069bf Temp fix for blank LDAP values when LDAP is disabled 2018-12-11 13:07:14 -08:00
Wes Hulette
b38d07064b Fixed: #5896 Custom fields of type "CUSTOM REGEX" are always saved as "ANY" (#6381)
* Fixed missing oauth tables during setup.

* Custom fields of type "CUSTOM REGEX" are always saved as "ANY" 

Fixes #5896

* Updated per PR

Fixed spelling

* Fixed logic error

Moved conditional code from view to controller
Added getFromatType function for dropdown
2018-12-06 20:05:04 -08:00
snipe
886b6dd265 Add @benrubson as a contributor 2018-12-06 15:19:16 -08:00
snipe
3d6bd73ed3 Port PR #6485 to develop 2018-12-06 14:40:28 -08:00
snipe
a874cc32a8 Fixed bonked merge conflict 2018-12-06 14:20:01 -08:00
Wes Hulette
34246ee4ef [WIP] v5 Develop: New LDAP implementation (#6352)
* Fixed missing oauth tables during setup.

* WIP New LDAP implementation

* WIP New LDAP implementation

* WIP New LDAP implementation


Merge remote-tracking branch 'origin/WIP_LDAP' into WIP_LDAP

* WIP New LDAP implementation

Added Adldap2 to handle ldap intergration.

* Updated per PR quality review

* Added specific LDAP settings method

* Corrected version number

* Added return documentation

* Added imports

* Changed class to be injected into controller

* Updated with PR suggestions
2018-12-06 14:05:43 -08:00
snipe
3ed2f55696 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-12-05 19:56:18 -08:00
snipe
09c4dd4891 Added ability to upload favicon and email logo
todo: refactor the image upload/resize for less copypasta
2018-12-05 19:56:12 -08:00
snipe
81f9f717dc Updated favicon with new branding 2018-12-05 18:55:23 -08:00
snipe
fb502df089 Fixed dumb code indenting 2018-12-05 18:36:39 -08:00
Ivan Nieto
f91e340178 Fixes #6341. Adding condition that checks offset of licenses seats when the user looks other page than the first. (#6392) 2018-12-05 18:09:54 -08:00
snipe
f6c0e7cc9c Fixed #6464 - added company name to searchable field in user search 2018-12-05 17:20:22 -08:00
snipe
9f73fcf308 Added #6463 - ability to override session path in .env 2018-12-05 17:17:11 -08:00
Evgeny
dd7db95135 A couple hardcoded strings replaced in according with #6406 (#6412)
Hardcoded strings was replaced with corresponding placeholders.
Log statuses mentioned in #6406 wasn't translated cause it needs more separate complicated development
2018-11-08 15:20:44 -08:00
snipe
b6daad7573 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-11-02 17:15:08 -07:00
snipe
67d2953080 Fixed #6386 - licenses not searching on category name 2018-11-02 17:15:04 -07:00
Wes Hulette
0d2eef5894 v5 Develop Fixed: Setup error because setting table does not exist (#6390)
* Fixed missing oauth tables during setup.

* Merge remote-tracking branch 'snipe-it-upstream/develop' into develop

* Merge remote-tracking branch 'snipe-it-upstream/develop' into develop



Merge remote-tracking branch 'origin/develop' into develop

* Fixed error during setup when settings table is not present
2018-11-02 12:23:41 -07:00
Wes Hulette
88b1da4260 Added: Caching of settings (#6378)
* Fixed missing oauth tables during setup.

* Cache settings

Cache the setting to reduce unnecessary database calls
2018-11-01 19:59:50 -07:00
Wes Hulette
0730685c29 v5 Develop Fixed: LDAP Settings Password being updated when field is empty (#6384)
* Fixed missing oauth tables during setup.

* Fixed password saving

Previous Input::has always returned true, even if password was an empty string.
2018-11-01 13:36:30 -07:00
snipe
ea91d59ffc Use username instead of email address in password reset (#6382)
* Switch to use username instead of email

* Fixed indenting

* Updated password language

* Updated blades to reflect username instead of email

* Changed password/reset controllers to use username instead of email

* Redirect to login page instead of repeating the password reset form
2018-10-31 18:03:24 -07:00
snipe
ce8d47b2b4 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-10-31 15:19:18 -07:00
snipe
65aef11ae3 Fixed #5879 - added user department filter for custom report 2018-10-31 15:19:13 -07:00
snipe
8f22cf7c3c Added dept update API endpoint 2018-10-31 14:31:57 -07:00
Tim Bishop
7835cc34c8 Insert audited items to the top of the list. (#6376)
When doing a large number of items they soon disappear off the bottom of
the screen. It's handy to be able to check the result as it's happening
so adding them to the top of the list keeps the most recent items visible.

Move the "Processing..." spinner to the header so it stays at the top.
2018-10-31 11:13:08 -07:00
snipe
2cecd5e056 One more fix for #6175 (non-accessories) 2018-10-31 10:23:47 -07:00
snipe
5da9120870 Fixed #6175 - crashing when accessory has been deleted
My guess would be this crash (reported in #6175) was happening because of deleted accessories/accessory rows.
2018-10-31 10:23:32 -07:00
snipe
5faf8bfcd0 Fixed error on unaccepted asset report 2018-10-29 14:39:42 -07:00
snipe
f0e6a5c905 Fixed error on asset creation 2018-10-29 14:35:34 -07:00
snipe
a483e9365b Add APP_LOG_LEVEL to new logging config 2018-10-25 21:01:14 -07:00
snipe
e443a576f7 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-10-19 16:46:53 -07:00
snipe
20b26effdb Fixed #6349 - add view permission for print all assigned 2018-10-19 16:46:46 -07:00
Wes Hulette
b3672d6365 Fixed: Settings seeder error (#6334)
* Fixed missing oauth tables during setup.

* Fixed seeding error

Required setting pwd_secure_min was missing from seeder.
2018-10-17 13:38:27 -07:00
Wes Hulette
a975117eaf Fixed: #6325 & #6317 (#6335)
* Fixed missing oauth tables during setup.

* Fixed missing manager_id and department_id
2018-10-17 13:38:13 -07:00
Wes Hulette
465b69516d Fixed: #6259 - Remove remote JS/CSS file loading (#6330)
* Fixed missing oauth tables during setup.

* Remove remote JS/CSS file loading


Updated gravatar to return to default image

* Updated default avatar path to user url

Removed local html5shim & response js files.
Removed copying html5shim & respond js file to public directory
2018-10-17 12:52:01 -07:00
snipe
2ded2ff53a Make dashboard snapshots clickable 2018-10-15 18:19:30 -07:00
snipe
74c20e62d5 Fixed - suppress the asset create button if the user is not allowed to create assets 2018-10-15 17:57:47 -07:00
snipe
0dbce93d1b Better logic for disallowing company selection when user is not a superadmin and FCS is enabled 2018-10-15 17:57:28 -07:00
snipe
9797412d23 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-10-15 17:02:39 -07:00
snipe
6b6cf06530 Display asset name if one exists and the user cannot edit 2018-10-15 17:02:31 -07:00
snipe
539c3023b9 Added - check whether the user can edit an asset before allowing them to change the name on checkout 2018-10-15 16:52:45 -07:00
snipe
78b6e84774 Allow min:0 for consumables 2018-10-15 16:52:20 -07:00
Joe Ferguson
704209de9c Fixed #6301: Do not allow duplicate asset tags (#6324)
* Fixed #6301: Do not allow duplicate asset tags

* Display error message for single asset_tag failure
2018-10-11 17:15:09 -07:00
snipe
b8f8d49927 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-10-11 15:29:48 -07:00
snipe
63a2727077 Fixed #6323 - typo in link for low inventory 2018-10-11 15:29:38 -07:00
Ivan Nieto
5516978f2e #5696 proposed fix (#6314)
* Another 'or' caught in a blade template

* Added the requireAcceptance() method in LicenseSeat.php. Changed the permission in checkout-license mail to view.

* Getting right the permission in the view checkout-license
2018-10-11 14:08:09 -07:00
Wes Hulette
19ee62f704 Fixed: Missing JS file import (#6320)
* Fixed missing oauth tables during setup.

* Fixed missing JS import
2018-10-11 13:51:37 -07:00
snipe
dff674b4ab Production assets 2018-10-09 16:12:43 -07:00
snipe
73788817ff Fixed #6306 - seed settings if no record exists 2018-10-09 16:12:26 -07:00
Wes Hulette
d715ecf41d Fixed: Tooltips (#6309)
* Fixed missing oauth tables during setup.

* Fixed missing tooltips

Updated spelling in bootstrap-table.blade.php to use bootstrap data element. 
Using custom jquery-ui without tooltips to fix conflict issue.

* Removed custom jQuery ui file added fix in bootstrap.js file
2018-10-09 15:20:27 -07:00
Wes Hulette
6818901f7d Fixed: Admin LTE left menu bar not working (#6308)
* Fixed missing oauth tables during setup.

* Updated per AdminLTE upgrade guide.
2018-10-09 15:20:12 -07:00
Wes Hulette
19bc158ff4 Fixed tooltips and iCheck (#6303)
* Fixed missing oauth tables during setup.

* Fixed duplicate style tag

This was causing webpack issues.

* Deleted erroneously created directory

* Removed duplicate imports

* Fixed tooltips, iCheck audit page

https://github.com/snipe/snipe-it/pull/6285#issuecomment-427235280

* Merge remote-tracking branch 'origin/#6285-Fixes' into #6285-Fixes
Fixed Manifest file
2018-10-05 07:41:04 -07:00
snipe
a516e4278e Remove company select option if the user is not an admin and FCS is enabled 2018-10-05 07:30:42 -07:00
snipe
c21524f240 Fixed NaN when no asset tag is provided 2018-10-05 05:42:28 -07:00
snipe
ecb8204c3c Associate serials with multiple asset creation 2018-10-05 05:30:13 -07:00
snipe
476b58632b Handle array of asset tags
TODO: figure out how to display validation errors more sanely
2018-10-05 04:34:47 -07:00
snipe
d4a97d0431 Increase max fields in multi-asset creation 2018-10-05 04:18:06 -07:00
snipe
ade9a0c995 Removed server-side validation right now
Getting an unexpected keyword ‘export’ error. I’ll come back to this.
2018-10-05 03:47:58 -07:00
snipe
7e33051c7a Remove console logging 2018-10-05 03:44:30 -07:00
snipe
4a448ba5f9 Increment tag based on first 2018-10-05 03:43:40 -07:00
snipe
8e90ed00d8 Added alt tags to logo 2018-10-05 03:43:16 -07:00
snipe
81d8c1cc52 Fixed remove-asset-tag javascript on asset edit, added server side validation support 2018-10-05 02:51:14 -07:00
snipe
4920dced6e Enabled toggleDisabled on form validator 2018-10-05 01:12:14 -07:00
snipe
4a4fc38c43 Style updates 2018-10-05 00:57:18 -07:00
snipe
907d1d0f61 Validate if name is required 2018-10-05 00:46:27 -07:00
snipe
6f71abd0e0 Fixed improper html nesting 2018-10-05 00:45:58 -07:00
snipe
c1b0b8dee2 Move validator to main layout 2018-10-05 00:45:45 -07:00
snipe
a6449fa16e Added jquery-form-validator 2018-10-04 23:50:57 -07:00
snipe
4cc1351bdd More JS tweaks (WIP) 2018-10-04 23:37:52 -07:00
snipe
701beeac2a Merge branch 'develop' into features/create_multiple_asset_tags_on_asset_create 2018-10-04 22:08:18 -07:00
snipe
6489081cd6 Pass variable to edit-form to determine whether we should have a top submit button
This is just so very short forms don’t look too silly
2018-10-04 21:19:25 -07:00
snipe
408f9978e9 More help text updates 2018-10-04 21:14:45 -07:00
snipe
4a7f61b554 Tenporarily replacing the blue@2x.png for icheck 2018-10-04 21:00:13 -07:00
snipe
db765e4a0b Reformatted help popover 2018-10-04 20:51:50 -07:00
snipe
54d916fd05 Pulled “more info” popover header into translated string 2018-10-04 20:41:09 -07:00
snipe
8d680d071b Create help translation file 2018-10-04 20:37:49 -07:00
snipe
252341fd5c Pulled help icon text into its own blade 2018-10-04 20:35:07 -07:00
snipe
c8210131b4 Updated assets 2018-10-04 20:24:59 -07:00
snipe
3843764d15 Added save button at top of form as well 2018-10-04 20:24:53 -07:00
snipe
accafbf1eb Added basic JS to edit blade 2018-10-04 19:28:56 -07:00
snipe
ef9817e12d Updated to latest rollbar 2018-10-04 18:24:41 -07:00
snipe
b5211b2dd5 Fixed #6291 - send-welcome argument in cli importer 2018-10-04 04:44:12 -07:00
snipe
0a72751b37 Fixed issue where admin users could disable activation when editing their own profile 2018-10-04 02:23:12 -07:00
Wes Hulette
3c59452e33 Fixed Tooltips and Rounded corners from previous cleanup (#6285)
* Fixed missing oauth tables during setup.

* Fixed tooltip and square boxes
2018-10-03 13:17:08 -07:00
snipe
84848fabd7 Fixed #6284 - missing checkout information on Status Labels API assets endpoint 2018-10-03 10:30:24 -07:00
Wes Hulette
4a88e155c4 Fixed: #6263 V5 (develop) - Missing oauth tables when using the setup wizard (#6268)
* Fixed missing oauth tables during setup.

* Merged develop changes

Removed PHP_CS file
2018-10-03 10:06:24 -07:00
snipe
66a9421fac Production assets 2018-10-03 01:07:47 -07:00
snipe
be609f4a8f Merge branch 'jwhulette-V5_cleanup_js' into develop 2018-10-03 00:56:49 -07:00
snipe
90b8acdd3b Merge branch 'V5_cleanup_js' of https://github.com/jwhulette/snipe-it into jwhulette-V5_cleanup_js
# Conflicts:
#	public/mix-manifest.json
2018-10-03 00:56:09 -07:00
snipe
c8ad45b11e Port import manager code to develop 2018-10-03 00:52:29 -07:00
snipe
8937edb97e Better validation for manager_id 2018-10-03 00:26:51 -07:00
Djamon Staal
80393d73ae Public function save() missing array. (#6276)
The class was missing as a result that the composer was unable to update and the site returned a 500 error
```
$ composer update
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Package guzzle/guzzle is abandoned, you should avoid using it. Use guzzlehttp/guzzle instead.
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover

In Asset.php line 30:
                                                                               
  Declaration of App\Models\Asset::save($params = Array) should be compatible  
   with Illuminate\Database\Eloquent\Model::save(array $options = Array)       
                                                                               

Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1
```
2018-10-02 04:40:20 -07:00
Wes Hulette
1742867df1 Removed inline signature.css
Added to webpack
2018-10-01 08:20:31 -04:00
Wes Hulette
ce0a40a20c Merge branch 'develop' into JS_Cleanup 2018-10-01 08:07:07 -04:00
snipe
e5e8c068ea Fixed mac address regex 2018-09-29 22:41:17 -07:00
snipe
366c1f81cc Added mac address to seeder 2018-09-29 22:21:32 -07:00
snipe
4975f9100c Added more seeders for better custom field displays on seeding 2018-09-29 22:17:36 -07:00
snipe
a481fa2921 Added accessories images for seeder 2018-09-29 22:17:14 -07:00
snipe
79367642b1 [WIP] Added #5957 - Flysystem support (#6262)
* Added AWS url to example env

* Upgrader - added check for new storage path and attempt to move

* Ignore symlink

* Updated paths for models

* Moved copy methods

* Added AWS_URL support

For some reasin, Flysystem was generating the wrong AWS url (with a region included)

* Switch to Flysystem for image uploads

* Nicer display of image preview

* Updated image preview on edit blades to use Flysystem

* Twiddled some more paths

* Working filesystems config

* Updated Asset Models and Departments to use Flysystem

* Janky workaround for differing S3/local urls/paths

* Try to smartly use S3 as public disk if S3 is configured

* Use public disk Storage options for public files

* Additional transformer edits for Flysystem

* Removed debugging

* Added missing use Storage directive

* Updated seeders to use Flysystem

* Default logo

* Set a default width

We can potentially override this in settings later

* Use Flysystem for logo upload

* Update downloadFile to use Flysystem

* Updated AssetFilesController to use Flysystem

* Updated acceptance signatures to use Flysystem

* Updated signature view to use Flysystem

This isn’t working 100% yet

* Use Flysystem facade for displaying asset image

* Set assets path

Should clean all these up when we’re done here

* Added Rackspace support for Flysystem

* Added Flysystem migrator console command

* Added use Storage directive for categories

* Added user avatars to Flysystem

* Added profile avatar to Flysystem

* Added the option to delete local files with the migrator

* Added a check to prevent people from trying to move from local to local

* Fixed the selectlists for Flysystem

* Fixed the getImageUrl method to reflect Flysystem

* Fixed AWS copy process

* Fixed models path

* More selectlist updates for Flysystem

* Updated example .envs with updated env variable names

* *sigh*

* Updated non-asset getImageUrl() methods to use Flysystem

* Removed S3 hardcoding

* Use Flysystem in email headers

* Fixed typo

* Removed camera support from asset file upload

We’ll find a way to add this in later (and add that support to all of the other image uploads as well)

* Fixed path for categories

* WIP - Switched to standard handleImages for asset upload.

This is currently broken as I refact the handleImages method. Because the assets store/create methods use their own Form Request, the handleImages method doesn’t exist in that Form Request so it wil error now.

* Fixed css URL error

* Updated Debugbar to latest version (#6265)

v3.2 adds support for Laravel 5.7

* Fixed: Missing CSS file in basic.blade.php (#6264)

* Fixed missing CSS file in basic.blade.php

* Added

* Changed stylesheet import for authorize.blade.php

* Updated composer lock

* Added AWS_BUCKET_ROOT as env variable

* Use nicer image preview for logo upload

* Removed AssetRequest form request

* Removed asset form request, moved custom field validation into model

* Added additional help text for logo upload

* Increased the size of the image resize - should make this a setting tho

* Few more formatting tweaks to logo section of branding blade preview

* Use Flysystem for asset/license file uploads

* Use Flysystem for removing images from models that have been deleted

* Enable backups to use Flysystem

This only handles part of the problem. This just makes it so we can ship files to S3 if we want, but does not account for how we backup files that are hosted on S3

* Use Flysystem to download license files

* Updated audits to use Flysystem
2018-09-29 21:33:52 -07:00
Wes Hulette
fc8096f8dc Fixed: Missing CSS file in basic.blade.php (#6264)
* Fixed missing CSS file in basic.blade.php

* Added

* Changed stylesheet import for authorize.blade.php
2018-09-29 14:15:25 -07:00
Wes Hulette
22c57c2862 Updated Debugbar to latest version (#6265)
v3.2 adds support for Laravel 5.7
2018-09-29 14:14:20 -07:00
Wes Hulette
5cb6744c1b Merge remote-tracking branch 'snipe-origin/develop' into develop 2018-09-29 11:30:33 -04:00
snipe
153a711de4 Fixed css URL error 2018-09-28 23:18:16 -07:00
snipe
71f60990db Ignore simlink 2018-09-28 23:18:04 -07:00
snipe
32951bbc90 Make CustomFieldset rules public instead of protected 2018-09-28 15:18:23 -07:00
Wes Hulette
aee15e8af4 Fixed select box rendering issue. (#6260)
Changed order of CSS files for combining
2018-09-28 15:18:15 -07:00
Wes Hulette
3016dcc616 WIP 2018-09-28 17:30:52 -04:00
Wes Hulette
c090a2f0b6 Fixed script imports in setup.blade.php 2018-09-28 17:28:49 -04:00
Wes Hulette
11f128a84f Moved chart.js library into vendor.js 2018-09-28 17:28:49 -04:00
Wes Hulette
991a00b38f Updated pGenerator jQuery Plugin to v1.0.5 2018-09-28 17:27:34 -04:00
Wes Hulette
e80dfc886e Added admin-lte-options file, moved pGenerator to main js file 2018-09-28 17:27:06 -04:00
Wes Hulette
b84eb836f4 Cleaned up JS
Removed unnecessary JS files
Moved JS to app & vendor, so neither file is so large, plus they can be downloaded concurrently.
2018-09-28 17:27:06 -04:00
Wes Hulette
b09afef46f Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-09-28 17:17:48 -04:00
Wes Hulette
ecf77e60df Added: #6246 - CSS Cleanup (#6247)
* Streamlined CSS by using NPM

Removed unnecessary CSS files
Merged CSS files into one file

* Streamlined CSS by using NPM

Removed unnecessary CSS files
Merged CSS files into one file

* Removed override.less

* Moved bootstrap-color-picker css to file

Removed inline calls

* Changed css import on setup.blade.php

* Updated signature-pad.css import

* NPM Prod compile

* Fixed font family loading issue.
2018-09-28 14:09:48 -07:00
snipe
a5abb3e6a6 Fixes #6252 - activated flag not checked when editing active user 2018-09-28 11:19:43 -07:00
Wes Hulette
ea63936947 Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-09-28 08:29:33 -04:00
snipe
68a04c7a23 Fixed #4151 - Undefined index: samaccountname on LDAP import 2018-09-27 16:13:27 -07:00
Wes Hulette
b64924440f Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-09-27 16:39:07 -04:00
snipe
e5d0f74ba7 Fixed #6248 - add free seats to licenses API endpoint 2018-09-27 12:07:13 -07:00
Ivan Nieto
309f102745 Weird syntax in layout.blade.php. Change the use of 'or' operator to the null coalesce operator (#6240) 2018-09-26 22:33:34 -07:00
Wes Hulette
16e56646b8 Fixed #5965: Allow multiple alert email addresses (#6233)
* Fixed #5965: Allow multiple alert email addresses

* Style changes based on PR feedback.
2018-09-26 14:07:41 -07:00
Wes Hulette
82affd5154 Added PHP_CS & PHPMD files (#6234)
Added PHP_CS & PHP mess detector file to help with code standardization.
2018-09-26 12:10:11 -07:00
Ivan Nieto
73e88faa00 Using changes proposed by dmeltzer to eliminate has_licenses function in some tests code (#6218) 2018-09-25 14:01:21 -07:00
snipe
bcf9d2f75c Don’t require statuslabel view to check for deployable status 2018-09-25 13:39:54 -07:00
nix
f85e3edfc7 fixed checking permissions for users with no permissions set (#6229)
When a user has no permissions set (=NULL) in the database (like after an
LDAP import) but is a member of a group with permissions, those group
permissions would not have be applied, effectively denying every access
regardless of group permissions.
2018-09-25 13:24:50 -07:00
snipe
115f718cd9 Fix view if model doesnt have a manufacturer 2018-09-24 19:10:24 -07:00
snipe
9108ff8caa fixed typo 2018-09-24 19:06:12 -07:00
snipe
d5cf0f1fbd Prevent deleting manufactureres via API if they have items/models 2018-09-24 19:04:00 -07:00
Brady Wetherington
c97db3259f Merge pull request #5913 from tilldeeke/refactore-checkout-checkin-notification-sending
Refactor: Decouple checkin/checkout notifications from logging
2018-09-21 18:18:27 -07:00
snipe
4b2093b485 Added counts to location show() API method 2018-09-21 15:51:26 -07:00
snipe
24aaa36532 Applied 3159e7713a to develop 2018-09-19 17:27:34 -07:00
snipe
d84366c6c5 Add empty array to groups if none filled in 2018-09-19 17:24:34 -07:00
Ivan Nieto
b692f67779 Revision of #5471 (#6148)
* Search functionality in accessories/{accessory} issue #5471:
From the collection of users displayed just filtered the data with the method where() and concat()
for the user can search for first name or last name. The solution is case sensitive.

* A better fix to issue #5471. Now using the established relationship to querying for the users. Also Case-insensitive.

* Fixed previous commit that has magic number in the find method parameter of AccessoriesController.
2018-09-12 22:50:45 -07:00
Juho Taipale
05b03df600 Fix for issue #6165 (#6168)
* Fix problem when using ValidatingTrait

* Checking that email alerts are enabled when trying to send expected check-in alerts (fix for issue #6169)
2018-09-12 22:49:50 -07:00
Till Deeke
e445e201f3 Adds a migration to create checkout acceptances
This creates the checkout acceptances for assets (basically what was previously shown in the „Unaccepted Assets“-report) when migrating the database
2018-09-10 17:37:24 +02:00
Till Deeke
bbd1d6059a Fixes errors in tests 2018-09-10 17:16:07 +02:00
Till Deeke
86f49d34c3 Redirects users from old acceptance screen to new overview 2018-09-10 17:13:16 +02:00
Till Deeke
007e8fbdf9 simplified checkout event handling per @uberbrady’s suggestion
This generalizes the checkout events into the CheckoutableCheckedOut and CheckoutableCheckedIn events.
2018-09-10 16:40:26 +02:00
snipe
e45b4efa1a Better info buttons on audit
TODO: translated strings instead
2018-09-07 18:08:18 -07:00
snipe
10d19be66c Added manager ID to validation 2018-09-07 17:27:31 -07:00
snipe
b903ca05f7 Added manager ID to fillable fields in Location model
This should enable the API to update manager ID
2018-09-07 17:27:19 -07:00
Wes Hulette
1dff71e4df Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-09-07 13:36:12 -04:00
snipe
827295a989 Fixed tooltips 2018-09-07 05:39:56 -07:00
snipe
ff879e2018 Added the ability to update asset location when auditing - per #5854 2018-09-07 05:39:41 -07:00
snipe
e89b33f865 Enable popovers 2018-09-07 05:09:24 -07:00
snipe
181e75adb4 Apply hotfix for fixed category model scope 2018-09-07 03:56:17 -07:00
snipe
827a86b2ef Only send inventory report to users if they have things checked out to them 2018-09-07 03:08:32 -07:00
snipe
451f4c3320 Bumped watson validating package version, laravel to v5.7.2 2018-09-07 03:08:08 -07:00
snipe
77cdb2f409 Added console command to send inventory reports to users 2018-09-07 02:24:41 -07:00
snipe
26fd7f7e79 I have no idea why this was necessary suddenly 2018-09-06 14:28:04 -07:00
snipe
b80b91514d Fixed Laravel 5.7 compatibility issue re: boot() method
This was causing the error: Undefined index: App\Models\CustomField (View: /Users/snipe/Sites/snipe-it/snipe-it/resources/views/hardware/index.blade.php)

and pointed to the Eloquent model library method:

```
protected function initializeTraits()
    {
        foreach (static::$traitInitializers[static::class] as $method) {
            $this->{$method}();
        }
    }
```

Thanks to https://github.com/laravel/framework/issues/25455 for the clue.
2018-09-05 19:27:37 -07:00
snipe
60459d07ce Added email image auto-embed support 2018-09-05 18:57:49 -07:00
snipe
84cc90e427 Added alt text to email headers 2018-09-05 18:57:36 -07:00
snipe
bd40bd5a22 Upgrade to laravel 5.7.0 2018-09-05 14:58:07 -07:00
snipe
0e46b6fd42 Fixed #6152 - typo in upgrade script 2018-09-05 14:58:07 -07:00
Wes Hulette
161271b53a Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-09-04 12:04:53 -04:00
Wes Hulette
a865f79d5e Fixed dashboard tables not showing data (#6143)
Needed data-pagination="true" set
2018-08-29 12:29:47 -07:00
Wes Hulette
e9f6cbc97f Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-08-29 12:45:56 -04:00
Wes Hulette
59559b1966 Merge remote-tracking branch 'snipe-it-upstream/develop' into develop 2018-08-28 16:39:07 -04:00
snipe
a8b5fbde91 Add @inietov as a contributor 2018-08-28 13:31:36 -07:00
Ivan Nieto
0014ef054b Search functionality in accessories/{accessory} issue #5471: (#6070)
From the collection of users displayed just filtered the data with the method where() and concat()
for the user can search for first name or last name. The solution is case sensitive.
2018-08-28 13:25:16 -07:00
snipe
652548957d Add @Seldaek as a contributor 2018-08-28 13:15:21 -07:00
snipe
761ea4e581 Add @liquidhorse as a contributor 2018-08-28 13:15:08 -07:00
snipe
9c24d4300c Add @VELIKII-DIVAN as a contributor 2018-08-28 13:14:55 -07:00
snipe
39ab488d90 Add @patrict as a contributor 2018-08-28 13:14:41 -07:00
snipe
ec67d03a7c Add @jwhulette as a contributor 2018-08-28 13:14:26 -07:00
Wes Hulette
bfb5920677 Added #5956 - Moved bootstrap-tables to npm (#6139)
Created single bootstrap-tables js and css file.
2018-08-28 13:10:27 -07:00
Wes Hulette
0c02ff70f6 Merge pull request #1 from snipe/develop
Develop
2018-08-28 15:47:16 -04:00
patrict
cf2d2ecdfc Depreciation Report: (#6135)
* Added monthly depreciation column
* Right aligned amount column headings
* Added monthly depreciation heading text for en, en-GB & af
2018-08-28 12:40:06 -07:00
Wes Hulette
3831ee9f5a Fixed #5811 - Non US Characters in user export (#6132)
* Added Freebsd as vagrant machine for development

* Ran npm audit fix

Manually added peer depenencies

* Added charset=UTF-8 to content-type

Removed reference to throttle model as is is not longer included.
2018-08-28 12:37:58 -07:00
Dmitriy Minaev
bc8fa31eb2 Add depreciation with half-year convention. Fixed #1237 (#6128)
* Add half-year convention in depreciation for Models/Depreciable.php

* Add a setting for the depreciation method

* Integrate half-year convention inside working output

* fix: add more checks at Depreciable.php

* depreciation value rounding

* Codestyle fix
2018-08-28 12:32:46 -07:00
liquidhorse
678ba228cb Fixed #6079: QR Code unnecessarily squished. (#6080)
* Fixed 2D barcode from being squished unnecessarily when 1D barcode is omitted.
Added one character of whitespace for code readability.
2018-08-28 12:29:50 -07:00
snipe
5b8cbe29e1 Possible fix for #5054 - OpenLDAP (non-AD) LDAP users being deactivated 2018-08-27 14:51:37 -07:00
snipe
0b8d70c7ec Fixed #6124 2018-08-23 21:06:04 -07:00
snipe
e4f6aefdad Added self-checkout permission option 2018-08-21 23:26:12 -07:00
snipe
771265113e Added cookie serialization for Laravel v5.6.30 2018-08-21 22:40:14 -07:00
snipe
3c23745508 Bumped to laravel 5.6.30
Lower Laravel versions still triggered Roave/SecurityAdvisories :-/
2018-08-21 20:08:34 -07:00
snipe
f6971b8ab2 Fixed #6082 - don’t show expected checkin in email if none given 2018-08-16 14:30:06 -07:00
snipe
aed769c0be Honor active status for forgotten password request forms 2018-08-14 20:05:57 -07:00
snipe
1543cdbc61 Set activated checkbox to 1 by default on new user 2018-08-14 18:17:37 -07:00
snipe
ce45c3af4e Updated password reset language 2018-08-14 18:11:32 -07:00
snipe
25097bce31 Only allow activated users to reset their password 2018-08-14 18:04:27 -07:00
Till Deeke
62195a805a Adding some comments 2018-08-06 14:47:26 +02:00
Till Deeke
8c96e8fd4b Updates asset acceptance report to show unaccepted assets 2018-08-06 14:47:26 +02:00
Till Deeke
1bdf71b584 Handle side effects of accepting/declining
When declining an asset, it gets checked in.
2018-08-06 14:47:26 +02:00
Till Deeke
8648d53d25 Adds checkout acceptances
A checkout acceptance gets generated for every item that needs to be checked out. This resource tracks the user user who can accept the item and their signature
2018-08-06 14:47:26 +02:00
Till Deeke
6b05106dcb Moves license checkout stuff to the license seat
Since we are really checking out a license seat instead of the whole license, we operate the checkin/checkout on the license seat instance.
2018-08-06 14:47:26 +02:00
Till Deeke
43437aac14 Adds acceptable contract to asset 2018-08-06 14:46:10 +02:00
Till Deeke
830a6cf67e Adds accepting/declining to new controller 2018-08-06 14:46:10 +02:00
Till Deeke
39e6b59335 Fixes some typos 2018-08-06 14:46:10 +02:00
Till Deeke
72b43b6526 Updates checkout notifications to use new routes for accepting 2018-08-06 14:46:10 +02:00
Till Deeke
e0423418d2 Moves logging checkin/checkout to separate listener 2018-08-06 14:46:10 +02:00
Till Deeke
e24f292a1a Updates checkout events to not depend on log 2018-08-06 14:46:10 +02:00
Till Deeke
17fc59f989 Adds back the checkin/checkout events after #5916 2018-08-06 14:46:10 +02:00
Till Deeke
775e46288e Cleanup of model attributes 2018-08-06 14:46:10 +02:00
Till Deeke
722f032895 Remove notification sending from loggable trait 2018-08-06 14:46:10 +02:00
Till Deeke
112a532618 Listen for checkout events and send appropriate notifications 2018-08-06 14:46:10 +02:00
Till Deeke
ef76908fce Listen for checkin events and send the appropriate notifications 2018-08-06 14:46:10 +02:00
Till Deeke
4a71542f23 Cleanup checkin notification constructors 2018-08-06 14:46:10 +02:00
Till Deeke
ea64abc607 Adds checkout events 2018-08-06 14:46:10 +02:00
Till Deeke
f0acf47101 Adds checkin events 2018-08-06 14:46:10 +02:00
Till Deeke
92a2a5ccbc Adds listeners for checking/checkout events 2018-08-06 14:46:10 +02:00
Daniel Meltzer
2d0df24ef3 Check for an existing username before getting creative. Fixes #6016 (#6039) 2018-08-03 16:35:13 -07:00
snipe
1a660911e7 Check for minimum PHP version in setup 2018-08-02 21:36:18 -07:00
snipe
84f20b6287 Added php version check to upgrade.php 2018-08-02 20:55:52 -07:00
snipe
134bddf4c7 Fixed #6029 - model number not appearing in checkin/checkout emails 2018-08-02 11:43:11 -07:00
Daniel Meltzer
e368a20427 Use filled instead of has. (#6033)
I think this merged in a weird order and was missed by the global
find/replace.  This fixes bulkassets/bulkusers editing.

At some point we should look at refactoring BulkAssetsController@edit to
only run one DB query, rather than one per item.
2018-08-02 09:54:19 -07:00
Daniel Meltzer
2637ce56a1 Allow importcontroller to return 200 for failed delete. (#6034) 2018-08-02 09:53:54 -07:00
snipe
ffed306ecd Removed old emaol templates (#6026)
Still testing this to make sure it doesn’t break anything else :)
2018-08-01 20:56:32 -07:00
snipe
f5a5d830a5 Better handling for deleting imports where the files may have been moved 2018-08-01 20:49:55 -07:00
snipe
9168979d9e Fixed #6027 - added model to asset maintenances listing 2018-08-01 18:24:52 -07:00
snipe
6f8680efa6 Added external link formatter to BS tables 2018-08-01 18:01:31 -07:00
snipe
3f394f42c7 Partial fix for better UI on deleting files
Still needs Vue stuff
2018-08-01 18:01:16 -07:00
snipe
b2c99c88bb Fixed #6028 - added supplier url to list view 2018-08-01 17:37:58 -07:00
snipe
9a3683ce7c Remove app_locked 2018-08-01 15:36:20 -07:00
snipe
2fa66f4551 Removed duplicate BACKUP_ENV in example env 2018-08-01 15:27:51 -07:00
snipe
a4019c9f63 NIcer UI for activated user + email credentials 2018-08-01 14:18:37 -07:00
snipe
694166862e Added attempted logins admin screen (#6018)
* Added attempted logins admin screen

* Smaller table spacing
2018-08-01 03:51:59 -07:00
snipe
911c2398ef Fixed #6004 - set a default next_audit_date if none provided 2018-08-01 03:04:29 -07:00
snipe
a209a92de8 Fixed #6011 - use correct file created at date 2018-08-01 02:43:08 -07:00
snipe
462878a307 Production assets 2018-08-01 00:27:19 -07:00
snipe
3941437ad4 Limit width of title logo so it doesn’t break with long names 2018-08-01 00:27:14 -07:00
snipe
664cc24481 Fixed PR #6002 2018-08-01 00:26:50 -07:00
snipe
2c38036123 Improvement: Better documentation, small refactors (#6017)
* Better documentation, small refactors

* Small comment fixes
2018-08-01 00:06:41 -07:00
Sam
6ae096a56f These changes will allow submissions during the AJAX process in Select2s. This allows for barcode scanners, magstrip and NFC scanners that enter a carriage return. (#6002) 2018-08-01 00:06:28 -07:00
snipe
436bf152ec Config for new bots 2018-07-31 17:40:32 -07:00
snipe
b1b5eeecba Fixed #6013 - add accessory checkout notes to detail page 2018-07-31 16:00:38 -07:00
snipe
400913631c Use language strings for bulk password reset 2018-07-30 20:37:19 -07:00
snipe
006a3adea0 Added ability to trigger forgotten password emails for users 2018-07-30 20:31:02 -07:00
Earl Ramirez
c2bb3892b7 [WIP] Fix apache default page on ubuntu (#5998)
* Added cron to list of packages

* Updated APP_PATH

* Rename virtual hosts config files
2018-07-30 18:09:37 -07:00
Jason
5c820dd7b8 Added code to snipeit.sh for Raspbian 9.4 install (#5980)
* Added code to snipeit.sh for Raspbian 9.4 install

* More additions to snipeit.sh to install php7.2  on raspian in prep for
version 5

* added public/phpinfo.php to .gitignore

* Deleted phpinfo.php

* removed phpinfo.php from .gitignore
2018-07-27 12:57:39 -07:00
Daniel Meltzer
248fcfa869 Move findLicenseSeatToCheckout back to controller. (#5970)
* Move findLicenseSeatToCheckout back to controller.

After discussion, move findLicenseSeatToCheckout method back to
controller from form request.  Also cleanup one tiny bit more with null
coalesce operator (Yay php 7).

* Revert Earlier change.

$target only exists in the checkoutTo* methods.  Need to log the
checkout individually in each of those.
2018-07-27 12:03:04 -07:00
Daniel Meltzer
b58c77c8b8 Feature: Import users department. (#5987)
Maps to the "Department" header key by default.  Bug: #5382
2018-07-27 12:02:18 -07:00
Daniel Meltzer
94c79fa69a Fixed #5964 - Feature: Group IDs can be passed to User Create. (#5990)
Also added to update, and adjusted api tests to confirm.  Long term it
might be nice to look at support for passing group names instead.

Bug: 5964
2018-07-27 12:01:59 -07:00
Daniel Meltzer
a3811f632d Licence != License (#5993) 2018-07-27 12:01:25 -07:00
snipe
d8f0102204 Merge branch 'features/restore_deleted_cmd' into develop
# Conflicts:
#	config/version.php
#	resources/views/layouts/basic.blade.php
2018-07-27 02:48:13 -07:00
snipe
6b013724aa Uncomment backup 2018-07-26 18:11:51 -07:00
snipe
8774f0cf45 Script to restore deleted users and put their asset assignments back 2018-07-26 18:08:49 -07:00
snipe
8762e158c4 Delete content from login attempts table 2018-07-26 18:08:25 -07:00
Daniel Meltzer
49d95892e3 Port asset history importer to league/csv9 (#5972) 2018-07-26 12:04:21 -07:00
snipe
9618878023 Restrict users asset listing to just assets checked out to users 2018-07-25 21:40:33 -07:00
snipe
b267b34762 Make sure there is a settings value 2018-07-25 19:02:35 -07:00
snipe
a5daee811b Split out custom_css from custom_header color 2018-07-25 19:00:03 -07:00
snipe
1fd0b14b98 Removed ide-helper config 2018-07-25 12:07:50 -07:00
snipe
ce44744dc6 Yoinked ide-helper 2018-07-25 12:07:03 -07:00
snipe
c90dee4cf4 Added barryvdh/laravel-ide-helper 2018-07-25 11:58:00 -07:00
snipe
373885ebd1 Use Storage for file uploads (not model images) 2018-07-25 11:57:49 -07:00
snipe
a9fd9c9e59 Moved gates to individual controller methods to fix “This action is unauthorized” in route:list 2018-07-25 10:45:32 -07:00
snipe
9535c68dfe Fixed base path in local file driver 2018-07-25 10:44:37 -07:00
snipe
63bf71b071 Use Storage methods for asset/license files 2018-07-25 09:48:50 -07:00
snipe
2e92ef7d77 Removed HTML colspan for bootstrap table in files display 2018-07-25 07:40:04 -07:00
snipe
068ae5805b Set base path for storage default on local disk 2018-07-25 07:39:47 -07:00
snipe
054de06522 Added cache variables to S3 2018-07-25 07:29:23 -07:00
snipe
9a99626b9e Added SFTP to filesystem config 2018-07-25 07:27:31 -07:00
snipe
7a9a78ec53 Make Codacy happy by reversing the signature params 2018-07-25 07:14:16 -07:00
snipe
684a892e8b Removed rackspace for now 2018-07-25 06:59:58 -07:00
snipe
91a9e410df Fixed weird whereNULL 2018-07-25 06:46:06 -07:00
snipe
e64e1207cf Reordered PHP versions because anal-retentive 2018-07-25 05:36:54 -07:00
snipe
999c4e781d Update version of PHP for travis 2018-07-25 05:33:28 -07:00
snipe
3df19d267f Fixed Ziggy routes 2018-07-25 03:00:27 -07:00
snipe
501f096a2c Updated spatie backup config file for their latest version 2018-07-25 01:36:30 -07:00
snipe
47ed328f0e Change fire() to handle() for Laravel 5.5 2018-07-25 01:28:44 -07:00
snipe
4cc7ed9ec7 Backup updates 2018-07-25 01:28:30 -07:00
snipe
7af633177a Fixed filledFile back to hasFile 2018-07-24 22:52:49 -07:00
snipe
86c1f11bec Change $request->has to $request->filled unilaterally 2018-07-24 22:51:31 -07:00
snipe
0714ac4248 Update withCounts because Laravel 5.5 :( 2018-07-24 22:40:05 -07:00
snipe
e6dd90e055 Added footer settings to settings reset 2018-07-24 22:21:16 -07:00
snipe
b2968ea6c6 Fixed paths to Aurhorizable/Authenticatable contracts 2018-07-24 21:29:08 -07:00
snipe
d81975e168 Laravel 5.6 requirement for filip/whoops 2018-07-24 21:28:37 -07:00
snipe
24c949c886 Bumped version to v5.0.0-pre 2018-07-24 21:13:02 -07:00
snipe
de84e1bd88 Added hashing config 2018-07-24 21:13:02 -07:00
snipe
60c4fa1d90 Updated trustedproxy w/config 2018-07-24 21:13:02 -07:00
snipe
3d3ed6ab03 Logging updates for 5.6 2018-07-24 21:13:02 -07:00
snipe
14735057a8 New spatie config file 2018-07-24 21:13:02 -07:00
snipe
75b3c66908 Spatie backup lang updates 2018-07-24 21:13:02 -07:00
snipe
52bd7e4dfb Added language file for spatie 2018-07-24 21:13:02 -07:00
snipe
b58314493a Use array for db dump configs 2018-07-24 21:13:02 -07:00
snipe
6ebc653620 Sparie backups config upgrade 2018-07-24 21:13:02 -07:00
snipe
f69e34f6ca Applying @dmeltzer’s changes from afc8ac5e72 2018-07-24 21:13:02 -07:00
snipe
7bb50a61a7 Applying @dmeltzer’s changes from afc8ac5e72 2018-07-24 21:13:02 -07:00
snipe
6b31e2aca9 php7 composer 2018-07-24 21:13:02 -07:00
Antti
a55a9ca4cd Fix: Made migration run original command for mysql (#5915)
* Fix: Made migration run original command for mysql

* Added sqlite_testing migration

* Review suggested fix
2018-07-24 20:57:47 -07:00
snipe
e332442132 Bumped version 2018-07-24 19:45:23 -07:00
Daniel Meltzer
64d649be7f Monster: Cleanup/Refactor http controllers. (#5916)
* Extract a handlesimages trait to centralize logic for parsing/storing images on upload in create/edit methods.

* Use same image upload/layout in accessories as consum+components.

* Monster: Cleanup/Refactor http controllers.

This cleans up docblocks, pulls most non-crudy actions into their own
controllers, and does general cleanup/logic refactoring.  There /should/
be no functional changes, but we all know how should works..

Extract checkin/checkout functions to a separate controller for accessories.

Move controllers to subdirectory.

Cleanup AssetModelsController

Extract component checkin/checkout

Assorted cleanups/doc/formatting in controllers.

Refactor LicenseController.

Refactor UsersController

Update viewassetscontroller.

* Codacy cleanups

* More codacy cleanups.  Extract a LicenseCheckout Form request as well.

* A bit more refactor/cleaning of the license checkout method.

* Review Related Cleanups

* Fix most of the item_not_found translations.  In many cases, the
string being generated did not even use the id parameter.  Where it
does, pass it as id instead of as a different value.

* Remove some old $data arrays from when we manually sent emails from
the controllers.  This has been superseeded by the notification system
(yay!)

* Bugfix: Only log the checkin of an accessory if the checkin completes sucessfully.
2018-07-24 19:35:26 -07:00
1730 changed files with 79051 additions and 357239 deletions

View File

@@ -1652,10 +1652,163 @@
]
},
{
"login": "timothyfarmer",
"name": "Tim Farmer",
"avatar_url": "https://avatars1.githubusercontent.com/u/7632599?v=4",
"profile": "https://github.com/timothyfarmer",
"login": "jwhulette",
"name": "Wes Hulette",
"avatar_url": "https://avatars1.githubusercontent.com/u/4930051?v=4",
"profile": "http://macfoo.wordpress.com/",
"contributions": [
"code"
]
},
{
"login": "patrict",
"name": "patrict",
"avatar_url": "https://avatars0.githubusercontent.com/u/8134591?v=4",
"profile": "https://github.com/patrict",
"contributions": [
"code"
]
},
{
"login": "VELIKII-DIVAN",
"name": "Dmitriy Minaev",
"avatar_url": "https://avatars3.githubusercontent.com/u/2611616?v=4",
"profile": "https://github.com/VELIKII-DIVAN",
"contributions": [
"code"
]
},
{
"login": "liquidhorse",
"name": "liquidhorse",
"avatar_url": "https://avatars0.githubusercontent.com/u/5132245?v=4",
"profile": "https://github.com/liquidhorse",
"contributions": [
"code"
]
},
{
"login": "Seldaek",
"name": "Jordi Boggiano",
"avatar_url": "https://avatars1.githubusercontent.com/u/183678?v=4",
"profile": "https://seld.be/",
"contributions": [
"code"
]
},
{
"login": "inietov",
"name": "Ivan Nieto",
"avatar_url": "https://avatars0.githubusercontent.com/u/653557?v=4",
"profile": "https://github.com/inietov",
"contributions": [
"code"
]
},
{
"login": "benrubson",
"name": "Ben RUBSON",
"avatar_url": "https://avatars2.githubusercontent.com/u/6764151?v=4",
"profile": "https://github.com/benrubson",
"contributions": [
"code"
]
},
{
"login": "NMathar",
"name": "NMathar",
"avatar_url": "https://avatars2.githubusercontent.com/u/8554558?v=4",
"profile": "https://github.com/NMathar",
"contributions": [
"code"
]
},
{
"login": "smb",
"name": "Steffen",
"avatar_url": "https://avatars1.githubusercontent.com/u/139566?v=4",
"profile": "https://github.com/smb",
"contributions": [
"code"
]
},
{
"login": "Sxderp",
"name": "Sxderp",
"avatar_url": "https://avatars0.githubusercontent.com/u/6609453?v=4",
"profile": "https://github.com/Sxderp",
"contributions": [
"code"
]
},
{
"login": "fanta8897",
"name": "fanta8897",
"avatar_url": "https://avatars1.githubusercontent.com/u/4807843?v=4",
"profile": "https://github.com/fanta8897",
"contributions": [
"code"
]
},
{
"login": "andreybolonin",
"name": "Andrey Bolonin",
"avatar_url": "https://avatars2.githubusercontent.com/u/2576509?v=4",
"profile": "https://andreybolonin.com/phpconsulting/",
"contributions": [
"code"
]
},
{
"login": "shinayoshi",
"name": "shinayoshi",
"avatar_url": "https://avatars3.githubusercontent.com/u/2173307?v=4",
"profile": "http://www.shinayoshi.net/",
"contributions": [
"code"
]
},
{
"login": "reuser",
"name": "Hubert",
"avatar_url": "https://avatars3.githubusercontent.com/u/2130159?v=4",
"profile": "https://github.com/reuser",
"contributions": [
"code"
]
},
{
"login": "KeenRivals",
"name": "KeenRivals",
"avatar_url": "https://avatars0.githubusercontent.com/u/6865789?v=4",
"profile": "https://brashear.me",
"contributions": [
"code"
]
},
{
"login": "omyno",
"name": "omyno",
"avatar_url": "https://avatars3.githubusercontent.com/u/2902513?v=4",
"profile": "https://github.com/omyno",
"contributions": [
"code"
]
},
{
"login": "jackka",
"name": "Evgeny",
"avatar_url": "https://avatars1.githubusercontent.com/u/6271335?v=4",
"profile": "https://github.com/jackka",
"contributions": [
"code"
]
},
{
"login": "colin-campbell",
"name": "Colin Campbell",
"avatar_url": "https://avatars2.githubusercontent.com/u/1169963?v=4",
"profile": "https://digitalist.se",
"contributions": [
"code"
]

View File

@@ -1,5 +1,13 @@
.git
.github
.gitattributes
.gitignore
.dockerignore
app/storage/logs/*
app/storage/views/*
vendor/*
storage/framework/cache/*
node_modules
.vagrant
.idea

View File

@@ -43,7 +43,8 @@ MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME='Snipe-IT'
MAIL_REPLYTO_ADDR=you@example.com
MAIL_REPLYTO_NAME='Snipe-IT'
MAIL_BACKUP_NOTIFICATION_ADDRESS=you@example.com
MAIL_AUTO_EMBED=true
MAIL_AUTO_EMBED_METHOD=base64
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
@@ -60,6 +61,7 @@ ENCRYPT=false
COOKIE_NAME=snipeit_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
SESSION_PATH=null
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
@@ -91,10 +93,12 @@ MEMCACHED_PORT=null
# --------------------------------------------
# OPTIONAL: AWS S3 SETTINGS
# --------------------------------------------
AWS_SECRET=null
AWS_KEY=null
AWS_REGION=null
AWS_SECRET_ACCESS_KEY=null
AWS_ACCESS_KEY_ID=null
AWS_DEFAULT_REGION=null
AWS_BUCKET=null
AWS_BUCKET_ROOT=null
AWS_URL=null
# --------------------------------------------
# OPTIONAL: LOGIN THROTTLING
@@ -107,7 +111,7 @@ LOGIN_LOCKOUT_DURATION=60
# --------------------------------------------
APP_LOG=single
APP_LOG_MAX_FILES=10
APP_LOCKED=false
APP_LOG_LEVEL=debug
FILESYSTEM_DISK=local
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false

View File

@@ -40,10 +40,12 @@ IMAGE_LIB=gd
# --------------------------------------------
# OPTIONAL: AWS S3 SETTINGS
# --------------------------------------------
AWS_SECRET=null
AWS_KEY=null
AWS_REGION=null
AWS_SECRET_ACCESS_KEY=null
AWS_ACCESS_KEY_ID=null
AWS_DEFAULT_REGION=null
AWS_BUCKET=null
AWS_BUCKET_ROOT=null
AWS_URL=null
# --------------------------------------------

View File

@@ -40,10 +40,12 @@ IMAGE_LIB=gd
# --------------------------------------------
# OPTIONAL: AWS S3 SETTINGS
# --------------------------------------------
AWS_SECRET=null
AWS_KEY=null
AWS_REGION=null
AWS_SECRET_ACCESS_KEY=null
AWS_ACCESS_KEY_ID=null
AWS_DEFAULT_REGION=null
AWS_BUCKET=null
AWS_BUCKET_ROOT=null
AWS_URL=null
# --------------------------------------------

5
.github/FUNDING.yml vendored
View File

@@ -1,5 +0,0 @@
# You can add one username per supported platform and one custom link
# patreon: # Replace with your Patreon username
# open_collective: # Replace with your Open Collective username
# ko_fi: # Replace with your Ko-fi username
custom: https://snipeitapp.com/donate

33
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: |
👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: |
💖 Thanks for this pull request! 💖
We use [semantic commit messages](https://snipe-it.readme.io/docs/contributing-overview#section-pull-request-guidelines) to streamline the release process and easily generate changelogs between versions. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix if it doesn't have one already.
Examples of commit messages with semantic prefixes:
- `Fixed #<issue number>: don't overwrite prevent_default if default wasn't prevented`
- `Added #<issue number>: add checkout functionality to assets`
- `Improved Asset Checkout: use new notification method for checkout`
Things that will help get your PR across the finish line:
- Document any user-facing changes you've made.
- Include tests when adding/changing behavior.
- Include screenshots and animated GIFs whenever possible.
We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
Congrats on merging your first pull request! 🎉🎉🎉

6
.gitignore vendored
View File

@@ -50,4 +50,10 @@ tests/_support/_generated/*
/storage/oauth-public.key
*.cache
.vagrant
\.php_cs\.dist
phpmd\.xml
/public/storage

View File

@@ -14,13 +14,11 @@ services:
# list any PHP version you want to test against
php:
- 7.1.2
- 7.1.3
- 7.1.4
- 7.2
- 7.3
- 7.3.0
matrix:
allow_failures:
- php: 7.3
# execute any number of scripts before the test run, custom env's are available as variables
before_script:
@@ -31,6 +29,7 @@ before_script:
- mysql -e 'CREATE USER "travis'@'localhost";'
- mysql -e 'GRANT ALL PRIVILEGES ON * . * TO "travis'@'localhost";'
- mysql -e 'FLUSH PRIVILEGES;'
- cp .env.testing-ci .env
- composer self-update
- composer install -n --prefer-source
- chmod -R 777 storage

View File

@@ -1,36 +1,58 @@
FROM ubuntu:xenial
LABEL maintainer="uberbrady, hinchk"
FROM ubuntu:bionic
LABEL maintainer Brady Wetherington <uberbrady@gmail.com>
RUN apt-get update && apt-get install -y software-properties-common
RUN LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php
RUN apt-get update && apt-get install -y \
RUN export DEBIAN_FRONTEND=noninteractive; \
export DEBCONF_NONINTERACTIVE_SEEN=true; \
echo 'tzdata tzdata/Areas select Etc' | debconf-set-selections; \
echo 'tzdata tzdata/Zones/Etc select UTC' | debconf-set-selections; \
apt-get update -qqy \
&& apt-get install -qqy --no-install-recommends \
apt-utils \
apache2 \
apache2-bin \
libapache2-mod-php7.1 \
php7.1-curl \
php7.1-ldap \
php7.1-mysql \
php7.1-mcrypt \
php7.1-gd \
php7.1-xml \
php7.1-mbstring \
php7.1-zip \
php7.1-bcmath \
libapache2-mod-php7.2 \
php7.2-curl \
php7.2-ldap \
php7.2-mysql \
php7.2-gd \
php7.2-xml \
php7.2-mbstring \
php7.2-zip \
php7.2-bcmath \
patch \
curl \
wget \
vim \
git \
cron \
mysql-client \
supervisor \
cron \
gcc \
make \
autoconf \
libc-dev \
pkg-config \
libmcrypt-dev \
php7.2-dev \
ca-certificates \
unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
RUN php go-pear.phar
RUN pecl install mcrypt-1.0.2
RUN bash -c "echo extension=/usr/lib/php/20170718/mcrypt.so > /etc/php/7.2/mods-available/mcrypt.ini"
RUN phpenmod mcrypt
RUN phpenmod gd
RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.1/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.1/cli/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/cli/php.ini
RUN useradd -m --uid 1000 --gid 50 docker
@@ -41,11 +63,11 @@ COPY docker/000-default.conf /etc/apache2/sites-enabled/000-default.conf
#SSL
RUN mkdir -p /var/lib/snipeit/ssl
COPY docker/001-default-ssl.conf /etc/apache2/sites-enabled/001-default-ssl.conf
#COPY docker/001-default-ssl.conf /etc/apache2/sites-available/001-default-ssl.conf
#COPY docker/001-default-ssl.conf /etc/apache2/sites-enabled/001-default-ssl.conf
COPY docker/001-default-ssl.conf /etc/apache2/sites-available/001-default-ssl.conf
RUN a2enmod ssl
#RUN a2ensite 001-default-ssl.conf
RUN a2ensite 001-default-ssl.conf
COPY . /var/www/html
@@ -68,20 +90,18 @@ RUN \
rm -r "/var/www/html/storage/private_uploads" && ln -fs "/var/lib/snipeit/data/private_uploads" "/var/www/html/storage/private_uploads" \
&& rm -rf "/var/www/html/public/uploads" && ln -fs "/var/lib/snipeit/data/uploads" "/var/www/html/public/uploads" \
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
&& mkdir "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
&& chown docker "/var/lib/snipeit/keys/" \
&& chmod +x /var/www/html/artisan \
&& echo "Finished setting up application in /var/www/html"
&& chown docker "/var/lib/snipeit/keys/"
############## DEPENDENCIES via COMPOSER ###################
#global install of composer
RUN cd /tmp;curl -sS https://getcomposer.org/installer | php;mv /tmp/composer.phar /usr/local/bin/composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Get dependencies
USER docker
RUN cd /var/www/html;composer install && rm -rf /home/docker/.composer/cache
RUN composer install --no-dev --working-dir=/var/www/html
USER root
############### APPLICATION INSTALL/INIT #################
@@ -99,11 +119,16 @@ VOLUME ["/var/lib/snipeit"]
##### START SERVER
COPY docker/startup.sh docker/supervisord.conf /
COPY docker/supervisor-exit-event-listener /usr/bin/supervisor-exit-event-listener
RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
CMD ["/startup.sh"]
# Add Tini
ENV TINI_VERSION v0.14.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["/entrypoint.sh"]
EXPOSE 80
EXPOSE 443

80
Dockerfile.alpine Normal file
View File

@@ -0,0 +1,80 @@
FROM alpine:3.8
# Apache + PHP
RUN apk add --update --no-cache \
apache2 \
php7 \
php7-common \
php7-apache2 \
php7-curl \
php7-ldap \
php7-mysqli \
php7-gd \
php7-xml \
php7-mbstring \
php7-zip \
php7-ctype \
php7-tokenizer \
php7-pdo_mysql \
php7-openssl \
php7-bcmath \
php7-phar \
php7-json \
php7-iconv \
php7-fileinfo \
php7-simplexml \
php7-session \
curl \
wget \
vim \
git \
mysql-client \
tini
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php7/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite
RUN sed -i '/LoadModule rewrite_module/s/^#//g' /etc/apache2/httpd.conf
COPY . /var/www/html
WORKDIR /var/www/html
COPY docker/docker.env /var/www/html/.env
RUN chown -R apache:apache /var/www/html
RUN \
rm -r "/var/www/html/storage/private_uploads" \
&& mkdir -p "/var/lib/snipeit/data/private_uploads" && ln -fs "/var/lib/snipeit/data/private_uploads" "/var/www/html/storage/private_uploads" \
&& rm -rf "/var/www/html/public/uploads" \
&& mkdir -p "/var/lib/snipeit/data/uploads" && ln -fs "/var/lib/snipeit/data/uploads" "/var/www/html/public/uploads" \
&& mkdir -p "/var/lib/snipeit/dumps" && rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
&& mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
&& chown -R apache "/var/lib/snipeit"
# Install composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN mkdir -p /var/www/.composer && chown apache /var/www/.composer
# Install dependencies
USER apache
RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html
USER root
VOLUME ["/var/lib/snipeit"]
# Entrypoints
COPY docker/entrypoint_alpine.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"]
EXPOSE 80

View File

@@ -1,14 +1,14 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-181-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![All Contributors](https://img.shields.io/badge/all_contributors-198-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
## Snipe-IT - Open Source Asset Management System
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
It is built on [Laravel 5.5](http://laravel.com).
It is built on [Laravel 5.4](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
Snipe-IT is actively developed and we're [releasing quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
__This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
@@ -58,14 +58,21 @@ Since the release of the JSON REST API, several third-party developers have been
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-II instance
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
-----
### Contributing
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
-----
### Security
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
@@ -103,16 +110,11 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
-----
### Contributing
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview).
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.

18
Vagrantfile vendored
View File

@@ -81,4 +81,22 @@ Vagrant.configure("2") do |config|
fedora26.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
fedora26.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "freebsd" do |freebsd|
freebsd.vm.box = "freebsd/FreeBSD-11.2-RELEASE"
freebsd.vm.hostname = 'freebsd12'
freebsd.vm.network "forwarded_port", guest: 80, host: 8080
freebsd.vm.network "forwarded_port", guest:3306, host:3306 # mysql
freebsd.vm.network "private_network", type: "dhcp"
freebsd.ssh.shell = "sh"
freebsd.vm.base_mac = "080027D14C66"
freebsd.vm.synced_folder ".", "/vagrant", :nfs => true, id: "vagrant-root",
:mount_options => ['rw', 'vers=3', 'tcp', 'actimeo=2']
freebsd.vm.provision "shell", inline: <<-SHELL
pkg install -y python27;
SHELL
freebsd.vm.provision "ansible" do |ansible|
ansible.playbook = "ansible/freebsd/vagrant_playbook.yml"
end
end
end

View File

@@ -0,0 +1,260 @@
---
- name: Set up local server
hosts: all
remote_user: vagrant
become_user: root
become_method: sudo
vars:
- ansible_python_interpreter: /usr/local/bin/python2.7
gather_facts: no
# Tasks
tasks:
#
# Update the PKG database
#
- name: Upgrade PKG database
raw: sudo pkg upgrade -y
#
# Mount the shared folders
#
- name: Update Vagrant Shared Folders
command: "{{ item }}"
with_items:
- sysrc rpc_lockd_enable=YES
- sysrc rpc_statd_enable=YES
become: true
#
# Install required utilities
#
- name: Install Utilities
pkgng:
name: "{{ item }}"
state: present
with_items:
- openssl
- node
- npm
- git
- nano
- wget
- bash
become: true
#
# Install php and php dependancies
#
- name: Install PHP dependancies
pkgng:
name: "{{ item }}"
state: present
with_items:
- php72
- php72-zip
- php72-zlib
- php72-extensions
- php72-mbstring
- php72-openssl
# - php72-mysqli
- php72-curl
- php72-soap
- php72-pdo_mysql
# - php72-pdo_pgsql
- php72-ldap
- php72-curl
- php72-fileinfo
- php72-bcmath
- php72-gd
become: true
#
# Create a php.ini file
#
- name: PHP INI check
stat:
path: /usr/local/etc/php.ini
register: php_ini_exits
- name: Create PHP ini
command: cp /usr/local/etc/php.ini-development /usr/local/etc/php.ini
become: true
when: not php_ini_exits.stat.exists
- name: Enable PHP-FPM auto-start
command: sysrc php_fpm_enable=YES
become: true
- name: Start PHP-FPM service
service:
name: php-fpm
state: started
become: true
#
# Install the lastest version of composer
#
- name: Composer check
stat:
path: /usr/local/bin/composer
register: composer_exits
- name: Install Composer
shell: |
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
mv composer.phar /usr/local/bin/composer
exit $RESULT
when: not composer_exits.stat.exists
become: true
#
# Install MySQL Server
- name: Install MySQL 5.7
pkgng:
name: mysql57-server
state: present
become: true
register: sql_server
- name: Start MySQL server
service:
name: mysql-server
state: started
become: true
- name: MySQL 5.7 auto-start
command: sysrc mysql_enable=YES
become: true
when: sql_server.changed == true
- name: Get MySQL root password
command: tail -1 /root/.mysql_secret
register: myql_root_pwd
become: true
when: sql_server.changed == true
- name: Change MySQL root password
command: mysqladmin -u root -p'{{myql_root_pwd.stdout}}' password vagrant
when: sql_server.changed == true
- name: Enable remote mysql
replace:
path: /usr/local/etc/mysql/my.cnf
regexp: "127.0.0.1"
replace: "0.0.0.0"
become: true
when: sql_server.changed == true
- name: Grant user vagrant privelages
shell: mysql -u root -pvagrant -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'vagrant' WITH GRANT OPTION; FLUSH PRIVILEGES;"
become: true
when: sql_server.changed == true
ignore_errors: true
- name: Restart MySQL server
service:
name: mysql-server
state: restarted
become: true
#
# Install Apache Web Server
#
- name: Install Apache 2.4
pkgng:
name: apache24
state: present
become: true
register: apache24_server
- name: Apache 2.4 auto-start
command: sysrc apache24_enable=YES
become: true
when: apache24_server.changed == true
- name: Enable Apache modules
replace:
path: /usr/local/etc/apache24/httpd.conf
regexp: "#{{ item }}"
replace: "{{ item }}"
become: true
with_items:
- LoadModule rewrite_module libexec/apache24/mod_rewrite.so
- LoadModule vhost_alias_module libexec/apache24/mod_vhost_alias.so
- LoadModule deflate_module libexec/apache24/mod_deflate.so
- LoadModule expires_module libexec/apache24/mod_expires.so
- LoadModule mpm_worker_module libexec/apache24/mod_mpm_worker.so
- LoadModule proxy_fcgi_module libexec/apache24/mod_proxy_fcgi.so
- LoadModule proxy_module libexec/apache24/mod_proxy.so
- Include etc/apache24/extra/httpd-vhosts.conf
when: apache24_server.changed == true
- name: Disable Apache modules
replace:
path: /usr/local/etc/apache24/httpd.conf
regexp: "{{ item }}"
replace: "#{{ item }}"
become: true
with_items:
- LoadModule mpm_prefork_module libexec/apache24/mod_mpm_prefork.so
when: apache24_server.changed == true
- name: Backup vhosts
command: cp /usr/local/etc/apache24/extra/httpd-vhosts.conf /usr/local/etc/apache24/extra/httpd-vhosts.conf.bak
become: true
when: apache24_server.changed == true
- name: Truncate vhosts
command: truncate -s 0 /usr/local/etc/apache24/extra/httpd-vhosts.conf
become: true
when: apache24_server.changed == true
- name: Set up vhost
blockinfile:
path: "/usr/local/etc/apache24/extra/httpd-vhosts.conf"
block: |
<VirtualHost *>
DocumentRoot /usr/local/www/apache24/data/public
ServerName vagrant.app
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/usr/local/www/apache24/data/public/$1
DirectoryIndex /index.php index.php
<Directory /usr/local/www/apache24/data/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
become: true
when: apache24_server.changed == true
- name: Map apache dir to local folder
shell: |
if ! [ -L /var/www ]; then
rm -rf /usr/local/www/apache24/data;
ln -fs /vagrant /usr/local/www/apache24/data;
fi
become: true
when: apache24_server.changed == true
- name: Start Apache 2.4 server
service:
name: apache24
state: started
become: true

View File

@@ -76,10 +76,4 @@ class CreateAdmin extends Command
}
// protected function getArguments()
// {
// return array(
// array('username', InputArgument::REQUIRED, 'Username'),
// );
// }
}

View File

@@ -41,7 +41,7 @@ class DisableLDAP extends Command
if ($this->confirm("\n****************************************************\nThis will disable LDAP support. You will not be able \nto login with an account that does not exist \nlocally in the Snipe-IT local database. \n****************************************************\n\nDo you wish to continue? [y|N]")) {
$setting = Setting::first();
$setting = Setting::getSettings();
$setting->ldap_enabled = 0;
if ($setting->save()) {
$this->info('LDAP has been set to disabled.');

View File

@@ -71,7 +71,7 @@ class FixDoubleEscape extends Command
foreach($classname::where("$field",'LIKE','%&%')->get() as $row) {
$this->info('Updating '.$field.' for '.$classname);
$row->{$field} = html_entity_decode($row->{$field});
$row->{$field} = html_entity_decode($row->{$field},ENT_QUOTES);
$row->save();
$count[$classname][$field]++;

View File

@@ -1,160 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use League\Csv\Reader;
use App\Models\Location;
class ImportLocations extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:import-locations {filename}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import locations and their parents';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (!ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$filename = $this->argument('filename');
$csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r');
$this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename);
$csv->setHeaderOffset(0); //because we don't want to insert the header
$results = $csv->getRecords();
// Import parent location names first if they don't exist
foreach ($results as $parent_index => $parent_row) {
if (array_key_exists('Parent Name', $parent_row)) {
$parent_name = trim($parent_row['Parent Name']);
if (array_key_exists('Name', $parent_row)) {
$this->info('- Parent: ' . $parent_name . ' in row as: ' . trim($parent_row['Parent Name']));
}
// Save parent location name
// This creates a sort of name-stub that we'll update later on in this script
$parent_location = Location::firstOrCreate(array('name' => $parent_name));
if (array_key_exists('Name', $parent_row)) {
$this->info('Parent for ' . $parent_row['Name'] . ' is ' . $parent_name . '. Attempting to save ' . $parent_name . '.');
}
// Check if the record was updated or created.
// This is mostly for clearer debugging.
if ($parent_location->exists) {
$this->info('- Parent location '.$parent_name.' already exists.');
} else {
$this->info('- Parent location '.$parent_name.' was created.');
}
} else {
$this->info('- No Parent Name provided, so no parent location will be created.');
}
}
$this->info('----- Parents Created.... backfilling additional details... --------');
// Loop through ALL records and add/update them if there are additional fields
// besides name
foreach ($results as $index => $row) {
if (array_key_exists('Parent Name', $row)) {
$parent_name = trim($row['Parent Name']);
}
// Set the location attributes to save
if (array_key_exists('Name', $row)) {
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
$location->name = trim($row['Name']);
$this->info('Checking location: '.$location->name);
} else {
$this->error('Location name is required and is missing from at least one row in this dataset. Check your CSV for extra trailing rows and try again.');
return false;
}
if (array_key_exists('Currency', $row)) {
$location->currency = trim($row['Currency']);
}
if (array_key_exists('Address 1', $row)) {
$location->address = trim($row['Address 1']);
}
if (array_key_exists('Address 2', $row)) {
$location->address2 = trim($row['Address 2']);
}
if (array_key_exists('City', $row)) {
$location->city = trim($row['City']);
}
if (array_key_exists('State', $row)) {
$location->state = trim($row['State']);
}
if (array_key_exists('Zip', $row)) {
$location->zip = trim($row['Zip']);
}
if (array_key_exists('Country', $row)) {
$location->country = trim($row['Country']);
}
if (array_key_exists('Country', $row)) {
$location->ldap_ou = trim($row['OU']);
}
// If a parent name is provided, we created it earlier in the script,
// so let's grab that ID
if ($parent_name) {
$this->info('-- Searching for Parent Name: '.$parent_name);
$parent = Location::where('name', '=', $parent_name)->first();
$location->parent_id = $parent->id;
$this->info('Parent: '.$parent_name.' - ID: '.$parent->id);
}
// Make sure the more advanced (non-name) fields pass validation
if (($location->isValid()) && ($location->save())) {
// Check if the record was updated or created.
// This is mostly for clearer debugging.
if ($location->exists) {
$this->info('Location ' . $location->name . ' already exists. Updating...');
} else {
$this->info('- Location '.$location->name.' was created. ');
}
// If there's a validation error, display that
} else {
$this->error('- Non-parent Location '.$location->name.' could not be created: '.$location->getErrors() );
}
}
}
}

View File

@@ -1,14 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Setting;
use App\Models\Ldap;
use App\Models\User;
use App\Services\LdapAd;
use Illuminate\Support\Facades\Log;
use Exception;
use App\Models\Location;
use Log;
use Illuminate\Console\Command;
use Adldap\Models\User as AdldapUser;
/**
* LDAP / AD sync command.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
class LdapSync extends Command
{
/**
@@ -16,23 +25,75 @@ class LdapSync extends Command
*
* @var string
*/
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--summary} {--json_summary}';
protected $signature = 'snipeit:ldap-sync
{--location= : A location name }
{--location_id= : A location id}
{--base_dn= : A diffrent base DN to use }
{--summary : Print summary }
{--json_summary : Print summary in json format }
{--dryrun : Run the sync process but don\'t update the database}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command line LDAP sync';
protected $description = 'Command line LDAP/AD sync';
/**
* An LdapAd instance.
*
* @var \App\Models\LdapAd
*/
private $ldap;
/**
* A default location collection.
*
* @var \Illuminate\Support\Collection
*/
private $defaultLocation = null;
/**
* Mapped locations collection.
*
* @var \Illuminate\Support\Collection
*/
private $mappedLocations = null;
/**
* The summary collection.
*
* @var \Illuminate\Support\Collection
*/
private $summary;
/**
* Is dry-run?
*
* @var bool
*/
private $dryrun = false;
/**
* Show users to be imported.
*
* @var array
*/
private $userlist = [];
/**
* Create a new command instance.
*
* @return void
* @param LdapAd $ldap
*/
public function __construct()
public function __construct(LdapAd $ldap)
{
parent::__construct();
$this->summary = collect();
$this->ldap = $ldap;
}
/**
@@ -42,208 +103,280 @@ class LdapSync extends Command
*/
public function handle()
{
ini_set('max_execution_time', 600); //600 seconds = 10 minutes
ini_set('max_execution_time', '600'); //600 seconds = 10 minutes
ini_set('memory_limit', '500M');
$ldap_result_username = Setting::getSettings()->ldap_username_field;
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
$ldap_result_active_flag = Setting::getSettings()->ldap_active_flag_field;
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
$ldap_result_email = Setting::getSettings()->ldap_email;
try {
$ldapconn = Ldap::connectToLdap();
Ldap::bindAdminToLdap($ldapconn);
} catch (\Exception $e) {
if ($this->option('json_summary')) {
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
$this->info(json_encode($json_summary));
}
LOG::info($e);
return [];
if ($this->option('dryrun')) {
$this->dryrun = true;
}
$summary = array();
$this->checkIfLdapIsEnabled();
$this->checkLdapConnetion();
$this->setBaseDn();
$this->getUserDefaultLocation();
/*
* Use the default location if set, this is needed for the LDAP users sync page
*/
if (!$this->option('base_dn') && null == $this->defaultLocation) {
$this->getMappedLocations();
}
$this->processLdapUsers();
try {
if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
$search_base = null;
}
$results = Ldap::findLdapUsers($search_base);
} catch (\Exception $e) {
if ($this->option('json_summary')) {
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
$this->info(json_encode($json_summary));
}
LOG::info($e);
return [];
// Print table of users
if ($this->dryrun) {
$this->info('The following users will be synced!');
$headers = ['First Name', 'Last Name', 'Username', 'Email', 'Employee #', 'Location Id', 'Status'];
$this->table($headers, $this->summary->toArray());
}
/* Determine which location to assign users to by default. */
$location = NULL;
if ($this->option('location')!='') {
$location = Location::where('name', '=', $this->option('location'))->first();
LOG::debug('Location name '.$this->option('location').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
} elseif ($this->option('location_id')!='') {
$location = Location::where('id', '=', $this->option('location_id'))->first();
LOG::debug('Location ID '.$this->option('location_id').' passed');
LOG::debug('Importing to '.$location->name.' ('.$location->id.')');
}
if (!isset($location)) {
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
/* Process locations with explicitly defined OUs, if doing a full import. */
if ($this->option('base_dn')=='') {
// Retrieve locations with a mapped OU, and sort them from the shallowest to deepest OU (see #3993)
$ldap_ou_locations = Location::where('ldap_ou', '!=', '')->get()->toArray();
$ldap_ou_lengths = array();
foreach ($ldap_ou_locations as $location) {
$ldap_ou_lengths[] = strlen($location["ldap_ou"]);
}
array_multisort($ldap_ou_lengths, SORT_ASC, $ldap_ou_locations);
if (sizeof($ldap_ou_locations) > 0) {
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
}
// Inject location information fields
for ($i = 0; $i < $results["count"]; $i++) {
$results[$i]["ldap_location_override"] = false;
$results[$i]["location_id"] = 0;
}
// Grab subsets based on location-specific DNs, and overwrite location for these users.
foreach ($ldap_ou_locations as $ldap_loc) {
$location_users = Ldap::findLdapUsers($ldap_loc["ldap_ou"]);
$usernames = array();
for ($i = 0; $i < $location_users["count"]; $i++) {
if (array_key_exists($ldap_result_username, $location_users[$i])) {
$location_users[$i]["ldap_location_override"] = true;
$location_users[$i]["location_id"] = $ldap_loc["id"];
$usernames[] = $location_users[$i][$ldap_result_username][0];
}
return $this->getSummary();
}
/**
* Generate the LDAP sync summary.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @return string
*/
private function getSummary(): string
{
if ($this->option('summary') && !$this->dryrun) {
$this->summary->each(function ($item) {
if ('ERROR' === $item['status']) {
$this->error('ERROR: '.$item['note']);
}
// Delete located users from the general group.
foreach ($results as $key => $generic_entry) {
if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) {
if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
unset($results[$key]);
}
}
else {
$this->info('USER: '.$item['note']);
}
$global_count = $results['count'];
$results = array_merge($location_users, $results);
$results['count'] = $global_count;
}
}
/* Create user account entries in Snipe-IT */
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$pass = bcrypt($tmp_pass);
for ($i = 0; $i < $results["count"]; $i++) {
if (empty($ldap_result_active_flag) || $results[$i][$ldap_result_active_flag][0] == "TRUE") {
$item = array();
$item["username"] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : "";
$item["employee_number"] = isset($results[$i][$ldap_result_emp_num][0]) ? $results[$i][$ldap_result_emp_num][0] : "";
$item["lastname"] = isset($results[$i][$ldap_result_last_name][0]) ? $results[$i][$ldap_result_last_name][0] : "";
$item["firstname"] = isset($results[$i][$ldap_result_first_name][0]) ? $results[$i][$ldap_result_first_name][0] : "";
$item["email"] = isset($results[$i][$ldap_result_email][0]) ? $results[$i][$ldap_result_email][0] : "" ;
$item["ldap_location_override"] = isset($results[$i]["ldap_location_override"]) ? $results[$i]["ldap_location_override"]:"";
$item["location_id"] = isset($results[$i]["location_id"]) ? $results[$i]["location_id"]:"";
$user = User::where('username', $item["username"])->first();
if ($user) {
// Updating an existing user.
$item["createorupdate"] = 'updated';
} else {
// Creating a new user.
$user = new User;
$user->password = $pass;
$user->activated = 0;
$item["createorupdate"] = 'created';
}
$user->first_name = $item["firstname"];
$user->last_name = $item["lastname"];
$user->username = $item["username"];
$user->email = $item["email"];
$user->employee_num = e($item["employee_number"]);
// Sync activated state for Active Directory.
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
$enabled_accounts = [
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224'
];
$user->activated = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
}
// If we're not using AD, and there isn't an activated flag set, activate all users
elseif (empty($ldap_result_active_flag)) {
$user->activated = 1;
}
if ($item['ldap_location_override'] == true) {
$user->location_id = $item['location_id'];
} elseif ((isset($location)) && (!empty($location))) {
if ((is_array($location)) && (array_key_exists('id', $location))) {
$user->location_id = $location['id'];
} elseif (is_object($location)) {
$user->location_id = $location->id;
}
}
$user->ldap_import = 1;
$errors = '';
if ($user->save()) {
$item["note"] = $item["createorupdate"];
$item["status"]='success';
} else {
foreach ($user->getErrors()->getMessages() as $key => $err) {
$errors .= $err[0];
}
$item["note"] = $errors;
$item["status"]='error';
}
array_push($summary, $item);
}
}
if ($this->option('summary')) {
for ($x = 0; $x < count($summary); $x++) {
if ($summary[$x]['status']=='error') {
$this->error('ERROR: '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].') was not imported: '.$summary[$x]['note']);
} else {
$this->info('User '.$summary[$x]['firstname'].' '.$summary[$x]['lastname'].' (username: '.$summary[$x]['username'].') was '.strtoupper($summary[$x]['createorupdate']).'.');
}
}
} else if ($this->option('json_summary')) {
$json_summary = [ "error" => false, "error_message" => "", "summary" => $summary ];
});
} elseif ($this->option('json_summary')) {
$json_summary = [
'error' => false,
'error_message' => '',
'summary' => $this->summary->toArray(),
];
$this->info(json_encode($json_summary));
} else {
return $summary;
}
return '';
}
/**
* Create a new user or update an existing user.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @param \Adldap\Models\User $snipeUser
*/
private function updateCreateUser(AdldapUser $snipeUser): void
{
$user = $this->ldap->processUser($snipeUser, $this->defaultLocation, $this->mappedLocations);
if(!$user) {
$summary['note'] = sprintf("'%s' was not imported. REASON: User inactive or not found", $snipeUser->ou);
$summary['status'] = 'ERROR';
$this->summary->push($summary);
return;
}
$summary = [
'firstname' => $user->first_name,
'lastname' => $user->last_name,
'username' => $user->username,
'employee_number' => $user->employee_num,
'email' => $user->email,
'location_id' => $user->location_id,
];
// Only update the database if is not a dry run
if (!$this->dryrun) {
if ($user->save()) {
$summary['note'] = sprintf("'%s' %s", $user->username, ($user->wasRecentlyCreated ? 'CREATED' : 'UPDATED'));
$summary['status'] = 'SUCCESS';
} else {
$errors = '';
foreach ($user->getErrors()->getMessages() as $error) {
$errors .= $error[0];
}
$summary['note'] = sprintf("'%s' was not imported. REASON: %s", $user->username, $errors);
$summary['status'] = 'ERROR';
}
}
//$summary['note'] = ($user->getOriginal('username') ? 'UPDATED' : 'CREATED');
$this->summary->push($summary);
}
/**
* Process the users to update / create.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @param int $page The page to get the result set
*/
private function processLdapUsers(int $page=0): void
{
try {
$ldapUsers = $this->ldap->getLdapUsers($page);
} catch (Exception $e) {
$this->outputError($e);
exit($e->getMessage());
}
if (0 == $ldapUsers->count()) {
$msg = 'ERROR: No users found!';
Log::error($msg);
if ($this->dryrun) {
$this->error($msg);
}
exit($msg);
}
// Process each individual users
foreach ($ldapUsers as $user) {
$this->updateCreateUser($user);
}
if ($ldapUsers->getCurrentPage() < $ldapUsers->getPages()) {
$this->processLdapUsers($ldapUsers->getCurrentPage() + 1);
}
}
/**
* Get the mapped locations if a base_dn is provided.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
private function getMappedLocations()
{
$ldapOuLocation = Location::where('ldap_ou', '!=', '')->select(['id', 'ldap_ou'])->get();
$locations = $ldapOuLocation->sortBy(function ($ou, $key) {
return strlen($ou->ldap_ou);
});
if ($locations->count() > 0) {
$msg = 'Some locations have special OUs set. Locations will be automatically set for users in those OUs.';
LOG::debug($msg);
if ($this->dryrun) {
$this->info($msg);
}
$this->mappedLocations = $locations->pluck('ldap_ou', 'id');
}
}
/**
* Set the base dn if supplied.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
private function setBaseDn(): void
{
if ($this->option('base_dn')) {
$this->ldap->baseDn = $this->option('base_dn');
$msg = sprintf('Importing users from specified base DN: "%s"', $this->ldap->baseDn);
LOG::debug($msg);
if ($this->dryrun) {
$this->info($msg);
}
}
}
/**
* Get a default location id for imported users.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
private function getUserDefaultLocation(): void
{
$location = $this->option('location_id') ?? $this->option('location');
if ($location) {
$userLocation = Location::where('name', '=', $location)
->orWhere('id', '=', intval($location))
->select(['name', 'id'])
->first();
if ($userLocation) {
$msg = 'Importing users with default location: '.$userLocation->name.' ('.$userLocation->id.')';
LOG::debug($msg);
if ($this->dryrun) {
$this->info($msg);
}
$this->defaultLocation = collect([
$userLocation->id => $userLocation->name,
]);
} else {
$msg = 'The supplied location is invalid!';
LOG::error($msg);
if ($this->dryrun) {
$this->error($msg);
}
exit(0);
}
}
}
/**
* Check if LDAP intergration is enabled.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
private function checkIfLdapIsEnabled(): void
{
if (!$this->ldap->init()) {
$msg = 'LDAP intergration is not enabled. Exiting sync process.';
$this->info($msg);
Log::info($msg);
exit(0);
}
}
/**
* Check to make sure we can access the server.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*/
private function checkLdapConnetion(): void
{
try {
$this->ldap->testLdapAdUserConnection();
$this->ldap->testLdapAdBindConnection();
} catch (Exception $e) {
$this->outputError($e);
exit(0);
}
}
/**
* Output the json summary to the screen if enabled.
*
* @param Exception $error
*/
private function outputError($error): void
{
if ($this->option('json_summary')) {
$json_summary = [
'error' => true,
'error_message' => $error->getMessage(),
'summary' => [],
];
$this->info(json_encode($json_summary));
}
$this->error($error->getMessage());
LOG::error($error);
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class MoveUploadsToNewDisk extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:move-uploads {delete_local?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This will move your uploaded files to whatever your current disk is.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (config('filesystems.default')=='local') {
$this->error('Your current disk is set to local so we cannot proceed.');
$this->warn("Please configure your .env settings for S3 or Rackspace, \nand change your FILESYSTEM_DISK value to 's3' or 'rackspace'.");
return false;
}
$delete_local = $this->argument('delete_local');
$public_uploads['accessories'] = glob('storage/app/public/accessories'."/*.*");
$public_uploads['assets'] = glob('storage/app/public/assets'."/*.*");
$public_uploads['avatars'] = glob('storage/app/public/avatars'."/*.*");
$public_uploads['barcodes'] = glob('storage/app/public/barcodes'."/*.*");
$public_uploads['categories'] = glob('storage/app/public/categories'."/*.*");
$public_uploads['companies'] = glob('storage/app/public/companies'."/*.*");
$public_uploads['components'] = glob('storage/app/public/components'."/*.*");
$public_uploads['consumables'] = glob('storage/app/public/consumables'."/*.*");
$public_uploads['departments'] = glob('storage/app/public/departments'."/*.*");
$public_uploads['locations'] = glob('storage/app/public/locations'."/*.*");
$public_uploads['manufacturers'] = glob('storage/app/public/manufacturers'."/*.*");
$public_uploads['suppliers'] = glob('storage/app/public/suppliers'."/*.*");
$public_uploads['assetmodels'] = glob('storage/app/public/models'."/*.*");
// iterate files
foreach($public_uploads as $public_type => $public_upload)
{
$type_count = 0;
$this->info("\nThere are ".count($public_upload).' PUBLIC '.$public_type.' files.');
for ($i = 0; $i < count($public_upload); $i++) {
$type_count++;
$filename = basename($public_upload[$i]);
try {
Storage::disk('public')->put($public_type.'/'.$filename, file_get_contents($public_upload[$i]));
$new_url = Storage::disk('public')->url($public_type.'/'.$filename, $filename);
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
} catch (\Exception $e) {
\Log::debug($e);
$this->error($e);
}
}
}
$logos = glob('public/uploads'."/logo*.*");
$this->info("\nThere are ".count($logos).' files that might be logos.');
$type_count=0;
for ($l = 0; $l < count($logos); $l++) {
$type_count++;
$filename = basename($logos[$l]);
$new_url = Storage::disk('public')->url($logos[$l], file_get_contents($public_upload[$i]));
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.$new_url);
}
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
$private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*");
$private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*");
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
foreach($private_uploads as $private_type => $private_upload)
{
$this->info("\nThere are ".count($private_upload).' PRIVATE '.$private_type.' files.');
// $this->info(print_r($private_upload, true));
$type_count = 0;
for ($x = 0; $x < count($private_upload); $x++) {
$type_count++;
$filename = basename($private_upload[$x]);
try {
Storage::disk('private_uploads')->put($private_type.'/'.$filename, file_get_contents($public_upload[$i]));
$new_url = Storage::url($private_type.'/'.$filename, $filename);
$this->info($type_count.'. PRIVATE: '.$filename.' was copied to '.$new_url);
} catch (\Exception $e) {
\Log::debug($e);
$this->error($e);
}
}
}
if ($delete_local=='true') {
$public_delete_count = 0;
$private_delete_count = 0;
$this->info("\n\n");
$this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
$this->warn("\nTHIS WILL DELETE ALL OF YOUR LOCAL UPLOADED FILES. \n\nThis cannot be undone, so you should take a backup of your system before you proceed.\n");
$this->error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
if ($this->confirm("Do you wish to continue?")) {
foreach($public_uploads as $public_type => $public_upload) {
for ($i = 0; $i < count($public_upload); $i++) {
$filename = $public_upload[$i];
try {
unlink($filename);
$public_delete_count++;
} catch (\Exception $e) {
\Log::debug($e);
$this->error($e);
}
}
}
foreach($private_uploads as $private_type => $private_upload)
{
for ($i = 0; $i < count($private_upload); $i++) {
$filename = $private_upload[$i];
try {
unlink($filename);
$private_delete_count++;
} catch (\Exception $e) {
\Log::debug($e);
$this->error($e);
}
}
}
$this->info($public_delete_count." PUBLIC local files and ".$private_delete_count." PRIVATE local files were delete from your filesystem.");
}
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Asset;
use App\Models\User;
use App\Notifications\CurrentInventory;
class SendCurrentInventoryToUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:user-inventory';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This will send users a report of all of the items currently checked out to them.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$users = User::whereNull('deleted_at')->whereNotNull('email')->with('assets', 'accessories', 'licenses')->get();
$count = 0;
foreach ($users as $user) {
if (($user->assets->count() > 0) || ($user->accessories->count() > 0) || ($user->licenses->count() > 0))
{
$count++;
$user->notify((new CurrentInventory($user)));
}
}
$this->info($count.' users notified.');
}
}

View File

@@ -2,17 +2,15 @@
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Console\Command;
use App\Notifications\ExpectedCheckinNotification;
use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
class SendExpectedCheckinAlerts extends Command
{
/**
* The console command name.
*
@@ -29,8 +27,6 @@ class SendExpectedCheckinAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@@ -44,15 +40,15 @@ class SendExpectedCheckinAlerts extends Command
*/
public function handle()
{
$settings = Setting::getSettings();
$settings = Setting::getSettings();
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
$this->info($whenNotify . ' is deadline');
$this->info($assets->count() . ' assets');
foreach ($assets as $asset) {
if ($asset->assigned && $asset->checkedOutToUser()) {
if ($asset->assigned && $asset->checkedOutToUser()) {
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
}
}
@@ -60,14 +56,9 @@ class SendExpectedCheckinAlerts extends Command
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
return new AlertRecipient($item);
});
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
}
}

View File

@@ -4,16 +4,14 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\License;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use DB;
use App\Notifications\ExpiringLicenseNotification;
use App\Notifications\ExpiringAssetsNotification;
use App\Notifications\ExpiringLicenseNotification;
use Illuminate\Console\Command;
class SendExpirationAlerts extends Command
{
/**
* The console command name.
*
@@ -30,8 +28,6 @@ class SendExpirationAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@@ -45,48 +41,35 @@ class SendExpirationAlerts extends Command
*/
public function handle()
{
$settings = Setting::getSettings();
$settings = Setting::getSettings();
$threshold = $settings->alert_interval;
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
return new AlertRecipient($item);
});
// Expiring Assets
$assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
$assets = Asset::getExpiringWarrantee($threshold);
if ($assets->count() > 0) {
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(),
['count' => $assets->count(), 'threshold' => $threshold]));
\Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold));
}
// Expiring licenses
$licenses = License::getExpiringLicenses($threshold);
if ($licenses->count() > 0) {
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
\Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold));
}
} else {
if ($settings->alert_email=='') {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif ($settings->alerts_enabled!=1) {
} elseif (1 != $settings->alerts_enabled) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}
}
}

View File

@@ -2,13 +2,12 @@
namespace App\Console\Commands;
use App\Models\Setting;
use DB;
use Mail;
use App\Helpers\Helper;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\InventoryAlert;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class SendInventoryAlerts extends Command
{
@@ -28,8 +27,6 @@ class SendInventoryAlerts extends Command
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
@@ -45,29 +42,24 @@ class SendInventoryAlerts extends Command
{
$settings = Setting::getSettings();
if (($settings->alert_email!='') && ($settings->alerts_enabled==1)) {
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
$items = Helper::checkLowInventory();
// Send a rollup to the admin, if settings dictate
if (($items) && (count($items) > 0)) {
$this->info(trans_choice('mail.low_inventory_alert', count($items)));
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
return new AlertRecipient($item);
});
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
}
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
}
} else {
if (Setting::getSettings()->alert_email=='') {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (Setting::getSettings()->alerts_enabled!=1) {
} elseif (1 != $settings->alerts_enabled) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}
}
}

View File

@@ -1,91 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\License;
use App\Models\Setting;
use App\Notifications\ExpiringAssetsNotification;
use App\Models\Recipients;
use DB;
use Illuminate\Console\Command;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
class SendUpcomingAuditReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:upcoming-audits';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send email/slack notifications for upcoming asset audits.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$settings = Setting::getSettings();
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
});
// Assets due for auditing
$assets = Asset::whereNotNull('next_audit_date')
->DueOrOverdueForAudit($settings)
->orderBy('last_audit_date', 'asc')->get();
if ($assets->count() > 0) {
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Audit report sent to '.$settings->alert_email);
} else {
$this->info('No assets to be audited. No report sent.');
}
} elseif ($settings->alert_email=='') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (!$settings->audit_warning_days) {
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
} elseif ($settings->alerts_enabled!=1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
} else {
$this->error('Something went wrong. :( ');
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
}
}
}

View File

@@ -64,7 +64,7 @@ class SyncAssetLocations extends Command
$output['info'][] = 'There are '.$assigned_user_assets->count().' assets checked out to users.';
foreach ($assigned_user_assets as $assigned_user_asset) {
if (($assigned_user_asset->assignedTo) && ($assigned_user_asset->assignedTo->userLoc)) {
$new_location = $assigned_user_asset->assignedTo->userLoc->id;
$new_location=$assigned_user_asset->assignedTo->userloc->id;
$output['info'][] ='Setting User Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') to ' . $assigned_user_asset->assignedTo->userLoc->name . ' which is id: ' . $new_location;
} else {
$output['warn'][] ='Asset ' . $assigned_user_asset->id . ' ('.$assigned_user_asset->asset_tag.') still has no location! ';

View File

@@ -2,7 +2,6 @@
namespace App\Console;
use App\Console\Commands\RestoreDeletedUsers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -32,30 +31,30 @@ class Kernel extends ConsoleKernel
Commands\RegenerateAssetTags::class,
Commands\SyncAssetCounters::class,
Commands\RestoreDeletedUsers::class,
Commands\SendUpcomingAuditReport::class,
Commands\ImportLocations::class,
Commands\SendCurrentInventoryToUsers::class,
Commands\MoveUploadsToNewDisk::class,
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
/**
* This method is required by Laravel to handle any console routes
* that are defined in routes/console.php.
*/
protected function commands()
{
require base_path('routes/console.php');
$this->load(__DIR__.'/Commands');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Events;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutAccepted
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(CheckoutAcceptance $acceptance)
{
$this->acceptance = $acceptance;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Events;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutDeclined
{
use Dispatchable, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(CheckoutAcceptance $acceptance)
{
$this->acceptance = $acceptance;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutableCheckedIn
{
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedInBy;
public $note;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note)
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CheckoutableCheckedOut
{
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedOutBy;
public $note;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedOutBy = $checkedOutBy;
$this->note = $note;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Events;
use App\Models\Setting;
class SettingSaved
{
public $settings;
/**
* Create a new event instance.
*
* @param \App\Models\Setting $settings
*/
public function __construct(Setting $settings)
{
$this->settings = $settings;
}
}

View File

@@ -23,7 +23,6 @@ class Handler extends ExceptionHandler
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
\Intervention\Image\Exception\NotSupportedException::class,
];
/**
@@ -118,16 +117,4 @@ class Handler extends ExceptionHandler
return redirect()->guest('login');
}
/**
* Convert a validation exception into a JSON response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception
* @return \Illuminate\Http\JsonResponse
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json($exception->errors(), $exception->status);
}
}

View File

@@ -225,8 +225,9 @@ class Helper
*/
public static function predefined_formats()
{
$keys = array_keys(CustomField::$PredefinedFormats);
$keys = array_keys(CustomField::PREDEFINED_FORMATS);
$stuff = array_combine($keys, $keys);
return $stuff;
}
@@ -669,7 +670,37 @@ class Helper
return false;
}
/**
* Generate a random encrypted password.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @return string
*/
public static function generateEncyrptedPassword(): string
{
return bcrypt(Helper::generateUnencryptedPassword());
}
/**
* Get a random unencrypted password.
*
* @author Steffen Buehl <sb@sbuehl.com>
*
* @since 5.0.0
*
* @return string
*/
public static function generateUnencryptedPassword(): string
{
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$password = '';
for ( $i = 0; $i < 20; $i++ ) {
$password .= substr( $chars, random_int( 0, strlen( $chars ) - 1 ), 1 );
}
return $password;
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Accessory;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Redirect;
use Illuminate\Support\Facades\Storage;
/** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class AccessoriesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('index', Accessory::class);
return view('accessories/index');
}
/**
* Returns a view with a form to create a new Accessory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', Accessory::class);
$category_type = 'accessory';
return view('accessories/edit')->with('category_type', $category_type)
->with('item', new Accessory);
}
/**
* Validate and save new Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
$this->authorize(Accessory::class);
// create a new model instance
$accessory = new Accessory();
// Update the accessory data
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory);
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
}
/**
* Return view for the Accessory update form, prepopulated with existing data
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($accessoryId = null)
{
if ($item = Accessory::find($accessoryId)) {
$this->authorize($item);
return view('accessories/edit', compact('item'))->with('category_type', 'accessory');
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Save edited Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request
* @param int $accessoryId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(ImageUploadRequest $request, $accessoryId = null)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory);
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
}
/**
* Delete the given accessory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($accessoryId)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize($accessory);
if ($accessory->hasUsers() > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', array('count'=> $accessory->hasUsers())));
}
if ($accessory->image) {
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
}
/**
* Returns a view that invokes the ajax table which contains
* the content for the accessory detail view, which is generated in getDataView.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryID
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($accessoryID = null)
{
$accessory = Accessory::find($accessoryID);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID]));
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
class AccessoryCheckinController extends Controller
{
/**
* Check the accessory back into inventory
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param integer $accessoryUserId
* @param string $backto
* @return View
* @internal param int $accessoryId
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($accessoryUserId = null, $backto = null)
{
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
}
/**
* Check in the item so that it can be checked out again to someone else
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param null $accessoryUserId
* @param string $backto
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param int $accessoryId
*/
public function store(Request $request, $accessoryUserId = null, $backto = null)
{
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
$return_to = e($accessory_user->assigned_to);
event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note')));
return redirect()->route("accessories.show", $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
class AccessoryCheckoutController extends Controller
{
/**
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
if ($accessory->category) {
$this->authorize('checkout', $accessory);
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'));
}
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
}
/**
* Save the Accessory checkout information.
*
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param int $accessoryId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
}
$this->authorize('checkout', $accessory);
if (!$user = User::find(Input::get('assigned_to'))) {
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
}
// Update the accessory data
$accessory->assigned_to = e(Input::get('assigned_to'));
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to')
]);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
}
}

View File

@@ -1,400 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Accessory;
use App\Models\Company;
use App\Models\User;
use Auth;
use Carbon\Carbon;
use Config;
use DB;
use Gate;
use Input;
use Lang;
use Redirect;
use Illuminate\Http\Request;
use Slack;
use Str;
use View;
use Image;
use App\Http\Requests\ImageUploadRequest;
/** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class AccessoriesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return View
*/
public function index(Request $request)
{
$this->authorize('index', Accessory::class);
return view('accessories/index');
}
/**
* Returns a view with a form to create a new Accessory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
*/
public function create(Request $request)
{
$this->authorize('create', Accessory::class);
$category_type = 'accessory';
return view('accessories/edit')->with('category_type', $category_type)
->with('item', new Accessory);
}
/**
* Validate and save new Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return Redirect
*/
public function store(ImageUploadRequest $request)
{
$this->authorize(Accessory::class);
// create a new model instance
$accessory = new Accessory();
// Update the accessory data
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
if ($request->hasFile('image')) {
if (!config('app.lock_passwords')) {
$image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = "accessory-".str_random(18).'.'.$ext;
$path = public_path('/uploads/accessories');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(null, 800, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
}
$accessory->image = $file_name;
}
}
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
}
/**
* Return view for the Accessory update form, prepopulated with existing data
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return View
*/
public function edit(Request $request, $accessoryId = null)
{
if ($item = Accessory::find($accessoryId)) {
$this->authorize($item);
$category_type = 'accessory';
return view('accessories/edit', compact('item'))->with('category_type', $category_type);
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Save edited Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
*/
public function update(ImageUploadRequest $request, $accessoryId = null)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
if ($request->hasFile('image')) {
if (!config('app.lock_passwords')) {
$image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = "accessory-".str_random(18).'.'.$ext;
$path = public_path('/uploads/accessories');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(null, 800, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
}
if (($accessory->image) && (file_exists($path.'/'.$accessory->image))) {
unlink($path.'/'.$accessory->image);
}
$accessory->image = $file_name;
}
}
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($accessory->getErrors());
}
/**
* Delete the given accessory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
*/
public function destroy(Request $request, $accessoryId)
{
if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize($accessory);
if ($accessory->hasUsers() > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', array('count'=> $accessory->hasUsers())));
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
}
/**
* Returns a view that invokes the ajax table which contains
* the content for the accessory detail view, which is generated in getDataView.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryID
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
* @return View
*/
public function show(Request $request, $accessoryID = null)
{
$accessory = Accessory::find($accessoryID);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', compact('id')));
}
/**
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return View
*/
public function getCheckout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
if ($accessory->category) {
$this->authorize('checkout', $accessory);
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'));
}
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
}
/**
* Save the Accessory checkout information.
*
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
*/
public function postCheckout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
}
$this->authorize('checkout', $accessory);
if (!$user = User::find(Input::get('assigned_to'))) {
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
}
// Update the accessory data
$accessory->assigned_to = e(Input::get('assigned_to'));
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to')
]);
$logaction = $accessory->logCheckout(e(Input::get('note')), $user);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
$data['log_id'] = $logaction->id;
$data['eula'] = $accessory->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $accessory->name;
$data['checkout_date'] = $logaction->created_at;
$data['item_tag'] = '';
$data['expected_checkin'] = '';
$data['note'] = $logaction->note;
$data['require_acceptance'] = $accessory->requireAcceptance();
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
}
/**
* Check the accessory back into inventory
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param integer $accessoryUserId
* @param string $backto
* @return View
* @internal param int $accessoryId
*/
public function getCheckin(Request $request, $accessoryUserId = null, $backto = null)
{
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
}
/**
* Check in the item so that it can be checked out again to someone else
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param integer $accessoryUserId
* @param string $backto
* @return Redirect
* @internal param int $accessoryId
*/
public function postCheckin(Request $request, $accessoryUserId = null, $backto = null)
{
// Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
$return_to = e($accessory_user->assigned_to);
$logaction = $accessory->logCheckin(User::find($return_to), e(Input::get('note')));
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
if (!is_null($accessory_user->assigned_to)) {
$user = User::find($accessory_user->assigned_to);
}
$data['log_id'] = $logaction->id;
$data['first_name'] = e($user->first_name);
$data['last_name'] = e($user->last_name);
$data['item_name'] = e($accessory->name);
$data['checkin_date'] = e($logaction->created_at);
$data['item_tag'] = '';
$data['note'] = e($logaction->note);
if ($backto=='user') {
return redirect()->route("users.show", $return_to)->with('success', trans('admin/accessories/message.checkin.success'));
}
return redirect()->route("accessories.show", $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Http\Controllers\Account;
use App\Events\CheckoutAccepted;
use App\Events\CheckoutDeclined;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\Contracts\Acceptable;
use App\Models\License;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
class AcceptanceController extends Controller {
/**
* Show a listing of pending checkout acceptances for the current user
*
* @return View
*/
public function index() {
$acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
return view('account/accept.index', compact('acceptances'));
}
/**
* Shows a form to either accept or decline the checkout acceptance
*
* @param int $id
* @return mixed
*/
public function create($id) {
$acceptance = CheckoutAcceptance::find($id);
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
}
return view('account/accept.create', compact('acceptance'));
}
/**
* Stores the accept/decline of the checkout acceptance
*
* @param Request $request
* @param int $id
* @return Redirect
*/
public function store(Request $request, $id) {
$acceptance = CheckoutAcceptance::find($id);
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
if (!Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
}
if (!$request->filled('asset_acceptance')) {
return redirect()->back()->with('error', trans('admin/users/message.error.accept_or_decline'));
}
/**
* Get the signature and save it
*/
if (!Storage::exists('private_uploads/signatures')) Storage::makeDirectory('private_uploads/signatures', 775);
if ($request->filled('signature_output')) {
$sig_filename = "siglog-" .Str::uuid() . '-'.date('Y-m-d-his').".png";
$data_uri = e($request->input('signature_output'));
$encoded_image = explode(",", $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/'.$sig_filename, (string)$decoded_image);
}
if ($request->input('asset_acceptance') == 'accepted') {
$acceptance->accept($sig_filename);
event(new CheckoutAccepted($acceptance));
$return_msg = trans('admin/users/message.accepted');
} else {
$acceptance->decline($sig_filename);
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
}
return redirect()->to('account/accept')->with('success', $return_msg);
}
}

View File

@@ -46,7 +46,7 @@ class AccessoriesController extends Controller
$accessories->where('supplier_id','=',$request->input('supplier_id'));
}
$offset = (($accessories) && (request('offset') > $accessories->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -132,18 +132,28 @@ class AccessoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function checkedout($id)
public function checkedout($id, Request $request)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::findOrFail($id);
$accessory = Accessory::with('lastCheckout')->findOrFail($id);
if (!Company::isCurrentUserHasAccess($accessory)) {
return ['total' => 0, 'rows' => []];
}
$accessory_users = $accessory->users;
$total = $accessory_users->count();
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory_users, $total);
$accessory->lastCheckoutArray = $accessory->lastCheckout->toArray();
$accessory_users = $accessory->users;
if ($request->filled('search')) {
$accessory_users = $accessory->users()
->where('first_name', 'like', '%'.$request->input('search').'%')
->orWhere('last_name', 'like', '%'.$request->input('search').'%')
->get();
}
$total = $accessory_users->count();
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
}

View File

@@ -34,17 +34,17 @@ class AssetMaintenancesController extends Controller
*/
public function index(Request $request)
{
$maintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company', 'admin');
$maintenances = AssetMaintenance::with('asset', 'asset.model','asset.location', 'supplier', 'asset.company', 'admin');
if (Input::has('search')) {
$maintenances = $maintenances->TextSearch(e($request->input('search')));
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
}
if ($request->filled('asset_id')) {
$maintenances->where('asset_id', '=', $request->input('asset_id'));
}
$offset = (($maintenances) && (request('offset') > $maintenances->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = [

View File

@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
/**
@@ -60,7 +61,7 @@ class AssetModelsController extends Controller
$assetmodels->TextSearch($request->input('search'));
}
$offset = (($assetmodels) && (request('offset') > $assetmodels->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';
@@ -177,9 +178,9 @@ class AssetModelsController extends Controller
if ($assetmodel->image) {
try {
unlink(public_path().'/uploads/models/'.$assetmodel->image);
Storage::disk('public')->delete('assetmodels/'.$assetmodel->image);
} catch (\Exception $e) {
\Log::info($e);
\Log::error($e);
}
}
@@ -234,7 +235,7 @@ class AssetModelsController extends Controller
$assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')';
}
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? url('/').'/uploads/models/'.$assetmodel->image : null;
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? Storage::disk('public')->url('assetmodels/'.e($assetmodel->image)) : null;
}
return (new SelectlistTransformer)->transformSelectlist($assetmodels);

View File

@@ -3,13 +3,14 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetRequest;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\License;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
@@ -52,7 +53,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function index(Request $request, $audit = null)
public function index(Request $request)
{
$this->authorize('index', Asset::class);
@@ -144,25 +145,10 @@ class AssetsController extends Controller
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
$offset = (($assets) && (request('offset') > $assets->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
}
}
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
@@ -170,7 +156,7 @@ class AssetsController extends Controller
// I am sad. :(
switch ($request->input('status')) {
case 'Deleted':
$assets->withTrashed()->Deleted();
$assets->onlyTrashed();
break;
case 'Pending':
$assets->join('status_labels AS status_alias',function ($join) {
@@ -285,9 +271,9 @@ class AssetsController extends Controller
break;
}
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
// dd($assets);
return (new AssetsTransformer)->transformAssets($assets, $total);
}
@@ -323,8 +309,7 @@ class AssetsController extends Controller
$this->authorize('index', Asset::class);
if ($assets = Asset::with('assetstatus')->with('assignedTo')
->withTrashed()->where('serial',$serial)->get()) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
@@ -341,14 +326,22 @@ class AssetsController extends Controller
*/
public function show($id)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as userRequests_count')->findOrFail($id)) {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
}
public function licenses($id)
{
$this->authorize('view', Asset::class);
$this->authorize('view', License::class);
$asset = Asset::where('id', $id)->withTrashed()->first();
$licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
/**
* Gets a paginated collection for the select2 menus
@@ -369,7 +362,7 @@ class AssetsController extends Controller
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id'
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(),'company_id', 'assets');
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(), 'company_id', 'assets');
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
@@ -394,7 +387,7 @@ class AssetsController extends Controller
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
}
if ($asset->assetstatus->getStatuslabelType()=='pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
}
@@ -415,7 +408,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function store(AssetRequest $request)
public function store(Request $request)
{
$this->authorize('create', Asset::class);
@@ -448,13 +441,7 @@ class AssetsController extends Controller
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug(), null));
}
}
@@ -489,28 +476,23 @@ class AssetsController extends Controller
{
$this->authorize('update', Asset::class);
$asset->fill($request->all());
if ($asset = Asset::find($id)) {
$asset->fill($request->all());
($request->filled('model_id')) ?
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
($request->filled('company_id')) ?
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : null;
($request->filled('rtd_location_id')) ?
$asset->location_id = $request->get('rtd_location_id') : null;
$asset->location_id = $request->get('rtd_location_id') : '';
($request->filled('company_id')) ?
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
// Update custom fields
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->convertUnicodeDbSlug())) {
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
if ($request->filled($field->convertUnicodeDbSlug())) {
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug()));
}
}
}
@@ -519,9 +501,9 @@ class AssetsController extends Controller
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id;
$location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id;
$location = $target->location_id;
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id;
}
@@ -717,13 +699,27 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
$asset = Asset::where('asset_tag','=', $request->input('asset_tag'))->first();
if ($asset) {
// We don't want to log this as a normal update, so let's bypass that
$asset->unsetEventDispatcher();
$asset->next_audit_date = $request->input('next_audit_date');
$asset->next_audit_date = $dt;
if ($request->filled('next_audit_date')) {
$asset->next_audit_date = $request->input('next_audit_date');
}
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location')=='1') {
$asset->location_id = $request->input('location_id');
}
$asset->last_audit_date = date('Y-m-d h:i:s');
if ($asset->save()) {
@@ -731,7 +727,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag),
'note'=> e($request->input('note')),
'next_audit_date' => Helper::getFormattedDateObject($log->calcNextAuditDate())
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date)
], trans('admin/hardware/message.audit.success')));
}
}

View File

@@ -8,6 +8,7 @@ use App\Helpers\Helper;
use App\Models\Category;
use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
class CategoriesController extends Controller
{
@@ -30,7 +31,7 @@ class CategoriesController extends Controller
$categories = $categories->TextSearch($request->input('search'));
}
$offset = (($categories) && (request('offset') > $categories->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
@@ -114,15 +115,15 @@ class CategoriesController extends Controller
public function destroy($id)
{
$this->authorize('delete', Category::class);
$category = Category::findOrFail($id);
$category = Category::withCount('models as models_count', 'accessories as accessories_count','consumables as consumables_count','components as components_count')->findOrFail($id);
if ($category->has_models() > 0) {
if ($category->models_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'model'])));
} elseif ($category->accessories()->count() > 0) {
} elseif ($category->accessories_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory'])));
} elseif ($category->consumables()->count() > 0) {
} elseif ($category->consumables_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable'])));
} elseif ($category->components()->count() > 0) {
} elseif ($category->components_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>'component'])));
}
$category->delete();
@@ -158,7 +159,7 @@ class CategoriesController extends Controller
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($categories as $category) {
$category->use_image = ($category->image) ? url('/').'/uploads/categories/'.$category->image : null;
$category->use_image = ($category->image) ? Storage::disk('public')->url('categories/'.$category->image, $category->image) : null;
}
return (new SelectlistTransformer)->transformSelectlist($categories);

View File

@@ -41,7 +41,7 @@ class CompaniesController extends Controller
$companies->TextSearch($request->input('search'));
}
$offset = (($companies) && (request('offset') > $companies->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -178,7 +178,7 @@ class CompaniesController extends Controller
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($companies as $company) {
$company->use_image = ($company->image) ? url('/').'/uploads/companies/'.$company->image : null;
$company->use_image = ($company->image) ? Storage::disk('public')->url('companies/'.$company->image, $company->image) : null;
}
return (new SelectlistTransformer)->transformSelectlist($companies);

View File

@@ -43,7 +43,7 @@ class ComponentsController extends Controller
$components->where('location_id','=',$request->input('location_id'));
}
$offset = (($components) && (request('offset') > $components->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location','image'];

View File

@@ -35,16 +35,12 @@ class ConsumablesController extends Controller
$consumables->where('company_id','=',$request->input('company_id'));
}
if ($request->filled('category_id')) {
$consumables->where('category_id','=',$request->input('category_id'));
}
if ($request->filled('manufacturer_id')) {
$consumables->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
$offset = (($consumables) && (request('offset') > $consumables->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','order_number','min_amt','purchase_date','purchase_cost','company','category','model_number', 'item_no', 'manufacturer','location','qty','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -156,7 +152,7 @@ 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.
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array

View File

@@ -16,7 +16,6 @@ use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Http\Transformers\CustomFieldsetsTransformer;
use App\Http\Requests\AssetRequest;
/**
* This controller handles all actions related to Custom Asset Fieldsets for

View File

@@ -9,6 +9,7 @@ use App\Http\Transformers\DepartmentsTransformer;
use App\Helpers\Helper;
use Auth;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
class DepartmentsController extends Controller
{
@@ -39,7 +40,7 @@ class DepartmentsController extends Controller
$departments = $departments->TextSearch($request->input('search'));
}
$offset = (($departments) && (request('offset') > $departments->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -100,14 +101,36 @@ class DepartmentsController extends Controller
return (new DepartmentsTransformer)->transformDepartment($department);
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->authorize('update', Department::class);
$department = Department::findOrFail($id);
$department->fill($request->all());
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
}
/**
* Validates and deletes selected location.
* Validates and deletes selected department.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v1.0]
* @since [v4.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy($id)
@@ -152,7 +175,7 @@ class DepartmentsController extends Controller
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($departments as $department) {
$department->use_image = ($department->image) ? url('/').'/uploads/departments/'.$department->image : null;
$department->use_image = ($department->image) ? Storage::disk('public')->url('departments/'.$department->image, $department->image) : null;
}
return (new SelectlistTransformer)->transformSelectlist($departments);

View File

@@ -28,7 +28,7 @@ class DepreciationsController extends Controller
$depreciations = $depreciations->TextSearch($request->input('search'));
}
$offset = (($depreciations) && (request('offset') > $depreciations->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -110,10 +110,10 @@ class DepreciationsController extends Controller
public function destroy($id)
{
$this->authorize('delete', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);
$this->authorize('delete', $depreciation);
if ($depreciation->has_models() > 0) {
if ($depreciation->models_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', trans('admin/depreciations/message.assoc_users')));
}

View File

@@ -28,7 +28,7 @@ class GroupsController extends Controller
$groups = $groups->TextSearch($request->input('search'));
}
$offset = (($groups) && (request('offset') > $groups->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';

View File

@@ -15,6 +15,7 @@ use League\Csv\Reader;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Artisan;
use App\Models\Asset;
use Illuminate\Support\Facades\Storage;
class ImportController extends Controller
{
@@ -25,7 +26,7 @@ class ImportController extends Controller
*/
public function index()
{
$this->authorize('import');
//
$imports = Import::latest()->get();
return (new ImportsTransformer)->transformImports($imports);
@@ -39,8 +40,10 @@ class ImportController extends Controller
*/
public function store()
{
$this->authorize('import');
if (!config('app.lock_passwords')) {
//
if (!Company::isCurrentUserAuthorized()) {
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
} elseif (!config('app.lock_passwords')) {
$files = Input::file('files');
$path = config('app.private_uploads').'/imports';
$results = [];
@@ -112,12 +115,12 @@ class ImportController extends Controller
/**
* Processes the specified Import.
*
* @param \App\Import $import
* @param int $import_id
* @return \Illuminate\Http\Response
*/
public function process(ItemImportRequest $request, $import_id)
{
$this->authorize('import');
$this->authorize('create', Asset::class);
// Run a backup immediately before processing
Artisan::call('backup:run');
$errors = $request->import(Import::find($import_id));
@@ -155,19 +158,26 @@ class ImportController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Import $import
* @param int $import_id
* @return \Illuminate\Http\Response
*/
public function destroy($import_id)
{
$this->authorize('import');
$import = Import::find($import_id);
try {
unlink(config('app.private_uploads').'/imports/'.$import->file_path);
$import->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.import.file_delete_success')));
} catch (\Exception $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.import.file_delete_error')), 500);
$this->authorize('create', Asset::class);
if ($import = Import::find($import_id)) {
try {
// Try to delete the file
Storage::delete('imports/'.$import->file_path);
$import->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.import.file_delete_success')));
} catch (\Exception $e) {
// If the file delete didn't work, remove it from the database anyway and return a warning
$import->delete();
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
}
}
}

View File

@@ -82,7 +82,7 @@ class LicensesController extends Controller
}
$offset = (($licenses) && (request('offset') > $licenses->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -150,7 +150,7 @@ class LicensesController extends Controller
public function show($id)
{
$this->authorize('view', License::class);
$license = License::findOrFail($id);
$license = License::withCount('freeSeats')->findOrFail($id);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
return (new LicensesTransformer)->transformLicense($license);
}
@@ -227,12 +227,20 @@ class LicensesController extends Controller
$seats = LicenseSeat::where('license_id', $licenseId)->with('license', 'user', 'asset');
$offset = (($seats) && (request('offset') > $seats->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if($seats->count() < $offset){
$offset = 0;
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$total = $seats->count();
if($total < $offset){
$offset = 0;
}
$seats = $seats->skip($offset)->take($limit)->get();
if ($seats) {

View File

@@ -8,6 +8,7 @@ use App\Helpers\Helper;
use App\Models\Location;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
class LocationsController extends Controller
{
@@ -51,7 +52,7 @@ class LocationsController extends Controller
$offset = (($locations) && (request('offset') > $locations->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -123,9 +124,9 @@ class LocationsController extends Controller
'locations.image',
'locations.currency'
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('users as users_count')->findOrFail($id);
->withCount('assignedAssets')
->withCount('assets')
->withCount('users')->findOrFail($id);
return (new LocationsTransformer)->transformLocation($location);
}
@@ -203,7 +204,7 @@ class LocationsController extends Controller
// they may not have a ->name value but we want to display something anyway
foreach ($locations as $location) {
$location->use_text = $location->name;
$location->use_image = ($location->image) ? url('/').'/uploads/locations/'.$location->image : null;
$location->use_image = ($location->image) ? Storage::disk('public')->url('locations/'.$location->image, $location->image): null;
}
return (new SelectlistTransformer)->transformSelectlist($locations);

View File

@@ -9,6 +9,7 @@ use App\Models\Manufacturer;
use App\Http\Transformers\DatatablesTransformer;
use App\Http\Transformers\ManufacturersTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
class ManufacturersController extends Controller
{
@@ -39,7 +40,7 @@ class ManufacturersController extends Controller
$offset = (($manufacturers) && (request('offset') > $manufacturers->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -83,7 +84,7 @@ class ManufacturersController extends Controller
public function show($id)
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
$manufacturer = Manufacturer::withCount('assets as assets_count', 'licenses as licenses_count', 'consumables as consumables_count', 'accessories as accessories_count', 'models as models_count' )->findOrFail($id);
return (new ManufacturersTransformer)->transformManufacturer($manufacturer);
}
@@ -120,11 +121,21 @@ class ManufacturersController extends Controller
*/
public function destroy($id)
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$manufacturer = Manufacturer::withCount('assets as assets_count', 'licenses as licenses_count', 'consumables as consumables_count', 'accessories as accessories_count', 'models as models_count' )->findOrFail($id);
$this->authorize('delete', $manufacturer);
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
if (($manufacturer->assets_count == 0) && ($manufacturer->licenses_count==0) && ($manufacturer->consumables_count==0) && ($manufacturer->accessories_count==0) && ($manufacturer->models_count==0) && ($manufacturer->deleted_at=='')) {
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.delete.error')));
}
@@ -156,7 +167,7 @@ class ManufacturersController extends Controller
// they may not have a ->name value but we want to display something anyway
foreach ($manufacturers as $manufacturer) {
$manufacturer->use_text = $manufacturer->name;
$manufacturer->use_image = ($manufacturer->image) ? url('/').'/uploads/manufacturers/'.$manufacturer->image : null;
$manufacturer->use_image = ($manufacturer->image) ? Storage::disk('public')->url('manufacturers/'.$manufacturer->image, $manufacturer->image) : null;
}
return (new SelectlistTransformer)->transformSelectlist($manufacturers);

View File

@@ -27,20 +27,15 @@ class ProfileController extends Controller
foreach ($checkoutRequests as $checkoutRequest) {
// Make sure the asset and request still exist
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
$results['rows'][] = [
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
'name' => $checkoutRequest->itemRequested()->present()->name(),
'type' => $checkoutRequest->itemType(),
'qty' => $checkoutRequest->quantity,
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
}
$results['rows'][] = [
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
'name' => $checkoutRequest->itemRequested()->present()->name(),
'type' => $checkoutRequest->itemType(),
'qty' => $checkoutRequest->quantity,
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
}
return $results;
}

View File

@@ -2,104 +2,85 @@
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ldap;
use Validator;
use App\Services\LdapAd;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
use App\Models\Setting;
use Mail;
use App\Notifications\SlackTest;
use Notification;
use Illuminate\Http\Request;
use App\Notifications\MailTest;
use App\Notifications\SlackTest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
use App\Http\Transformers\LoginAttemptsTransformer;
class SettingsController extends Controller
{
public function ldaptest()
/**
* Test the ldap settings
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @param App\Models\LdapAd $ldap
*
* @return \Illuminate\Http\JsonResponse
*/
public function ldapAdSettingsTest(LdapAd $ldap): JsonResponse
{
if (Setting::getSettings()->ldap_enabled!='1') {
\Log::debug('LDAP is not enabled cannot test.');
if(!$ldap->init()) {
Log::info('LDAP is not enabled cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
}
\Log::debug('Preparing to test LDAP connection');
// The connect, bind and resulting users message
$message = [];
Log::info('Preparing to test LDAP user login');
// Test user can connect to the LDAP server
try {
$connection = Ldap::connectToLdap();
try {
\Log::debug('attempting to bind to LDAP for LDAP test');
Ldap::bindAdminToLdap($connection);
return response()->json(['message' => 'It worked!'], 200);
} catch (\Exception $e) {
\Log::debug('Bind failed');
return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
\Log::debug('Connection failed');
return response()->json(['message' => $e->getMessage()], 600);
$ldap->testLdapAdUserConnection();
$message['login'] = [
'message' => 'Successfully connected to LDAP server.'
];
} catch (\Exception $ex) {
return response()->json([
'message' => 'Error logging into LDAP server, error: ' . $ex->getMessage() . ' - Verify your that your username and password are correct'
], 400);
}
}
public function ldaptestlogin(Request $request)
{
if (Setting::getSettings()->ldap_enabled!='1') {
\Log::debug('LDAP is not enabled. Cannot test.');
return response()->json(['message' => 'LDAP is not enabled, cannot test.'], 400);
}
$rules = array(
'ldaptest_user' => 'required',
'ldaptest_password' => 'required'
);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
\Log::debug('LDAP Validation test failed.');
$validation_errors = implode(' ',$validator->errors()->all());
return response()->json(['message' => $validator->errors()->all()], 400);
}
\Log::debug('Preparing to test LDAP login');
Log::info('Preparing to test LDAP bind connection');
// Test user can bind to the LDAP server
try {
$connection = Ldap::connectToLdap();
try {
Ldap::bindAdminToLdap($connection);
\Log::debug('Attempting to bind to LDAP for LDAP test');
try {
$ldap_user = Ldap::findAndBindUserLdap($request->input('ldaptest_user'), $request->input('ldaptest_password'));
if ($ldap_user) {
\Log::debug('It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.');
return response()->json(['message' => 'It worked! '. $request->input('ldaptest_user').' successfully binded to LDAP.'], 200);
}
return response()->json(['message' => 'Login Failed. '. $request->input('ldaptest_user').' did not successfully bind to LDAP.'], 400);
} catch (\Exception $e) {
\Log::debug('LDAP login failed');
return response()->json(['message' => $e->getMessage()], 400);
}
} catch (\Exception $e) {
\Log::debug('Bind failed');
return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
\Log::debug('Connection failed');
return response()->json(['message' => $e->getMessage()], 500);
$ldap->testLdapAdBindConnection();
$message['bind'] = [
'message' => 'Successfully binded to LDAP server.'
];
} catch (\Exception $ex) {
return response()->json([
'message' => 'Error binding to LDAP server, error: ' . $ex->getMessage()
], 400);
}
Log::info('Preparing to get sample user set from LDAP directory');
// Get a sample of 10 users so user can verify the data is correct
try {
$users = $ldap->testUserImportSync();
$message['user_sync'] = [
'users' => $users
];
} catch (\Exception $ex) {
$message['user_sync'] = [
'message' => 'Error getting users from LDAP directory, error: ' . $ex->getMessage()
];
return response()->json($message, 400);
}
return response()->json($message, 200);
}
public function slacktest()
{
@@ -143,6 +124,30 @@ class SettingsController extends Controller
}
/**
* Get a list of login attempts
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.0]
* @param \Illuminate\Http\Request $request
* @return array
*/
public function showLoginAttempts(Request $request)
{
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent','successful','created_at'];
$login_attempts = DB::table('login_attempts');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'created_at';
$total = $login_attempts->count();
$login_attempts->orderBy($sort, $order);
$login_attempt_results = $login_attempts->skip(request('offset', 0))->take(request('limit', 20))->get();
return (new LoginAttemptsTransformer)->transformLoginAttempts($login_attempt_results, $total);
}
}

View File

@@ -30,7 +30,7 @@ class StatuslabelsController extends Controller
$statuslabels = $statuslabels->TextSearch($request->input('search'));
}
$offset = (($statuslabels) && (request('offset') > $statuslabels->count())) ? 0 : request('offset', 0);
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -204,11 +204,11 @@ class StatuslabelsController extends Controller
{
$this->authorize('view', Statuslabel::class);
$this->authorize('index', Asset::class);
$assets = Asset::where('status_id','=',$id);
$assets = Asset::where('status_id','=',$id)->with('assignedTo');
$allowed_columns = [
'id',
'name'
'name',
];
$offset = request('offset', 0);
@@ -238,8 +238,6 @@ class StatuslabelsController extends Controller
*/
public function checkIfDeployable($id) {
$statuslabel = Statuslabel::findOrFail($id);
$this->authorize('view', Asset::class);
if ($statuslabel->getStatuslabelType()=='deployable') {
return '1';
}

View File

@@ -8,6 +8,7 @@ use App\Helpers\Helper;
use App\Models\Supplier;
use App\Http\Transformers\SuppliersTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Support\Facades\Storage;
class SuppliersController extends Controller
@@ -22,10 +23,10 @@ class SuppliersController extends Controller
public function index(Request $request)
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count'];
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count','url'];
$suppliers = Supplier::select(
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes')
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes', 'url')
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count');
@@ -33,7 +34,7 @@ class SuppliersController extends Controller
$suppliers = $suppliers->TextSearch($request->input('search'));
}
$offset = (($suppliers) && (request('offset') > $suppliers->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -164,7 +165,7 @@ class SuppliersController extends Controller
// they may not have a ->name value but we want to display something anyway
foreach ($suppliers as $supplier) {
$supplier->use_text = $supplier->name;
$supplier->use_image = ($supplier->image) ? url('/').'/uploads/suppliers/'.$supplier->image : null;
$supplier->use_image = ($supplier->image) ? Storage::disk('public')->url('suppliers/'.$supplier->image, $supplier->image) : null;
}
return (new SelectlistTransformer)->transformSelectlist($suppliers);

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Http\Transformers\LicensesTransformer;
use App\Models\License;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Transformers\UsersTransformer;
@@ -12,7 +14,6 @@ use App\Http\Requests\SaveUserRequest;
use App\Models\Asset;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\AccessoriesTransformer;
class UsersController extends Controller
{
@@ -52,18 +53,17 @@ class UsersController extends Controller
'users.phone',
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
'users.updated_at',
'users.username',
'users.zip',
])->with('manager', 'groups', 'userloc', 'company', 'department','assets','licenses','accessories','consumables')
->withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count');
->withCount('assets as assets_count','licenses as licneses_count','accessories as accessories_count','consumables as consumables_count');
$users = Company::scopeCompanyables($users);
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
$users = $users->GetDeleted();
$users = $users->onlyTrashed();
}
if ($request->filled('company_id')) {
@@ -87,7 +87,7 @@ class UsersController extends Controller
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
$offset = request('offset', 0);
$limit = request('limit', 20);
switch ($request->input('sort')) {
@@ -100,9 +100,6 @@ class UsersController extends Controller
case 'department':
$users = $users->OrderDepartment($order);
break;
case 'company':
$users = $users->OrderCompany($order);
break;
default:
$allowed_columns =
[
@@ -152,7 +149,8 @@ class UsersController extends Controller
$users = Company::scopeCompanyables($users);
if ($request->filled('search')) {
$users = $users->SimpleNameSearch($request->get('search'))
$users = $users->where('first_name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('last_name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
}
@@ -203,8 +201,9 @@ class UsersController extends Controller
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$user->password = bcrypt($request->get('password', $tmp_pass));
if ($user->save()) {
if ($request->filled('groups')) {
if ($request->has('groups')) {
$user->groups()->sync($request->input('groups'));
} else {
$user->groups()->sync(array());
@@ -244,6 +243,16 @@ class UsersController extends Controller
$this->authorize('update', User::class);
$user = User::findOrFail($id);
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
// Thanks, jerks. You are why we can't have nice things. - snipe
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
}
$user->fill($request->all());
if ($user->id == $request->input('manager_id')) {
@@ -259,13 +268,9 @@ class UsersController extends Controller
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
} else {
$user->groups()->sync(array());
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
@@ -291,6 +296,15 @@ class UsersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
}
// Remove the user's avatar if they have one
if (Storage::disk('public')->exists('avatars/'.$user->avatar)) {
try {
Storage::disk('public')->delete('avatars/'.$user->avatar);
} catch (\Exception $e) {
\Log::debug($e);
}
}
if ($user->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
}
@@ -314,20 +328,20 @@ class UsersController extends Controller
}
/**
* Return JSON containing a list of accessories assigned to a user.
* Return JSON containing a list of licenses assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
* @author [N. Mathar] [<snipe@snipe.net>]
* @since [v5.0]
* @param $userId
* @return string JSON
*/
public function accessories($id)
public function licenses($id)
{
$this->authorize('view', User::class);
$user = User::findOrFail($id);
$this->authorize('view', Accessory::class);
$accessories = $user->accessories;
return (new AccessoriesTransformer)->transformAccessories($accessories, $accessories->count());
$this->authorize('view', License::class);
$user = User::where('id', $id)->withTrashed()->first();
$licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
/**

View File

@@ -165,6 +165,7 @@ class AssetMaintenancesController extends Controller
} elseif (!$assetMaintenance->asset) {
return redirect()->route('maintenances.index')
->with('error', 'The asset associated with this maintenance does not exist.');
} elseif (!Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}

View File

@@ -1,26 +1,15 @@
<?php
namespace App\Http\Controllers;
use App\Models\CustomField;
use Image;
use Input;
use Lang;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\View;
use App\Models\AssetModel;
use Redirect;
use Auth;
use DB;
use Str;
use Validator;
use View;
use App\Models\Asset;
use App\Models\Company;
use Config;
use App\Helpers\Helper;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* This class controls all actions related to asset models for
* the Snipe-IT Asset Management application.
@@ -31,13 +20,14 @@ use Symfony\Component\HttpFoundation\JsonResponse;
class AssetModelsController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
* Returns a view that invokes the ajax tables which actually contains
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('index', AssetModel::class);
@@ -45,29 +35,31 @@ class AssetModelsController extends Controller
}
/**
* Returns a view containing the asset model creation form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
* Returns a view containing the asset model creation form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', AssetModel::class);
$category_type = 'asset';
return view('models/edit')->with('category_type',$category_type)
->with('depreciation_list', Helper::depreciationList())
->with('item', new AssetModel);
return view('models/edit')->with('category_type', 'asset')
->with('depreciation_list', Helper::depreciationList())
->with('item', new AssetModel);
}
/**
* Validate and process the new Asset Model data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return Redirect
*/
* Validate and process the new Asset Model data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param ImageUploadRequest $request
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
@@ -90,23 +82,7 @@ class AssetModelsController extends Controller
$model->fieldset_id = e($request->input('custom_fieldset'));
}
if (Input::file('image')) {
$image = Input::file('image');
$file_name = str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
$path = app('models_upload_path');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
}
$model->image = $file_name;
}
$model = $request->handleImages($model);
// Was it created?
if ($model->save()) {
@@ -121,13 +97,14 @@ class AssetModelsController extends Controller
}
/**
* Returns a view containing the asset model edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return View
*/
* Returns a view containing the asset model edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($modelId = null)
{
$this->authorize('update', AssetModel::class);
@@ -144,14 +121,16 @@ class AssetModelsController extends Controller
/**
* Validates and processes form data from the edit
* Asset Model form based on the model ID passed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
*/
* Validates and processes form data from the edit
* Asset Model form based on the model ID passed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param ImageUploadRequest $request
* @param int $modelId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(ImageUploadRequest $request, $modelId = null)
{
$this->authorize('update', AssetModel::class);
@@ -182,37 +161,7 @@ class AssetModelsController extends Controller
}
}
$old_image = $model->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$model->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $model->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('models_upload_path').$file_name);
} else {
$image->move(app('models_upload_path'), $file_name);
}
$model->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('models_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$model = $request->handleImages($model);
if ($model->save()) {
return redirect()->route("models.index")->with('success', trans('admin/models/message.update.success'));
@@ -221,14 +170,15 @@ class AssetModelsController extends Controller
}
/**
* Validate and delete the given Asset Model. An Asset Model
* cannot be deleted if there are associated assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
*/
* Validate and delete the given Asset Model. An Asset Model
* cannot be deleted if there are associated assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($modelId)
{
$this->authorize('delete', AssetModel::class);
@@ -244,9 +194,9 @@ class AssetModelsController extends Controller
if ($model->image) {
try {
unlink(public_path().'/uploads/models/'.$model->image);
Storage::disk('public')->delete('models/'.$model->image);
} catch (\Exception $e) {
\Log::info($e);
\Log::error($e);
}
}
@@ -259,13 +209,14 @@ class AssetModelsController extends Controller
/**
* Restore a given Asset Model (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
*/
* Restore a given Asset Model (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getRestore($modelId = null)
{
$this->authorize('create', AssetModel::class);
@@ -273,16 +224,8 @@ class AssetModelsController extends Controller
$model = AssetModel::withTrashed()->find($modelId);
if (isset($model->id)) {
// Restore the model
$model->restore();
// Prepare the success message
$success = trans('admin/models/message.restore.success');
// Redirect back
return redirect()->route('models.index')->with('success', $success);
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
}
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
@@ -290,13 +233,14 @@ class AssetModelsController extends Controller
/**
* Get the model information to present to the model view page
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return View
*/
* Get the model information to present to the model view page
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($modelId = null)
{
$this->authorize('view', AssetModel::class);
@@ -305,11 +249,8 @@ class AssetModelsController extends Controller
if (isset($model->id)) {
return view('models/view', compact('model'));
}
// Prepare the error message
$error = trans('admin/models/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('models.index')->with('error', $error);
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
/**
@@ -331,12 +272,10 @@ class AssetModelsController extends Controller
$model->id = null;
// Show the page
$view = View::make('models/edit');
$view->with('depreciation_list', Helper::depreciationList());
$view->with('item', $model);
$view->with('clone_model', $model_to_clone);
return $view;
return view('models/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
->with('clone_model', $model_to_clone);
}
@@ -350,150 +289,10 @@ class AssetModelsController extends Controller
*/
public function getCustomFields($modelId)
{
$model = AssetModel::find($modelId);
return view("models.custom_fields_form")->with("model", $model);
return view("models.custom_fields_form")->with("model", AssetModel::find($modelId));
}
/**
* Returns a view that allows the user to bulk edit model attrbutes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @return \Illuminate\Contracts\View\View
*/
public function postBulkEdit(Request $request)
{
$models_raw_array = Input::get('ids');
// Make sure some IDs have been selected
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
$models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->orderBy('assets_count', 'ASC')->get();
// If deleting....
if ($request->input('bulk_actions')=='delete') {
$valid_count = 0;
foreach ($models as $model) {
if ($model->assets_count == 0) {
$valid_count++;
}
}
return view('models/bulk-delete', compact('models'))->with('valid_count', $valid_count);
// Otherwise display the bulk edit screen
} else {
$nochange = ['NC' => 'No Change'];
$fieldset_list = $nochange + Helper::customFieldsetList();
$depreciation_list = $nochange + Helper::depreciationList();
return view('models/bulk-edit', compact('models'))
->with('fieldset_list', $fieldset_list)
->with('depreciation_list', $depreciation_list);
}
}
return redirect()->route('models.index')
->with('error', 'You must select at least one model to edit.');
}
/**
* Returns a view that allows the user to bulk edit model attrbutes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @return \Illuminate\Contracts\View\View
*/
public function postBulkEditSave(Request $request)
{
$models_raw_array = Input::get('ids');
$update_array = array();
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
}
if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) {
$update_array['category_id'] = $request->input('category_id');
}
if ($request->input('fieldset_id')!='NC') {
$update_array['fieldset_id'] = $request->input('fieldset_id');
}
if ($request->input('depreciation_id')!='NC') {
$update_array['depreciation_id'] = $request->input('depreciation_id');
}
if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
return redirect()->route('models.index')
->with('success', trans('admin/models/message.bulkedit.success'));
}
return redirect()->route('models.index')
->with('warning', trans('admin/models/message.bulkedit.error'));
}
/**
* Validate and delete the given Asset Models. An Asset Model
* cannot be deleted if there are associated assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @return Redirect
*/
public function postBulkDelete(Request $request)
{
$models_raw_array = Input::get('ids');
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
$models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->get();
$del_error_count = 0;
$del_count = 0;
foreach ($models as $model) {
\Log::debug($model->id);
if ($model->assets_count > 0) {
$del_error_count++;
} else {
$model->delete();
$del_count++;
}
}
\Log::debug($del_count);
\Log::debug($del_error_count);
if ($del_error_count == 0) {
return redirect()->route('models.index')
->with('success', trans('admin/models/message.bulkdelete.success',['success_count'=> $del_count] ));
}
return redirect()->route('models.index')
->with('warning', trans('admin/models/message.bulkdelete.success_partial', ['fail_count'=>$del_error_count, 'success_count'=> $del_count]));
}
return redirect()->route('models.index')
->with('error', trans('admin/models/message.bulkdelete.error'));
}
/**
* Returns true if a fieldset is set, 'add default values' is ticked and if
* any default values were entered into the form.

View File

@@ -1,25 +1,29 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Assets;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
class AssetCheckinController extends Controller
{
/**
* Returns a view that presents a form to check an asset back into inventory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param string $backto
* @since [v1.0]
* @return View
*/
* Returns a view that presents a form to check an asset back into inventory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param string $backto
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
public function create($assetId, $backto = null)
{
// Check if the asset exists
@@ -40,6 +44,7 @@ class AssetCheckinController extends Controller
* @param int $assetId
* @param null $backto
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
@@ -65,7 +70,7 @@ class AssetCheckinController extends Controller
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
$asset->accepted = null;
$asset->name = $request->get('name');
$asset->name = e($request->get('name'));
if ($request->filled('status_id')) {
$asset->status_id = e($request->get('status_id'));
@@ -79,21 +84,10 @@ class AssetCheckinController extends Controller
// Was the asset updated?
if ($asset->save()) {
$logaction = $asset->logCheckin($target, e(request('note')));
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note')));
$data['log_id'] = $logaction->id;
$data['first_name'] = get_class($target) == User::class ? $target->first_name : '';
$data['last_name'] = get_class($target) == User::class ? $target->last_name : '';
$data['item_name'] = $asset->present()->name();
$data['checkin_date'] = $logaction->created_at;
$data['item_tag'] = $asset->asset_tag;
$data['item_serial'] = $asset->serial;
$data['note'] = $logaction->note;
$data['manufacturer_name'] = $asset->model->manufacturer->name;
$data['model_name'] = $asset->model->name;
$data['model_number'] = $asset->model->model_number;
if ((isset($user)) && ($backto =='user')) {
if ($backto=='user') {
return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
}
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkin.success'));

View File

@@ -1,9 +1,11 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Assets;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\Asset;
use App\Models\Location;
@@ -28,6 +30,7 @@ class AssetCheckoutController extends Controller
{
// Check if the asset exists
if (is_null($asset = Asset::find(e($assetId)))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
@@ -38,6 +41,7 @@ class AssetCheckoutController extends Controller
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
// Get the dropdown of users and then pass it to the checkout view
}

View File

@@ -1,13 +1,14 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Assets;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
class AssetFilesController extends Controller
{
@@ -19,6 +20,7 @@ class AssetFilesController extends Controller
* @param int $assetId
* @return Redirect
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(AssetFileRequest $request, $assetId = null)
{
@@ -28,15 +30,15 @@ class AssetFilesController extends Controller
$this->authorize('update', $asset);
$destinationPath = config('app.private_uploads').'/assets';
if ($request->hasFile('file')) {
if (!Storage::exists('private_uploads/assets')) Storage::makeDirectory('private_uploads/assets', 775);
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$filename = 'hardware-'.$asset->id.'-'.str_random(8);
$filename .= '-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$file->move($destinationPath, $filename);
$asset->logUpload($filename, e($request->get('notes')));
$file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
Storage::put('private_uploads/assets/'.$file_name, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
}
@@ -45,14 +47,15 @@ class AssetFilesController extends Controller
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
*/
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($assetId = null, $fileId = null, $download = true)
{
$asset = Asset::find($assetId);
@@ -65,24 +68,25 @@ class AssetFilesController extends Controller
->header('Content-Type', 'text/plain');
}
$file = $log->get_src('assets');
$file = 'private_uploads/assets/'.$log->filename;
\Log::debug('Checking for '.$file);
if ($log->action_type =='audit') {
$file = $log->get_src('audits');
$file = 'private_uploads/audits/'.$log->filename;
}
if (!file_exists($file)) {
if (!Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents($file)) {
return Response::make($contents)->header('Content-Type', mime_content_type($file));
if ($contents = file_get_contents(Storage::url($file))) {
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Response::download($file);
return Storage::download($file);
}
// Prepare the error message
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
@@ -92,35 +96,31 @@ class AssetFilesController extends Controller
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
*/
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
$this->authorize('update', $asset);
$destinationPath = config('app.private_uploads').'/imports/assets';
$rel_path = 'storage/private_uploads/assets';
// the asset is valid
if (isset($asset->id)) {
$this->authorize('update', $asset);
$log = Actionlog::find($fileId);
if ($log) {
$full_filename = $destinationPath.'/'.$log->filename;
if (file_exists($full_filename)) {
unlink($destinationPath.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
if (file_exists(base_path().'/'.$rel_path.'/'.$log->filename)) {
Storage::disk('public')->delete($rel_path.'/'.$log->filename);
}
return redirect()->back()->with('error', 'Could not find matching upload log.');
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the hardware management page

View File

@@ -1,18 +1,13 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Assets;
use App\Helpers\Helper;
use App\Http\Requests\AssetCheckinRequest;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\AssetRequest;
use App\Http\Requests\ItemImportRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\Import;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
@@ -27,6 +22,8 @@ use Image;
use Input;
use Lang;
use League\Csv\Reader;
use League\Csv\Statement;
use Illuminate\Support\Facades\Cache;
use Log;
use Mail;
use Paginator;
@@ -34,11 +31,11 @@ use Redirect;
use Response;
use Slack;
use Str;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use TCPDF;
use Validator;
use View;
use App\Models\CheckoutRequest;
use Illuminate\Support\Facades\Storage;
/**
* This class controls all actions related to assets for
@@ -66,7 +63,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @param Request $request
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request)
{
@@ -110,116 +109,107 @@ class AssetsController extends Controller
* @since [v1.0]
* @return Redirect
*/
public function store(AssetRequest $request)
public function store(ImageUploadRequest $request)
{
$this->authorize(Asset::class);
// Handle asset tags - there could be one, or potentially many.
// This is only necessary on create, not update, since bulk editing is handled
// differently
$asset_tags = $request->input('asset_tags');
$asset = new Asset();
$asset->model()->associate(AssetModel::find($request->input('model_id')));
$success = false;
$serials = $request->input('serials');
$asset->name = $request->input('name');
$asset->serial = $request->input('serial');
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->asset_tag = $request->input('asset_tag');
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = request('status_id', 0);
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', 0);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
for ($a = 1; $a <= count($asset_tags); $a++) {
if ($asset->assigned_to=='') {
$asset->location_id = $request->input('rtd_location_id', null);
}
$asset = new Asset();
$asset->model()->associate(AssetModel::find($request->input('model_id')));
$asset->name = $request->input('name');
// Check for a corresponding serial
if (($serials) && (array_key_exists($a, $serials))) {
$asset->serial = $serials[$a];
}
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = request('status_id', 0);
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', 0);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
if ($asset->assigned_to=='') {
$asset->location_id = $request->input('rtd_location_id', null);
}
// Create the image (if one was chosen.)
if ($request->filled('image')) {
if ($request->hasFile('image')) {
$image = $request->input('image');
// After modification, the image is prefixed by mime info like the following:
// data:image/jpeg;base64,; This causes the image library to be unhappy, so we need to remove it.
$header = explode(';', $image, 2)[0];
// Grab the image type from the header while we're at it.
$extension = substr($header, strpos($header, '/')+1);
// Start reading the image after the first comma, postceding the base64.
$image = substr($image, strpos($image, ',')+1);
$file_name = str_random(25).".".$extension;
$directory= public_path('uploads/assets/');
// Check if the uploads directory exists. If not, try to create it.
if (!file_exists($directory)) {
mkdir($directory, 0755, true);
}
$path = public_path('uploads/assets/'.$file_name);
try {
Image::make($image)->resize(800, 800, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$asset->image = $file_name;
} catch (\Exception $e) {
\Input::flash();
$messageBag = new \Illuminate\Support\MessageBag();
$messageBag->add('image', $e->getMessage());
\Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag)
->put('default', $messageBag));
return response()->json(['image' => $e->getMessage()], 422);
}
$asset->asset_tag = $asset_tags[$a];
$asset = $request->handleImages($asset);
}
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
}
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {
if (request('assigned_user')) {
$target = User::find(request('assigned_user'));
$location = $target->location_id;
} elseif (request('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
$location = $target->location_id;
} elseif (request('assigned_location')) {
$target = Location::find(request('assigned_location'));
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')), $location);
}
$success = true;
}
}
// Was the asset created?
if ($asset->save()) {
if (request('assigned_user')) {
$target = User::find(request('assigned_user'));
$location = $target->location_id;
} elseif (request('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
$location = $target->location_id;
} elseif (request('assigned_location')) {
$target = Location::find(request('assigned_location'));
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')), $location);
}
if ($success) {
// Redirect to the asset listing page
\Session::flash('success', trans('admin/hardware/message.create.success'));
return response()->json(['redirect_url' => route('hardware.index')]);
return redirect()->route('hardware.index')
->with('success', trans('admin/hardware/message.create.success'));
}
\Input::flash();
\Session::flash('errors', $asset->getErrors());
return response()->json(['errors' => $asset->getErrors()], 500);
return redirect()->back()->withInput()->withErrors($asset->getErrors());
}
/**
@@ -297,7 +287,7 @@ class AssetsController extends Controller
* @return Redirect
*/
public function update(AssetRequest $request, $assetId = null)
public function update(ImageUploadRequest $request, $assetId = null)
{
// Check if the asset exists
if (!$asset = Asset::find($assetId)) {
@@ -326,61 +316,32 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::info($e);
\Log::error($e);
}
}
// Update the asset data
$asset_tag = $request->input('asset_tags');
$serial = $request->input('serials');
$asset->name = $request->input('name');
$asset->serial = $request->input('serial');
$asset->serial = $serial[1];
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->asset_tag = $request->input('asset_tag');
$asset->asset_tag = $asset_tag[1];
$asset->notes = $request->input('notes');
$asset->physical = '1';
// Update the image
if ($request->filled('image')) {
$image = $request->input('image');
// See postCreate for more explaination of the following.
$header = explode(';', $image, 2)[0];
$extension = substr($header, strpos($header, '/')+1);
$image = substr($image, strpos($image, ',')+1);
$directory= public_path('uploads/assets/');
// Check if the uploads directory exists. If not, try to create it.
if (!file_exists($directory)) {
mkdir($directory, 0755, true);
}
$file_name = str_random(25).".".$extension;
$path = public_path('uploads/assets/'.$file_name);
try {
Image::make($image)->resize(800, 800, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$asset->image = $file_name;
} catch (\Exception $e) {
\Input::flash();
$messageBag = new \Illuminate\Support\MessageBag();
$messageBag->add('image', $e->getMessage());
\Session()->flash('errors', \Session::get('errors', new \Illuminate\Support\ViewErrorBag)
->put('default', $messageBag));
return response()->json(['image' => $e->getMessage()], 422);
}
$asset->image = $file_name;
}
$asset = $request->handleImages($asset);
// Update custom fields in the database.
// Validation for these fields is handlded through the AssetRequest form request
// FIXME: No idea why this is returning a Builder error on db_column_name.
// Need to investigate and fix. Using static method for now.
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
if ($model->fieldset) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
@@ -394,13 +355,12 @@ class AssetsController extends Controller
if ($asset->save()) {
// Redirect to the new asset page
\Session::flash('success', trans('admin/hardware/message.update.success'));
return response()->json(['redirect_url' => route("hardware.show", $assetId)]);
return redirect()->route("hardware.show", $assetId)
->with('success', trans('admin/hardware/message.update.success'));
}
\Input::flash();
\Session::flash('errors', $asset->getErrors());
return response()->json(['errors' => $asset->getErrors()], 500);
return redirect()->back()->withInput()->withErrors()->with('error', trans('admin/hardware/message.does_not_exist'));
}
/**
@@ -425,6 +385,14 @@ class AssetsController extends Controller
->where('id', $asset->id)
->update(array('assigned_to' => null));
if ($asset->image) {
try {
Storage::disk('public')->delete('assets'.'/'.$asset->image);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$asset->delete();
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success'));
@@ -554,7 +522,7 @@ class AssetsController extends Controller
*/
public function getImportHistory()
{
$this->authorize('checkout', Asset::class);
$this->authorize('admin');
return view('hardware/history');
}
@@ -564,8 +532,8 @@ class AssetsController extends Controller
* This needs a LOT of love. It's done very inelegantly right now, and there are
* a ton of optimizations that could (and should) be done.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.3]
* @author [herroworrd]
* @since [v5.0]
* @return View
*/
public function postImportHistory(Request $request)
@@ -574,129 +542,103 @@ class AssetsController extends Controller
ini_set("auto_detect_line_endings", '1');
}
$csv = Reader::createFromPath(Input::file('user_import_csv'));
$csv->setNewline("\r\n");
//get the first row, usually the CSV header
//$headers = $csv->fetchOne();
$requiredcolumns = ['Asset Tag', 'Checkout Date', 'Checkin Date', 'Full Name'];
$csv = Reader::createFromPath(Input::file('user_import_csv'));
$csv->setHeaderOffset(0);
//Stop here if we don't have the columns we need
if(count(array_intersect($requiredcolumns, $csv->getHeader())) != count($requiredcolumns)) {
$status['error'][]['csv'][]['msg'] = 'Headers do not match';
return view('hardware/history')->with('status', $status);
}
$statement = (new Statement())
->orderBy(\Closure::fromCallable([$this, 'sortByName']));
$results = $statement->process($csv);
$results = $csv->getRecords();
$item = array();
$status = array();
$status['error'] = array();
$status['success'] = array();
$base_username = null;
$cachetime = Carbon::now()->addSeconds(120);
foreach ($results as $record) {
foreach ($results as $row) {
if (is_array($row)) {
$row = array_change_key_case($row, CASE_LOWER);
$asset_tag = Helper::array_smart_fetch($row, "asset tag");
if (!array_key_exists($asset_tag, $item)) {
$item[$asset_tag] = array();
}
$batch_counter = count($item[$asset_tag]);
$asset_tag = $record['Asset Tag'];
$item[$asset_tag][$batch_counter]['checkout_date'] = Carbon::parse(Helper::array_smart_fetch($row, "date"))->format('Y-m-d H:i:s');
$item[$asset_tag][$batch_counter]['asset_tag'] = Helper::array_smart_fetch($row, "asset tag");
$item[$asset_tag][$batch_counter]['name'] = Helper::array_smart_fetch($row, "name");
$item[$asset_tag][$batch_counter]['email'] = Helper::array_smart_fetch($row, "email");
if ($asset = Asset::where('asset_tag', '=', $asset_tag)->first()) {
$item[$asset_tag][$batch_counter]['asset_id'] = $asset->id;
$base_username = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $item[$asset_tag][$batch_counter]['name']);
$user = User::where('username', '=', $base_username['username']);
$user_query = ' on username '.$base_username['username'];
if ($request->input('match_firstnamelastname')=='1') {
$firstnamedotlastname = User::generateFormattedNameFromFullName('firstname.lastname', $item[$asset_tag][$batch_counter]['name']);
$item[$asset_tag][$batch_counter]['username'][] = $firstnamedotlastname['username'];
$user->orWhere('username', '=', $firstnamedotlastname['username']);
$user_query .= ', or on username '.$firstnamedotlastname['username'];
}
if ($request->input('match_flastname')=='1') {
$flastname = User::generateFormattedNameFromFullName('filastname', $item[$asset_tag][$batch_counter]['name']);
$item[$asset_tag][$batch_counter]['username'][] = $flastname['username'];
$user->orWhere('username', '=', $flastname['username']);
$user_query .= ', or on username '.$flastname['username'];
}
if ($request->input('match_firstname')=='1') {
$firstname = User::generateFormattedNameFromFullName('firstname', $item[$asset_tag][$batch_counter]['name']);
$item[$asset_tag][$batch_counter]['username'][] = $firstname['username'];
$user->orWhere('username', '=', $firstname['username']);
$user_query .= ', or on username '.$firstname['username'];
}
if ($request->input('match_email')=='1') {
if ($item[$asset_tag][$batch_counter]['email']=='') {
$item[$asset_tag][$batch_counter]['username'][] = $user_email = User::generateEmailFromFullName($item[$asset_tag][$batch_counter]['name']);
$user->orWhere('username', '=', $user_email);
$user_query .= ', or on username '.$user_email;
}
}
// A matching user was found
if ($user = $user->first()) {
$item[$asset_tag][$batch_counter]['checkedout_to'] = $user->id;
$item[$asset_tag][$batch_counter]['user_id'] = $user->id;
Actionlog::firstOrCreate(array(
'item_id' => $asset->id,
'item_type' => Asset::class,
'user_id' => Auth::user()->id,
'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer',
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
'target_type' => User::class,
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
'action_type' => 'checkout',
));
$asset->assigned_to = $user->id;
if ($asset->save()) {
$status['success'][]['asset'][$asset_tag]['msg'] = 'Asset successfully matched for '.Helper::array_smart_fetch($row, "name").$user_query.' on '.$item[$asset_tag][$batch_counter]['checkout_date'];
} else {
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset and user was matched but could not be saved.';
}
} else {
$item[$asset_tag][$batch_counter]['checkedout_to'] = null;
$status['error'][]['user'][Helper::array_smart_fetch($row, "name")]['msg'] = 'User does not exist so no checkin log was created.';
}
} else {
$item[$asset_tag][$batch_counter]['asset_id'] = null;
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset does not exist so no match was attempted.';
}
try {
$checkoutdate = Carbon::parse($record['Checkout Date'])->format('Y-m-d H:i:s');
$checkindate = Carbon::parse($record['Checkin Date'])->format('Y-m-d H:i:s');
}
catch (\Exception $err) {
$status['error'][]['asset'][$asset_tag]['msg'] = 'Your dates are screwed up. Format needs to be Y-m-d H:i:s';
continue;
}
}
// Loop through and backfill the checkins
foreach ($item as $key => $asset_batch) {
$total_in_batch = count($asset_batch);
for ($x = 0; $x < $total_in_batch; $x++) {
$next = $x + 1;
if($asset = Cache::remember('asset:' . $asset_tag, $cachetime, function () use( &$asset_tag) {
$tocache = Asset::where('asset_tag', '=', $asset_tag)->value('id');
return is_null($tocache) ? false : $tocache;}))
{
//we've found our asset, now lets look for a user
if($base_username != User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format)) {
// Only do this if a matching user was found
if ((array_key_exists('checkedout_to', $asset_batch[$x])) && ($asset_batch[$x]['checkedout_to']!='')) {
if (($total_in_batch > 1) && ($x < $total_in_batch) && (array_key_exists($next, $asset_batch))) {
$checkin_date = Carbon::parse($asset_batch[$next]['checkout_date'])->subDay(1)->format('Y-m-d H:i:s');
$asset_batch[$x]['real_checkin'] = $checkin_date;
$base_username = User::generateFormattedNameFromFullName($record['Full Name'], Setting::getSettings()->username_format);
if(!$user = Cache::remember('user:' . $base_username['username'], $cachetime, function () use( &$base_username) {
$tocache = User::where('username', '=', $base_username['username'])->value('id');
return is_null($tocache) ? false : $tocache;}))
{
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset was found but user (' . $record['Full Name'] . ') not matched';
$base_username = null;
continue;
}
}
if($checkoutdate < $checkindate) {
Actionlog::firstOrCreate(array(
'item_id' => $asset_batch[$x]['asset_id'],
'item_id' => $asset,
'item_type' => Asset::class,
'user_id' => Auth::user()->id,
'note' => 'Checkin imported by ' . Auth::user()->present()->fullName() . ' from history importer',
'target_id' => null,
'created_at' => $checkin_date,
'note' => 'Historical record added by ' . Auth::user()->present()->fullName(),
'target_id' => $user,
'target_type' => User::class,
'created_at' => $checkoutdate,
'action_type' => 'checkout',
));
Actionlog::firstOrCreate(array(
'item_id' => $asset,
'item_type' => Asset::class,
'user_id' => Auth::user()->id,
'note' => 'Historical record added by ' . Auth::user()->present()->fullName(),
'target_id' => $user,
'target_type' => User::class,
'created_at' => $checkindate,
'action_type' => 'checkin'
));
}
$status['success'][]['asset'][$asset_tag]['msg'] = 'Asset successfully matched for ' . $record['Full Name'] . ' on ' . $checkoutdate;
}
else {
$status['error'][]['asset'][$asset_tag]['msg'] = 'Checkin date needs to be after checkout date.';
}
}
else {
$status['error'][]['asset'][$asset_tag]['msg'] = 'Asset not found in Snipe';
}
}
return view('hardware/history')->with('status', $status);
}
protected function sortByName(array $recordA, array $recordB): int
{
return strcmp($recordB['Full Name'], $recordA['Full Name']);
}
/**
* Retore a deleted asset.
*
@@ -744,20 +686,8 @@ class AssetsController extends Controller
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function dueForAudit()
{
$this->authorize('audit', Asset::class);
return view('hardware/audit-due');
}
public function overdueForAudit()
{
$this->authorize('audit', Asset::class);
return view('hardware/audit-overdue');
}
public function auditStore(AssetFileRequest $request, $id)
public function auditStore(Request $request, $id)
{
$this->authorize('audit', Asset::class);
@@ -780,24 +710,28 @@ class AssetsController extends Controller
$asset->next_audit_date = $request->input('next_audit_date');
$asset->last_audit_date = date('Y-m-d h:i:s');
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location')=='1') {
\Log::debug('update location in audit');
$asset->location_id = $request->input('location_id');
}
if ($asset->save()) {
$filename = '';
$file_name = '';
// Upload an image, if attached
if ($request->hasFile('image')) {
$file = $request->file('image');
try {
$destinationPath = config('app.private_uploads').'/audits';
$extension = $file->getClientOriginalExtension();
$filename = 'audit-'.$asset->id.'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$file->move($destinationPath, $filename);
} catch (\Exception $e) {
\Log::info($e);
}
$path = 'private_uploads/audits';
if (!Storage::exists($path)) Storage::makeDirectory($path, 775);
$upload = $image = $request->file('image');
$ext = $image->getClientOriginalExtension();
$file_name = 'audit-'.str_random(18).'.'.$ext;
Storage::putFileAs($path, $upload, $file_name);
}
$asset->logAudit($request->input('note'), $request->input('location_id'), $filename);
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
}
}

View File

@@ -1,9 +1,10 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Assets;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
@@ -14,6 +15,7 @@ use Illuminate\Support\Facades\DB;
class BulkAssetsController extends Controller
{
use CheckInOutRequest;
/**
* Display the bulk edit page.
*
@@ -21,6 +23,7 @@ class BulkAssetsController extends Controller
* @return View
* @internal param int $assetId
* @since [v2.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit(Request $request)
{
@@ -130,10 +133,11 @@ class BulkAssetsController extends Controller
* @var Array
*/
private $update_array;
/**
* Adds parameter to update array for an item if it exists in request
* @param String $field field name
* @return this Model for Chaining
* @param String $field field name
* @return BulkAssetsController Model for Chaining
*/
protected function conditionallyAddItem($field)
{
@@ -147,7 +151,9 @@ class BulkAssetsController extends Controller
* Save bulk deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param array $assets
* @since [v2.0]
*/

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use App\Models\User;
class ForgotPasswordController extends Controller
{
@@ -49,27 +50,28 @@ class ForgotPasswordController extends Controller
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$this->validate($request, ['username' => 'required'], ['username.required' => 'Please enter your username.']);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
// Make sure the user is active, and their password is not controlled via LDAP
$response = $this->broker()->sendResetLink(
array_merge(
$request->only('email'),
['activated' => '1']
$request->only('username'),
['activated' => '1'],
['ldap_import' => '0']
)
);
if ($response === \Password::RESET_LINK_SENT) {
return redirect()->route('login')->with('status', trans($response));
\Log::info('Password reset attempt: User '.$request->input('username').' found, password reset sent');
} else {
\Log::info('Password reset attempt: User '.$request->input('username').' not found or user is inactive');
}
// If an error was returned by the password broker, we will get this message
// translated so we can notify a user of the problem. We'll redirect back
// to where the users came from so they can attempt this process again.
return back()->withErrors(
['email' => trans($response)]
);
// Regardless of response, we do not want to disclose the status of a user account,
// so we give them a generic "If this exists, we're TOTALLY gonna email you" response
return redirect()->route('login')->with('success',trans('passwords.sent'));
}
}

View File

@@ -2,20 +2,19 @@
namespace App\Http\Controllers\Auth;
use Validator;
use App\Services\LdapAd;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use App\Models\Setting;
use App\Models\Ldap;
use App\Models\User;
use Auth;
use Config;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Input;
use Illuminate\Support\Facades\Input;
use Redirect;
use Log;
use View;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Support\Facades\Log;
/**
* This controller handles authentication for the user, including local
@@ -39,15 +38,24 @@ class LoginController extends Controller
*/
protected $redirectTo = '/';
/**
* @var LdapAd
*/
protected $ldap;
/**
* Create a new authentication controller instance.
*
* @param LdapAd $ldap
*
* @return void
*/
public function __construct()
public function __construct(LdapAd $ldap)
{
parent::__construct();
$this->middleware('guest', ['except' => ['logout','postTwoFactorAuth','getTwoFactorAuth','getTwoFactorEnroll']]);
\Session::put('backUrl', \URL::previous());
Session::put('backUrl', \URL::previous());
$this->ldap = $ldap;
}
function showLoginForm(Request $request)
@@ -64,6 +72,29 @@ class LoginController extends Controller
return view('auth.login');
}
/**
* Log in a user by LDAP
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @param Request $request
*
* @return User
*
* @throws \Exception
*/
private function loginViaLdap(Request $request): User
{
try {
return $this->ldap->ldapLogin($request->input('username'), $request->input('password'));
} catch (\Exception $ex) {
LOG::debug("LDAP user login: " . $ex->getMessage());
throw new \Exception($ex->getMessage());
}
}
private function loginViaRemoteUser(Request $request)
{
$remote_user = $request->server('REMOTE_USER');
@@ -85,53 +116,6 @@ class LoginController extends Controller
}
}
private function loginViaLdap(Request $request)
{
Log::debug("Binding user to LDAP.");
$ldap_user = Ldap::findAndBindUserLdap($request->input('username'), $request->input('password'));
if (!$ldap_user) {
Log::debug("LDAP user ".$request->input('username')." not found in LDAP or could not bind");
throw new \Exception("Could not find user in LDAP directory");
} else {
Log::debug("LDAP user ".$request->input('username')." successfully bound to LDAP");
}
// Check if the user already exists in the database and was imported via LDAP
$user = User::where('username', '=', Input::get('username'))->whereNull('deleted_at')->where('ldap_import', '=', 1)->where('activated', '=', '1')->first();
Log::debug("Local auth lookup complete");
// The user does not exist in the database. Try to get them from LDAP.
// If user does not exist and authenticates successfully with LDAP we
// will create it on the fly and sign in with default permissions
if (!$user) {
Log::debug("Local user ".Input::get('username')." does not exist");
Log::debug("Creating local user ".Input::get('username'));
if ($user = Ldap::createUserFromLdap($ldap_user)) { //this handles passwords on its own
Log::debug("Local user created.");
} else {
Log::debug("Could not create local user.");
throw new \Exception("Could not create local user");
}
// If the user exists and they were imported from LDAP already
} else {
Log::debug("Local user ".$request->input('username')." exists in database. Updating existing user against LDAP.");
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
if (Setting::getSettings()->ldap_pw_sync=='1') {
$user->password = bcrypt($request->input('password'));
}
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname'];
$user->save();
} // End if(!user)
return $user;
}
/**
* Account sign in form processing.
*
@@ -160,9 +144,10 @@ class LoginController extends Controller
$user = null;
// Should we even check for LDAP users?
if (Setting::getSettings()->ldap_enabled=='1') {
Log::debug("LDAP is enabled.");
if ($this->ldap->init()) {
LOG::debug("LDAP is enabled.");
try {
LOG::debug("Attempting to log user in by LDAP authentication.");
$user = $this->loginViaLdap($request);
Auth::login($user, true);
@@ -192,7 +177,8 @@ class LoginController extends Controller
}
if ($user = Auth::user()) {
$user->last_login = \Carbon::now();
$user->last_login = Carbon::now();
Log::debug('Last login:'.$user->last_login);
$user->save();
}
// Redirect to the users page
@@ -208,33 +194,26 @@ class LoginController extends Controller
public function getTwoFactorEnroll()
{
// Make sure the user is logged in
if (!Auth::check()) {
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
return redirect()->route('login')->with('error', 'You must be logged in.');
}
$settings = Setting::getSettings();
$user = Auth::user();
$google2fa = app()->make('pragmarx.google2fa');
// We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
// but let's check check anyway in case there's a browser history or back button thing.
// While you can access this page directly, enrolling a device when 2FA isn't enforced
// won't cause any harm.
if (($user->two_factor_secret!='') && ($user->two_factor_enrolled==1)) {
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.already_enrolled'));
if ($user->two_factor_secret=='') {
$user->two_factor_secret = $google2fa->generateSecretKey(32);
$user->save();
}
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$user->two_factor_secret = $secret;
$user->save();
$barcode = new \Com\Tecnick\Barcode\Barcode();
$barcode_obj = $barcode->getBarcodeObj('QRCODE', 'otpauth://totp/'.urlencode($settings->site_name).':'.urlencode($user->username).'?secret='.urlencode($secret).'&issuer=Snipe-IT&period=30', 300, 300, 'black', array(-2, -2, -2, -2));
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
$google2fa_url = $google2fa->getQRCodeInline(
urlencode(Setting::getSettings()->site_name),
urlencode($user->username),
$user->two_factor_secret
);
return view('auth.two_factor_enroll')->with('google2fa_url', $google2fa_url);
}
@@ -246,51 +225,36 @@ class LoginController extends Controller
*/
public function getTwoFactorAuth()
{
// Check that the user is logged in
if (!Auth::check()) {
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
$user = Auth::user();
// Check whether there is a device enrolled.
// This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware
// but we're just making sure (in case someone edited the database directly, etc)
if (($user->two_factor_secret=='') || ($user->two_factor_enrolled!=1)) {
return redirect()->route('two-factor-enroll');
}
return view('auth.two_factor');
}
/**
* Two factor code submission
*
* @param Request $request
*
* @return Redirect
*/
public function postTwoFactorAuth(Request $request)
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
if (!$request->filled('two_factor_secret')) {
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
return redirect()->route('login')->with('error', 'You must be logged in.');
}
$user = Auth::user();
$google2fa = new Google2FA();
$secret = $request->input('two_factor_secret');
$secret = $request->get('two_factor_secret');
$google2fa = app()->make('pragmarx.google2fa');
$valid = $google2fa->verifyKey($user->two_factor_secret, $secret);
if ($google2fa->verifyKey($user->two_factor_secret, $secret)) {
if ($valid) {
$user->two_factor_enrolled = 1;
$user->save();
$request->session()->put('2fa_authed', 'true');
return redirect()->route('home')->with('success', 'You are logged in!');
}
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
return redirect()->route('two-factor')->with('error', 'Invalid two-factor code');
}
@@ -299,6 +263,8 @@ class LoginController extends Controller
/**
* Logout page.
*
* @param Request $request
*
* @return Redirect
*/
public function logout(Request $request)
@@ -313,7 +279,7 @@ class LoginController extends Controller
return redirect()->away($customLogoutUrl);
}
return redirect()->route('login')->with('success', trans('auth/message.logout.success'));
return redirect()->route('login')->with('success', 'You have successfully logged out!');
}
@@ -338,11 +304,11 @@ class LoginController extends Controller
}
/**
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
@@ -353,18 +319,18 @@ class LoginController extends Controller
$message = \Lang::get('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back()
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([$this->username() => $message]);
}
/**
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
$lockoutTime = config('auth.throttle.lockout_duration');

View File

@@ -4,9 +4,9 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use App\Models\User;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
/*
@@ -39,7 +39,36 @@ class ResetPasswordController extends Controller
$this->middleware('guest');
}
protected function rules()
{
return [
'token' => 'required',
'username' => 'required',
'password' => 'required|confirmed|min:6',
];
}
protected function credentials(Request $request)
{
return $request->only(
'username', 'password', 'password_confirmation', 'token'
);
}
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'username' => $request->input('username')]
);
}
protected function sendResetFailedResponse(Request $request, $response)
{
return redirect()->back()
->withInput(['username'=>$request->input('username')])
->withErrors(['username' => trans($response)]);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Redirect;
class BulkAssetModelsController extends Controller
{
/**
* Returns a view that allows the user to bulk edit model attrbutes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function edit(Request $request)
{
$models_raw_array = Input::get('ids');
// Make sure some IDs have been selected
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
$models = AssetModel::whereIn('id', $models_raw_array)
->withCount('assets as assets_count')
->orderBy('assets_count', 'ASC')
->get();
// If deleting....
if ($request->input('bulk_actions')=='delete') {
$valid_count = 0;
foreach ($models as $model) {
if ($model->assets_count == 0) {
$valid_count++;
}
}
return view('models/bulk-delete', compact('models'))->with('valid_count', $valid_count);
// Otherwise display the bulk edit screen
}
$nochange = ['NC' => 'No Change'];
return view('models/bulk-edit', compact('models'))
->with('fieldset_list', $nochange + Helper::customFieldsetList())
->with('depreciation_list', $nochange + Helper::depreciationList());
}
return redirect()->route('models.index')
->with('error', 'You must select at least one model to edit.');
}
/**
* Returns a view that allows the user to bulk edit model attrbutes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function update(Request $request)
{
$models_raw_array = Input::get('ids');
$update_array = array();
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
}
if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) {
$update_array['category_id'] = $request->input('category_id');
}
if ($request->input('fieldset_id')!='NC') {
$update_array['fieldset_id'] = $request->input('fieldset_id');
}
if ($request->input('depreciation_id')!='NC') {
$update_array['depreciation_id'] = $request->input('depreciation_id');
}
if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
return redirect()->route('models.index')
->with('success', trans('admin/models/message.bulkedit.success'));
}
return redirect()->route('models.index')
->with('warning', trans('admin/models/message.bulkedit.error'));
}
/**
* Validate and delete the given Asset Models. An Asset Model
* cannot be deleted if there are associated assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return Redirect
*/
public function destroy()
{
$models_raw_array = Input::get('ids');
if ((is_array($models_raw_array)) && (count($models_raw_array) > 0)) {
$models = AssetModel::whereIn('id', $models_raw_array)->withCount('assets as assets_count')->get();
$del_error_count = 0;
$del_count = 0;
foreach ($models as $model) {
if ($model->assets_count > 0) {
$del_error_count++;
} else {
$model->delete();
$del_count++;
}
}
if ($del_error_count == 0) {
return redirect()->route('models.index')
->with('success', trans('admin/models/message.bulkdelete.success',['success_count'=> $del_count] ));
}
return redirect()->route('models.index')
->with('warning', trans('admin/models/message.bulkdelete.success_partial', ['fail_count'=>$del_error_count, 'success_count'=> $del_count]));
}
return redirect()->route('models.index')
->with('error', trans('admin/models/message.bulkdelete.error'));
}
}

View File

@@ -17,6 +17,7 @@ use Str;
use View;
use Image;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
/**
* This class controls all actions related to Categories for
@@ -29,13 +30,14 @@ class CategoriesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the categories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the categories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
@@ -46,30 +48,32 @@ class CategoriesController extends Controller
/**
* Returns a form view to create a new category.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::store() method that stores the data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a form view to create a new category.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::store() method that stores the data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
// Show the page
$this->authorize('create', Category::class);
$category_types= Helper::categoryTypeList();
return view('categories/edit')->with('item', new Category)
->with('category_types', $category_types);
->with('category_types', Helper::categoryTypeList());
}
/**
* Validates and stores the new category data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::create() method that makes the form.
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Validates and stores the new category data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::create() method that makes the form.
* @since [v1.0]
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
@@ -83,17 +87,7 @@ class CategoriesController extends Controller
$category->checkin_email = $request->input('checkin_email', '0');
$category->user_id = Auth::id();
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/categories/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$category->image = $file_name;
}
$category = $request->handleImages($category);
if ($category->save()) {
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.create.success'));
@@ -103,13 +97,14 @@ class CategoriesController extends Controller
}
/**
* Returns a view that makes a form to update a category.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::postEdit() method saves the data
* @param int $categoryId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that makes a form to update a category.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::postEdit() method saves the data
* @param int $categoryId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($categoryId = null)
{
@@ -117,10 +112,8 @@ class CategoriesController extends Controller
if (is_null($item = Category::find($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
$category_types= Helper::categoryTypeList();
return view('categories/edit', compact('item'))
->with('category_types', $category_types);
->with('category_types', Helper::categoryTypeList());
}
@@ -129,9 +122,10 @@ class CategoriesController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getEdit() method that makes the form.
* @param Request $request
* @param ImageUploadRequest $request
* @param int $categoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $categoryId = null)
@@ -152,37 +146,7 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$old_image = $category->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$category->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $category->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('categories_upload_path').$file_name);
} else {
$image->move(app('categories_upload_path'), $file_name);
}
$category->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('categories_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$category = $request->handleImages($category);
if ($category->save()) {
// Redirect to the new category page
@@ -193,31 +157,33 @@ class CategoriesController extends Controller
}
/**
* Validates and marks a category as deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $categoryId
* @return \Illuminate\Http\RedirectResponse
* Validates and marks a category as deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $categoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($categoryId)
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::find($categoryId))) {
if (is_null($category = Category::withCount('models as models_count', 'accessories as accessories_count','consumables as consumables_count','components as components_count')->findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
if ($category->has_models() > 0) {
if ($category->models_count > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'model']));
} elseif ($category->accessories()->count() > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory']));
} elseif ($category->consumables()->count() > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable']));
} elseif ($category->components()->count() > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component']));
} elseif ($category->accessories_count > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory']));
} elseif ($category->consumables_count > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable']));
} elseif ($category->components_count > 0) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component']));
}
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
// Redirect to the locations management page
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
@@ -225,14 +191,15 @@ class CategoriesController extends Controller
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the categories detail view, which is generated in getDataView.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDataView() method that generates the JSON response
* @param int $categoryId
* @since [v1.8]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the categories detail view, which is generated in getDataView.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDataView() method that generates the JSON response
* @param $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
public function show($id)
{
@@ -255,10 +222,8 @@ class CategoriesController extends Controller
}
// Prepare the error message
$error = trans('admin/categories/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('categories.index')->with('error', $error);
return redirect()->route('categories.index')
->with('error', trans('admin/categories/message.does_not_exist'));
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Exceptions\CheckoutNotAllowed;
use App\Models\Asset;
use App\Models\Location;
use App\Models\SnipeModel;
use App\Models\User;
trait CheckInOutRequest

View File

@@ -2,13 +2,10 @@
namespace App\Http\Controllers;
use App\Models\Company;
use Input;
use Lang;
use Redirect;
use View;
use Illuminate\Http\Request;
use Image;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
/**
* This controller handles all actions related to Companies for
@@ -21,11 +18,12 @@ final class CompaniesController extends Controller
{
/**
* Returns view to display listing of companies.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Contracts\View\View
* Returns view to display listing of companies.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
@@ -35,11 +33,12 @@ final class CompaniesController extends Controller
}
/**
* Returns view to create a new company.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Contracts\View\View
* Returns view to create a new company.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -55,6 +54,7 @@ final class CompaniesController extends Controller
* @since [v1.8]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
@@ -63,16 +63,7 @@ final class CompaniesController extends Controller
$company = new Company;
$company->name = $request->input('name');
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/companies/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$company->image = $file_name;
}
$company = $request->handleImages($company);
if ($company->save()) {
return redirect()->route('companies.index')
@@ -83,12 +74,13 @@ final class CompaniesController extends Controller
/**
* Return form to edit existing company.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @param int $companyId
* @return \Illuminate\Contracts\View\View
* Return form to edit existing company.
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @param int $companyId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($companyId)
{
@@ -107,9 +99,10 @@ final class CompaniesController extends Controller
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @param Request $request
* @param ImageUploadRequest $request
* @param int $companyId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(ImageUploadRequest $request, $companyId)
{
@@ -121,37 +114,7 @@ final class CompaniesController extends Controller
$company->name = $request->input('name');
$old_image = $company->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$company->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $company->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('companies_upload_path').$file_name);
} else {
$image->move(app('companies_upload_path'), $file_name);
}
$company->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('companies_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$company = $request->handleImages($company);
if ($company->save()) {
return redirect()->route('companies.index')
@@ -162,38 +125,47 @@ final class CompaniesController extends Controller
}
/**
* Delete company
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @param int $companyId
* @return \Illuminate\Http\RedirectResponse
* Delete company
*
* @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8]
* @param int $companyId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($companyId)
{
$this->authorize('delete', $company);
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.not_found'));
} else {
}
$this->authorize('delete', $company);
try {
try {
$company->delete();
return redirect()->route('companies.index')
->with('success', trans('admin/companies/message.delete.success'));
} catch (\Illuminate\Database\QueryException $exception) {
/*
* NOTE: This happens when there's a foreign key constraint violation
* For example when rows in other tables are referencing this company
*/
if ($exception->getCode() == 23000) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.assoc_users'));
} else {
throw $exception;
if ($company->image) {
try {
Storage::disk('public')->delete('companies'.'/'.$company->image);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$company->delete();
return redirect()->route('companies.index')
->with('success', trans('admin/companies/message.delete.success'));
} catch (\Illuminate\Database\QueryException $exception) {
/*
* NOTE: This happens when there's a foreign key constraint violation
* For example when rows in other tables are referencing this company
*/
if ($exception->getCode() == 23000) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.assoc_users'));
}
throw $exception;
}
}
@@ -203,9 +175,8 @@ final class CompaniesController extends Controller
if (is_null($company = Company::find($id))) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.not_found'));
} else {
return view('companies/view')->with('company',$company);
}
return view('companies/view')->with('company',$company);
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Http\Controllers\Controller;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Component;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class ComponentCheckinController extends Controller
{
/**
* Returns a view that allows the checkin of a component from an asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentCheckinController::store() method that stores the data.
* @since [v4.1.4]
* @param $component_asset_id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($component_asset_id)
{
// This could probably be done more cleanly but I am very tired. - @snipe
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
}
if (is_null($asset = Asset::find($component_assets->asset_id))) {
return redirect()->route('components.index')->with('error',
trans('admin/components/message.not_found'));
}
$this->authorize('checkin', $component);
return view('components/checkin', compact('component_assets','component','asset'));
}
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
}
/**
* Validate and store checkin data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentCheckinController::create() method that returns the form.
* @since [v4.1.4]
* @param Request $request
* @param $component_asset_id
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request, $component_asset_id)
{
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
return redirect()->route('components.index')->with('error',
trans('admin/components/message.not_found'));
}
$this->authorize('checkin', $component);
$max_to_checkin = $component_assets->assigned_qty;
$validator = Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// Validation passed, so let's figure out what we have to do here.
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty'));
// We have to modify the record to reflect the new qty that's
// actually checked out.
$component_assets->assigned_qty = $qty_remaining_in_checkout;
DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) {
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
$asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note')));
return redirect()->route('components.index')->with('success',
trans('admin/components/message.checkout.success'));
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Component;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
class ComponentCheckoutController extends Controller
{
/**
* Returns a view that allows the checkout of a component to an asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentCheckoutController::store() method that stores the data.
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($componentId)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
return view('components/checkout', compact('component'));
}
/**
* Validate and store checkout data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentCheckoutController::create() method that returns the form.
* @since [v3.0]
* @param Request $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request, $componentId)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
$max_to_checkout = $component->numRemaining();
$validator = Validator::make($request->all(), [
"asset_id" => "required",
"assigned_qty" => "required|numeric|between:1,$max_to_checkout"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$admin_user = Auth::user();
$asset_id = e(Input::get('asset_id'));
// Check if the user exists
if (is_null($asset = Asset::find($asset_id))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.asset_does_not_exist'));
}
// Update the component data
$component->asset_id = $asset_id;
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'user_id' => $admin_user->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => Input::get('assigned_qty'),
'asset_id' => $asset_id
]);
event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace App\Http\Controllers\Components;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Storage;
/**
* This class controls all actions related to Components for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class ComponentsController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the components listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getDatatable() method that generates the JSON response
* @since [v3.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('view', Component::class);
return view('components/index');
}
/**
* Returns a form to create a new component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postCreate() method that stores the data
* @since [v3.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', Component::class);
return view('components/edit')->with('category_type', 'component')
->with('item', new Component);
}
/**
* Validate and store data for new component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getCreate() method that generates the view
* @since [v3.0]
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Component::class);
$component = new Component();
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
$component = $request->handleImages($component);
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
}
/**
* Return a view to edit a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postEdit() method that stores the data.
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($componentId = null)
{
if ($item = Component::find($componentId)) {
$this->authorize('update', $item);
return view('components/edit', compact('item'))->with('category_type', 'component');
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
/**
* Return a view to edit a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getEdit() method presents the form.
* @param ImageUploadRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v3.0]
*/
public function update(ImageUploadRequest $request, $componentId = null)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$this->authorize('update', $component);
// Update the component data
$component->name = Input::get('name');
$component->category_id = Input::get('category_id');
$component->location_id = Input::get('location_id');
$component->company_id = Company::getIdForCurrentUser(Input::get('company_id'));
$component->order_number = Input::get('order_number');
$component->min_amt = Input::get('min_amt');
$component->serial = Input::get('serial');
$component->purchase_date = Input::get('purchase_date');
$component->purchase_cost = request('purchase_cost');
$component->qty = Input::get('qty');
$component = $request->handleImages($component);
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
}
/**
* Delete a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($componentId)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$this->authorize('delete', $component);
// Remove the image if one exists
if (Storage::disk('public')->exists('components/'.$component->image)) {
try {
Storage::disk('public')->delete('components/'.$component->image);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$component->delete();
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
}
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getDataView() method that generates the JSON response
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null)
{
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('view', $component);
return view('components/view', compact('component'));
}
// Redirect to the user management page
return redirect()->route('components.index')
->with('error', trans('admin/components/message.does_not_exist'));
}
}

View File

@@ -1,397 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Company;
use App\Models\Component;
use App\Models\CustomField;
use App\Models\Setting;
use App\Models\User;
use App\Models\Asset;
use Auth;
use Config;
use DB;
use Input;
use Lang;
use Mail;
use Redirect;
use Slack;
use Str;
use View;
use Validator;
use Illuminate\Http\Request;
use Gate;
use Image;
/**
* This class controls all actions related to Components for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class ComponentsController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the components listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getDatatable() method that generates the JSON response
* @since [v3.0]
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$this->authorize('view', Component::class);
return view('components/index');
}
/**
* Returns a form to create a new component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postCreate() method that stores the data
* @since [v3.0]
* @return \Illuminate\Contracts\View\View
*/
public function create()
{
$this->authorize('create', Component::class);
$category_type = 'component';
return view('components/edit')->with('category_type',$category_type)
->with('item', new Component);
}
/**
* Validate and store data for new component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getCreate() method that generates the view
* @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Component::class);
$component = new Component();
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/components/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$component->image = $file_name;
}
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
}
/**
* Return a view to edit a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postEdit() method that stores the data.
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
*/
public function edit($componentId = null)
{
if ($item = Component::find($componentId)) {
$this->authorize('update', $item);
$category_type = 'component';
return view('components/edit', compact('item'))->with('category_type', $category_type);
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
/**
* Return a view to edit a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getEdit() method presents the form.
* @param int $componentId
* @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function update(ImageUploadRequest $request, $componentId = null)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$this->authorize('update', $component);
// Update the component data
$component->name = Input::get('name');
$component->category_id = Input::get('category_id');
$component->location_id = Input::get('location_id');
$component->company_id = Company::getIdForCurrentUser(Input::get('company_id'));
$component->order_number = Input::get('order_number');
$component->min_amt = Input::get('min_amt');
$component->serial = Input::get('serial');
$component->purchase_date = Input::get('purchase_date');
$component->purchase_cost = request('purchase_cost');
$component->qty = Input::get('qty');
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/components/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$component->image = $file_name;
} elseif ($request->input('image_delete')=='1') {
$component->image = null;
}
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($component->getErrors());
}
/**
* Delete a component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy($componentId)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$this->authorize('delete', $component);
$component->delete();
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
}
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getDataView() method that generates the JSON response
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
*/
public function show($componentId = null)
{
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('view', $component);
return view('components/view', compact('component'));
}
// Prepare the error message
$error = trans('admin/components/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('components.index')->with('error', $error);
}
/**
* Returns a view that allows the checkout of a component to an asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postCheckout() method that stores the data.
* @since [v3.0]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
*/
public function getCheckout($componentId)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
return view('components/checkout', compact('component'));
}
/**
* Validate and store checkout data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getCheckout() method that returns the form.
* @since [v3.0]
* @param Request $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
*/
public function postCheckout(Request $request, $componentId)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
$max_to_checkout = $component->numRemaining();
$validator = Validator::make($request->all(), [
"asset_id" => "required",
"assigned_qty" => "required|numeric|between:1,$max_to_checkout"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$admin_user = Auth::user();
$asset_id = e(Input::get('asset_id'));
// Check if the user exists
if (is_null($asset = Asset::find($asset_id))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.asset_does_not_exist'));
}
// Update the component data
$component->asset_id = $asset_id;
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'user_id' => $admin_user->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => Input::get('assigned_qty'),
'asset_id' => $asset_id
]);
$component->logCheckout(e(Input::get('note')), $asset);
return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
}
/**
* Returns a view that allows the checkin of a component from an asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::postCheckout() method that stores the data.
* @since [v4.1.4]
* @param int $componentId
* @return \Illuminate\Contracts\View\View
*/
public function getCheckin($component_asset_id)
{
// This could probably be done more cleanly but I am very tired. - @snipe
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
}
if (is_null($asset = Asset::find($component_assets->asset_id))) {
return redirect()->route('components.index')->with('error',
trans('admin/components/message.not_found'));
}
$this->authorize('checkin', $component);
return view('components/checkin', compact('component_assets','component','asset'));
}
return redirect()->route('components.index')->with('error', trans('admin/components/messages.not_found'));
}
/**
* Validate and store checkin data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getCheckout() method that returns the form.
* @since [v4.1.4]
* @param Request $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
*/
public function postCheckin(Request $request, $component_asset_id)
{
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
return redirect()->route('components.index')->with('error',
trans('admin/components/message.not_found'));
}
$this->authorize('checkin', $component);
$max_to_checkin = $component_assets->assigned_qty;
$validator = Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// Validation passed, so let's figure out what we have to do here.
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty'));
// We have to modify the record to reflect the new qty that's
// actually checked out.
$component_assets->assigned_qty = $qty_remaining_in_checkout;
DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
$log = new Actionlog();
$log->user_id = Auth::user()->id;
$log->action_type = 'checkin from';
$log->target_type = Asset::class;
$log->target_id = $component_assets->asset_id;
$log->item_id = $component_assets->component_id;
$log->item_type = Component::class;
$log->note = $request->input('note');
$log->save();
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) {
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
return redirect()->route('components.index')->with('success',
trans('admin/components/message.checkout.success'));
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
class ConsumableCheckoutController extends Controller
{
/**
* Return a view to checkout a consumable to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable'));
}
/**
* Saves the checkout information
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumableCheckoutController::create() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request, $consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize('checkout', $consumable);
$admin_user = Auth::user();
$assigned_to = e(Input::get('assigned_to'));
// Check if the user exists
if (is_null($user = User::find($assigned_to))) {
// Redirect to the consumable management page with error
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
}
// Update the consumable data
$consumable->assigned_to = e(Input::get('assigned_to'));
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $admin_user->id,
'assigned_to' => e(Input::get('assigned_to'))
]);
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace App\Http\Controllers\Consumables;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
/**
* This controller handles all actions related to Consumables for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class ConsumablesController extends Controller
{
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('index', Consumable::class);
return view('consumables/index');
}
/**
* Return a view to display the form view to create a new consumable
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::postCreate() method that stores the form data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', Consumable::class);
return view('consumables/edit')->with('category_type', 'consumable')
->with('item', new Consumable);
}
/**
* Validate and store new consumable data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getCreate() method that returns the form view
* @since [v1.0]
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable();
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat($request->input('purchase_cost'));
$consumable->qty = $request->input('qty');
$consumable->user_id = Auth::id();
$consumable = $request->handleImages($consumable);
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
}
/**
* Returns a form view to edit a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $consumableId
* @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($consumableId = null)
{
if ($item = Consumable::find($consumableId)) {
$this->authorize($item);
return view('consumables/edit', compact('item'))->with('category_type', 'consumable');
}
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
/**
* Returns a form view to edit a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $consumableId = null)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$this->authorize($consumable);
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat(Input::get('purchase_cost'));
$consumable->qty = Helper::ParseFloat(Input::get('qty'));
$consumable = $request->handleImages($consumable);
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
}
/**
* Delete a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $consumableId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize($consumable);
$consumable->delete();
// Redirect to the locations management page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success'));
}
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getDataView() method that generates the JSON response
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null)
{
$consumable = Consumable::find($consumableId);
$this->authorize($consumable);
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
}
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.does_not_exist'));
}
}

View File

@@ -1,286 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\Setting;
use App\Models\User;
use Auth;
use Config;
use DB;
use Input;
use Lang;
use Redirect;
use Slack;
use Str;
use View;
use Gate;
use Image;
use App\Http\Requests\ImageUploadRequest;
/**
* This controller handles all actions related to Consumables for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class ConsumablesController extends Controller
{
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$this->authorize('index', Consumable::class);
return view('consumables/index');
}
/**
* Return a view to display the form view to create a new consumable
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::postCreate() method that stores the form data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function create()
{
$this->authorize('create', Consumable::class);
$category_type = 'consumable';
return view('consumables/edit')->with('category_type', $category_type)
->with('item', new Consumable);
}
/**
* Validate and store new consumable data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getCreate() method that returns the form view
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable();
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat($request->input('purchase_cost'));
$consumable->qty = $request->input('qty');
$consumable->user_id = Auth::id();
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/consumables/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$consumable->image = $file_name;
}
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
}
/**
* Returns a form view to edit a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $consumableId
* @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function edit($consumableId = null)
{
if ($item = Consumable::find($consumableId)) {
$this->authorize($item);
$category_type = 'consumable';
return view('consumables/edit', compact('item'))->with('category_type', $category_type);
}
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
/**
* Returns a form view to edit a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $consumableId
* @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function update(ImageUploadRequest $request, $consumableId = null)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$this->authorize($consumable);
$consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat(Input::get('purchase_cost'));
$consumable->qty = Helper::ParseFloat(Input::get('qty'));
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/consumables/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$consumable->image = $file_name;
} elseif ($request->input('image_delete')=='1') {
$consumable->image = null;
}
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($consumable->getErrors());
}
/**
* Delete a consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $consumableId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize($consumable);
$consumable->delete();
// Redirect to the locations management page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success'));
}
/**
* Return a view to display component information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getDataView() method that generates the JSON response
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Contracts\View\View
*/
public function show($consumableId = null)
{
$consumable = Consumable::find($consumableId);
$this->authorize($consumable);
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
}
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist', compact('id')));
}
/**
* Return a view to checkout a consumable to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::postCheckout() method that stores the data.
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Contracts\View\View
*/
public function getCheckout($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable'));
}
/**
* Saves the checkout information
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumablesController::getCheckout() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
*/
public function postCheckout($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
}
$this->authorize('checkout', $consumable);
$admin_user = Auth::user();
$assigned_to = e(Input::get('assigned_to'));
// Check if the user exists
if (is_null($user = User::find($assigned_to))) {
// Redirect to the consumable management page with error
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
}
// Update the consumable data
$consumable->assigned_to = e(Input::get('assigned_to'));
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $admin_user->id,
'assigned_to' => e(Input::get('assigned_to'))
]);
$logaction = $consumable->logCheckout(e(Input::get('note')), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $consumable->name;
$data['checkout_date'] = $logaction->created_at;
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
}
}

View File

@@ -1,19 +1,14 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CustomFieldRequest;
use View;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Input;
use Validator;
use Redirect;
use App\Models\AssetModel;
use Lang;
use Auth;
use Illuminate\Http\Request;
use App\Helpers\Helper;
use Log;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use App\Http\Requests\CustomFieldRequest;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -29,49 +24,53 @@ class CustomFieldsController extends Controller
{
/**
* Returns a view with a listing of custom fields.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return View
*/
* Returns a view with a listing of custom fields.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('view', CustomField::class);
$fieldsets = CustomFieldset::with("fields", "models")->get();
$fields = CustomField::with("fieldset")->get();
return view("custom_fields.index")->with("custom_fieldsets", $fieldsets)->with("custom_fields", $fields);
}
/**
* Returns a view with a form to create a new custom field.
*
* @see CustomFieldsController::storeField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return View
*/
* Returns a view with a form to create a new custom field.
*
* @see CustomFieldsController::storeField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', CustomField::class);
return view("custom_fields.fields.edit")->with('field', new CustomField());
return view("custom_fields.fields.edit",[
'predefinedFormats' => Helper::predefined_formats(),
'customFormat' => ''
])->with('field', new CustomField());
}
/**
* Validates and stores a new custom field.
*
* @see CustomFieldsController::createField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
*/
* Validates and stores a new custom field.
*
* @see CustomFieldsController::createField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(CustomFieldRequest $request)
{
$this->authorize('create', CustomField::class);
@@ -83,22 +82,24 @@ class CustomFieldsController extends Controller
"field_values" => $request->get("field_values"),
"field_encrypted" => $request->get("field_encrypted", 0),
"show_in_email" => $request->get("show_in_email", 0),
"user_id" => Auth::user()->id
"user_id" => Auth::id()
]);
if ($request->has("custom_format")) {
if ($request->filled("custom_format")) {
$field->format = e($request->get("custom_format"));
} else {
$field->format = e($request->get("format"));
}
if ($field->save()) {
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.create.success'));
} else {
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.create.error'));
}
return redirect()->back()->withInput()
->with('error', trans('admin/custom_fields/message.field.create.error'));
}
@@ -108,6 +109,7 @@ class CustomFieldsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function deleteFieldFromFieldset($field_id, $fieldset_id)
{
@@ -116,19 +118,21 @@ class CustomFieldsController extends Controller
$this->authorize('update', $field);
if ($field->fieldset()->detach($fieldset_id)) {
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])->with("success", trans('admin/custom_fields/message.field.delete.success'));
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
->with("success", trans('admin/custom_fields/message.field.delete.success'));
}
return redirect()->back()->withErrors(['message' => "Field is in-use"]);
}
/**
* Delete a custom field.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
*/
* Delete a custom field.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($field_id)
{
$field = CustomField::find($field_id);
@@ -137,56 +141,66 @@ class CustomFieldsController extends Controller
if ($field->fieldset->count()>0) {
return redirect()->back()->withErrors(['message' => "Field is in-use"]);
} else {
$field->delete();
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.delete.success'));
}
$field->delete();
return redirect()->route("fields.index")
->with("success", trans('admin/custom_fields/message.field.delete.success'));
}
/**
* Return a view to edit a custom field
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return View
*/
* Return a view to edit a custom field
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
{
$field = CustomField::find($id);
$this->authorize('update', $field);
return view("custom_fields.fields.edit")->with('field', $field);
$customFormat = '';
if((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) {
$customFormat = $field->format;
}
return view("custom_fields.fields.edit",[
'field' => $field,
'customFormat' => $customFormat,
'predefinedFormats' => Helper::predefined_formats()
]);
}
/**
* Store the updated field
*
* @todo Allow encrypting/decrypting if encryption status changes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return Redirect
*/
* Store the updated field
*
* @todo Allow encrypting/decrypting if encryption status changes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(CustomFieldRequest $request, $id)
{
$field = CustomField::find($id);
$this->authorize('update', $field);
$field->name = e($request->get("name"));
$field->element = e($request->get("element"));
$field->field_values = e($request->get("field_values"));
$field->user_id = Auth::user()->id;
$field->help_text = $request->get("help_text");
$field->name = e($request->get("name"));
$field->element = e($request->get("element"));
$field->field_values = e($request->get("field_values"));
$field->user_id = Auth::id();
$field->help_text = $request->get("help_text");
$field->show_in_email = $request->get("show_in_email", 0);
if (!in_array(Input::get('format'), array_keys(CustomField::$PredefinedFormats))) {
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get("custom_format"));
} else {
$field->format = e($request->get("format"));

View File

@@ -1,17 +1,14 @@
<?php
namespace App\Http\Controllers;
use View;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Input;
use Validator;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator;
use Redirect;
use App\Models\AssetModel;
use Lang;
use Auth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Log;
/**
* This controller handles all actions related to Custom Asset Fields for
@@ -26,48 +23,60 @@ use Log;
class CustomFieldsetsController extends Controller
{
/**
* Validates and stores a new custom field.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id
* @since [v1.8]
* @return View
*/
/**
* Validates and stores a new custom field.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
public function show($id)
{
$cfset = CustomFieldset::with('fields')->where('id', '=', $id)->orderBy('id', 'ASC')->first();
$cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
$this->authorize('view', $cfset);
if ($cfset) {
$custom_fields_list = ["" => "Add New Field to Fieldset"] + CustomField::pluck("name", "id")->toArray();
$maxid = 0;
foreach ($cfset->fields as $field) {
if ($field->pivot->order > $maxid) {
$maxid=$field->pivot->order;
}
if (isset($custom_fields_list[$field->id])) {
unset($custom_fields_list[$field->id]);
foreach ($cfset->fields() as $field) {
if ($field) {
if ($field->pivot->order > $maxid) {
$maxid=$field->pivot->order;
}
if (isset($custom_fields_list[$field->id])) {
unset($custom_fields_list[$field->id]);
}
}
}
return view("custom_fields.fieldsets.view")->with("custom_fieldset", $cfset)->with("maxid", $maxid+1)->with("custom_fields_list", $custom_fields_list);
return view("custom_fields.fieldsets.view")
->with("custom_fieldset", $cfset)
->with("maxid", $maxid+1)
->with("custom_fields_list", $custom_fields_list);
}
return redirect()->route("fields.index")->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
return redirect()->route("fields.index")
->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
}
/**
* Returns a view with a form for creating a new custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return View
*/
* Returns a view with a form for creating a new custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', CustomFieldset::class);
@@ -77,29 +86,30 @@ class CustomFieldsetsController extends Controller
/**
* Validates and stores a new custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return Redirect
*/
* Validates and stores a new custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @param Request $request
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request)
{
$this->authorize('create', CustomFieldset::class);
$cfset = new CustomFieldset(
[
$cfset = new CustomFieldset([
"name" => e($request->get("name")),
"user_id" => Auth::user()->id]
);
"user_id" => Auth::user()->id
]);
$validator = Validator::make(Input::all(), $cfset->rules);
if ($validator->passes()) {
$cfset->save();
return redirect()->route("fieldsets.show", [$cfset->id])->with('success', trans('admin/custom_fields/message.fieldset.create.success'));
} else {
return redirect()->back()->withInput()->withErrors($validator);
return redirect()->route("fieldsets.show", [$cfset->id])
->with('success', trans('admin/custom_fields/message.fieldset.create.success'));
}
return redirect()->back()->withInput()->withErrors($validator);
}
@@ -136,13 +146,14 @@ class CustomFieldsetsController extends Controller
/**
* Validates a custom fieldset and then deletes if it has no models associated.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id
* @since [v1.8]
* @return View
*/
* Validates a custom fieldset and then deletes if it has no models associated.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id
* @since [v1.8]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
$fieldset = CustomFieldset::find($id);
@@ -154,9 +165,8 @@ class CustomFieldsetsController extends Controller
if ($models->count() == 0) {
$fieldset->delete();
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.fieldset.delete.success'));
} else {
return redirect()->route("fields.index")->with("error", trans('admin/custom_fields/message.fieldset.delete.in_use'));
}
return redirect()->route("fields.index")->with("error", trans('admin/custom_fields/message.fieldset.delete.in_use'));
}
return redirect()->route("fields.index")->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
@@ -166,13 +176,14 @@ class CustomFieldsetsController extends Controller
/**
* Associate the custom field with a custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return View
*/
public function associate(Request $request, $id)
* Associate the custom field with a custom fieldset.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function associate($id)
{
$set = CustomFieldset::find($id);
@@ -180,12 +191,12 @@ class CustomFieldsetsController extends Controller
$this->authorize('update', $set);
foreach ($set->fields as $field) {
if ($field->id == $request->input('field_id')) {
if ($field->id == Input::get('field_id')) {
return redirect()->route("fieldsets.show", [$id])->withInput()->withErrors(['field_id' => trans('admin/custom_fields/message.field.already_added')]);
}
}
$results = $set->fields()->attach(Input::get('field_id'), ["required" => ($request->input('required') == "on"),"order" => $request->input('order', 1)]);
$results=$set->fields()->attach(Input::get('field_id'), ["required" => (Input::get('required') == "on"),"order" => Input::get('order')]);
return redirect()->route("fieldsets.show", [$id])->with("success", trans('admin/custom_fields/message.field.create.assoc_success'));
}

View File

@@ -4,10 +4,10 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Department;
use App\Helpers\Helper;
use Auth;
use Illuminate\Support\Facades\Auth;
use Image;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
class DepartmentsController extends Controller
{
@@ -24,7 +24,9 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AssetController::getDatatable() method that generates the JSON response
* @since [v4.0]
* @return View
* @param Request $request
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request)
{
@@ -42,27 +44,19 @@ class DepartmentsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param ImageUploadRequest $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
$department->user_id = Auth::user()->id;
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
$department->user_id = Auth::id();
$department->manager_id = $request->input('manager_id', null);
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/departments/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$department->image = $file_name;
}
$department = $request->handleImages($department);
if ($department->save()) {
return redirect()->route("departments.index")->with('success', trans('admin/departments/message.create.success'));
@@ -78,6 +72,7 @@ class DepartmentsController extends Controller
* @param int $id
* @since [v4.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($id)
{
@@ -88,7 +83,7 @@ class DepartmentsController extends Controller
if (isset($department->id)) {
return view('departments/view', compact('department'));
}
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist', compact('id')));
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
}
@@ -99,6 +94,7 @@ class DepartmentsController extends Controller
* @see DepartmentsController::postCreate() method that validates and stores the data
* @since [v4.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -115,6 +111,7 @@ class DepartmentsController extends Controller
* @param int $locationId
* @since [v4.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
@@ -128,23 +125,32 @@ class DepartmentsController extends Controller
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.assoc_users'));
}
if ($department->image) {
try {
Storage::disk('public')->delete('departments'.'/'.$department->image);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$department->delete();
return redirect()->back()->with('success', trans('admin/departments/message.delete.success'));
}
/**
* Makes a form view to edit location information.
* Makes a form view to edit Department information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores
* @param int $locationId
* @param int $departmentId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id = null)
public function edit($departmentId = null)
{
if (is_null($item = Department::find($id))) {
if (is_null($item = Department::find($departmentId))) {
return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -164,36 +170,7 @@ class DepartmentsController extends Controller
$department->fill($request->all());
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
$old_image = $department->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$department->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $department->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('departments_upload_path').$file_name);
} else {
$image->move(app('departments_upload_path'), $file_name);
}
$department->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('departments_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$department = $request->handleImages($department);
if ($department->save()) {
return redirect()->route("departments.index")->with('success', trans('admin/departments/message.update.success'));

View File

@@ -1,15 +1,8 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use Lang;
use App\Models\Depreciation;
use Redirect;
use App\Models\Setting;
use DB;
use Str;
use View;
use Auth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
/**
@@ -21,13 +14,14 @@ use Illuminate\Http\Request;
class DepreciationsController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the depreciation listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the depreciation listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
@@ -39,12 +33,13 @@ class DepreciationsController extends Controller
/**
* Returns a view that displays a form to create a new depreciation.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postCreate()
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that displays a form to create a new depreciation.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postCreate()
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -63,6 +58,7 @@ class DepreciationsController extends Controller
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request)
{
@@ -84,13 +80,14 @@ class DepreciationsController extends Controller
}
/**
* Returns a view that displays a form to update a depreciation.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that displays a form to update a depreciation.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($depreciationId = null)
{
@@ -115,6 +112,7 @@ class DepreciationsController extends Controller
* @param int $depreciationId
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, $depreciationId = null)
{
@@ -147,17 +145,18 @@ class DepreciationsController extends Controller
* @since [v1.0]
* @param integer $depreciationId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($depreciationId)
{
// Check if the depreciation exists
if (is_null($depreciation = Depreciation::find($depreciationId))) {
if (is_null($depreciation = Depreciation::withCount('models as models_count')->find($depreciationId))) {
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.not_found'));
}
$this->authorize('delete', $depreciation);
if ($depreciation->has_models() > 0) {
if ($depreciation->models_count > 0) {
// Redirect to the asset management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.assoc_users'));
}
@@ -175,6 +174,7 @@ class DepreciationsController extends Controller
* @param int $depreciationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($id)
{

View File

@@ -1,13 +1,7 @@
<?php
namespace App\Http\Controllers;
use Config;
use Input;
use Lang;
use Redirect;
use App\Models\Setting;
use Validator;
use View;
use Illuminate\Support\Facades\Input;
use App\Models\Group;
use App\Helpers\Helper;
@@ -109,7 +103,6 @@ class GroupsController extends Controller
*/
public function update($id = null)
{
$permissions = config('permissions');
if (!$group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
}
@@ -126,13 +119,14 @@ class GroupsController extends Controller
}
/**
* Validates and deletes the User Group.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Validates and deletes the User Group.
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see GroupsController::getEdit()
* @param int $id
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Exception
*/
public function destroy($id = null)
{
@@ -152,9 +146,9 @@ class GroupsController extends Controller
* the content for the group detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v4.0.11]
* @param $id
* @return \Illuminate\Contracts\View\View
* @since [v4.0.11]
*/
public function show($id)
{

View File

@@ -4,17 +4,19 @@ namespace App\Http\Controllers;
use App\Http\Transformers\ImportsTransformer;
use App\Models\Import;
use Illuminate\Http\Request;
use App\Models\Asset;
class ImportsController extends Controller
{
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('import');
$imports = Import::latest()->get();
$imports = (new ImportsTransformer)->transformImports($imports);
$this->authorize('create', Asset::class);
$imports = (new ImportsTransformer)->transformImports(Import::latest()->get());
return view('importer/import')->with('imports', $imports);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
class LicenseCheckinController extends Controller
{
/**
* Makes the form view to check a license seat back into inventory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $seatId
* @param string $backTo
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($seatId = null, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId)) || is_null($license = License::find($licenseSeat->license_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('checkout', $license);
return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo);
}
/**
* Validates and stores the license checkin action.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicenseCheckinController::create() method that provides the form view
* @since [v1.0]
* @param int $seatId
* @param string $backTo
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request, $seatId = null, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$license = License::find($licenseSeat->license_id);
$this->authorize('checkout', $license);
if (!$license->reassignable) {
// Not allowed to checkin
Session::flash('error', 'License not reassignable.');
return redirect()->back()->withInput();
}
// Declare the rules for the form validation
$rules = [
'note' => 'string',
'notes' => 'string',
];
// Create a new validator instance from our validation rules
$validator = Validator::make(Input::all(), $rules);
// If validation fails, we'll exit the operation now.
if ($validator->fails()) {
// Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator);
}
$return_to = User::find($licenseSeat->assigned_to);
// Update the asset data
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
// Was the asset updated?
if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note')));
if ($backTo=='user') {
return redirect()->route("users.show", $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}
return redirect()->route("licenses.show", $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
}
// Redirect to the license page with error
return redirect()->route("licenses.index")->with('error', trans('admin/licenses/message.checkin.error'));
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
class LicenseCheckoutController extends Controller
{
/**
* Provides the form view for checking out a license to a user.
* Here we pass the license seat ID instead of the license ID,
* because licenses themselves are never checked out to anyone,
* only the seats associated with them.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param $licenseId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($licenseId)
{
// Check that the license is valid
if ($license = License::find($licenseId)) {
// If the license is valid, check that there is an available seat
if ($license->avail_seats_count < 1) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
}
$this->authorize('checkout', $license);
return view('licenses/checkout', compact('license'));
}
/**
* Validates and stores the license checkout action.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param LicenseCheckoutRequest $request
* @param $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(LicenseCheckoutRequest $request, $licenseId, $seatId = null)
{
if (!$license = License::find($licenseId)) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('checkout', $license);
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
$licenseSeat->user_id = Auth::id();
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
if ($this->$checkoutMethod($licenseSeat)) {
return redirect()->route("licenses.index")->with('success', trans('admin/licenses/message.checkout.success'));
}
return redirect()->route("licenses.index")->with('error', trans('Something went wrong handling this checkout.'));
}
protected function findLicenseSeatToCheckout($license, $seatId)
{
$licenseSeat = LicenseSeat::find($seatId) ?? $license->freeSeat();
if (!$licenseSeat) {
if ($seatId) {
return redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.');
}
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
if(!$licenseSeat->license->is($license)) {
return redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.');
}
return $licenseSeat;
}
protected function checkoutToAsset($licenseSeat)
{
if (is_null($target = Asset::find(request('asset_id')))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.asset_does_not_exist'));
}
$licenseSeat->asset_id = request('asset_id');
// Override asset's assigned user if available
if ($target->checkedOutToUser()) {
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
return true;
}
return false;
}
protected function checkoutToUser($licenseSeat)
{
// Fetch the target and set the license user
if (is_null($target = User::find(request('assigned_to')))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.user_does_not_exist'));
}
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
return true;
}
return false;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(AssetFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
// the license is valid
$destinationPath = config('app.private_uploads').'/licenses';
if (isset($license->id)) {
$this->authorize('update', $license);
if (Input::hasFile('file')) {
if (!Storage::exists('private_uploads/licenses')) Storage::makeDirectory('private_uploads/licenses', 775);
$upload_success = false;
foreach (Input::file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$upload_success = Storage::put('private_uploads/licenses/'.$file_name, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
}
// This being called from a modal seems to confuse redirect()->back()
// It thinks we should go to the dashboard. As this is only used
// from the modal at present, hardcode the redirect. Longterm
// maybe we evaluate something else.
if ($upload_success) {
return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.error'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles'));
}
// Prepare the error message
return redirect()->route('licenses.index')
->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Deletes the selected license file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($licenseId = null, $fileId = null)
{
$license = License::find($licenseId);
// the asset is valid
if (isset($license->id)) {
$this->authorize('update', $license);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('licenses/'.$log->filename)) {
try {
Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $licenseId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
// the license is valid
if (isset($license->id)) {
$this->authorize('view', $license);
if (!$log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/licenses/'.$log->filename;
\Log::debug('Checking for '.$file);
if (!Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents(Storage::url($file))) {
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Storage::download($file);
}
return redirect()->route('hardware.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Http\Controllers\Controller;
use App\Models\License;
use Illuminate\Support\Facades\DB;
use App\Models\Company;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Licenses for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class LicensesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the licenses listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
$this->authorize('view', License::class);
return view('licenses/index');
}
/**
* Returns a form view that allows an admin to create a new licence.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
$this->authorize('create', License::class);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
return view('licenses/edit')
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list)
->with('item', new License);
}
/**
* Validates and stores the license form data submitted from the new
* license form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getCreate() method that provides the form view
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request)
{
$this->authorize('create', License::class);
// create a new model instance
$license = new License();
// Save the license data
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0);
$license->manufacturer_id = $request->input('manufacturer_id');
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->seats = $request->input('seats');
$license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
if ($license->save()) {
return redirect()->route("licenses.index")->with('success', trans('admin/licenses/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($license->getErrors());
}
/**
* Returns a form with existing license data to allow an admin to
* update license information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($licenseId = null)
{
if (is_null($item = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $item);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
return view('licenses/edit', compact('item'))
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list);
}
/**
* Validates and stores the license form data submitted from the edit
* license form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getEdit() method that provides the form view
* @since [v1.0]
* @param Request $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, $licenseId = null)
{
if (is_null($license = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $license);
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained',0);
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->serial = $request->input('serial');
$license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
}
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors());
}
/**
* Checks to see whether the selected license can be deleted, and
* if it can, marks it as deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($licenseId)
{
// Check if the license exists
if (is_null($license = License::find($licenseId))) {
// Redirect to the license management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('delete', $license);
if ($license->assigned_seats_count == 0) {
// Delete the license and the associated license seats
DB::table('license_seats')
->where('id', $license->id)
->update(array('assigned_to' => null,'asset_id' => null));
$licenseSeats = $license->licenseseats();
$licenseSeats->delete();
$license->delete();
// Redirect to the licenses management page
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.delete.success'));
// Redirect to the license management page
}
// There are still licenses in use.
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.assoc_users'));
}
/**
* Makes the license detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($licenseId = null)
{
$license = License::with('assignedusers', 'licenseSeats.user', 'licenseSeats.asset')->find($licenseId);
if ($license) {
$this->authorize('view', $license);
return view('licenses/view', compact('license'));
}
return redirect()->route('licenses.index')
->with('error', trans('admin/licenses/message.does_not_exist'));
}
public function getClone($licenseId = null)
{
if (is_null($license_to_clone = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('create', License::class);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
//clone the orig
$license = clone $license_to_clone;
$license->id = null;
$license->serial = null;
// Show the page
return view('licenses/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $license)
->with('maintained_list', $maintained_list);
}
}

View File

@@ -1,643 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\AssetFileRequest;
use Assets;
use Illuminate\Support\Facades\Session;
use Input;
use Lang;
use App\Models\License;
use App\Models\Asset;
use App\Models\User;
use App\Models\Actionlog;
use DB;
use App\Models\LicenseSeat;
use App\Models\Company;
use Validator;
use View;
use Response;
use Slack;
use Config;
use App\Helpers\Helper;
use Auth;
use Gate;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Licenses for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class LicensesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the licenses listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$this->authorize('view', License::class);
return view('licenses/index');
}
/**
* Returns a form view that allows an admin to create a new licence.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function create()
{
$this->authorize('create', License::class);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
return view('licenses/edit')
//->with('license_options',$license_options)
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list)
->with('item', new License);
}
/**
* Validates and stores the license form data submitted from the new
* license form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getCreate() method that provides the form view
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
$this->authorize('create', License::class);
// create a new model instance
$license = new License();
// Save the license data
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0);
$license->manufacturer_id = $request->input('manufacturer_id');
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->seats = $request->input('seats');
$license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
if ($license->save()) {
return redirect()->route("licenses.index")->with('success', trans('admin/licenses/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($license->getErrors());
}
/**
* Returns a form with existing license data to allow an admin to
* update license information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
*/
public function edit($licenseId = null)
{
if (is_null($item = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $item);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
return view('licenses/edit', compact('item'))
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list);
}
/**
* Validates and stores the license form data submitted from the edit
* license form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getEdit() method that provides the form view
* @since [v1.0]
* @param Request $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, $licenseId = null)
{
if (is_null($license = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $license);
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained',0);
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
if (Gate::allows('viewKeys', $license)) {
$license->serial = $request->input('serial');
}
$license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
}
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors());
}
/**
* Checks to see whether the selected license can be deleted, and
* if it can, marks it as deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy($licenseId)
{
// Check if the license exists
if (is_null($license = License::find($licenseId))) {
// Redirect to the license management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('delete', $license);
if ($license->assigned_seats_count == 0) {
// Delete the license and the associated license seats
DB::table('license_seats')
->where('id', $license->id)
->update(array('assigned_to' => null,'asset_id' => null));
$licenseSeats = $license->licenseseats();
$licenseSeats->delete();
$license->delete();
// Redirect to the licenses management page
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.delete.success'));
// Redirect to the license management page
}
// There are still licenses in use.
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.assoc_users'));
}
/**
* Provides the form view for checking out a license to a user.
* Here we pass the license seat ID instead of the license ID,
* because licenses themselves are never checked out to anyone,
* only the seats associated with them.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $seatId
* @return \Illuminate\Contracts\View\View
*/
public function getCheckout($licenceId)
{
// Check that the license is valid
if ($license = License::where('id',$licenceId)->first()) {
// If the license is valid, check that there is an available seat
if ($license->getAvailSeatsCountAttribute() < 1) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
}
$this->authorize('checkout', $license);
return view('licenses/checkout', compact('license'));
}
/**
* Validates and stores the license checkout action.
*
* @todo Switch to using a FormRequest for validation here.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param Request $request
* @param int $licenseId
* @param int $seatId
* @return \Illuminate\Http\RedirectResponse
*/
public function postCheckout(Request $request, $licenseId, $seatId = null)
{
// Check that the license is valid
if ($license = License::where('id', $licenseId)->first()) {
// If the license is valid, check that there is an available seat
if ($license->getAvailSeatsCountAttribute() < 1) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
if (!$seatId) {
// Get the next available seat for this license
$next = $license->freeSeat();
if (!$next) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
if (!$licenseSeat = LicenseSeat::where('id', '=', $next->id)->first()) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
}
} else {
$licenseSeat = LicenseSeat::where('id', '=', $seatId)->first();
if (!$licenseSeat) {
return redirect()->route('licenses.index')->with('error', 'License seat is not available for checkout');
}
}
$this->authorize('checkout', $license);
// Declare the rules for the form validation
$rules = [
'note' => 'string|nullable',
'asset_id' => 'required_without:assigned_to',
];
// Create a new validator instance from our validation rules
$validator = Validator::make(Input::all(), $rules);
// If validation fails, we'll exit the operation now.
if ($validator->fails()) {
// Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator);
}
$target = null;
// This item is checked out to a an asset
if (request('checkout_to_type')=='asset') {
if (is_null($target = Asset::find(request('asset_id')))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.asset_does_not_exist'));
}
$licenseSeat->asset_id = $request->input('asset_id');
// Override asset's assigned user if available
if ($target->checkedOutToUser()) {
$licenseSeat->assigned_to = $target->assigned_to;
}
} else {
// Fetch the target and set the license user
if (is_null($target = User::find(request('assigned_to')))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.user_does_not_exist'));
}
$licenseSeat->assigned_to = request('assigned_to');
}
$licenseSeat->user_id = Auth::user()->id;
if ($licenseSeat->save()) {
$licenseSeat->logCheckout($request->input('note'), $target);
return redirect()->route("licenses.index")->with('success', trans('admin/licenses/message.checkout.success'));
}
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
return redirect()->route("licenses.index")->with('error', trans('admin/licenses/message.checkout.error'));
}
/**
* Makes the form view to check a license seat back into inventory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $seatId
* @param string $backTo
* @return \Illuminate\Contracts\View\View
*/
public function getCheckin($seatId = null, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
if (is_null($license = License::find($licenseSeat->license_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('checkout', $license);
return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo);
}
/**
* Validates and stores the license checkin action.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getCheckin() method that provides the form view
* @since [v1.0]
* @param int $seatId
* @param string $backTo
* @return \Illuminate\Http\RedirectResponse
*/
public function postCheckin($seatId = null, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$license = License::find($licenseSeat->license_id);
$this->authorize('checkout', $license);
if (!$license->reassignable) {
// Not allowed to checkin
Session::flash('error', 'License not reassignable.');
return redirect()->back()->withInput();
}
$return_to = User::find($licenseSeat->assigned_to);
if (!$return_to) {
$return_to = Asset::find($licenseSeat->asset_id);
}
// Update the asset data
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckin($license, e(request('note')));
if ($backTo=='user') {
return redirect()->route("users.show", $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}
return redirect()->route("licenses.show", $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
}
// Redirect to the license page with error
return redirect()->route("licenses.index")->with('error', trans('admin/licenses/message.checkin.error'));
}
/**
* Makes the license detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
*/
public function show($licenseId = null)
{
$license = License::with('assignedusers', 'licenseSeats.user', 'licenseSeats.asset')->find($licenseId);
if ($license) {
$this->authorize('view', $license);
return view('licenses/view', compact('license'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', compact('id')));
}
public function getClone($licenseId = null)
{
if (is_null($license_to_clone = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('create', License::class);
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',
'0' => 'No'
];
//clone the orig
$license = clone $license_to_clone;
$license->id = null;
$license->serial = null;
// Show the page
return view('licenses/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $license)
->with('maintained_list', $maintained_list);
}
/**
* Validates and stores files associated with a license.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
*/
public function postUpload(AssetFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
// the license is valid
$destinationPath = config('app.private_uploads').'/licenses';
if (isset($license->id)) {
$this->authorize('update', $license);
if (Input::hasFile('file')) {
foreach (Input::file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$filename = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$upload_success = $file->move($destinationPath, $filename);
//Log the upload to the log
$license->logUpload($filename, e($request->input('notes')));
}
// This being called from a modal seems to confuse redirect()->back()
// It thinks we should go to the dashboard. As this is only used
// from the modal at present, hardcode the redirect. Longterm
// maybe we evaluate something else.
if ($upload_success) {
return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.error'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles'));
}
// Prepare the error message
$error = trans('admin/licenses/message.does_not_exist', compact('id'));
return redirect()->route('licenses.index')->with('error', $error);
}
/**
* Deletes the selected license file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
*/
public function getDeleteFile($licenseId = null, $fileId = null)
{
$license = License::find($licenseId);
$destinationPath = config('app.private_uploads').'/licenses';
// the license is valid
if ($license) {
$this->authorize('edit', $license);
$log = Actionlog::find($fileId);
if ($log) {
$full_filename = $destinationPath.'/'.$log->filename;
if (file_exists($full_filename)) {
unlink($destinationPath.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->with('success', trans('admin/licenses/message.deletefile.success'));
}
return redirect()->back()->with('error', 'Could not locate that file.');
}
// Redirect to the licence management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', compact('id')));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $licenseId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function displayFile($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
// the license is valid
if (isset($license->id)) {
$this->authorize('view', $license);
$log = Actionlog::find($fileId);
if ($log) {
$file = $log->get_src('licenses');
if ($file =='') {
return response('File not found on server', 404)
->header('Content-Type', 'text/plain');
}
$mimetype = \File::mimeType($file);
if (!file_exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents($file)) {
return Response::make($contents)->header('Content-Type', $mimetype);
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Response::download($file);
}
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', compact('id')));
}
/**
* Generates the next free seat ID for checkout.
*
* @todo This is a dumb way to solve this problem.
* Author should refactor. And go hide in a hole and
* think about what she's done. And perhaps find a new
* line of work. And get in the sea.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
*/
public function getFreeLicense($licenseId)
{
$this->authorize('checkout', License::class);
if (is_null($license = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$seatId = $license->freeSeat($licenseId);
return redirect()->route('licenses.checkout', $seatId);
}
}

View File

@@ -1,23 +1,12 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use Input;
use Lang;
use App\Models\Location;
use phpDocumentor\Reflection\Types\Array_;
use Redirect;
use App\Models\Setting;
use App\Models\User;
use App\Models\Asset;
use DB;
use Str;
use Validator;
use View;
use Auth;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Image;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
/**
* This controller handles all actions related to Locations for
@@ -29,32 +18,32 @@ class LocationsController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the locations listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the locations listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
// Grab all the locations
$this->authorize('view', Location::class);
$locations = Location::orderBy('created_at', 'DESC')->with('parent', 'assets', 'assignedassets')->get();
// Show the page
return view('locations/index');
}
/**
* Returns a form view used to create a new location.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores the data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a form view used to create a new location.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores the data
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -72,13 +61,15 @@ class LocationsController extends Controller
/**
* Validates and stores a new location.
*
* @todo Check if a Form Request would work better here.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getCreate() method that makes the form
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Validates and stores a new location.
*
* @todo Check if a Form Request would work better here.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getCreate() method that makes the form
* @since [v1.0]
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
@@ -97,16 +88,7 @@ class LocationsController extends Controller
$location->manager_id = $request->input('manager_id');
$location->user_id = Auth::id();
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/locations/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$location->image = $file_name;
}
$location = $request->handleImages($location, 'public/uploads/locations');
if ($location->save()) {
return redirect()->route("locations.index")->with('success', trans('admin/locations/message.create.success'));
@@ -116,13 +98,14 @@ class LocationsController extends Controller
/**
* Makes a form view to edit location information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Makes a form view to edit location information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($locationId = null)
{
@@ -144,13 +127,15 @@ class LocationsController extends Controller
/**
* Validates and stores updated location data from edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getEdit() method that makes the form view
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Validates and stores updated location data from edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getEdit() method that makes the form view
* @param ImageUploadRequest $request
* @param int $locationId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $locationId = null)
{
@@ -173,36 +158,7 @@ class LocationsController extends Controller
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$old_image = $location->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$location->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $location->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('locations_upload_path').$file_name);
} else {
$image->move(app('locations_upload_path'), $file_name);
}
$location->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('locations_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$location = $request->handleImages($location, 'public/uploads/locations');
if ($location->save()) {
@@ -212,12 +168,13 @@ class LocationsController extends Controller
}
/**
* Validates and deletes selected location.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Validates and deletes selected location.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($locationId)
{
@@ -226,22 +183,29 @@ class LocationsController extends Controller
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
}
if ($location->users->count() > 0) {
if ($location->users()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
} elseif ($location->childLocations->count() > 0) {
} elseif ($location->childLocations()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
} elseif ($location->assets->count() > 0) {
} elseif ($location->assets()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
} elseif ($location->assignedassets->count() > 0) {
} elseif ($location->assignedassets()->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
} else {
$location->delete();
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
}
if ($location->image) {
try {
Storage::disk('public')->delete('locations/'.$location->image);
} catch (\Exception $e) {
\Log::error($e);
}
}
$location->delete();
return redirect()->to(route('locations.index'))->with('success', trans('admin/locations/message.delete.success'));
}
@@ -250,19 +214,19 @@ class LocationsController extends Controller
* the content for the locations detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @param int $id
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function show($locationId = null)
public function show($id = null)
{
$location = Location::find($locationId);
$location = Location::find($id);
if (isset($location->id)) {
return view('locations/view', compact('location'));
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist', compact('id')));
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
}

View File

@@ -1,20 +1,13 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\CustomField;
use App\Models\Manufacturer;
use Auth;
use Exception;
use Gate;
use Input;
use Lang;
use Illuminate\Support\Facades\Auth;
use Redirect;
use Str;
use View;
use Illuminate\Http\Request;
use Image;
use Illuminate\Support\Facades\Storage;
/**
* This controller handles all actions related to Manufacturers for
@@ -25,13 +18,14 @@ use Image;
class ManufacturersController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the manufacturers listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the manufacturers listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
@@ -41,12 +35,13 @@ class ManufacturersController extends Controller
/**
* Returns a view that displays a form to create a new manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::store()
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that displays a form to create a new manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::store()
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -61,32 +56,24 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::create()
* @since [v1.0]
* @param Request $request
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
$manufacturer->name = $request->input('name');
$manufacturer->user_id = Auth::user()->id;
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->name = $request->input('name');
$manufacturer->user_id = Auth::id();
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_slug($image->getClientOriginalName()).".".$image->getClientOriginalExtension();
$path = public_path('uploads/manufacturers/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$manufacturer->image = $file_name;
}
$manufacturer = $request->handleImages($manufacturer,'manufacturers');
@@ -97,19 +84,20 @@ class ManufacturersController extends Controller
}
/**
* Returns a view that displays a form to edit a manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::update()
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that displays a form to edit a manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::update()
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id = null)
public function edit($manufacturerId = null)
{
$this->authorize('edit', Manufacturer::class);
// Check if the manufacturer exists
if (is_null($item = Manufacturer::find($id))) {
if (is_null($item = Manufacturer::find($manufacturerId))) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
// Show the page
@@ -126,6 +114,7 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(ImageUploadRequest $request, $manufacturerId = null)
{
@@ -137,42 +126,13 @@ class ManufacturersController extends Controller
}
// Save the data
$manufacturer->name = $request->input('name');
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->name = $request->input('name');
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
$old_image = $manufacturer->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$manufacturer->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $manufacturer->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('manufacturers_upload_path').$file_name);
} else {
$image->move(app('manufacturers_upload_path'), $file_name);
}
$manufacturer->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('manufacturers_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$manufacturer = $request->handleImages($manufacturer);
if ($manufacturer->save()) {
@@ -182,32 +142,30 @@ class ManufacturersController extends Controller
}
/**
* Deletes a manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* Deletes a manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($manufacturerId)
{
$this->authorize('delete', Manufacturer::class);
// Check if the manufacturer exists
if (is_null($manufacturer = Manufacturer::find($manufacturerId))) {
// Redirect to the manufacturers page
if (is_null($manufacturer = Manufacturer::withCount('models as models_count')->find($manufacturerId))) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
}
if ($manufacturer->has_models() > 0) {
// Redirect to the asset management page
if ($manufacturer->models_count > 0) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
}
if ($manufacturer->image) {
try {
unlink(public_path().'/uploads/manufacturers/'.$manufacturer->image);
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
\Log::info($e);
\Log::error($e);
}
}
@@ -219,14 +177,15 @@ class ManufacturersController extends Controller
}
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the manufacturers detail listing, which is generated via API.
* This data contains a listing of all assets that belong to that manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* Returns a view that invokes the ajax tables which actually contains
* the content for the manufacturers detail listing, which is generated via API.
* This data contains a listing of all assets that belong to that manufacturer.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($manufacturerId = null)
{
@@ -249,6 +208,7 @@ class ManufacturersController extends Controller
* @since [v4.1.15]
* @param int $manufacturers_id
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($manufacturers_id)
{

View File

@@ -12,6 +12,7 @@ use Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
/**
* This controller handles all actions related to User Profiles for
@@ -49,9 +50,6 @@ class ProfileController extends Controller
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->phone = $request->input('phone');
if (!config('app.lock_passwords')) {
$user->locale = $request->input('locale', 'en');
@@ -64,18 +62,38 @@ class ProfileController extends Controller
if (Gate::allows('self.edit_location') && (!config('app.lock_passwords'))) {
$user->location_id = $request->input('location_id');
}
if (Input::file('avatar')) {
$image = Input::file('avatar');
$file_name = str_slug($user->first_name."-".$user->last_name).".".$image->getClientOriginalExtension();
$path = public_path('uploads/avatars/'.$file_name);
Image::make($image->getRealPath())->resize(84, 84)->save($path);
if ($request->input('avatar_delete') == 1) {
$user->avatar = null;
}
if ($request->hasFile('avatar')) {
$path = 'avatars';
if(!Storage::disk('public')->exists($path)) Storage::disk('public')->makeDirectory($path, 775);
$upload = $image = $request->file('avatar');
$ext = $image->getClientOriginalExtension();
$file_name = 'avatar-'.str_random(18).'.'.$ext;
if ($image->getClientOriginalExtension()!='svg') {
$upload = Image::make($image->getRealPath())->resize(84, 84);
}
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string)$upload->encode());
// Remove Current image if exists
if (($user->avatar) && (Storage::disk('public')->exists($path.'/'.$user->avatar))) {
Storage::disk('public')->delete($path.'/'.$user->avatar);
}
$user->avatar = $file_name;
}
if (Input::get('avatar_delete') == 1 && Input::file('avatar') == "") {
$user->avatar = null;
}
if ($user->save()) {
return redirect()->route('profile')->with('success', 'Account successfully updated');
@@ -128,7 +146,8 @@ class ProfileController extends Controller
$rules = array(
'current_password' => 'required',
'password' => Setting::passwordComplexityRulesSaving('store').'|confirmed',
'password' => Setting::passwordComplexityRulesSaving('store'),
'password_confirm' => 'required|same:password',
);
$validator = \Validator::make($request->all(), $rules);

View File

@@ -7,6 +7,7 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\CustomField;
use App\Models\CheckoutAcceptance;
use App\Models\Depreciation;
use App\Models\License;
use App\Models\Setting;
@@ -31,8 +32,6 @@ class ReportsController extends Controller
*/
public function __construct() {
parent::__construct();
$this->authorize('reports.view');
}
/**
@@ -44,6 +43,7 @@ class ReportsController extends Controller
*/
public function getAccessoryReport()
{
$this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->with('company')->get();
return view('reports/accessories', compact('accessories'));
}
@@ -59,6 +59,7 @@ class ReportsController extends Controller
*/
public function exportAccessoryReport()
{
$this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->get();
$rows = array();
@@ -99,7 +100,7 @@ class ReportsController extends Controller
*/
public function getDeprecationReport()
{
$this->authorize('reports.view');
$depreciations = Depreciation::get();
// Grab all the assets
$assets = Asset::with( 'assignedTo', 'assetstatus', 'defaultLoc', 'location', 'assetlog', 'company', 'model.category', 'model.depreciation')
@@ -118,7 +119,7 @@ class ReportsController extends Controller
*/
public function exportDeprecationReport()
{
$this->authorize('reports.view');
// Grab all the assets
$assets = Asset::with('model', 'assignedTo', 'assetstatus', 'defaultLoc', 'assetlog')
->orderBy('created_at', 'DESC')->get();
@@ -172,7 +173,7 @@ class ReportsController extends Controller
if ($asset->location) {
$currency = e($asset->location->currency);
} else {
$currency = e(Setting::first()->default_currency);
$currency = e(Setting::getSettings()->default_currency);
}
$row[] = $asset->purchase_date;
@@ -197,6 +198,7 @@ class ReportsController extends Controller
*/
public function audit()
{
$this->authorize('reports.view');
return view('reports/audit');
}
@@ -210,7 +212,7 @@ class ReportsController extends Controller
*/
public function getActivityReport()
{
$this->authorize('reports.view');
return view('reports/activity');
}
@@ -224,7 +226,7 @@ class ReportsController extends Controller
*/
public function getLicenseReport()
{
$this->authorize('reports.view');
$licenses = License::with('depreciation')->orderBy('created_at', 'DESC')
->with('company')
->get();
@@ -242,6 +244,7 @@ class ReportsController extends Controller
*/
public function exportLicenseReport()
{
$this->authorize('reports.view');
$licenses = License::orderBy('created_at', 'DESC')->get();
$rows = [ ];
@@ -292,6 +295,7 @@ class ReportsController extends Controller
*/
public function getCustomReport()
{
$this->authorize('reports.view');
$customfields = CustomField::get();
return view('reports/custom')->with('customfields', $customfields);
}
@@ -306,7 +310,7 @@ class ReportsController extends Controller
*/
public function postCustom(Request $request)
{
$this->authorize('reports.view');
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
@@ -427,6 +431,7 @@ class ReportsController extends Controller
if ($request->filled('depreciation')) {
$header[] = 'Value';
$header[] = 'Diff';
$header[] = 'Fully Depreciated';
}
if ($request->filled('checkout_date')) {
@@ -496,6 +501,11 @@ class ReportsController extends Controller
$assets->InCategory($request->input('by_category_id'));
}
if ($request->filled('by_dept_id')) {
\Log::debug('Only users in dept '.$request->input('by_dept_id'));
$assets->CheckedOutToTargetInDepartment($request->input('by_dept_id'));
}
if ($request->filled('by_manufacturer_id')) {
$assets->ByManufacturer($request->input('by_manufacturer_id'));
}
@@ -658,6 +668,7 @@ class ReportsController extends Controller
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = ($asset->depreciated_date()!='') ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('checkout_date')) {
@@ -721,6 +732,7 @@ class ReportsController extends Controller
*/
public function getAssetMaintenancesReport()
{
$this->authorize('reports.view');
// Grab all the improvements
$assetMaintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company')
->orderBy('created_at', 'DESC')
@@ -739,6 +751,7 @@ class ReportsController extends Controller
*/
public function exportAssetMaintenancesReport()
{
$this->authorize('reports.view');
// Grab all the improvements
$assetMaintenances = AssetMaintenance::with('asset', 'supplier')
->orderBy('created_at', 'DESC')
@@ -799,7 +812,21 @@ class ReportsController extends Controller
*/
public function getAssetAcceptanceReport()
{
$assetsForReport = Asset::notYetAccepted()->with('company')->get();
$this->authorize('reports.view');
/**
* Get all assets with pending checkout acceptances
*/
$acceptances = CheckoutAcceptance::pending()->get();
$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return $acceptance->checkoutable;
});
return view('reports/unaccepted_assets', compact('assetsForReport'));
}
@@ -813,7 +840,7 @@ class ReportsController extends Controller
*/
public function exportAssetAcceptanceReport()
{
$this->authorize('reports.view');
// Grab all the improvements
$assetsForReport = Actionlog::whereIn('id', $this->getAssetsNotAcceptedYet())
->get();
@@ -862,6 +889,7 @@ class ReportsController extends Controller
*/
protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance)
{
$this->authorize('reports.view');
$assets = Asset::deployed()
->inModelList($modelsInCategoriesThatRequireAcceptance)
->select('id')
@@ -881,7 +909,7 @@ class ReportsController extends Controller
*/
protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance)
{
$this->authorize('reports.view');
return array_pluck(Model::inCategory($assetCategoriesRequiringAcceptance)
->select('id')
->get()
@@ -897,7 +925,7 @@ class ReportsController extends Controller
*/
protected function getCategoriesThatRequireAcceptance()
{
$this->authorize('reports.view');
return array_pluck(Category::requiresAcceptance()
->select('id')
->get()
@@ -913,7 +941,7 @@ class ReportsController extends Controller
*/
protected function getAssetsCheckedOutRequiringAcceptance()
{
$this->authorize('reports.view');
return $this->getCheckedOutAssetsRequiringAcceptance(
$this->getModelsInCategoriesThatRequireAcceptance($this->getCategoriesThatRequireAcceptance())
);
@@ -928,6 +956,7 @@ class ReportsController extends Controller
*/
protected function getAssetsNotAcceptedYet()
{
$this->authorize('reports.view');
return Asset::unaccepted();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,12 @@
<?php
namespace App\Http\Controllers;
use Input;
use Lang;
use Illuminate\Support\Facades\Input;
use App\Models\Statuslabel;
use App\Models\Asset;
use Redirect;
use DB;
use App\Models\Setting;
use Str;
use View;
use App\Helpers\Helper;
use Auth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* This controller handles all actions related to Status Labels for
* the Snipe-IT Asset Management application.
@@ -28,6 +19,7 @@ class StatuslabelsController extends Controller
* Show a list of all the statuslabels.
*
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
@@ -43,25 +35,25 @@ class StatuslabelsController extends Controller
return view('statuslabels.view')->with('statuslabel', $statuslabel);
}
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist', compact('id')));
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
/**
* Statuslabel create.
*
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
// Show the page
$this->authorize('create', Statuslabel::class);
$item = new Statuslabel;
$use_statuslabel_type = $item->getStatuslabelType();
$statuslabel_types = Helper::statusTypeList();
return view('statuslabels/edit', compact('statuslabel_types', 'item'))->with('use_statuslabel_type', $use_statuslabel_type);
return view('statuslabels/edit')
->with('item', new Statuslabel)
->with('statuslabel_types', Helper::statusTypeList())
->with('use_statuslabel_type', (new Statuslabel)->getStatuslabelType());
}
@@ -70,6 +62,7 @@ class StatuslabelsController extends Controller
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(Request $request)
{
@@ -106,8 +99,9 @@ class StatuslabelsController extends Controller
/**
* Statuslabel update.
*
* @param int $statuslabelId
* @param int $statuslabelId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($statuslabelId = null)
{
@@ -129,8 +123,9 @@ class StatuslabelsController extends Controller
/**
* Statuslabel update form processing page.
*
* @param int $statuslabelId
* @param int $statuslabelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, $statuslabelId = null)
{
@@ -169,8 +164,9 @@ class StatuslabelsController extends Controller
/**
* Delete the given Statuslabel.
*
* @param int $statuslabelId
* @param int $statuslabelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($statuslabelId)
{

View File

@@ -1,21 +1,12 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use Image;
use App\Models\AssetMaintenance;
use Input;
use Lang;
use App\Models\Supplier;
use Redirect;
use App\Models\Setting;
use Str;
use View;
use Auth;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage;
/**
* This controller handles all actions related to Suppliers for
@@ -29,6 +20,7 @@ class SuppliersController extends Controller
* Show a list of all suppliers
*
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
{
@@ -44,6 +36,7 @@ class SuppliersController extends Controller
* Supplier create.
*
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
@@ -55,8 +48,9 @@ class SuppliersController extends Controller
/**
* Supplier create form processing.
*
* @param Request $request
* @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(ImageUploadRequest $request)
{
@@ -79,16 +73,7 @@ class SuppliersController extends Controller
$supplier->url = $supplier->addhttp(request('url'));
$supplier->user_id = Auth::id();
if ($request->file('image')) {
$image = $request->file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/suppliers/'.$file_name);
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$supplier->image = $file_name;
}
$supplier = $request->handleImages($supplier);
if ($supplier->save()) {
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.create.success'));
@@ -99,8 +84,9 @@ class SuppliersController extends Controller
/**
* Supplier update.
*
* @param int $supplierId
* @param int $supplierId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($supplierId = null)
{
@@ -119,8 +105,9 @@ class SuppliersController extends Controller
/**
* Supplier update form processing page.
*
* @param int $supplierId
* @param int $supplierId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update($supplierId = null, ImageUploadRequest $request)
{
@@ -146,37 +133,7 @@ class SuppliersController extends Controller
$supplier->url = $supplier->addhttp(request('url'));
$supplier->notes = request('notes');
$old_image = $supplier->image;
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$supplier->image = null;
}
if ($request->file('image')) {
$image = $request->file('image');
$file_name = $supplier->id.'-'.str_slug($image->getClientOriginalName()) . "." . $image->getClientOriginalExtension();
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save(app('suppliers_upload_path').$file_name);
} else {
$image->move(app('suppliers_upload_path'), $file_name);
}
$supplier->image = $file_name;
}
if ((($request->file('image')) && (isset($old_image)) && ($old_image!='')) || ($request->input('image_delete') == 1)) {
try {
unlink(app('suppliers_upload_path').$old_image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$supplier = $request->handleImages($supplier);
if ($supplier->save()) {
@@ -190,8 +147,9 @@ class SuppliersController extends Controller
/**
* Delete the given supplier.
*
* @param int $supplierId
* @param int $supplierId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($supplierId)
{
@@ -236,11 +194,8 @@ class SuppliersController extends Controller
if (isset($supplier->id)) {
return view('suppliers/view', compact('supplier'));
}
// Prepare the error message
$error = trans('admin/suppliers/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('suppliers.index')->with('error', $error);
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
}
}

View File

@@ -0,0 +1,229 @@
<?php
namespace App\Http\Controllers\Users;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Group;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Password;
class BulkUsersController extends Controller
{
/**
* Returns a view that confirms the user's a bulk delete will be applied to.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]
* @param Request $request
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit(Request $request)
{
$this->authorize('update', User::class);
// Make sure there were users selected
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
$statuslabel_list = Helper::statusLabelList();
// Get the list of affected users
$users = User::whereIn('id', array_keys(request('ids')))
->with('groups', 'assets', 'licenses', 'accessories')->get();
if ($request->input('bulk_actions') == 'edit') {
return view('users/bulk-edit', compact('users'))
->with('groups', Group::pluck('name', 'id'));
} elseif ($request->input('bulk_actions') == 'delete') {
return view('users/confirm-bulk-delete', compact('users', 'statuslabel_list'));
} elseif ($request->input('bulk_actions') == 'bulkpasswordreset') {
if ($users) {
foreach ($users as $user) {
if (($user->activated=='1') && ($user->email!='')) {
$credentials = ['email' => $user->email];
Password::sendResetLink($credentials, function (Message $message) {
$message->subject($this->getEmailSubject());
});
}
}
}
return redirect()->back()->with('success', trans('admin/users/message.password_resets_sent'));
}
}
return redirect()->back()->with('error', 'No users selected');
}
/**
* Save bulk-edited users
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request)
{
$this->authorize('update', User::class);
if((!$request->filled('ids')) || $request->input('ids') <= 0) {
return redirect()->back()->with('error', 'No users selected');
}
$user_raw_array = $request->input('ids');
// Remove the user from any updates.
$user_raw_array = array_diff($user_raw_array, [Auth::id()]);
$manager_conflict = false;
$users = User::whereIn('id', $user_raw_array)->where('id', '!=', Auth::user()->id)->get();
$return_array = [
'success' => trans('admin/users/message.success.update_bulk')
];
$this->conditionallyAddItem('location_id')
->conditionallyAddItem('department_id')
->conditionallyAddItem('company_id')
->conditionallyAddItem('locale')
->conditionallyAddItem('activated')
;
// If the manager_id is one of the users being updated, generate a warning.
if (array_search($request->input('manager_id'), $user_raw_array)) {
$manager_conflict = true;
$return_array = [
'warning' => trans('admin/users/message.bulk_manager_warn')
];
}
if (!$manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
// Save the updated info
User::whereIn('id', $user_raw_array)
->where('id', '!=', Auth::id())->update($this->update_array);
// Only sync groups if groups were selected
if ($request->filled('groups')) {
foreach ($users as $user) {
$user->groups()->sync($request->input('groups'));
}
}
return redirect()->route('users.index')
->with($return_array);
}
/**
* Array to store update data per item
* @var Array
*/
private $update_array = [];
/**
* Adds parameter to update array for an item if it exists in request
* @param String $field field name
* @return BulkUsersController Model for Chaining
*/
protected function conditionallyAddItem($field)
{
if(request()->filled($field)) {
$this->update_array[$field] = request()->input($field);
}
return $this;
}
/**
* Soft-delete bulk users
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy(Request $request)
{
$this->authorize('update', User::class);
if ((!$request->filled('ids')) || (count($request->input('ids')) == 0)) {
return redirect()->back()->with('error', 'No users selected');
}
if ((!$request->filled('status_id')) || ($request->input('status_id')=='')) {
return redirect()->route('users.index')->with('error', 'No status selected');
}
if (config('app.lock_passwords')) {
return redirect()->route('users.index')->with('error', 'Bulk delete is not enabled in this installation');
}
$user_raw_array = request('ids');
if (($key = array_search(Auth::id(), $user_raw_array)) !== false) {
unset($user_raw_array[$key]);
}
$users = User::whereIn('id', $user_raw_array)->get();
$assets = Asset::whereIn('assigned_to', $user_raw_array)->get();
$accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get();
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
$this->logItemCheckinAndDelete($assets, Asset::class);
$this->logItemCheckinAndDelete($accessories, Accessory::class);
$this->logItemCheckinAndDelete($licenses, LicenseSeat::class);
Asset::whereIn('id', $assets->pluck('id'))->update([
'status_id' => e(request('status_id')),
'assigned_to' => null,
'assigned_type' => null,
]);
LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]);
foreach ($users as $user) {
$user->accessories()->sync([]);
$user->delete();
}
return redirect()->route('users.index')->with('success', 'Your selected users have been deleted and their assets have been updated.');
}
/**
* Generate an action log entry for each of a group of items.
* @param $items
* @param $itemType string name of items being passed.
*/
protected function logItemCheckinAndDelete($items, $itemType) {
foreach($items as $item) {
$logAction = new Actionlog();
$logAction->item_id = $item->id;
// We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays.
$logAction->item_type = $itemType;
$logAction->target_id = $item->assigned_to;
$logAction->target_type = User::class;
$logAction->user_id = Auth::id();
$logAction->note = 'Bulk checkin items and delete user';
$logAction->logaction('checkin from');
}
}
}

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