Compare commits

..

2226 Commits

Author SHA1 Message Date
snipe
daf748e531 Bumped hash 2020-07-17 12:32:01 -07:00
snipe
799a93c46a Allow for email/username search on users 2020-07-17 12:11:32 -07:00
snipe
34aa12e229 Merge pull request #8239 from snipe/fixes/api_rtd_to_location_on_create
Set location_id to rtd_location_id on asset creation
2020-07-16 17:44:13 -07:00
snipe
897757bd04 Removed added line for location 2020-07-16 17:43:44 -07:00
snipe
c7125c3937 Set location_id to rtd_location_id on asset creation 2020-07-16 16:34:39 -07:00
snipe
81a6332889 Removed license ID from seats table cookie info
This typically wouldn’t be necessary, since most people would want to view the same *types* of data across licenses
2020-07-14 13:55:38 -07:00
snipe
6e563f6e4b Merge branch 'master' of https://github.com/snipe/snipe-it 2020-07-13 21:16:54 -07:00
snipe
5320f5c67c Disallow non-super users from editing their own permissions 2020-07-13 21:16:45 -07:00
snipe
7f69ae953b Merge pull request #8227 from snipe/fix_select2_ajax_pulldowns
Changes how we do AJAX calls via Select2 for dynamic drop-down menus
2020-07-13 21:16:00 -07:00
Brady Wetherington
17f6fbabfa Switch to 'items' to maintain compatbility with other internal API's 2020-07-13 21:12:03 -07:00
snipe
c79f8c1baf Merge pull request #8207 from EDVLeer/patch-1
Update snipeit.sh
2020-07-13 17:42:16 -07:00
Brady Wetherington
e7a820f7c9 Changes how we do AJAX calls via Select2 for dynamic drop-down menus 2020-07-13 17:14:31 -07:00
snipe
12c92e30b7 Show whether or not the user was imported via LDAP in the view page 2020-07-10 16:21:27 -07:00
snipe
fd10b755b0 Removed the sr-only tag in table headers
It was breaking Bootstrap Tables column selector :(
2020-07-10 11:30:01 -07:00
snipe
dbbb7680d9 A few more fixes for the cli
Do not check out a piece of software if it’s already been checked out to the user
2020-07-09 21:12:50 -07:00
snipe
cf0dd5bbad Small fixes for cli tool 2020-07-09 20:43:13 -07:00
snipe
25e53d8c7f Merge pull request #8216 from snipe/features/checkout_license_to_all_users
Added CLI tool to checkout license to all users
2020-07-09 20:27:01 -07:00
snipe
89d433b41a Removed duplicate seat call 2020-07-09 20:26:02 -07:00
snipe
e2570ada6f CLI tool to checkout a license to ALL users 2020-07-09 20:04:05 -07:00
snipe
45afe725a1 Only try to get the company if there is an auth’d user
(Needed for command line tools, where no Auth::user() is present)
2020-07-09 20:03:47 -07:00
EDVLeer
536401fe0f Update snipeit.sh
Ubuntu 20.04
2020-07-07 08:21:36 +02:00
snipe
ec6ed256fb Bumped minor version 2020-07-06 18:45:43 -07:00
snipe
2aaa7bed2d Merge pull request #8183 from snipe/features/merge_users
Added merge utility
2020-06-25 18:37:41 -07:00
snipe
cc9f1577a4 Removed unused use directives 2020-06-25 17:43:53 -07:00
snipe
ab1fe8be0c Added merge utility 2020-06-25 17:42:39 -07:00
snipe
339bdddc38 Fix for Vue js not loading due to CSP :( 2020-06-25 11:00:33 -07:00
snipe
35b9cf4b70 Fixed missing db prefix on scopeDueOrOverdueForAudit 2020-06-23 02:41:59 -07:00
snipe
7ccb41371e Removed unoptimized images directive
securityheaders.com is claiming it’s onrecognized, even though I got that directive from their site, so… whatever. ¯\_(ツ)_/¯
2020-06-23 01:09:39 -07:00
snipe
2e60a457bf Dumb fix for feature-policy being dumb. 2020-06-23 01:07:00 -07:00
snipe
2390d2160b Merge pull request #8164 from snipe/features/additional_security_headers
Additional security headers
2020-06-23 00:27:47 -07:00
snipe
00b051b8c7 Added a few more comments 2020-06-23 00:26:09 -07:00
snipe
05b3a9ad7e Config variable for HSTS 2020-06-22 23:17:27 -07:00
snipe
4fb880384f Changed comment 2020-06-22 22:37:14 -07:00
snipe
43042ad841 Consolidated ReferrerPolicy into new SecurityHeaders file 2020-06-22 22:35:59 -07:00
snipe
a716382ac4 Removed CSP middleware (it’s added in the general header) 2020-06-22 22:33:37 -07:00
snipe
36c8f7f4f1 Additional security headers 2020-06-22 22:31:01 -07:00
snipe
b42801f6ae Merge pull request #8163 from snipe/fixes/fix-for-css-on-column-selector
Fixed weird padlock display in asset listing with encrypted custom fields
2020-06-22 20:47:35 -07:00
snipe
946129f206 Made quote style consistent 2020-06-22 20:45:20 -07:00
snipe
b941ef1e08 Pulled CSS font awesome styles out of the blade and into overrides.css 2020-06-22 20:41:40 -07:00
snipe
d1aa11ec89 Fix for weird padlock display in asset listing with encrypted custom fields 2020-06-22 20:29:19 -07:00
snipe
de4934f21d Merge pull request #8162 from Godmartinz/godfreymartinez-ghi-font-size-of-qr_text
Fixed #8161 and #8114 - font-size for labels used static values in blade instead of using values from settings
2020-06-22 17:28:38 -07:00
Godfrey M
b10076b015 corrected an error where font-size for labels were static in settings. 2020-06-22 17:04:39 -07:00
snipe
af06e42056 Bumped version 2020-06-17 11:17:25 -07:00
snipe
9a2440dc4b Merge pull request #8141 from snipe/fixes/better_handling_when_license_is_invalid
Better handle the logic to determine if we should display the license checkout blade [ch13792]
2020-06-16 20:20:07 -07:00
snipe
2ac1c1636c Better handle the logic to determine if we should display the license checkout blade 2020-06-16 16:12:57 -07:00
snipe
beae8efb21 Merge pull request #8088 from Godmartinz/Label_Woes
Barcode resizing and text adjustment
2020-05-27 23:01:33 -07:00
Godfrey M
9839e5e566 adjusted for all label text, removed local variable 2020-05-27 12:27:40 -07:00
snipe
d14ab7e3e1 Porting change from #8053 to master
Signed-off-by: snipe <snipe@snipe.net>
2020-05-27 00:22:44 -07:00
Godfrey M
e7f74d94c1 Label_Woes 2020-05-26 17:22:45 -07:00
Godfrey M
e97cf011b6 Label_Woes 2020-05-26 17:15:39 -07:00
Godfrey M
ed23505054 Label_Woes 2020-05-26 17:10:45 -07:00
snipe
001e721530 Merge pull request #8063 from dmeltzer/backport-8092
BACKPORT: Fix Missing Category selection in Asset Model Modal dialog - [ch14635]
2020-05-20 10:21:52 -07:00
Daniel Meltzer
8210da6e82 Fix Missing Category selection in Asset Model Modal dialog.
A select html tag needs a full closing tag. is not valid. This was causing the select2 js to barf and eat additional information.
2020-05-20 10:29:27 -04:00
snipe
f88683766b Roll back previous change
Signed-off-by: snipe <snipe@snipe.net>
2020-05-14 00:55:47 -07:00
snipe
e4385c0f8c Fixes #8051 regression
Signed-off-by: snipe <snipe@snipe.net>
2020-05-14 00:48:30 -07:00
snipe
0550fe0ffa Fix for session fixation vulnerability
Signed-off-by: snipe <snipe@snipe.net>
2020-05-12 10:31:54 -07:00
snipe
7fb3a9b82c Merge pull request #8043 from snipe/features/backup-optional-in-import-and-ldap
Added option to disable backup in import
2020-05-11 22:41:36 -07:00
snipe
ecb1e87fe6 Updated assets
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 20:45:15 -07:00
snipe
f43df5f041 Fixed form label
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 20:44:46 -07:00
snipe
95cc48e422 Added option to disable backup in import
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 20:41:10 -07:00
snipe
9a2ed804ca Fixed mismatched HTML header tags
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 20:28:42 -07:00
snipe
d20fad28e5 Use more modern request helper
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 20:28:24 -07:00
snipe
ae813ddf75 Add @alek13 as a contributor 2020-05-11 18:11:16 -07:00
snipe
bb42109c0c Added a clarifying comment
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 18:10:45 -07:00
snipe
f46ecf8ec0 Updated composer lock
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 18:07:20 -07:00
snipe
b9e821c0e6 Small fix for Group Functional Tests
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 18:07:14 -07:00
snipe
9ee28c7513 Switched to use info instead of danger on undeployable statuses
Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 18:07:02 -07:00
snipe
1a8ba06702 Merge branch 'master' of https://github.com/snipe/snipe-it 2020-05-11 17:53:32 -07:00
snipe
0fd232e70d Fixed group functional test
(We had changed the minimum to 2 instead of 3)

Signed-off-by: snipe <snipe@snipe.net>
2020-05-11 17:53:24 -07:00
snipe
ee4d69b1c5 Merge pull request #8041 from alek13/patch-1
use supported package for slack
2020-05-11 17:52:45 -07:00
Alexander Chibrikin
d1ad111949 use supported package for slack
see https://github.com/maknz/slack/issues/94
2020-05-11 20:31:13 +03:00
snipe
31c5350941 Fixed incorrect route for groups edit
Signed-off-by: snipe <snipe@snipe.net>
2020-05-01 01:05:48 -07:00
snipe
7eb70e17e0 Merge pull request #7993 from snipe/fixes/7989_column_selector
Fixed #7989 - Converted table heading icons in People to CSS glyphs
2020-04-24 04:50:37 -07:00
snipe
3dfcb46991 Minor formatting changes
Signed-off-by: snipe <snipe@snipe.net>
2020-04-24 04:41:08 -07:00
snipe
96eb96f964 Removed stray val (typo)
Signed-off-by: snipe <snipe@snipe.net>
2020-04-24 04:27:00 -07:00
snipe
a2f08bd3ba Added comments
Signed-off-by: snipe <snipe@snipe.net>
2020-04-24 04:08:54 -07:00
snipe
e009fbe59f Converted table heading icons in People to CSS glyphs
Signed-off-by: snipe <snipe@snipe.net>
2020-04-24 04:04:53 -07:00
snipe
5bb4f271aa Fixed #7987 - allow toggle of required/optional in custom fields/fieldsets
Signed-off-by: snipe <snipe@snipe.net>
2020-04-24 00:47:19 -07:00
snipe
154db9a416 This literally never fucking worked. Ever. Shoot me. 2020-04-09 22:20:05 -07:00
snipe
cf9d0201e0 Catch weird edge case where target wouldn’t have an ID (?!) 2020-04-09 20:23:02 -07:00
snipe
7ebd21bc04 Added sr-only to the unstyles image upload button 2020-04-09 19:29:16 -07:00
snipe
5707df0239 Check that location isset in checkout 2020-04-09 18:32:34 -07:00
snipe
197a84be94 Commented out rtd_location_id override - why did we do that? 2020-04-09 14:17:39 -07:00
snipe
b4fa4c77d7 Check for rtd_location_id before trying to assign 2020-04-09 14:14:30 -07:00
snipe
cfec142c3b Better handle models without a fieldset in the asset request [RB 9935] 2020-04-09 11:18:54 -07:00
snipe
48dfc699d7 Bumped version 2020-04-09 10:49:50 -07:00
snipe
ec723a3da1 Changed LDAP messaging 2020-04-09 10:37:51 -07:00
snipe
f8a72db696 Changed LDAP 600 to 500, clearer error messages on LDAP test 2020-04-09 09:55:44 -07:00
snipe
83ee64f155 Added missing CSS class to text-only logo 2020-04-08 17:25:14 -07:00
snipe
b7d12ff944 Changed placeholder text back to lighter grey - it was confusing when darker 2020-04-08 17:24:59 -07:00
snipe
0858fec7f1 Fixed CSS issue where text-only logos at the top would be the wrong color 2020-04-08 17:24:38 -07:00
snipe
206bd675f2 Pulled slack validation out of setting model validation so it doesn’t fail mysteriously on other pages 2020-04-08 15:07:02 -07:00
snipe
92695782ff Add @anthonypburns as a contributor 2020-04-08 11:48:18 -07:00
snipe
c447e4d29b Add @joshi-redbridge as a contributor 2020-04-08 11:48:03 -07:00
snipe
811f89b1de Add @JoKneeMo as a contributor 2020-04-08 11:47:37 -07:00
snipe
be3e572440 Bumped version 2020-04-08 11:24:30 -07:00
snipe
824ebc19c0 Updated assets 2020-04-08 11:24:17 -07:00
snipe
a0f7fdc57a Merge branch 'fixes/accessibility_fixes'
# Conflicts:
#	public/css/build/all.css
#	public/css/dist/all.css
#	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
2020-04-08 11:19:42 -07:00
snipe
450c1b9d56 Updated faker library to be compatible with PHP 7.4 2020-04-08 11:13:15 -07:00
snipe
79232fc434 Fixed #7947 - Added rtd_location_id to API search 2020-04-08 11:00:04 -07:00
snipe
0b3f511534 Fixed compact() errors 2020-04-07 17:26:56 -07:00
snipe
7f18983a49 Update local instance of composer.phar on upgrade. (#7940)
Co-authored-by: Johnny Moore <jmoore@eventide.com>
2020-04-07 13:21:55 -07:00
snipe
b7d9790acb Fixed color style for btn-sm.btn-warning and btn-sm.btn-danger 2020-04-06 21:59:30 -07:00
snipe
1a5785a8d3 Fixed dashboard header size 2020-04-06 21:30:28 -07:00
snipe
320d660e83 Updated chartjs 2020-04-06 21:17:26 -07:00
snipe
fb903b2fda Added comments, better indenting 2020-04-06 21:05:42 -07:00
snipe
c18646d096 Yellow highlight on selected rows 2020-04-06 21:00:11 -07:00
snipe
7bf398aca4 More settings filter fixes 2020-04-06 20:47:05 -07:00
snipe
f6bb655383 Prevent form submission 2020-04-06 20:17:44 -07:00
snipe
19f71face9 Added filtering to settings page 2020-04-06 20:11:13 -07:00
snipe
d82b94e281 Pull blue stylesheet if no skin was selected 2020-04-06 19:04:49 -07:00
snipe
893944403e Check for location_id being set before trying to set it on checkout via API 2020-04-06 15:54:40 -07:00
snipe
0d3c18d1df Fixed importer vue code for niceer layout 2020-04-06 15:09:37 -07:00
snipe
d7873f257d Fixed CSP for importer 2020-04-06 14:18:45 -07:00
Johnny Moore
7e3f718797 Update local instance of composer.phar on upgrade. 2020-04-03 13:13:30 -04:00
snipe
be79a1f3d6 Bumped hash 2020-04-02 19:27:41 -07:00
snipe
a8032ac388 Switched to btn-primary for submit 2020-04-02 18:38:54 -07:00
snipe
21d8225696 Use button primary for higher contrast 2020-04-02 18:33:26 -07:00
snipe
766c2b22cb Removed stray tag 2020-04-02 18:33:10 -07:00
snipe
db79f92423 Updatedb select2 to latest for aria fixes 2020-04-02 18:22:25 -07:00
snipe
bdddab5b8b Added role=“option” to option items, additional icon font fixes 2020-04-02 18:17:21 -07:00
snipe
031adc3be4 Small display fixes to importer table 2020-04-01 19:51:36 -07:00
snipe
e7c1418314 Fixed possible typo in CSP 2020-04-01 19:47:42 -07:00
snipe
c906026acd Merge branch 'master' into fixes/accessibility_fixes 2020-04-01 14:04:02 -07:00
snipe
56c2740b68 Radio button tweaks 2020-04-01 06:56:27 -07:00
snipe
4688d62b9f Small UI fixes 2020-04-01 06:50:37 -07:00
snipe
99686bd73a Added labels for bulk actions 2020-04-01 06:48:02 -07:00
snipe
120e224961 Add the ability to remove all group memberships in bulk 2020-04-01 06:35:13 -07:00
snipe
e27d69a31d Remove viewport restrictions 2020-04-01 06:18:37 -07:00
Ivan Nieto
c492ba7245 Adding changes to 'upload-table' for being painted in CSS (#7934) 2020-04-01 05:24:05 -07:00
snipe
53658e365f Removed duplicate HTML tag 2020-04-01 04:36:28 -07:00
snipe
4cfa0e36b1 Added labels to password reset 2020-04-01 04:30:54 -07:00
snipe
9d9b5d3885 Added form labels to auth screens 2020-04-01 04:29:54 -07:00
snipe
36f9905be0 Removed test code 2020-04-01 04:29:35 -07:00
snipe
a815e0ab8c Fixed stupid curly quotes 2020-04-01 03:55:44 -07:00
snipe
6bfec08a8c Updated vue files with aria tags and labels 2020-04-01 03:53:05 -07:00
snipe
2d2cd68061 Switched to h2 from h4 2020-04-01 03:51:08 -07:00
snipe
fd642e95eb Hide success icons from screen readers 2020-04-01 03:25:07 -07:00
snipe
9ab3370be5 Hide icons in error messages 2020-04-01 03:21:15 -07:00
snipe
4dcc1ffdbc More form labels 2020-04-01 02:22:24 -07:00
snipe
7d466f3584 Update user uploads for more data to work with recport 2020-04-01 02:22:16 -07:00
snipe
7718abaa72 Added aria-hidden 2020-04-01 01:26:44 -07:00
snipe
59c5a1ea87 Added aria label to form helper 2020-04-01 01:26:32 -07:00
snipe
0cf70c9e16 Fixed select2 placeholder 2020-04-01 01:26:19 -07:00
snipe
6174f9b93f Check that there is actually a filed ID submitted 2020-04-01 01:25:31 -07:00
snipe
c3d2e8ff26 Added aria-hidden to inline form error text 2020-04-01 00:18:20 -07:00
snipe
192f703885 Trying placeholder CSS styling for the high contrast skin
Doesn’t seem to work :(
2020-04-01 00:15:58 -07:00
snipe
be93b23488 Added aria-label to form fields 2020-04-01 00:15:33 -07:00
snipe
b079d0d6d5 Use btn-primary for submit button 2020-03-31 23:38:10 -07:00
snipe
c6c75cc11f Fixed missing </h3 tags 2020-03-31 23:36:56 -07:00
snipe
b188285bc9 Fixed empty table headings 2020-03-31 23:02:24 -07:00
snipe
6d659a84b8 Removed extra div tag 2020-03-31 22:53:58 -07:00
snipe
3e3828229d Removed _tab from tab names 2020-03-31 22:52:21 -07:00
snipe
bf6a0f8d2f Added aria-hidden 2020-03-31 22:50:38 -07:00
snipe
3873c4b253 Added aria form labels to upload modal 2020-03-31 22:50:29 -07:00
snipe
6cc23f69f9 Added aria form labels 2020-03-31 22:50:14 -07:00
snipe
a467a6999e Use upload modal 2020-03-31 22:50:07 -07:00
snipe
e0eb10ca1e Added aria-hidden tags 2020-03-31 22:49:49 -07:00
snipe
99c4c73c09 Fixed aria labels on dropdowns 2020-03-31 21:53:26 -07:00
snipe
bde45cbb34 Fixed h3 to h2 for semantic header levels 2020-03-31 21:30:24 -07:00
snipe
c408c27bf4 Updated dark skins 2020-03-31 21:10:55 -07:00
Joshi
b14f37d966 Purposal for fix on issue 6251 - max upload filesize (#7930)
* Added a check and sed to startup.sh for managing max upload size inside the docker container. Issue encountered in 6251

* Changed startup.sh to bash instead of sh to have better variable control. Changed single ticks to double ticks for the sed so that the variable PHP_UPLOAD_LIMIT is expanded
2020-03-31 21:02:10 -07:00
snipe
bfa9c0c528 Updated dark skins with updated styles 2020-03-31 20:58:54 -07:00
snipe
9cc9cddd68 Fixed visited button text color 2020-03-31 20:39:41 -07:00
snipe
fe2261c88d A few more aria-hidden fixes 2020-03-31 19:34:01 -07:00
snipe
6aeb3c0a47 Use divs instead of layout table on view 2020-03-31 19:27:21 -07:00
snipe
dfaa1c9578 Use the same “no results” treatment for each tab 2020-03-31 18:45:43 -07:00
snipe
0ef1dfe061 Switched from layout tables to decorated divs for asset detail page 2020-03-31 18:39:58 -07:00
snipe
ba8bcd6413 Slight tweaks to div table styles 2020-03-31 18:39:41 -07:00
snipe
7854003ec2 Added sr-only text to “made with love” footer 2020-03-31 17:29:20 -07:00
snipe
c71dd9b68a Added sr-only text 2020-03-31 17:29:06 -07:00
snipe
dfeabbc85d Added table-styles div CSS
(Still needs to be applied for dark mode skins)
2020-03-31 17:28:40 -07:00
snipe
b8b9ac8a1b Fixed mismatched <b>/<strong> tags 2020-03-28 18:00:46 -07:00
snipe
80ac2607cd Added alt tags to assigned assets view 2020-03-28 18:00:19 -07:00
snipe
3552fb1fd8 Added alt tags to profile image 2020-03-28 18:00:05 -07:00
snipe
54a96b8453 Fixed <b> to <strong> 2020-03-28 17:30:17 -07:00
snipe
03be4e74df Higher contrast pagination 2020-03-28 04:00:46 -07:00
snipe
e9ddd1af81 Added th content on activity report 2020-03-28 04:00:26 -07:00
snipe
f305885e8e Regenerated assets 2020-03-28 03:43:35 -07:00
snipe
f0b9cd7820 Changed header for h2 2020-03-28 03:43:24 -07:00
snipe
59accca89d Added form labelss 2020-03-28 03:43:08 -07:00
snipe
e72ebfb94b Added placeholder text for modal header 2020-03-28 03:42:45 -07:00
snipe
0b7316d548 Changed side heading to h2 2020-03-28 03:42:27 -07:00
snipe
d0cf76989a Fixed table headers for custom fields 2020-03-28 02:52:14 -07:00
snipe
90a2bf7c9c Regenerated high contrast menu 2020-03-28 02:42:34 -07:00
snipe
95945412b1 Fixed label 2020-03-28 02:42:04 -07:00
snipe
c299efca0c Darkened the home screen boxes for high contrast 2020-03-28 02:41:43 -07:00
snipe
5e4918579a Added table headers 2020-03-28 02:41:27 -07:00
snipe
db75f0e894 Regenerated assets 2020-03-28 01:38:01 -07:00
snipe
5a6c13e364 Added aria-hidden and sr-only tags to minus/collapse symbol 2020-03-28 01:37:49 -07:00
snipe
4b22f07dd7 Fixed less attribute names 2020-03-28 01:37:27 -07:00
snipe
57cb5146fc Just use navy for links 2020-03-28 01:37:13 -07:00
snipe
07708f530e Use label for field in column dropdown 2020-03-28 01:36:55 -07:00
snipe
5c68353e62 Use non-min bs tables for now 2020-03-28 01:36:35 -07:00
snipe
53728e5c71 Removed whitespace 2020-03-28 01:36:22 -07:00
snipe
b965d170ab Added accessibility features to bootstrap tables 2020-03-28 00:51:26 -07:00
snipe
34a1bb7152 Fixed contrast skin 2020-03-28 00:24:37 -07:00
snipe
8787f228d9 Fixed asset build 2020-03-28 00:16:28 -07:00
snipe
03cde9a72c Added less processing for skin files 2020-03-28 00:08:21 -07:00
snipe
623655b6f6 New dark background skins 2020-03-28 00:07:34 -07:00
snipe
da6830225a Remove light colored less files - it looks crappy 2020-03-28 00:06:51 -07:00
snipe
a729410fe8 Regenerated JS 2020-03-27 22:03:30 -07:00
snipe
bba4036e53 Updated skin theme 2020-03-27 22:03:09 -07:00
snipe
39c71c6027 Regenerate new skin css files 2020-03-27 22:02:57 -07:00
snipe
03a9219a7c Fixed duplicate color: attribute 2020-03-27 22:01:43 -07:00
snipe
a8f6bbd86a Added alt text to image formatter in bootstrap tables 2020-03-27 22:01:24 -07:00
snipe
9a2ee2638b Updated color-specific skins 2020-03-27 21:46:00 -07:00
snipe
2a813244a2 Wrapped text in h5s, added aria hidden to icons 2020-03-27 21:45:27 -07:00
snipe
b50894fca1 Use a variable in the settings to determine what css classes to use 2020-03-27 21:44:50 -07:00
snipe
41fa2d1aa1 Added aria handlers and sr-only text 2020-03-27 21:44:32 -07:00
snipe
54d39c04ad Added additional themes to dropdown 2020-03-27 21:44:18 -07:00
snipe
3c1365b2c8 Added color variables 2020-03-27 21:44:00 -07:00
snipe
5858c90e71 Minor formatting change (spacing) 2020-03-27 15:50:01 -07:00
snipe
f0ef06ebe1 Added more alt tags 2020-03-27 15:35:29 -07:00
snipe
700f7de748 Added alt text to logo 2020-03-27 14:10:56 -07:00
snipe
af2ea7ac03 Added aria-hidden="true" to create dropdown in topnav
Since it’s technically duplicated content
2020-03-27 14:08:01 -07:00
snipe
690d8255c9 Removed the title attribute from the select2 output
It contains duplicate info since its auto-generated by select2
2020-03-27 14:07:30 -07:00
snipe
aded2193a2 Added aria-hidden="true" to top navigation elements 2020-03-27 13:29:00 -07:00
snipe
6d99b2a68c Added “skip to content” link 2020-03-27 13:28:48 -07:00
snipe
55a619778f Added language header 2020-03-25 16:32:33 -07:00
snipe
6066c249d5 Moved gate to the top of the method 2020-03-06 16:01:13 -08:00
Ivan Nieto
025ea93f05 Fix for when a user with the correct permissions couldn't update Manufacturers. (#7882)
* Changed the ability name from 'edit' to 'update'. Changed the order of execution: first checks if the manufacturer exists, then checks permissions

* Handles the update method, that also has the ability parameter as edit instead of update"
q

* Revert "Handles the update method, that also has the ability parameter as edit instead of update""

This reverts commit d7dc0e451e.

* Handles the update method, that also has the ability parameter as 'edit' instead of 'update'
2020-03-06 15:59:51 -08:00
snipe
54fd8f81ff Added permissions on user api (#7883)
* Add permissions to user edit API

* Add user permissions on user create/update API endpoint
2020-03-06 15:28:46 -08:00
snipe
ca43554327 Fixes search by serial or tag even if they have slashes in them (#7879)
* Fixes search by serial or tag even if they have slashes in them

* Added support for url param byTag and bySerial

* Fixed typo comments

* Sojme additional comments to clarify use-cases

* Updated comments for clarity
2020-03-06 14:55:20 -08:00
snipe
61bdb88ba5 Add @ColinMcNeil as a contributor 2020-03-04 22:38:09 -08:00
snipe
36696ab56e Add @bigtreeEdo as a contributor 2020-03-04 22:37:57 -08:00
snipe
f0f9b93652 Add @Godmartinz as a contributor 2020-03-04 22:37:45 -08:00
snipe
a2fae76eaf Bumped version 2020-03-04 22:37:17 -08:00
snipe
8b2f8ef3cb Spelling is hard :( 2020-03-04 22:19:59 -08:00
snipe
5307e57bd9 Fix for CVE-2019-10772
Vuln in SVG sanitizer library
2020-03-04 22:15:31 -08:00
snipe
15518852aa Added validation to reject email addresses over 250 characters 2020-03-04 22:08:07 -08:00
snipe
60fc1d3f6d Added/matched forgotten password strings in lang files 2020-03-04 22:07:35 -08:00
snipe
d1a8d76d85 Set maxlength in password reset form to 250 2020-03-04 22:06:43 -08:00
snipe
803f5ad0ab Fixed #7870: fixed SSL connectivity for PaaS DBs (#7874) 2020-03-04 19:39:23 -08:00
Godfrey Martinez
0e0fe967e4 BadMethodCallException Method update does [ch10544] (#7804) 2020-02-10 19:27:23 -08:00
snipe
192917cc84 Slightly better fix for requestable import bug 2020-02-10 17:34:32 -08:00
snipe
81880645ed Possible requestable fix 2020-02-10 11:40:39 -08:00
snipe
9eb4b0dda7 Disallow 0 as a number for labels per page 2020-02-04 19:14:58 -08:00
snipe
2f0ed129f0 Use “invalid barcode” image and suppress errors when barcode format is wrong 2020-02-04 18:15:01 -08:00
snipe
3361b859c0 Changes offset to use the actual item count as override instead of 0 (#7788) 2020-02-04 12:32:24 -08:00
bigtreeEdo
e27a9b137b added 'requestable' to fillable attributes. (#7787) 2020-02-03 19:37:03 -08:00
snipe
89e2a3ae3c Fixed #7752 - reformat /api/v1/users/me to use transformer 2020-01-30 13:12:43 -08:00
snipe
5f85d8132b Fix for weird JSON parsing in actionlogs (#7753)
* Fix for weird JSON parsing in actionlogs

* Removed debugging code

* Check for the meta array

(If no fields, no array)
2020-01-24 17:31:43 -08:00
snipe
ca1285ec08 Updated favicon 2020-01-23 19:49:46 -08:00
Ivan Nieto
75bf8f3d58 Remove not existent variable 'id' in the redirect causing [ch10602] (#7732) 2020-01-17 16:12:24 -08:00
snipe
324da7c0c8 Include correct license, asset, etc count on user show API call 2019-12-19 18:09:53 -08:00
snipe
779fc6d195 Added license endpoint for users 2019-12-19 18:00:36 -08:00
Colin McNeil
db59106c3e Move ldap import ini settings to config (#7679) 2019-12-19 11:51:55 -08:00
snipe
88fb1370f0 Added slightly friendlier error handling for assets without models
This scenario should never happen, barring someone manually editing their data, but better to handle that scenario in a more user-friendly way.
2019-12-06 18:17:03 -08:00
snipe
943cf40247 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-12-06 13:14:31 -08:00
snipe
ff57f10e9f Fix for searching on child location names (#7646)
* Fix for child locations

* Reverts temp changes to indenter
2019-12-06 13:14:10 -08:00
snipe
91bb76fd8a Bumped version 2019-12-06 13:05:20 -08:00
snipe
893454dca7 Updated translations 2019-12-06 12:03:04 -08:00
snipe
de0b5a6149 Fixes #6440 - quote marks in the right place 2019-12-06 11:04:16 -08:00
Dustin B
8fd4e35244 Closes #6440 Print All Assigned - New Tab (#7135)
Should add the functionality to, by default open in a new tab and not reference back to the source page. Reduces overhead and should resolve #6440. 

Untested, need confirmation.
2019-12-06 11:00:01 -08:00
snipe
e71e57f16a Fixed XSS vulnerability in SVG image uploads [ch10476] (#7639)
* Added enshrined/svg-sanitize

* Added modular image resizing/SVG cleaning method

(This already exists in v5, so I mostly ported it forward and added the SVG sanitizer.)

* Use improved handleImages method to upload/resize/clean images

* Removed $old_image

This is handled in the ImageUpload request now
2019-12-05 22:23:05 -08:00
snipe
3f5840d390 Bumped vendor files 2019-12-05 19:53:01 -08:00
dependabot[bot]
d3f4205f09 Bump symfony/http-foundation from 3.4.30 to 3.4.36 (#7638)
Bumps [symfony/http-foundation](https://github.com/symfony/http-foundation) from 3.4.30 to 3.4.36.
- [Release notes](https://github.com/symfony/http-foundation/releases)
- [Changelog](https://github.com/symfony/http-foundation/blob/master/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-foundation/compare/v3.4.30...v3.4.36)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-05 19:37:00 -08:00
Godfrey Martinez
5b946087c4 added a proper response for password errors (#7636) 2019-12-05 17:49:56 -08:00
snipe
ff8d98c97c Update child assets to reflect asset parent location (#7458) 2019-12-04 16:19:25 -08:00
snipe
2fbbe430b5 Removed escaping on custom fields in presenter (#7631) 2019-12-03 17:42:13 -08:00
Godfrey Martinez
f0af750b0a Fixed comment (#7617)
* Set theme jekyll-theme-hacker

* fixed commenty about scopebyDeprecationID being identified as a method to location ID

* fixed commenty about scopebyDeprecationID being identified as a method to location ID
2019-11-22 16:13:42 -08:00
snipe
88cf456386 Adding Dept to license seats (#7609)
* Adding Dept to license seats

* Added query scope to order by department

* Make license seat department sortable

* Disable license seat internal search - this never actually worked
2019-11-21 22:03:56 -08:00
snipe
d8049209ca Fixed bug where deleted consumable would throw an error on print page 2019-11-21 21:43:54 -08:00
snipe
dd40ddf5a5 Fixed an error on audit due list when no audit_warning_days had been set [ch9764] 2019-11-21 21:34:41 -08:00
snipe
a73fd24695 Fix maintenances permissions check to allow users who can edit assets to edit maintenances 2019-11-08 17:02:17 -08:00
snipe
70c8ad9797 Bumped minor version 2019-10-28 13:55:21 -07:00
snipe
0290257734 Limit license seats to 999 to prevent latency 2019-10-28 13:48:18 -07:00
snipe
4fe689dc5d Merge branch 'master' of https://github.com/snipe/snipe-it 2019-10-21 15:45:17 -07:00
snipe
0769f585ea Disallow locations from being their own parents 2019-10-21 15:45:05 -07:00
snipe
04562e6d4a Added 4260352 to ldapsync enabled account constraint 2019-10-18 17:48:50 -07:00
snipe
22d2ad9248 Fixes nested location selectlist (#7483)
* Rename child locations method

* Use Ajax dropdown for locations selectlist for edit/create

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

* Updated locations controller to use the new iterator

* Increase pagination on locations controller to 500

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

* Fixed the else to make codacy happy

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

* Improve the design and performance of the nested location selectlist

* Fixed parse errors

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

* Added rekey console command

* Removed unused code

* Comment clarifiction

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

* Changed order so CORS will still work if throttle hit

* Added APP_CORS_ALLOWED_ORIGINS env option

* Fixed typo

* Clarified header comments

* More clarification

* DIsable CORS allowed origins by default to replicate existing behavior

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

* Removed debug info

* More fixes for #7252

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

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

* Removed experimental method

* Check for digits_between:0,240 for warranty

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

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

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

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

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

Handle this via model
2019-06-12 18:39:55 -07:00
snipe
2bfa05fd2d Back in time fix FOR #7145 for new installs on MySQL 8+ 2019-06-12 16:24:55 -07:00
snipe
30904dd019 Reduce minimum group name length to 2 (from 3)
eg: IT
2019-06-12 15:56:19 -07:00
snipe
1d0d25db37 Fixed #7145 - rename groups table to permissions_group for mysql 8 reserved word compatibility 2019-06-12 15:51:47 -07:00
snipe
cbff66c9db Improved error checking in locations importer 2019-06-10 18:50:41 -07:00
snipe
27231d49ea Small fixes for phpleague CSB reader v9 2019-06-03 22:05:16 -07:00
snipe
765417c0be Bumped point version 2019-05-31 14:17:26 -07:00
snipe
49a255c8fb Changed has to filled to fix bulk asset editing 2019-05-31 14:11:43 -07:00
snipe
925d3a23c6 Bumped hash 2019-05-31 13:11:07 -07:00
snipe
6966c132d0 Update language strings 2019-05-31 12:17:09 -07:00
snipe
c29ab90029 Added Filipino, corrected order for Spanish variations 2019-05-31 11:58:14 -07:00
snipe
d2bbc09892 Increased throttle to 120 requests per minute 2019-05-31 11:57:57 -07:00
snipe
74a2c29bc2 Fixed #7100 - Check if $user isset on checkin 2019-05-30 19:06:30 -07:00
snipe
2c64739e8f Removed old comments 2019-05-30 19:02:20 -07:00
snipe
37f950ab42 Fixed #7099 - set email to null by default for backup notifications 2019-05-29 19:09:37 -07:00
snipe
b07a254e60 Bumped version 2019-05-29 15:51:54 -07:00
snipe
087cdd859e Fixed #7092 - handle weird port forwarding/port numbers for baseUrl 2019-05-29 14:57:58 -07:00
snipe
829d44bd27 Fixed #7098 - updated backup config for deleteFile() method 2019-05-29 14:47:55 -07:00
snipe
db76090e10 Missed one 2019-05-28 19:12:53 -07:00
snipe
03cf3b5431 Bumped release again :( 2019-05-28 19:12:42 -07:00
snipe
40f101d471 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-05-28 19:08:39 -07:00
snipe
7aa6ef5f6c Bumped version 2019-05-28 19:08:35 -07:00
Kasey
186b94751d Dockerfile update to bring us up to php v7.1 for Laravel 5.5 (#7084)
* bump up to php7.1

& change deprecated MAINTAINER to a LABEL so it is visible with `docker inspect`

* AND modapache ><

* 2 updates required to get software-properties+ppa
2019-05-28 16:58:32 -07:00
snipe
6f2717a876 Updated branch in version file 2019-05-28 13:22:59 -07:00
snipe
a014af4c47 Fixed #7083 - Removed user_exists constraint on department save
If the user has been deleted, this prevented the department from being successfully saved on edit
2019-05-28 13:18:31 -07:00
snipe
2dd31544fe Increased image size to 800px, added lightboxes 2019-05-24 19:11:08 -07:00
snipe
237acdcff0 Show accessory image on view page 2019-05-24 18:22:57 -07:00
snipe
5db7a7c196 More Atlassian clarifications 2019-05-24 18:10:19 -07:00
snipe
68acdff11b Added link to Atlassian plugin 2019-05-24 18:08:40 -07:00
snipe
58e72e5ee6 Derp - typo 2019-05-24 18:06:15 -07:00
snipe
6b43cd10ba Updated README 2019-05-24 18:05:28 -07:00
snipe
b2c9a38db8 Fixed casing in sync command 2019-05-24 16:12:43 -07:00
snipe
6dcdb5abae Only display the file if the log record can be found 2019-05-24 16:06:52 -07:00
snipe
56576d9e45 Fixed more camel-casing -> snake-casing 2019-05-24 16:01:12 -07:00
snipe
d5c3ee5ed0 Only try to get fieldset if model is valid 2019-05-24 15:44:54 -07:00
snipe
18db0a50f1 Only gtry to delete the file if a record is found in the log 2019-05-24 15:44:40 -07:00
snipe
d596ced0a0 Fixed free_seats_count variable name
(I forgot that Laravel switched camel case to snake case for their old 5.4 withCount variables)
2019-05-24 15:44:18 -07:00
snipe
78fb2b2239 Only build the log upload destination path if there is a matching record
Fixes [ch1232]
2019-05-24 15:28:53 -07:00
snipe
1472e9d5b5 Check for valid model before attempting to access fieldsets
Fixes [ch1249]
2019-05-24 15:03:15 -07:00
snipe
fcbc7e4540 Fixed Undefined variable user in $backto if checked out to a non-user
Fixes [ch9194]
2019-05-24 14:51:27 -07:00
snipe
93bf541ce7 Fixed missed consumables_count withCount() statement 2019-05-24 14:21:53 -07:00
snipe
0e48d7b080 Removed alert-danger from tests 2019-05-24 14:21:37 -07:00
snipe
1e6c85da41 Removed custom fields from AssignedSearch to prevent confusing data in selectlist
Fixes [ch9193]
2019-05-24 13:50:11 -07:00
snipe
c5a23e8f5e Fixed bug where sorting by company name in Users API did not work
Fixes [ch9200]
2019-05-24 13:37:20 -07:00
snipe
b6d2392303 Small fix for reordering fields
Fixes Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'order' cannot be null (SQL: insert into `custom_field_custom_fieldset` (`custom_field_id`, `custom_fieldset_id`, `order`, `required`) values (12, 7, , 0)) [ch1151]

This needs revisiting for a more solid fix, especially for data that was already entered bad.
2019-05-24 12:05:52 -07:00
snipe
9995f1a743 Fixed field mapping 2019-05-24 11:45:07 -07:00
snipe
d6f251e992 Updated importer to work with newer CSV Reader::getRecords() method 2019-05-24 11:44:57 -07:00
snipe
4be95eac4b Removed extra escaping on checkin 2019-05-24 11:44:39 -07:00
snipe
8914d14681 Tidied up license check 2019-05-24 05:26:52 -07:00
snipe
d4725b61be Check that a model exists before trying to fiddle with fieldsets 2019-05-24 04:48:04 -07:00
snipe
aa0b627fe7 Fixed missing asset validation 2019-05-24 04:47:35 -07:00
snipe
5be5e3271d Trying to fix ajax asset validation
This I think gets us closer, but still not handling the validation on the asset properly.

When I do a print_r of the validation in the other items, its looking for an error bag that looks something like this:

```
Illuminate\Support\MessageBag Object
(
    [messages:protected] => Array
        (
            [name] => Array
                (
                    [0] => The name field is required.
                )

            [seats] => Array
                (
                    [0] => The seats field is required.
                )

            [category_id] => Array
                (
                    [0] => The category id field is required.
                )

        )

    [format:protected] => :message
)
```

Currently the Assets ajax returns:

```
[2019-05-24 06:52:06] develop.ERROR: array (
  'messages' =>
  array (
    'model_id' =>
    array (
      0 => 'The model id field is required.',
    ),
    'status_id' =>
    array (
      0 => 'The status id field is required.',
    ),
    'asset_tag' =>
    array (
      0 => 'The asset tag field is required.',
    ),
  ),
)
```

So not sure why it’s not working.
2019-05-24 03:55:31 -07:00
snipe
dd5d5cc07c Handle JSON validation errors like 5.4 2019-05-24 01:12:38 -07:00
snipe
84c3709161 Handle JSON validation errors like 5.4 2019-05-24 01:12:21 -07:00
snipe
96e2d74ae3 Handle JSON validation errors like 5.4 2019-05-24 00:46:30 -07:00
snipe
bf93e8cc32 Use getReader instead of fetchAssoc for CSV parser
https://csv.thephpleague.com/9.0/upgrading/
2019-05-23 19:09:58 -07:00
snipe
d1a8955ef9 Bumped packages 2019-05-23 19:09:14 -07:00
snipe
149ac4bdf8 Removed cosole log 2019-05-23 17:52:53 -07:00
snipe
2d036c64e9 Change ->has() to ->filled() 2019-05-23 17:39:50 -07:00
snipe
8db2470ac4 Switch has() to filled() 2019-05-23 17:17:46 -07:00
snipe
79156ff8f4 Bumped version 2019-05-23 17:10:47 -07:00
snipe
8e86d780bf Fix for included files in backup 2019-05-23 17:08:51 -07:00
snipe
f6ef139111 Fixed baseUrl is undefined error
I literally cannot figure out how this ever worked before.
2019-05-23 16:56:22 -07:00
snipe
12ec2d1f7a Fixed custom field edit screen 2019-05-22 01:07:14 -07:00
snipe
0dfc28b0e8 Bumped laravel version in readme 2019-05-22 01:04:42 -07:00
snipe
b3132a4a8f Updated travis with new php versions 2019-05-22 01:03:11 -07:00
snipe
6cd25fbdeb Updated backup path in backup admin 2019-05-22 00:56:14 -07:00
snipe
4be8ba9f17 Updated withCount to use manual naming 2019-05-22 00:52:51 -07:00
snipe
df8008f1ed Renamed fire() to handle() 2019-05-22 00:52:32 -07:00
snipe
77547c528b Added the command loader to console kernel 2019-05-22 00:52:14 -07:00
snipe
bfb910f375 Set the serialization 2019-05-22 00:51:43 -07:00
snipe
57e80ee317 Removed old laravel backups config
This config file was renamed in a newer version of spatie laravel-backup
2019-05-22 00:51:33 -07:00
snipe
de1189295a Added spatie language files 2019-05-22 00:51:00 -07:00
snipe
20d0dce73e Use laravel v5.5 withCount manual aliases 2019-05-22 00:50:48 -07:00
snipe
144a32b1ca Removed debugbar service provider (autodiscovery) 2019-05-22 00:50:00 -07:00
snipe
8244a2ad23 New backups config for spatie 2019-05-22 00:49:38 -07:00
snipe
77c3b8f8c1 Updated packages 2019-05-22 00:07:49 -07:00
snipe
7b34cf1a31 Bumped version 2019-05-21 19:17:56 -07:00
snipe
21ec670531 Updated language strings 2019-05-21 19:17:00 -07:00
snipe
b2eacb147b Fixed #7046 - added user website url back into UI 2019-05-21 18:55:12 -07:00
snipe
729e3eb70d Updated email strings 2019-05-21 18:43:00 -07:00
snipe
978533b2f4 Fixed XSS vulnerability when creating a new categories, etc via modal on create
Same fix as before, because of the weird select2 post-parsing ajax behavior
2019-05-21 18:29:50 -07:00
snipe
0358d13ddb Fixed #7044 - API update deleted custom fields if they are not re-presented 2019-05-20 11:49:18 -07:00
snipe
c944304444 Updated packages
- Updating erusev/parsedown (v1.7.2 => 1.7.3): Downloading (100%)
  - Updating squizlabs/php_codesniffer (3.4.1 => 3.4.2): Downloading (100%)
  - Updating symfony/polyfill-mbstring (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/var-dumper (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating league/flysystem (1.0.50 => 1.0.51): Downloading (100%)
  - Updating symfony/translation (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating nesbot/carbon (1.36.2 => 1.37.1): Downloading (100%)
  - Updating symfony/debug (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/console (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/finder (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/polyfill-ctype (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/polyfill-php70 (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/http-foundation (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/event-dispatcher (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/http-kernel (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/process (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/routing (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/polyfill-util (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/polyfill-php56 (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/psr-http-message-bridge (v1.1.1 => v1.1.2): Downloading (failed)
Downloading (100%)
  - Updating rollbar/rollbar (v1.7.5 => v1.8.1): Downloading (100%)
  - Updating symfony/yaml (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/browser-kit (v3.4.23 => v3.4.27): Downloading (100%)
2019-05-20 10:10:46 -07:00
Bob Clough
096393389c Fixes #5054: LDAP users deactivated for none-ad (#7032)
When using none-AD ldap, users are automatically deactivated every LDAP
sync.  This commit changes the behaviour so that if the active flag isn't set,
the users are enabled.

Fixed #5054, at least for 4.X
2019-05-16 09:31:55 -07:00
snipe
9eb7b668d1 Fixed #6880 - correctly encrypt encrypted fields via the API 2019-05-15 19:33:30 -07:00
snipe
6728089106 Fixed #6883 - remove escaping of fields on LDAP import 2019-05-15 19:15:41 -07:00
snipe
33b59d7bed Bumped version 2019-05-15 16:45:07 -07:00
snipe
888bdbdb68 Added ability to update groups via API
Fixes [ch9139]
2019-05-15 16:39:34 -07:00
snipe
d67c931f6a Import locations from CSV via command line (#7021)
* Added import locations command

* Small fixes to location importer

* Added country, LDAP OU

* Cleaned up comments, added more clarification to what the script does
2019-05-13 02:27:19 -07:00
snipe
dbdc511eff Merge branch 'master' of https://github.com/snipe/snipe-it 2019-05-08 09:23:58 -04:00
snipe
f47b960566 Added API middleware to API routes to enable throttling
TODO: Figure out how to make this costumizable without touching the code
2019-05-08 09:23:54 -04:00
snipe
d016076806 Fixed #6956 - viewKeys policy inconsistent (#7009)
* Fixed #6956 - Added additional gates show showing/hiding license keys

* Modified gate to allow user to see licenses if they can create or edit the license as well
2019-05-08 08:14:49 -04:00
snipe
23fa5d0bf4 Fixed #7003 - crash when warranty months or purchase date is null 2019-05-07 15:33:57 -04:00
Joris van Eijden
486c708911 Leave the activated state for users alone in normal LDAP synchronisation. (#6988) 2019-05-06 09:40:53 -04:00
snipe
e5c2d77c7d Fixes #6204 - added email alerts and web/API access to assets due for audits (#6992)
* Added upcoming audit report

TODO: Fid diff/threshold math

* Added route to list overdue / upcoming assets via API

* Controller/API methods for due/overdue audits

We could probably skip this and just handle it via view in the routes…

* Added query scopes for due and overdue audits

* Added audit due console command to kernel

* Added ability to pass audit specs to main API asset search method

* Added audit presenter

* Added bootstrap-tables presenter formatter to display an audit button

* Added gated sidenav items to left nav

* Added audit due/overdue blades

* Cleanup on audit due/overdue console command

* Added language strings for audit views

* Fixed :threshold placeholder

* Removed unused setting variable

* Fixed next audit date math

* Added scope for both overdue and upcoming

* Derp. Wrong version

* Bumped version

(I will release this version officially tomorrow)
2019-05-05 22:32:52 -04:00
snipe
ce16eae508 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-05-02 15:20:52 -07:00
snipe
dc73dbfbfd Fixed #6911 - note must be a string on license checkin 2019-05-02 15:20:47 -07:00
snipe
dae26e0378 Remove “Imported from LDAP” note override 2019-04-18 17:56:08 -04:00
snipe
1bb1f7342f Fixed #6922 - date_add crashing if EOL is null 2019-04-18 15:49:59 -04:00
snipe
420e8bc85a Allow phone number to be changed in Profile 2019-04-18 14:13:50 -04:00
snipe
a521523d45 Truncate imports table on seed 2019-04-18 14:13:33 -04:00
snipe
25884a893e Bumped version 2019-03-20 02:41:33 -07:00
snipe
d1e9fbfa24 Updated compoer 2019-03-20 02:37:44 -07:00
snipe
da015ec4a8 Fixed #6834 and #6402 - use inline QR code generation for 2FA (#6840)
* Fixed  #6834 and #6402 - use inline QR code generation for

* Update auth controllers to use translations

* Updated composer lock

* Added comments

* Moar comments

* Typo
2019-03-20 01:24:31 -07:00
snipe
1451b4f45d Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-18 20:51:11 -07:00
snipe
b6da68a69c Bumped version 2019-03-18 20:50:47 -07:00
snipe
dee92cfc6c Fixes XSS vulnerabilities (#6831)
* Properly escape log_meta values

* Vue syntax fix to allow npm run dev to work again

* Janky fix for Select2 bug

* Compiled production assets

* Escape user’s last name in API

* Removed duplicate alertClass

* Compiled production assets
2019-03-18 20:49:32 -07:00
snipe
dec77890bd Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-18 11:59:48 -07:00
snipe
0e1289f12f Fixes #6821 - fixed 2 fa active for users list (#6822)
* Fixed #6821 - confusing UI for 2FA when 2FA is universally enforced

I also updated the language in the user’s listing table to clarify what “activated” means

* Added login enabled info to user view

* Clarified comments

* Added info about 2FA on user profile

Because why not

* Added nowrap to table, and added 2FA reset for superadmins
2019-03-18 11:59:02 -07:00
snipe
7b33f95e83 Fixes/import permissions mask (#6826)
* Check for empty headers in import

* Added import permission

* Fixed model path in docblock

* Added import gate to default blade

* Check if the user is an admin OR idf they have import permissions

* Walked back that admin permission

Since admins are bound by full company support, it makes less sense to let admins have this permission by default, versus having them specifically designated to the import permission
2019-03-18 11:58:08 -07:00
snipe
ab6744dfba Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-14 15:38:20 -07:00
snipe
0fd940ffa4 Check for empty headers in import 2019-03-14 15:38:07 -07:00
Tim Farmer
5893e25b43 Label bugs in alerts settings (#6808)
* Label bugs in alerts settings

Found another label bug where the alert_email label should be alerts_enabled.

* Update alerts.blade.php
2019-03-14 10:53:05 -07:00
snipe
7c3bbe3097 Fixes #6776 hungary date format (#6823)
* Add @timothyfarmer as a contributor

* Fixed #6776 - added hungary time format
2019-03-13 19:39:38 -07:00
snipe
858d382e26 Changed logging to info level for LDAP 2019-03-13 15:14:03 -07:00
snipe
de16fee00a Change image unlink error log to info from error 2019-03-13 12:22:12 -07:00
Tim Farmer
7deab0f53b Label bugs in general settings (#6801)
* checkbox label bug

The checkbox label in general settings for require_accept_signature was set to full_multiple_companies_support instead so when you click the label for require accept signature it would check the wrong box.

* Update general.blade.php

* Update general.blade.php

Another label that is wrong in general settings
2019-03-08 12:24:24 -08:00
Jonathon Reinhart
e59ec8b27f Run Laravel schedule in docker image using supervisord (#6606)
* docker: Rename /entrypoint.sh to /startup.sh

This script is not configured as the docker image ENTRYPOINT, thus it is
misleading to name it so.

* docker: Terminate supervisord if a process enters the FATAL state

By terminating PID 1, this will also terminate the Docker container.

* docker: Use supervisord to start up apache and cron

Note that this uses `apache2ctl -DFOREGROUND` rather than manually
sourcing /etc/apache2/envvars and running apache2, as recommended at
https://advancedweb.hu/2018/07/03/supervisor_docker/.

* docker: Add artisan schedule:run to crontab

This also switches to executing /var/www/html/artisan directly.

* docker: Run artisan schedule:run directly from supervisor

This has the following benefits over using cron:
- Cron doesn't need to be installed
- Docker-provided environment variables are preserved
- It's easy and explicit to run as the docker user
2019-03-07 12:42:00 -08:00
snipe
6d98bd6846 Fixed error if item requested or request was deleted (#6786)
ch628
2019-03-05 23:47:36 -08:00
snipe
58768e5aee Added ability to search consumables by item number (#6785)
Fixes ch1086
2019-03-05 23:21:22 -08:00
snipe
28a450ea25 Added ability to do full name search in user dropdown selectlist (#6784) 2019-03-05 21:13:39 -08:00
snipe
1393f44070 Create FUNDING.yml 2019-03-04 23:57:17 -08:00
snipe
8016939f31 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-01 17:39:55 -08:00
snipe
c1ad2f9376 Added link to Marksman - A Windows agent for Snipe-IT
Per https://github.com/Scope-IT/marksman/issues/9#issuecomment-468275142
2019-03-01 17:39:50 -08:00
snipe
9575cd2651 Add accessories endpoint to user API (#6775) 2019-03-01 17:21:03 -08:00
snipe
cf086b711e Merge branch 'master' of https://github.com/snipe/snipe-it 2019-02-22 13:24:58 -08:00
snipe
53db96edad Bumped minor version 2019-02-22 13:24:45 -08:00
snipe
3b62c4a83a Fixes/integrity constraint (#6754)
* Migration to fix nullables

This should fix an issue introduced in 90cddb7aee where we’re passing null instead of an empty string (necessary to nullify values via the API)

* Removed asset migration - serial was already fixed
2019-02-22 13:20:42 -08:00
snipe
5f3147cf36 Added support for enum that was added :( 2019-02-20 21:04:03 -08:00
snipe
738896bdc2 Make serial nullable
Guessing this is new due to later versions of mysql
2019-02-20 20:47:14 -08:00
snipe
e2834fab90 Bumped minor version 2019-02-16 11:48:20 -08:00
snipe
d687e1d762 Fixed #6725 - revert auro_increment_prefix to string
This was mis-migrated as boolean.

It’s essentially a duplicate migration, as it is fixed in-place on the existing migration, and then a new migratio was created to handle anyone who already upgraded.
2019-02-16 11:37:05 -08:00
snipe
6256abddf2 Bumped point release 2019-02-14 15:07:11 -08:00
snipe
b26fbf986f Fixed issue where offset could be greater than total items, resulting in “No results” confusion 2019-02-14 14:49:08 -08:00
snipe
5c9b1ed43a Fixed #6676 - consumables API not respecting category id 2019-02-14 14:48:43 -08:00
snipe
14eb6b387b Possible fix for #6710 - explicitly make auto_increment_prefix nullable (#6716)
* Possible fix for #6710 - explicitly make auto_increment_prefix nullable

* Added migration to make auto_increment_prefix explicitly nullable
2019-02-14 12:46:09 -08:00
snipe
35ebe33e4e Fixed #6703 - fixes password confirmation (#6711)
* Fixed #6703 - fixes password confirmation

* Removed debugging

* Fixed tests

* I guess we use 10 as the settings for password min in tests

* One more try to fix tests - confirmation won’t validate until password validates
2019-02-13 23:01:19 -08:00
snipe
9035707bd6 Bumped point version 2019-02-13 07:40:52 -08:00
snipe
aa1e06f021 One more time…. Fixed #6704 - don’t apply gate to $arrays collection, just check that they can view assets 2019-02-13 04:46:19 -08:00
snipe
30b1cfabf5 Fixed dumb formatting 2019-02-13 04:45:21 -08:00
snipe
e75d22ab73 Revert "Fixed #6704 - don’t apply gate to $arrays collection, just check that they can view assets"
This reverts commit b1e17743b8.
2019-02-13 04:44:19 -08:00
snipe
b1e17743b8 Fixed #6704 - don’t apply gate to $arrays collection, just check that they can view assets 2019-02-13 04:35:55 -08:00
snipe
e2c0f01a10 Fixed #6367 - pass table name and column_id to scopeCompanyables
Solves error: Integrity constraint violation: 1052 Column 'company_id' in where clause is ambiguous
2019-02-13 01:26:11 -08:00
snipe
f88fee0f21 Make user notes field editable via API 2019-02-12 23:58:30 -08:00
snipe
c0669150fb Bumped point version 2019-02-12 23:48:31 -08:00
snipe
f3c12f38b6 Fixed #6061 - Assigned user group cannot be removed
This bug was a result of attempting to check if the groups field had a value, and only THEN trying to sync the groups. This meant that uf you were removing ALL groups, the  sync wouldn’t be triggered.

This still needs to be updated in the API.
2019-02-12 23:43:38 -08:00
snipe
5e19178a30 Do not count deleted locations in managedLocation check on user delete 2019-02-12 23:32:10 -08:00
snipe
90cddb7aee Fixed #6113 - use $asset->fill vs filled() to allow blanking values via API (#6693)
Need to confirm that re-enabling `\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,` won’t mangle anything. I know we ran into some issues when testing a long time ago, but not sure those issues apply anymore, and I can’t remember what they were.
2019-02-12 22:08:38 -08:00
snipe
6d828964be Merge branch 'master' of https://github.com/snipe/snipe-it 2019-02-04 18:58:32 -08:00
snipe
971fcf5800 Fixed #6633 - return 200 status code 2019-02-04 18:58:28 -08:00
Daniel Ruf
2ad270cf33 ci: fix indentation (#6669) 2019-01-30 15:14:17 -08:00
Daniel Ruf
8ce78c6b31 tests: allow to fail on PHP 7.3 and use the latest PHP 7.1 release (#6666) 2019-01-30 14:32:33 -08:00
Daniel Ruf
af3c8195af tests: fix expected string (#6665) 2019-01-30 14:12:20 -08:00
snipe
117b4c59cc Bumped minor version 2019-01-25 21:06:08 -08:00
snipe
194d0733d4 Fixed #6644 - asset name not linked in Reports > Asset Maintenance Report
This report will likely be deprecated.
2019-01-25 20:57:14 -08:00
snipe
a371e8d53f Added calibration as a maintenance type
Should just make these custmizable options
2019-01-24 15:17:33 -08:00
snipe
8f09cca043 Fixed incorrect group route 2019-01-24 15:17:11 -08:00
snipe
39bca49e8f Specify table name in deleted user display 2019-01-24 14:38:18 -08:00
snipe
b8269020ae Specify table name in deleted user display 2019-01-24 14:37:39 -08:00
snipe
601c129bbf Embed images in emails 2019-01-17 20:45:24 -08:00
snipe
b293d00699 Switch LDAP error to debug, to avoid crapping up the logs 2019-01-17 20:18:03 -08:00
snipe
75a0cf97e2 Return an error if asset maintenance is associated with a non-existant asset 2019-01-16 02:19:57 -08:00
snipe
c055e3af21 Only try to return the asset tag link if a valid asset id has been passed 2019-01-16 02:19:35 -08:00
snipe
a1f93e733c Fixed undefined error when maintenance is associated with a deleted asset 2019-01-16 01:45:51 -08:00
fanta8897
49073742b5 Updating LDAP such that each user is not required to be bindable to LDAP (#6571)
* Update Ldap.php

* Update Ldap.php

* Update Ldap.php

* Update Ldap.php

* Update Ldap.php

Updating LDAP.php such that the admin bind will ONLY occur if the user attempting auth cannot bind. If that is the case, it will attempt to bind as admin and search for that user, prior to failing.
2019-01-15 14:04:21 -08:00
Sxderp
187206cb88 Fix saving of REMOTE_USER setting broken by 1a64879b6 (#6565)
The previous commit made it such that remote user login could only
be enabled if two factor authentication was also enabled. Unnest
the configuration so that the setting can be applied without.
2019-01-15 13:59:36 -08:00
Hubert
8420cb7ec1 Fixed problem with import when using snipeit:import command (#6550) 2019-01-15 13:58:23 -08:00
Andrey Bolonin
75252bce05 add php 7.3 (#6556) 2019-01-10 13:21:04 -08:00
snipe
794824713e Bumped minor version 2019-01-03 11:22:06 -08:00
snipe
8f6ea84fca Fixed #4568 - escaping values in custom report 2018-12-12 19:38:24 -08:00
snipe
ea1b792a93 Fixed #6491 - cleaner return methods for PHP 7.3 compact() 2018-12-12 18:23:39 -08:00
Ben RUBSON
4ffb8f14b8 Improve Memcached settings (#6485)
* Improved memcached settings

* Improved memcached settings
2018-12-06 14:39:14 -08:00
Wes Hulette
d023f61bc4 Fixed missing importer (#6413)
Fixed missing manager_id
Fixed missing department_id
2018-12-04 13:06:12 -08:00
snipe
dd5ca73602 Set support footer and version footer to on when resetting the demo 2018-11-08 12:52:37 -08:00
snipe
2632f730d1 Sets activated to 0 in UserImporter if the activated column isn’t set 2018-11-07 22:36:58 -08:00
snipe
24c158bfe6 Added missing use statement for departments in importer 2018-11-07 18:33:43 -08:00
snipe
3d4a5a8066 More importer tweaks for dept and manager 2018-11-07 18:05:53 -08:00
snipe
db7e0b56f2 Fixed department id on asset import with users 2018-11-07 17:36:34 -08:00
snipe
f2478d813c Fix manager id if no manager is given in importer 2018-11-07 17:33:27 -08:00
snipe
192aa9eb71 Fixed #6386 - licenses not searching on category name 2018-11-02 17:14:08 -07:00
Tim Bishop
0eef0fc1dd Sync with develop branch. (#6377)
Without this change argv[1] is ignored.
2018-10-31 11:11:41 -07:00
snipe
81f8fe34cd Removed debugging line 2018-10-30 18:11:27 -07:00
snipe
f744696043 Fixed #6375 - lowercase keys on findAndBindUser to address LDAP syncing issue 2018-10-30 13:12:10 -07:00
snipe
29b0780c6c Added company info to asset maintenances transforrmer 2018-10-30 00:20:16 -07:00
Wes Hulette
6b3b673daa Changed NULL coalesce from ?? (#6353)
PHP 5 does not have the double question mark null coalesce support.
2018-10-26 15:53:18 -07:00
snipe
a4876e9f3e Updated language files 2018-10-26 15:51:38 -07:00
snipe
925258bfb4 Bumped version 2018-10-26 15:29:10 -07:00
snipe
1a10aa0dda Fixed #6349 - add view permission for print all assigned 2018-10-19 16:43:28 -07:00
snipe
d6f8d1b464 Updated composer lock 2018-10-19 16:40:54 -07:00
snipe
09a102fea8 Only try to return a department if there is a matching field 2018-10-19 01:44:45 -07:00
snipe
304fce73fc Null if blank on user import 2018-10-19 01:38:14 -07:00
snipe
295a68bb7a Try false instead of null 2018-10-19 01:36:15 -07:00
snipe
3aeb521782 Patch PR #6335 to master 2018-10-19 01:30:05 -07:00
snipe
f587d2248b WIP: Better handle activation column in importer (#6290)
* Better handle activation column

* Added comments for clarity on importer methods
2018-10-19 00:23:12 -07:00
snipe
8579c5a68a Allow 0 as a consumable min amt 2018-10-15 17:04:51 -07:00
snipe
835b461d7d Fixed #6323 - typo in link for low inventory 2018-10-11 15:31:10 -07:00
Wes Hulette
b8a37a0c73 Fixed Expiring Assets Email (#6321) 2018-10-11 14:03:00 -07:00
snipe
41b5b1dfd0 Bumped hash 2018-10-09 17:32:46 -07:00
snipe
c7596e7741 Fixed image not uploading on asset create 2018-10-09 17:31:52 -07:00
snipe
d4fa81301d Check if user can see assets in statuslabels gate 2018-10-09 16:34:12 -07:00
snipe
ec7245965f Bumped to rollbar 2.4.1
https://github.com/rollbar/rollbar-php-laravel/issues/65
2018-10-04 17:09:12 -07:00
snipe
de76e8db5f Re-enable rollbar 2018-10-04 12:11:36 -07:00
snipe
a52575c7bf Lock rollbar to v2.3.0
https://github.com/rollbar/rollbar-php-laravel/issues/65
https://github.com/rollbar/rollbar-php-laravel/issues/67
2018-10-04 12:11:36 -07:00
Nenad Ticaric
bf6703c2e8 fixing double word typo (#6292)
Thanks!
2018-10-04 09:41:50 -07:00
snipe
4db1dd8afc Fixed #6291 - send-welcome argument in cli importer 2018-10-04 04:43:06 -07:00
snipe
c39e3acb59 Bumped hash 2018-10-04 02:24:41 -07:00
snipe
7a44da85a0 Fixed issue where admin users could disable activation when editing their own profile 2018-10-04 02:18:20 -07:00
snipe
890b613f71 Temporarily suppress rollbar 2018-10-03 15:47:56 -07:00
snipe
1014bd74e0 Updated rollbar 2018-10-03 14:58:14 -07:00
snipe
db385e024b Possible proxy issue fix 2018-10-03 13:04:25 -07:00
snipe
c8bff3ef38 Features/add manager and dept to importer (#6277)
* Ignore the simlink for public storage

* Added manager and department to user import

* More UI importer tweaks

* Fisxed typos
2018-10-02 15:43:54 -07:00
snipe
10bc35d604 Fixed PHP warning Undefined offset: 1 in upgrade.php 2018-09-28 12:13:35 -07:00
snipe
eea65a3f26 Fixed manufacturers item count 2018-09-28 12:03:27 -07:00
snipe
3b21a19491 Updated language strings 2018-09-28 11:54:52 -07:00
snipe
7a52477294 Added Icelandic and Serbian to locale list 2018-09-28 11:47:59 -07:00
snipe
ef0bd72076 Bumped version 2018-09-28 11:21:39 -07:00
snipe
ff824ec4db Fixes #6252 - activated flag not checked when editing active user 2018-09-28 11:18:33 -07:00
snipe
75032def9e Fixed #4151 - Undefined index: samaccountname on LDAP import 2018-09-27 16:11:09 -07:00
snipe
3a0f738fb0 Added some hepful comments 2018-09-26 19:20:50 -07:00
snipe
55846cc717 Changed LOG:: to Log:: 2018-09-26 19:06:31 -07:00
snipe
1784278a59 Fixed importer email test 2018-09-26 19:06:09 -07:00
snipe
afac0bc441 Removed old isActive model (unused) 2018-09-26 19:05:42 -07:00
snipe
ffbee77f6f Patch for 5965 - multiple email recipients no longer working (#6238) 2018-09-26 15:47:53 -07:00
snipe
b69b5fdf84 Added counts to location show() API method 2018-09-21 15:50:14 -07:00
Brady Wetherington
89e06054bf Merge pull request #6205 from dasjoe/patch-1
Fixes #5630
2018-09-17 21:45:48 -07:00
Hajo Möller
3159e7713a Fixes #5630 2018-09-11 13:07:45 +02:00
snipe
adf6e7d1cd Added group support for user API 2018-09-07 18:25:58 -07:00
snipe
ba3662a9ed Bumped hash 2018-09-07 18:12:29 -07:00
snipe
05ea61421f Added manager_id to fillable for locations API 2018-09-07 18:11:38 -07:00
snipe
22ef2ce0b6 Bumped hash 2018-09-07 03:20:31 -07:00
snipe
51d3d130e4 Fixed Not unique table/alias: 'models' on custom report triggered when category is selected 2018-09-07 03:19:54 -07:00
snipe
d8a8e1cc09 Bumped version 2018-08-28 11:43:56 -07:00
snipe
522dc1db2a Fixed #6124 2018-08-23 21:05:10 -07:00
snipe
db907815ff Removed check for active in password reset form 2018-08-21 18:40:27 -07:00
snipe
ae6abdddad Check the user is active before displaying password reset
This would only come into play if an inactive user already received a password reset email and then the system was upgraded to prevent those emails from being sent to inactive users
2018-08-14 19:04:47 -07:00
snipe
63c9fbe10c Temporarily disbable notifying users on import 2018-08-14 18:27:46 -07:00
snipe
101dfd01f2 Bumped hash 2018-08-14 18:22:53 -07:00
snipe
5db5134ae0 Set activated to default on when new user is created 2018-08-14 18:14:41 -07:00
snipe
5294489b0e Fixed HTML typo 2018-08-14 18:14:29 -07:00
snipe
05b2b8fb59 Tweaked code/language for password reset 2018-08-14 18:09:33 -07:00
snipe
0100c56046 Only allow password reset if user is active 2018-08-14 17:46:29 -07:00
Jordi Boggiano
e81b221fd1 Fix license identifier (#6043) 2018-08-08 20:36:45 -07:00
snipe
f374ac1bf7 Removed duplicate BACKUP_ENV in example env 2018-08-01 15:31:43 -07:00
snipe
524c6c502e Features/restore deleted cmd (#5982)
* Delete content from login attempts table

* Script to restore deleted users and put their asset assignments back

* Uncomment backup
2018-07-27 02:42:55 -07:00
snipe
614e858e44 Restrict users asset listing to just assets checked out to users 2018-07-25 21:38:14 -07:00
snipe
708b1a962c Split out custom_css from custom_header
This makes it so that the custom_css will still be respected even if there is no custom header
2018-07-25 19:01:47 -07:00
snipe
4e55a18a60 Bumped version 2018-07-24 19:46:23 -07:00
snipe
3de1de9dc6 Merge branch 'develop' 2018-07-24 14:04:16 -07:00
snipe
e320d2ba05 Fixed #5944 - added logo option for print-assets page (#5950) 2018-07-24 13:37:02 -07:00
snipe
ed78a4b8a0 Fixed activated issue for strict mode 2018-07-24 13:28:59 -07:00
snipe
376eb52f00 Fixed #5938 - added “self location edit” as permission 2018-07-24 12:42:16 -07:00
snipe
8ecceeacda Fixed weird display for self options 2018-07-24 12:41:58 -07:00
snipe
f4cfb31bf4 Use request object 2018-07-24 12:10:02 -07:00
snipe
227dc7e81d Save model display setting - fix for issue in #5301 2018-07-24 12:10:02 -07:00
snipe
152d985ebc Add @Azerothian as a contributor 2018-07-24 12:10:02 -07:00
Earl Ramirez
ef1e8df001 Disable file browsing (#5922)
* Added cron to list of packages

* Disable file browsing from the public directory
2018-07-23 20:40:04 -07:00
snipe
5c2b1a3b70 Add @Azerothian as a contributor 2018-07-23 20:29:15 -07:00
Azerothian
66c3f5432d implemented specific seat checkout (#5887) 2018-07-23 20:28:45 -07:00
Daniel Meltzer
de413408f5 Port/reenable most unit tests. (#5921)
* Port/reenable most unit tests.

Should probably flesh out notifications tests in the next few days.

* Disable json checkin in ApiAssetsTest@index for now.  It's broken, but hiding other real broken things.

* Re Disable Groups allowDelete
2018-07-23 06:48:21 -07:00
Daniel Meltzer
059126f642 Checkout update locationid (#5919)
* Fix missing punctuation.  Bad merge.

* If we're checking out to an location, use it's id instead of location_id
2018-07-23 06:47:21 -07:00
Daniel Meltzer
3bc43210ab Add ID to the allowed sort fields in api/Users. (#5929) 2018-07-23 06:46:50 -07:00
Daniel Meltzer
82194cef8a bugfix: updating a user when an admin (not a superuser) would remove any groups from the user. (#5914) 2018-07-21 23:02:06 -07:00
snipe
1956a16d1e Merge branch 'develop' 2018-07-20 13:25:03 -07:00
Till Deeke
e1c095adca Removes the typehint for search term string (#5904)
The „string“ typehint only works in PHP >= 7.0.0.
Since we are still supporting versions below that, remove the type hint.
2018-07-20 13:23:44 -07:00
Till Deeke
45a2932f4b Fixes the generation of where conditions (#5902) 2018-07-20 13:23:29 -07:00
Till Deeke
b6e3715cd8 Fix: No Notifications for checking out Consumables (#5898)
* Adds a method to consumables to check if a notification should be sent

Adds the checkin_email method to Consumables, this gets checked in notifications when checking out the consumable.

Without the method, no notifications get sent for checking out consumables.

* Fixes the checkin_email method on the License model

This should allow the License to also send checkout/checkin notifications again.
2018-07-20 13:22:49 -07:00
snipe
aa9c0078a1 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-19 14:47:27 -07:00
snipe
9677115055 Bumped version 2018-07-19 14:46:08 -07:00
snipe
d45e90e358 One more fix for #5893 2018-07-19 14:45:28 -07:00
snipe
f692fb5bff Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-19 10:50:18 -07:00
snipe
83692696ba Bumped version 2018-07-19 10:49:40 -07:00
snipe
bbb15d610f Merge branch 'develop' 2018-07-19 10:40:38 -07:00
snipe
7ebb7876c4 Partial fix for #5896
Still need to fix the front end on edit, which seems to be defaulting to boolean
2018-07-19 10:40:07 -07:00
snipe
bd581d01a3 Use full row width for image field 2018-07-19 10:40:07 -07:00
snipe
de2ebba577 Category add/edit UI tweaks 2018-07-19 10:40:07 -07:00
Daniel Meltzer
351274c633 Hotfix: the checkin_email does not exist on consumable. (#5891) 2018-07-19 10:38:50 -07:00
snipe
53fab3e9ba Merge branch 'develop' 2018-07-19 10:23:18 -07:00
snipe
a0c0b7b1eb Fixed #5893 - activated typo 2018-07-19 10:22:08 -07:00
snipe
5a34d43a86 Fixed #5895 - wrong date validation in maintenances 2018-07-19 10:19:55 -07:00
snipe
86a0f77e6f Merge branch 'develop' 2018-07-19 10:15:38 -07:00
snipe
9d00ae6e50 Fixed #5894 - lookup by asset tag in top search broken 2018-07-19 10:14:02 -07:00
snipe
8d23398176 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-18 20:07:14 -07:00
snipe
5aae930e8c Bumped version 2018-07-18 20:05:48 -07:00
snipe
6db096c336 Add @takuy as a contributor 2018-07-18 20:04:09 -07:00
snipe
78d27e82c8 Updated language strings 2018-07-18 20:03:31 -07:00
Sam
2a4fef6a61 adds select-id as an option for the asset-select partial. allows you to manually override the ID of an element. currently, the bulk-checkout partial has two asset-selects, with conflicting IDs. attempts to resolve #5882 (#5883) 2018-07-18 19:55:13 -07:00
snipe
9daeeeb851 Features/nicer notifications (#5886)
* Improved expiring licenses notification

* Improved expiring assets notification

* Nicee low inventory notification

* Refactored stupid language strings

* Oops

* Use settings variable
2018-07-18 19:15:45 -07:00
Daniel Meltzer
92671823d8 Userimport fixes/improvements (#5884)
* Send notification when user is created.
* Flesh out default user mappings
* Add user importing test.
2018-07-18 19:15:07 -07:00
Daniel Meltzer
19396b2107 Logic Fix (#5877)
If we have a username, we should look that up even if we do not have a
first name.
2018-07-18 19:10:36 -07:00
Brady Wetherington
f0332a7388 Add DB port number to docker
which gets copied to the docker image's `.env` file during installation. This closes #5718
2018-07-18 19:10:06 -07:00
snipe
70cb5ed215 Updated language strings 2018-07-18 08:55:38 -07:00
snipe
d309f67df0 Set activated to zero if no values passed for active user 2018-07-18 08:27:26 -07:00
snipe
7dc070ec2c Add @engrzhou as a contributor 2018-07-18 07:39:46 -07:00
snipe
2ecdf19569 Add @GodUseVPN as a contributor 2018-07-18 07:39:23 -07:00
snipe
c56f2625b7 Add @grayhoax as a contributor 2018-07-18 07:38:57 -07:00
snipe
3dc154991a Add @wira-sandy as a contributor 2018-07-18 07:37:56 -07:00
snipe
a5f57c050f Add @vipsystem as a contributor 2018-07-18 07:37:30 -07:00
snipe
36c4cd98e0 Add @vinzruzell as a contributor 2018-07-18 07:36:31 -07:00
snipe
f6ef6039f4 Add @fraccie as a contributor 2018-07-18 07:35:55 -07:00
snipe
6867563e4a Add @dheche as a contributor 2018-07-18 07:33:19 -07:00
snipe
21820262ce Add @reinvanhaaren as a contributor 2018-07-18 07:29:12 -07:00
snipe
bae1203b64 Add @ragnarcx as a contributor 2018-07-18 07:28:41 -07:00
snipe
0f2ff7aba2 Add @ProfFan as a contributor 2018-07-18 07:28:03 -07:00
snipe
0ce90834f6 Add @priatna as a contributor 2018-07-18 07:27:42 -07:00
snipe
0e182bb2b9 Add @bodrovics as a contributor 2018-07-18 07:26:54 -07:00
snipe
7afff69fab Add @pawel1615 as a contributor 2018-07-18 07:26:12 -07:00
snipe
136d59e4b9 Add @drcryo as a contributor 2018-07-18 07:25:49 -07:00
snipe
6d9cbac928 Add @pooot as a contributor 2018-07-18 07:25:25 -07:00
snipe
efcd8d339b Add @saymd as a contributor 2018-07-18 07:24:51 -07:00
snipe
7e09ce468b Add @omego as a contributor 2018-07-18 07:24:08 -07:00
snipe
baf714b41f Add @MohammedFota as a contributor 2018-07-18 07:22:56 -07:00
snipe
b0808e846d Add @IxFail as a contributor 2018-07-18 07:22:15 -07:00
snipe
0e3e10a707 Add @mikaelssen as a contributor 2018-07-18 07:21:32 -07:00
snipe
cbf3d5d071 Add @micaelrodrigues as a contributor 2018-07-18 07:20:20 -07:00
snipe
68d355a33d Add @meyerf99 as a contributor 2018-07-18 07:19:59 -07:00
snipe
853923013a Add @stubben as a contributor 2018-07-18 07:19:00 -07:00
snipe
737061a983 Add @msjohansen as a contributor 2018-07-18 07:17:34 -07:00
snipe
0438d55284 Add @mariejoyacajes as a contributor 2018-07-18 07:17:08 -07:00
snipe
924962c156 Add @MarcosBL as a contributor 2018-07-18 07:16:30 -07:00
snipe
a8c42843f6 Add @lstrojny as a contributor 2018-07-18 07:15:10 -07:00
snipe
63c4b7b6b6 Add @laopangzi as a contributor 2018-07-18 07:14:47 -07:00
snipe
ff06387d90 Add @joxelito94 as a contributor 2018-07-18 07:11:31 -07:00
snipe
fa46622bad Add @JohnWillker as a contributor 2018-07-18 07:07:13 -07:00
snipe
fd65780287 Add @jarby1211 as a contributor 2018-07-18 07:05:34 -07:00
snipe
6478ec86ac Add @itangiang as a contributor 2018-07-18 07:04:23 -07:00
snipe
748e0eb44f Add @igolman as a contributor 2018-07-18 07:03:30 -07:00
snipe
507c07e9eb Add @abaalkh as a contributor 2018-07-18 07:03:09 -07:00
snipe
5185a9e590 Add @husnulyaqien as a contributor 2018-07-18 07:02:34 -07:00
snipe
1ade088a8f Add @Kentsson as a contributor 2018-07-18 07:00:50 -07:00
snipe
5c087181be Add @fofwisdom as a contributor 2018-07-18 07:00:21 -07:00
snipe
08bb314bbc Add @Hafidzi as a contributor 2018-07-18 06:59:43 -07:00
snipe
7ddb9becd4 Add @AdnanAbuShahad as a contributor 2018-07-18 06:59:03 -07:00
snipe
7d96709ee2 Add @mrgluek as a contributor 2018-07-18 06:58:28 -07:00
snipe
4413a9f0b6 Add @jgroblesr85 as a contributor 2018-07-18 06:58:10 -07:00
snipe
9611b55fb9 Add @georgwallisch as a contributor 2018-07-18 06:57:39 -07:00
snipe
dffd535f68 Add @gdraque as a contributor 2018-07-18 06:57:17 -07:00
snipe
4aaf2da651 Add @possebon as a contributor 2018-07-18 06:55:47 -07:00
snipe
80ac46af78 Add @fgbs as a contributor 2018-07-18 06:55:12 -07:00
snipe
13c58b54fa Add @frapposelli as a contributor 2018-07-18 06:54:39 -07:00
snipe
355119187a Add @Erlpil as a contributor 2018-07-18 06:54:03 -07:00
snipe
0d96587b04 Add @EpixFr as a contributor 2018-07-18 06:53:30 -07:00
snipe
be806def91 Add @dominiksenti as a contributor 2018-07-18 06:50:59 -07:00
snipe
8acf8027e9 Add @danielcb as a contributor 2018-07-18 06:49:25 -07:00
snipe
893a1fec57 Add @danielheene as a contributor 2018-07-18 06:48:58 -07:00
snipe
d182e40aef Add @da-friedl as a contributor 2018-07-18 06:48:34 -07:00
snipe
6118aca949 Add @dpyroc as a contributor 2018-07-18 06:48:11 -07:00
snipe
f0ac83179f Add @Wxcafe as a contributor 2018-07-18 06:47:18 -07:00
snipe
8969ffc14b Update @CronKz as a contributor 2018-07-18 06:46:45 -07:00
snipe
9fb130146a Add @kopi-item as a contributor 2018-07-18 06:46:24 -07:00
snipe
d285ff673a Add @Againstreality as a contributor 2018-07-18 06:46:00 -07:00
snipe
7f586da856 Add @cwlin0416 as a contributor 2018-07-18 06:45:24 -07:00
snipe
c35f5b3116 Add @chibacityblues as a contributor 2018-07-18 06:44:40 -07:00
snipe
12927d2d54 Add @rudashi as a contributor 2018-07-18 06:43:43 -07:00
snipe
6b06b87547 Add @benunter as a contributor 2018-07-18 06:42:30 -07:00
snipe
058f8ae3b8 Add @aschiavon91 as a contributor 2018-07-18 06:40:14 -07:00
snipe
1faedac04b Add @angeldeejay as a contributor 2018-07-18 06:39:39 -07:00
snipe
666c7321af Add @xelan as a contributor 2018-07-18 06:39:05 -07:00
snipe
6cbaf7396a Add @sirrus as a contributor 2018-07-18 06:38:25 -07:00
snipe
b706acaa9f Add @RealEnder as a contributor 2018-07-18 06:36:42 -07:00
snipe
5870acb193 Add @albertoaldrigo as a contributor 2018-07-18 06:36:04 -07:00
snipe
c00b633312 Add @a-royal as a contributor 2018-07-18 06:33:05 -07:00
snipe
1c0ee7c4c5 Fixed license checkout gate 2018-07-18 05:33:14 -07:00
snipe
961741b809 Fixed license checkout gate 2018-07-18 05:31:59 -07:00
snipe
5a66f0f373 Include show_in_list option in select 2018-07-18 04:00:04 -07:00
snipe
5a1e1c73c9 Include show_in_list option in select 2018-07-18 03:59:02 -07:00
snipe
3be68ec721 Fix location edit permissions 2018-07-18 03:43:45 -07:00
snipe
0f81e494ee Fix location update permission 2018-07-18 03:42:43 -07:00
snipe
d88f66b019 Updated to stale.yml 2018-07-18 02:57:05 -07:00
snipe
027edbdb21 Fixed #5872 - asset maintenances listing showing created_at instead of start_date 2018-07-18 02:24:53 -07:00
Daniel Meltzer
cf03d25934 Fix importer emailformat (#5871)
* Fix Importer emailformat

Str::slug() strips periods from the string, which caused our existing
logic to misbehave when generating a user's email on an import.  Adjust
logic to use generateEmail() helper on user instead.  Also clean up some
of the logic in this method.

* Remove dead code.

* More refactor/cleanup of the user create method.  I think it is almost readable now.
2018-07-17 16:46:08 -07:00
snipe
75232d2a70 Added link to jamf2snipe 2018-07-17 14:17:13 -07:00
snipe
bcb966af12 Updated composer 2018-07-17 14:17:02 -07:00
snipe
0272e58868 Add @DeusMaximus as a contributor 2018-07-17 01:11:39 -07:00
snipe
bcd988bb81 Merge branch 'develop' of https://github.com/DeusMaximus/snipe-it into develop
# Conflicts:
#	app/Http/Controllers/Auth/LoginController.php
2018-07-17 01:11:15 -07:00
snipe
aa6c21f38d Fixed typo 2018-07-17 01:03:32 -07:00
snipe
3fb5f6f5be Revert 2018-07-17 01:03:25 -07:00
snipe
5dc2ac9e22 Try to fix test
This is a listing, not a single asset
2018-07-17 00:20:06 -07:00
snipe
0f85d6810b Added login log 2018-07-16 23:49:08 -07:00
snipe
bf761946da Fix activated check for login 2018-07-16 23:48:46 -07:00
snipe
d9fa2f0e91 Fixed #5842 - added components to location detail view 2018-07-16 21:50:14 -07:00
snipe
8a25677a8d Bumped hash 2018-07-16 21:35:21 -07:00
DeusMaximus
7c2da81700 Fix REMOTE_USER Header with IIS and AD
Remove DOMAIN\ portion of DOMAIN\user when using Windows Authentication and IIS with REMOTE_USER.
2018-07-17 14:03:19 +10:00
snipe
a4799a495a Fixes #5859 - add file name/size to file upload UI (#5861)
* Fixes #5859 - add file name/size to file upload UI

* Reverting assetcontroller

Not sure exactly what happened here…

* Production assets
2018-07-16 20:09:53 -07:00
Till Deeke
b5de5ac19c Fix: Searching for multiple terms on assets (#5860)
* Give advancedTextSearch all search terms at one

The additional conditions for assets had some problems, since they were joining tables for the additional attributes. The method was called once for every search term, so the join was added multiple times if the user entered multiple search terms.

* Allows search to handle multiple search terms better

The search now better handles multiple search terms, adding additional orWhere clauses, instead of duplicating all queries.

* Fixing typo
2018-07-16 17:44:31 -07:00
Daniel Meltzer
638a7b2d91 Assetcontroller cleanup (#5858)
* Extract method/cleanup

* Remove apiStore method that is unusued since api controllers.

* Use proper model exception

* Remove old user importer.  This is now supported by the general importer framework.

* Refactor AssetsController methods.

This is a giant diff without many functional changes, mostly cosmetic.
I've pulled a number of methods out of assetscontroller, preferring
instead to create some more targetted controllers for related actions.
I think this cleans up the file some, and suggests some places for
future targetted improvement.

Fix weird missing things.

* Fix Unit test failing after date changes.

* Pass valid string to be translated.

* Some method cleanup for codacy.

* Extract trait for common checkout uses and codacy fixes.
2018-07-16 17:44:03 -07:00
Daniel Meltzer
dcf72ce2da Strip timestamps from comparison for api tests, in an attempt to fix the test failures. (#5847) 2018-07-16 14:41:51 -07:00
Daniel Meltzer
8f6e0ad5be Only error if checking out to asset with same id (#5845)
A user with an id of 2 is perfectly fine as a checkout_target of an asset with an id of 2.
2018-07-16 14:25:36 -07:00
Daniel Meltzer
50e0b9b84e category_id not category_i (#5844) 2018-07-16 14:23:24 -07:00
Daniel Meltzer
b6b93550fe Remove old helpers (#5843)
* Cleanup model bulk-edit

Use the general partials where appropriate, as well as display a list of
what models we are editing in the bulk edit.

* Use new api based fetch/display for modal select2.

This is just copy/pasting the code currently because I'm not entirely
sure how the two pieces of code interact.

* Remove old helper functions that are no longer necessary with our populating of select2 dropdowns via ajax.
2018-07-16 14:22:25 -07:00
Till Deeke
baa3be728d Refactoring: A nicer and easier syntax for searching models (#5841)
* Adds the ability to search by dates

Adding extra „where“-conditions to the „TextSearch“ queries, allowing the users to search by dates

* Adds missing dates to $dates in models

* Removes duplicated „where“ conditions

* Adds the Searchable trait to models, defining the searchable attributes and relations

* Removes the old text search methods

* Adds back additional conditions to the search

These conditions could not be modeled in the „attributes“ or „relations“, so we include them here

* Removes unnecessary check for the deleted_at attribute

* Fixes typo in comments

* suppresses errors from Codacy

We can safely ignore the error codacy is throwing here, since this method is a standin/noop for models who need to implement more advanced searches
2018-07-16 14:13:07 -07:00
Till Deeke
240e642fe9 Removes the unused bulk operations for components (#5840) 2018-07-16 14:11:38 -07:00
Till Deeke
07a92d20d7 Fixing #5773: Refactoring the "clearing" of select2 lists (#5839)
* adds select2 placeholders to select lists

To allow us to clear the selection on „select2“ selects, we need a placeholder attribute

See: https://select2.org/placeholders

* Removes empty option from multiple select

select2 requires an empty option value on singular selects, but not on multiple selects.

When selecting multiple options, this empty option would be shown as selectable otherwise, not clearing the selection.

* Adds the option to clear select2 instances

Sets the correct options to allow clearing of out select2 instances. The empty placeholder is required, since clearing only works when a placeholder ist set (event an empty one).

See: https://select2.org/placeholders

* Removes the „Clear selection“ option from select lists

Since we can clear the select2 lists with their native clearing method, we can remove this hack

* Updates generated assets (css/js)
2018-07-16 14:10:54 -07:00
Till Deeke
3f334406d1 Fixing #5470: Checkin emails not working (#5838)
* Always send checkin notifications to users

This fixes the routing of the notifications, to only send „checkin“ emails if the „mail on checkin“ flag on the category was set. (and we checkout to a user with a non-empty email)

* Fixes checkout notification routing

Notifications to users should be send if the category of the resource (accessory/asset/consumable/license):

a) requires the user to confirm acceptance
b) should send notifications on checkin/checkout

* adds a check for EULAs

Adds back a check for the EULA, since the user should receive the EULA if it was set (regardless of other setings on the category, etc)
2018-07-16 14:09:04 -07:00
Till Deeke
dbd177576e Brings back the „pending“ state when checking out (#5837)
When a user would get an asset checked out for them, and the assets category required acceptance of the asset, the „pending“ state would not get set.
2018-07-16 14:07:24 -07:00
Till Deeke
0fb9f42ba4 Removes setting the encryption status on update (#5833)
When we are updating a custom field, we don’t want to change the „field_encrypted“-setting on it.
2018-07-13 04:04:30 -07:00
snipe
b4542d4d42 Fix migration for models on labels 2018-07-12 21:49:44 -07:00
snipe
4bfd7a7e4e Add @chemfy as a contributor 2018-07-12 18:28:43 -07:00
snipe
3fe1562b92 Add @jasonlshelton as a contributor 2018-07-12 18:28:43 -07:00
Till Deeke
27699aa99c Adds permission checks for custom fields and custom fieldsets (#5645) (#5795)
* adds permission checks to custom fields

* adds permission checks to custom fieldsets

* adds separate permissions for custom fieldsets

* check for permissions in views

* Removes custom fieldsets from permissions config

* Proxy the authorization for custom fieldsets down to custom fields.

This allows us to use the existing permissions in use and have more semantically correct authorization checks for custom fieldsets.

* simplifies the authorization check for the custom fields overview

* removes special handling of custom fieldsets in base policy

I just realised that this code duplicates the logic from the custom fieldset policy.
Since we are checking for the authorization of custom fields anyway, we can just use the columnName for the fields.

* cleanup of unused imports
2018-07-12 18:28:20 -07:00
Till Deeke
48bbbe0f40 Fixing authorization issues (#5807)
* adds permission checks for companies

* adds permission checks for depreciations

* adds permission check for all reports

* fixes permissions for departments

* fixes permission naming (edit -> update)

* fixes authorization checking wrong permission in API

The authorization was checking for the non-existent „edit“ method where it should have checked for the „update“ method.

* adds authorization checks for select2 lists

* adds missing authorization checks for api

* fixes user authorization check for creating users

* adds additional check viewing assets on showing a users assets

* Removes authorization checks for select2 lists

Reference: https://github.com/snipe/snipe-it/pull/5807#pullrequestreview-136018755
2018-07-12 18:28:02 -07:00
Antti
9dc226e3d6 Feature: PostgreSQL support (#5642)
* Made migrations work with pgsql and changing empty integers to null

* Fixed the last functional test
2018-07-12 18:24:43 -07:00
Jason
98b20fc1cd Added option to include model information on asset labels. (#5301)
* Added option to include model information on asset labels.

Cleaned up label page to fix skewed label alignment on last row per page.

* Changes made per Snipe's direction

changed type from tinyint to boolean in DB
changed labels back to initials
2018-07-12 18:23:12 -07:00
snipe
980dccf31c Add @5quirrel as a contributor 2018-07-12 18:19:35 -07:00
snipe
bb2193d481 Add @tilldeeke as a contributor 2018-07-12 18:19:14 -07:00
5quirrel
bf8fe316df Fix for #4901 (#5829) 2018-07-12 16:45:12 -07:00
snipe
96716626c6 Fixed #5828 - typo 2018-07-12 14:07:07 -07:00
snipe
e20338ff9e Merge branch 'develop' 2018-07-09 22:38:04 -07:00
snipe
78530ae123 Fix tests 2018-07-09 21:57:45 -07:00
snipe
52d605d13e Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-09 19:21:23 -07:00
snipe
0182615e7e Bumped version 2018-07-09 19:18:38 -07:00
snipe
fad84d4437 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-09 19:07:49 -07:00
snipe
c162e9a4de Bumped hash 2018-07-09 19:06:56 -07:00
snipe
bf1e742df6 Merge branch 'develop' 2018-07-09 19:05:19 -07:00
snipe
0e88a6b268 Fixed bug in branding image upload size text 2018-07-09 19:04:18 -07:00
Till Deeke
c1e870528e Fixes the label association (#5510) (#5790) 2018-07-09 14:51:17 -07:00
snipe
35fc001c58 Fixed #5742 - create_function() is deprecated 2018-07-05 20:49:01 -07:00
snipe
339263a295 Fixed #5751 - added option for unique constraint on serial 2018-07-05 19:30:36 -07:00
snipe
a44bd9abe0 Disallow deleting category if there are licenses 2018-07-05 18:02:25 -07:00
snipe
b850d47282 Merge branch 'develop' 2018-07-05 15:40:07 -07:00
snipe
4099c06b27 fix middleware priority: handle trusted proxies prior setup check
From @plexorama
2018-07-05 15:36:59 -07:00
snipe
e559879f91 Add @plexorama as a contributor 2018-07-05 15:35:37 -07:00
snipe
abb95e7872 Tweaked custom field default value layout
This still needs work. It’s ugly.
2018-07-05 15:31:27 -07:00
snipe
869de3d251 Fixed broken pagination on status labels API 2018-07-05 14:42:39 -07:00
snipe
f3526eccb9 Merge branch 'features/textarea-custom-field' into develop
# Conflicts:
#	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
2018-07-05 12:37:07 -07:00
Daniel Meltzer
880faa83a6 Importer2 checkout (#5771)
* Importer: checkout to location, backend changes+tests.

* Import location checkout. Frontend changes.

* Allow importing of item number/model number for consumables.
2018-07-05 12:22:24 -07:00
Juan Font
311f9fcefb Implemented method to get info on the current user of the API (#5722)
* Implemented method to get info on the current user of the API

* Move userinfo method to UsersController

* Added missing files
2018-07-02 20:35:10 -07:00
snipe
8732f299e6 Added logo class for logo override in custom CSS 2018-07-02 18:47:30 -07:00
Arunas Skirius
b30aac536a fixed the alignment of a couple navbar icons (#5764) 2018-07-02 18:10:25 -07:00
snipe
d7dc4ae0c0 Added manager to custom report 2018-06-27 00:45:09 -07:00
snipe
5bb4c85ccb Update twitter handle 2018-06-27 00:44:54 -07:00
snipe
80dda198c5 Parse line breaks in the detail view 2018-06-21 09:44:10 -07:00
snipe
9442736518 Adds textarea as a custom field type 2018-06-21 09:35:04 -07:00
snipe
2fbad52c71 Merge branch 'develop' 2018-06-21 07:54:01 -07:00
snipe
5975c9fac7 Add @ParadoxGuitarist as a contributor 2018-06-21 07:52:24 -07:00
snipe
7b0e392ecd Add @thelamer as a contributor 2018-06-21 07:52:04 -07:00
snipe
b51a10b46b Add @RichardRay as a contributor 2018-06-21 07:51:39 -07:00
snipe
6c58f59d72 Add @EarlRamirez as a contributor 2018-06-21 07:51:24 -07:00
snipe
cd9caa24ad Add @SjamonDaal as a contributor 2018-06-21 07:51:13 -07:00
snipe
cbb4b4d846 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-06-21 07:46:27 -07:00
snipe
ea4cdadc6e Bumped version 2018-06-21 07:44:58 -07:00
snipe
6638d64d68 Merge remote-tracking branch 'origin/master' into develop 2018-06-21 07:43:32 -07:00
snipe
eb412c2bcb Missed one 2018-06-21 07:43:12 -07:00
snipe
fde4a59510 Bumped version 2018-06-21 07:40:37 -07:00
snipe
1ee394aa69 Added Select2 to class for dropdowns 2018-06-21 07:40:30 -07:00
snipe
707f90573c Merge branch 'thelamer-docker-fixes' into develop 2018-06-21 07:31:55 -07:00
snipe
f8429ad357 Merge branch 'docker-fixes' of https://github.com/thelamer/snipe-it into thelamer-docker-fixes 2018-06-21 07:31:15 -07:00
snipe
aa5003d297 Merge branch 'develop' 2018-06-21 07:21:24 -07:00
snipe
e9901f5e58 Set composer timeout to 3000 2018-06-21 07:17:51 -07:00
Djamon Staal
f0d04a4a57 End help text with a period consistently. (#5731) 2018-06-21 07:13:29 -07:00
Djamon Staal
32e3f748d8 Make version footer configurable. (#5730) 2018-06-21 07:12:16 -07:00
Earl Ramirez
fa465a84df Added cron to list of packages (#5729) 2018-06-20 23:28:35 -07:00
Earl Ramirez
82cf1a4467 Updated SELinux label (#5728) 2018-06-20 23:28:20 -07:00
tiagom62
3bbd49dbad Don't run composer as root (#5689)
* dont run composer as root

* better naming
2018-06-20 19:59:44 -07:00
Daniel Meltzer
ad21857cae Update my email address across files. (#5716) 2018-06-20 01:59:59 -07:00
Daniel Meltzer
2d18b73138 Fix #5408. (#5715)
The temporary password cannot be added to the users data until after do
any update-related logic, otherwise their password will be overwritten.
2018-06-20 01:59:04 -07:00
Richard Ray Thomas
e7bc18dad4 Fixed inconsistent or incorrect comment labels (#5691)
Accessories table was labeled 'Checked out License table' likely just a duplicate of the above comment for the actual licenses table. Very minor.
2018-06-11 20:11:58 -07:00
snipe
62e4eabab0 Fixed #5693 - don’t truncate license key 2018-06-11 18:47:02 -07:00
tiagom62
d204eebab9 Ubuntu Bionic Beaver 18.04 support (#5687)
* bionic box

* bionic beaver support
2018-06-09 11:21:28 -07:00
thelamer
ed5823151b in order to avoid manual user intervention of running "php artisan migrate" before installation on a clean database pulling in migration scripts on startup if needed, also ownership of keys needs to be the docker user 2018-06-07 22:29:00 -07:00
snipe
968d7d1f11 Fixed #5354 - adds dd/mm/yyyy to localization 2018-06-05 15:30:50 -07:00
snipe
086683319a Fixed #5172 - autosum on assets detail view for components tab 2018-06-02 06:21:51 -07:00
snipe
e5c1f4847d Added emoji to exempt list to match current labels 2018-06-01 13:55:39 -07:00
snipe
087e114d34 Merge branch 'develop' 2018-05-31 16:34:25 -07:00
snipe
5a6b8bb856 Make default custom field value null 2018-05-31 14:21:18 -07:00
snipe
102f567cb5 Fixed typo 2018-05-31 10:55:48 -07:00
snipe
1a64879b65 Only allow remote user settings to be saved if the app is not in demo mode 2018-05-31 10:55:48 -07:00
Brady Wetherington
4a0e5e4b88 Possible fix for oauth issue in Docker 2018-05-31 01:36:51 -07:00
snipe
01857fb056 Added created_at to file upload UI, added header/footer to files modal, fixed string for actions 2018-05-29 16:38:23 -07:00
snipe
3afe4938f9 Fixed #5616 - removed duplicate call to ekkoLightbox on asset view
It’s already in the default blade
2018-05-29 16:36:42 -07:00
snipe
9e0544e735 Fixed #5617 - incorrect url to fieldset editing 2018-05-29 10:41:46 -07:00
snipe
3993c6ad6b Use custom header color in emails 2018-05-29 10:23:39 -07:00
Brian Monroe
649563457d Added notes field to acessories and consumable checkout pages. Resolves request #5607. (#5608) 2018-05-28 14:30:18 -07:00
snipe
6ec75714f0 Force default srtorage engine 2018-05-22 14:57:05 -07:00
snipe
15916e6668 Allow checkout to non-user objects even if the object requires checkout 2018-05-21 17:35:04 -07:00
snipe
76d0562716 Added last name to expected checkin notification
For non-US countries like Germany where it’s considered rude not to include last name
2018-05-21 17:34:27 -07:00
snipe
2ecc4aead7 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 15:43:48 -07:00
snipe
d8210847a4 Bumped version 2018-05-21 15:43:04 -07:00
snipe
ece916e12f Merge branch 'develop' 2018-05-21 15:41:40 -07:00
snipe
1a29d4f60f Check for > 0 expected assets 2018-05-21 15:41:19 -07:00
snipe
11832daf5c Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 15:17:40 -07:00
snipe
79555f5647 Bumped version 2018-05-21 15:16:56 -07:00
snipe
1868013704 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 14:59:08 -07:00
snipe
39cc13f155 Bumped version 2018-05-21 14:58:16 -07:00
snipe
e636875797 Merge branch 'develop' 2018-05-21 14:56:57 -07:00
snipe
260749337e Updated languages 2018-05-21 14:54:36 -07:00
snipe
20a3b556bb Removed log 2018-05-21 14:33:27 -07:00
Tim Bishop
fa0c58e42a Don't install require-dev packages. (#5549)
This is already set for a local composer install, but not a global
one. They should be consistent.
2018-05-18 16:06:05 -07:00
snipe
abbb94239d Add @doekman as a contributor 2018-05-18 16:05:12 -07:00
snipe
d89ef43834 Make category counters ints 2018-05-18 16:05:12 -07:00
Doeke Zanstra
f84ab2beda Fixed bug #5540 (#5550)
Error message with button "Test LDAP" is empty #5540
2018-05-18 16:04:34 -07:00
snipe
86d398abda Fixed category test 2018-05-18 14:58:06 -07:00
snipe
883c65981b More test fixes 2018-05-16 20:09:18 -07:00
snipe
8eb96efa13 Merge branch 'develop' 2018-05-16 19:38:23 -07:00
snipe
e9973670ea Could should not be equal to 1 2018-05-16 19:38:02 -07:00
snipe
ef8d2d06df Fixes #5519 - count() for php 7.2 2018-05-16 19:35:14 -07:00
snipe
0b5bb520a7 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-16 19:24:31 -07:00
lea-mink
233fb23cb8 Create asset maintenance - Added orange bar for required asset to edit view (#5520)
* Added orange bar for required asset to edit view

* disable redirection to asset maintenances view

* Update - disable redirection to asset maintenances view
2018-05-16 19:23:23 -07:00
snipe
fa89f45cb8 Bumped hash 2018-05-16 19:22:59 -07:00
snipe
4c656c0321 De-normalize new counters from 4.3.0 (#5547)
* Added de-norm counter migration for assets

* Renaming counter columns, since Eloquent has a magical *_count helper

* Added artisan command to sync counters (one-off)

* Update API to use de-normed fields

* Increment counters for checkin;/checkout

* Derp.

* Added request increment/decrementer

* Move increment for checkout to the Asset::checkout method

* Added “could take a while” message
2018-05-16 19:20:43 -07:00
snipe
87c6ee2035 Importer test fixes 2018-05-16 19:05:00 -07:00
snipe
aab190423f Partial fix for license+category tests 2018-05-16 18:35:11 -07:00
snipe
05e3e6bda6 Added indexes for speed 2018-05-14 20:27:56 -07:00
Tim Bishop
1f299ed73e Fix padding on next page with Chrome. (#5509)
It looks like Chrome (and probably other browsers) is optimising the
next-padding div out since it has no content. This means the margin
doesn't apply. Adding the nbsp, and making sure it takes up no space
itself, is enough to make the margin do the right thing.

I also tried using padding instead of margin, but this results in an
extra page at the end of the output (there'd need to be a way to stop
page-break and next-padding appearing at the end of the document). I also
tried setting a min-height on next-padding, but this had the same issue.
2018-05-09 17:00:36 -07:00
snipe
4ba9792fbe Merge branch 'develop' 2018-05-09 15:29:59 -07:00
snipe
f405511b6b Fixed #5501 - regression disallowing license files to be downloaded 2018-05-08 14:24:51 -07:00
snipe
8ad5eb3e59 Fixed #5500 - present() on correct location value 2018-05-08 09:21:43 -07:00
snipe
3df8fa99f0 Merge branch 'develop' 2018-05-08 07:37:44 -07:00
snipe
cca97341e9 More flexible date range in datepicker for expected checkin filter on custom reports 2018-05-08 07:37:18 -07:00
snipe
13195d06fd Fixed #5491 - added default location filter for custom report 2018-05-08 07:34:14 -07:00
snipe
9b6e86b55c Merge branch 'develop' 2018-05-08 05:59:54 -07:00
snipe
65cf7527b0 Added model name to expected checkin reminders 2018-05-08 05:59:34 -07:00
snipe
f74d50439c Merge branch 'develop' 2018-05-08 05:39:28 -07:00
snipe
28e7ca5a84 Formatting tweaks to email notifications 2018-05-08 05:39:11 -07:00
snipe
8f64da5bc7 Added admin alert on expiring notifications 2018-05-08 05:27:03 -07:00
snipe
25f537e730 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-08 03:49:28 -07:00
snipe
6d8af3d9c0 Bumped hash 2018-05-08 03:48:35 -07:00
snipe
e56a46882d Include EULA/acceptance in license interfaces 2018-05-08 03:47:28 -07:00
snipe
0476ffecdb Removed debugging comments 2018-05-08 03:46:48 -07:00
snipe
47a0400a72 Fixed comment typo 2018-05-08 03:14:25 -07:00
snipe
de3417d557 Added link to SnipeitPS powershell wrapper 2018-05-08 01:34:53 -07:00
snipe
83b546c1c5 Disable privacy policy link in footer if in demo mode 2018-05-08 01:07:15 -07:00
snipe
04709dc1df Fixed #5477 - added GDPR privacy policy link in email and webpage 2018-05-08 00:50:13 -07:00
snipe
f48171dcab Add category to licenses 2018-05-08 00:14:38 -07:00
snipe
7b8362b64c Added license categories 2018-05-04 21:01:38 -07:00
snipe
188538651a Fixed slack notification error if location is not set on checkin 2018-05-04 21:01:25 -07:00
snipe
a9fc7e04e9 Fixed php7.2 count issue 2018-05-04 21:00:58 -07:00
snipe
4812285512 Fixed #5482 - typo in custom fields info 2018-05-04 14:52:19 -07:00
snipe
ec1fa8e90a Merge branch 'develop' 2018-05-03 08:06:58 -07:00
snipe
3a1b432234 Fixed #5472 - show_in_email for custom fields missing in edit field UI 2018-05-03 08:06:28 -07:00
snipe
98f853128a Merge branch 'develop' 2018-05-03 05:43:49 -07:00
snipe
276d2bc866 Fixed advanced search on model number
(RB:347)
2018-05-03 05:43:25 -07:00
snipe
0472e3a3e5 Merge branch 'develop' 2018-05-02 14:41:10 -07:00
lea-mink
a0afa9f2e8 Modified the affectation of the value of the password in credential mail sent for the first user sign up (#5446)
* Modified the affectation of the value of the password

* Remove e()
2018-05-02 14:40:41 -07:00
snipe
0116fa9b95 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-02 14:35:16 -07:00
snipe
42f0eebf8f Bumped version 2018-05-02 14:33:01 -07:00
snipe
8194660d16 Updated languages 2018-05-02 14:32:50 -07:00
snipe
532a9ef9fe Additional languages in language selector 2018-05-02 14:20:42 -07:00
snipe
0be69f57ac Improved files display 2018-05-02 14:13:06 -07:00
snipe
97f748d58e Removed old reports methods and routes
We only use the custom asset report now
2018-05-02 03:44:31 -07:00
snipe
3662a58ad8 Removed unusued parameters in BS table formmatters 2018-05-01 21:35:07 -07:00
snipe
f5bf6a0beb Added larger icon size 2018-05-01 21:34:31 -07:00
snipe
0fc0aa39b1 Use flexible language string for confirm delete 2018-05-01 21:33:51 -07:00
snipe
c8cf46f62b Fixed #5431 - category widget error on dashboard 2018-05-01 21:33:03 -07:00
tiagom62
28a1960fda PHP 7.1 for all (#5456)
* PHP 7.1 for all

* fedora support. strict versioning.

* Add fedora 26 and 27
2018-05-01 21:02:14 -07:00
Brady Wetherington
44bb79c90e Fix to lockfile, update to package.json to non-vuln versions (#5460) 2018-05-01 21:01:45 -07:00
snipe
2f01bb2e63 Update issue templates 2018-05-01 21:01:27 -07:00
snipe
71708e349c PHP7.2 count fixes (#5427)
* PHP 7.2 count() fixes

* Re-enable php travis 7.2
2018-04-29 06:10:49 -07:00
tiagom62
6ad3d40216 installer automation? (#5433)
* cli

* less spaces!

* rename functions

* move progress spinner variables into progress ()

* $hosts was used in one place

* missed this one

* make testing snipeit.sh easier
2018-04-29 06:09:46 -07:00
snipe
d6d498bc8f Fixes incorrect gate for “new” button for status labels on asset create/edit form 2018-04-26 16:35:08 -07:00
snipe
6df7f6d6ec Fixes wrong users index route name 2018-04-26 16:31:02 -07:00
snipe
5365182c86 Fixed advanced search on supplier, count for PHP7.2 2018-04-25 20:25:03 -07:00
snipe
e5121b33e6 Shorter syntax for demo disabled 2018-04-25 19:23:03 -07:00
snipe
5fb4eacf5b Disable history importer on the demo 2018-04-25 19:18:05 -07:00
Hannah Tinkler
c4c520c1a3 Fixes #4445: prevents assigned assets from being checked out in bulk checkout (#5421)
* Fixes #4445: prevents assigned assets from being checked out in bulk checkout

* Updates data attribute to more versatile 'data-asset-status-type'

* Fixes broken unit test
2018-04-25 02:39:23 -07:00
snipe
9eab9ad40d Merge branch 'develop' 2018-04-24 16:25:42 -07:00
snipe
a2fef11016 Use Bootstrap Tables on custom fields screens for column selector 2018-04-24 16:25:10 -07:00
snipe
088eb3da14 Merge branch 'develop' 2018-04-24 13:24:24 -07:00
snipe
a0706b8780 Added gitignore 2018-04-24 13:24:15 -07:00
snipe
0e1dfcf408 Changed directory for audits image dir 2018-04-24 13:20:15 -07:00
snipe
3ca9f5f389 Merge branch 'develop' 2018-04-24 12:49:28 -07:00
snipe
5acd225f0f Fixed #5423 - removed required text on preflight 2018-04-24 12:48:58 -07:00
snipe
1708bb5cdf Fixes #5422 - remove extension ending from uploaded file name 2018-04-24 12:47:09 -07:00
snipe
8127484081 Better error checking for private file display method 2018-04-24 03:12:30 -07:00
snipe
103c75e78c Removed max cap in image validation 2018-04-24 03:12:17 -07:00
snipe
d886dcc7c3 Reset skin for demo 2018-04-24 03:00:56 -07:00
snipe
ea54d73911 Merge branch 'develop' 2018-04-24 02:59:19 -07:00
snipe
1ef4cc9fc2 Fixed #4301 - added image upload to audit 2018-04-24 02:54:54 -07:00
snipe
4785db4471 Update factory format for custom fields 2018-04-23 21:24:59 -07:00
lea-mink
c8cbc55b59 Bulk Checkout to Assets and Location (#5385) 2018-04-23 21:24:49 -07:00
Hannah Tinkler
8d501e1c24 Feature/custom fields default values (#5389)
* Fixes CustomFieldsetsController::fields() which I think is not used anywhere else and don't think ever worked as you can't call get() on a Collection.
Have tested extensively and doesn't seem to affect anywhere else?

* Adds default value functionality

* Adds built assets

* Fixes assignment to asset_model_id which should have been evaluation and alters route so it sits more in line with existing work

* Updates built assets

* Remove silly docker.env file; fix Dockerfile to preserve Oauth keys (#5377)

* Added department to custom asset export
Updates build assets

* Adds translation support for 'add default values' checkbox label
2018-04-23 21:16:55 -07:00
snipe
132a5d424d Check for valid accessory category 2018-04-23 16:04:01 -07:00
snipe
4c5f20fde4 Merge branch 'develop'
# Conflicts:
#	app/Importer/Importer.php
#	config/version.php
2018-04-23 13:33:34 -07:00
snipe
35a20fb197 Fixes #5410 - missing subject string for license delivery 2018-04-23 13:30:32 -07:00
snipe
63848e8fc2 Additional rules for non-green skins 2018-04-23 13:30:32 -07:00
lea-mink
16a7409ce1 Matched Asset currency and Asset Maintenance currency in Asset Maintenance view (#5386)
* Matched Asset currency and Asset Maintenance currency in Asset Maintenance editing view

* Cleaning code & add condition on currency of an asset location
2018-04-20 14:03:31 -07:00
Stephen
c23955d0b5 Allow setting of "ldap_import" through the API (#5218)
* Allow setting of "ldap_import" through the API, this will allow cusom scripts to be made to import data from Active directory using the API, this would allow any field to be filled such as the manager (based on the ID), department etc.

* Password fix for LDAP through API
2018-04-20 14:02:52 -07:00
Tim F
18ef355d2a Updated rules (#5325)
Added .modal-content, as I either forgot it previously or an update started calling it for popups on the New Asset pages.

Brought .main-header, .navbar, .main-header, and .logo into the fold with the variables -- good addition there :)
2018-04-20 13:56:45 -07:00
snipe
def1eb0b5f Bumped hash 2018-04-20 13:52:32 -07:00
snipe
bdbc189a4f Bumped hash 2018-04-20 13:51:54 -07:00
snipe
6efe9efab8 Fixes #5393 - added notes to suppliers API (#5400) 2018-04-19 18:28:22 -07:00
Daniel Meltzer
7b72dde222 Another importer fix. (#5383)
* Fix condition where matching user fails when providing a username but no full name.  Also shortcircuit username matching if a user exists.

* Simplify Logic

If the user provided is numeric, but doesn't exist in the database, assume that the user's name is a number and go through all relevant generation.  of email/first+last names.  Alternatively we may want to abort or remove the is_numeric bits.. it seems a little counterintuitive
2018-04-18 07:58:26 -07:00
snipe
5948a0b235 Added department to custom asset export 2018-04-16 20:10:38 -07:00
Brady Wetherington
a6fc7ba07a Remove silly docker.env file; fix Dockerfile to preserve Oauth keys (#5377) 2018-04-16 16:29:42 -07:00
snipe
a326adc863 Add @hannahtinkler as a contributor 2018-04-13 14:55:11 -07:00
snipe
ab31c633d0 Possible fix for #5211 2018-04-13 14:55:11 -07:00
Hannah Tinkler
48254a93f0 Fixes #5338 - mark required setup fields during setup (#5359) 2018-04-13 14:52:32 -07:00
snipe
365c8c18d7 Fixed #5319 - signature pad too small on mobile 2018-04-06 19:19:31 -07:00
snipe
bbc0695a8f Added count of checkins, checkouts, requests (#5314)
* Added count of checkins, checkouts, requests

* Removed old commented items

* Use actionlog instead of redefining the relationship
2018-04-06 16:23:39 -07:00
snipe
1d0f8f01f2 Fixed #5266 - small scroll window for EULA accept screen 2018-04-06 15:50:45 -07:00
snipe
2253439940 Added default location/address to custom report 2018-04-05 17:33:25 -07:00
snipe
c1838a60df Bumped hash 2018-04-04 17:36:47 -07:00
snipe
8a6713d5c0 WIP - Improved requested assets (#5289)
* WIP - beginning of improved requested assets

- Use Ajax tables for faster loading
- Use new notifications for requesting an asset

TODO:
- Use ajax tables for requestable asset models
- Use new notifications for canceling an asset request
- Expire requests once the asset has been checked out to the requesting user

* Only show asset name in email if it has one

* Refactor requested method to only include non-canceled requests

* Refactored requestable assets to log request and cancelation

* Added softdeletes on checkout requests

* Differentiate between canceling and deleting requests

* Added asset request cancelation notification

* Added timestamps and corrected unique key on requests table

* Improved requests view

* Re-use blade for cancel/request email

* Refactored BS table formatter for requested assets

* Location name min reduced to 2

* Added PAT test as maintenance option

This needs to be refactored into database-driven options with a UI

* Better slack message

* Added getImageUrl method for assets

* Include qty in request notifications

TODO:
- Try to pull requested info from original request for cancelation, otherwise it will default to 1

* Removed old asset request/cancel emails

* Added user profile asset request routes

* Added profile controller requested assets method

* Added blade link to requested assets for profile view

* Sort user history desc

* Added requested assets blade

* Added canceled at to checkoutRequest method

* Include qty in request

* Fixed comment, removed allowed_columns

* Removed Queable methods, since we don’t use a queue

* Fixed return type in method doc

* Fixed version number

* Changed id to user_id for clarity
2018-04-04 17:33:02 -07:00
snipe
201efecafa Fixed #5293 - component category drilldown 2018-04-02 16:12:19 -07:00
Daniel Meltzer
79f061be93 Move first_name and last_name to only be displayed for user importer. Also sort items alphabetically regardless of their source. (#5292) 2018-03-30 19:32:43 -07:00
snipe
4786c1c59f Check for custom fields in Importer 2018-03-30 18:50:09 -07:00
snipe
116cad88a0 Fixed #5279 - [regression] edit button not appearing in asset view 2018-03-29 11:28:37 -07:00
snipe
7d1200c434 Add @lea-mink as a contributor 2018-03-29 05:32:52 -07:00
lea-mink
99a9707a34 Add title field in Asset Maintenances list/filter/export (#5287) 2018-03-29 05:32:09 -07:00
Daniel Meltzer
787f2390fb Add location_id to fillable (#5286)
Should fix #5268
2018-03-29 05:11:07 -07:00
snipe
a510ac4052 Fixed #5272 - make city min length 2 instead of 3 2018-03-29 04:36:18 -07:00
snipe
9f414baa99 Remove notifiable from asset 2018-03-28 19:08:08 -07:00
snipe
086711e467 Fixed checkout to checkin in loggable checkin trait 2018-03-28 19:01:51 -07:00
Daniel Meltzer
bf2d6dd0fa Avoid populating db manually. (#5255)
* Avoid populating db manually.  Instead rely on a seeded database existing and use api/fucntional tests based on that.

* Seed the Setting object with default values.

* Update Setting seeder to match web default.  Also only generate one Setting instance.
2018-03-28 19:01:51 -07:00
Daniel Meltzer
4c43226b99 Fix #4798. (#5271)
Filter down the custom fields we are iterating over to only custom fields in the header row.
This prevents nulling out fields when performing an import-update that doesn't include
all custom fields as well as properly nulls out values between items being imported.
2018-03-28 19:01:51 -07:00
snipe
dfe8aac10b Fixed checkout to checkin in loggable checkin trait 2018-03-28 19:01:10 -07:00
snipe
983786c29f Fixed component checkout bug 2018-03-28 18:53:18 -07:00
snipe
edb81425cf Fixed component checkout bug (#5282) 2018-03-28 18:53:03 -07:00
Daniel Meltzer
69478aea58 Avoid populating db manually. (#5255)
* Avoid populating db manually.  Instead rely on a seeded database existing and use api/fucntional tests based on that.

* Seed the Setting object with default values.

* Update Setting seeder to match web default.  Also only generate one Setting instance.
2018-03-27 17:43:28 -07:00
Daniel Meltzer
e9d9c0c42d Fix #4798. (#5271)
Filter down the custom fields we are iterating over to only custom fields in the header row.
This prevents nulling out fields when performing an import-update that doesn't include
all custom fields as well as properly nulls out values between items being imported.
2018-03-27 16:52:44 -07:00
snipe
b41adc2eee Merge branch 'develop' 2018-03-26 16:05:50 -07:00
snipe
115d6e29df Added location address to custom asset report export 2018-03-26 15:59:09 -07:00
snipe
83781f3a70 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-03-26 15:20:40 -07:00
snipe
5f1ec550ec Fixed hash 2018-03-26 15:16:35 -07:00
snipe
95f8dccc14 Bumped hash 2018-03-26 15:15:43 -07:00
snipe
0134ec7b04 Added asset presenter, fixed asset maintenances button in asset view 2018-03-26 14:49:49 -07:00
snipe
590938fa33 Added asset maintenances presenter, fixed broken action buttons on asset view 2018-03-26 14:46:37 -07:00
snipe
46f5f21368 Notification improvements (#5254)
* Added “show fields in email” to custom fields

* Added “show images in email” to settings

* Added nicer HTML emails

* Break notifications out into their own, instead of trying to mash them all together

* Remove old notification for accessory checkout

* Janky fix for #5076 - “The asset you have attempted to accept was not checked out to you”

* Add method for image url for accessories

* Added accessory checkout email blade

* Make accessory email notification on checkout screen consistent with assets

* Added native consumables notifications

* Fixes for asset notification

* Updated notification blades with correct-er fields

* Updated notifications

* License checkin notification - does not work yet

Need to figure out whether the license seat is assigned to a person or an asset before we can pass the target

* Added alternate “cc” email for admins

* Only try to trigger notifications if the target is a user

* Fix tests

* Fixed consumable URL

* Removed unused notification

* Pass target type in params

* Show slack status

* Pass additional parameters

There is a logic bug in this :( Will send to slack twice, since the admin CC and the user are both using the same notification. Fuckity fuck fuck fuck.

* Pass a variable to the notification to supress the duplicate slack message

* Slack is broken :( Trying to fix

Will try a git bisect

* Put preview back into checkout

* Pulled old archaic mail

* Removed debugging

* Fixed wrong email title

* Fixed slack endpoint not firing

* Poobot, we hardly knew ye.

* Removed old, manual mail from API

* Typo :-/

* Code cleanup

* Use defined formatted date in JSON

* Use static properties for checkin/checkout notifiers for cleaner code

* Removed debugging

* Use date formatter

* Fixed target_type

* Fixed language in consumable email
2018-03-25 13:46:57 -07:00
snipe
b6bf3800c7 Merge branch 'develop' 2018-03-23 16:20:09 -07:00
snipe
6043d37b05 Added the backup env flag to the example env 2018-03-23 16:19:26 -07:00
snipe
34919b0396 Added API calls to look up assets by tag and serial 2018-03-23 14:50:11 -07:00
snipe
846613c244 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-03-22 20:48:02 -07:00
snipe
c4c137dc08 Bumped to 4.2.0 2018-03-22 20:47:09 -07:00
snipe
130bb19a11 Merge branch 'develop' 2018-03-22 20:31:04 -07:00
snipe
d0f14d7a3c Fixed #5186 - email being sent to admin as well as user on checkout
This should be introduced as a setting, but not arbitrarily send
2018-03-22 20:30:45 -07:00
snipe
795c2cf540 Merge branch 'develop' 2018-03-22 20:27:40 -07:00
snipe
bcd02ce718 Inelegantly fixed #5186 - placeholder notification being sent
This needs work
2018-03-22 20:27:14 -07:00
snipe
469efc923b Merge branch 'develop'
# Conflicts:
#	README.md
#	config/version.php
2018-03-22 20:08:55 -07:00
snipe
dcd74f6922 Bumped version 2018-03-22 20:07:06 -07:00
snipe
4b7eaf6dae Updated translations 2018-03-22 20:01:45 -07:00
Brady Wetherington
085f909f35 Change duplicate header check to return 1-based header indices 2018-03-22 19:40:12 -07:00
snipe
4f394b683d Added test CSV with blank column headers 2018-03-22 19:35:43 -07:00
snipe
7096ebedbc Sample CSV with duplicated headers 2018-03-22 19:22:28 -07:00
Brady Wetherington
0b0243a5e0 Hoist file-reading before file-renaming, catch duplicate headers (#5244) 2018-03-22 19:16:15 -07:00
snipe
5c9f9b1685 Add @cepacs as a contributor 2018-03-22 15:49:46 -07:00
snipe
bf74bb196d Merge branch 'develop' 2018-03-22 14:57:15 -07:00
snipe
c6e14caa31 Removed e() from array_smart_custom_field_fetch 2018-03-22 14:36:36 -07:00
snipe
506602b257 Remove escaping on import value checker 2018-03-22 14:25:52 -07:00
snipe
a79ec0d408 Merge branch 'develop' 2018-03-22 14:18:30 -07:00
snipe
140724be2e Fixed #5217 - permissions error with importer for non-admins 2018-03-22 14:17:44 -07:00
snipe
04af1f3c97 Moved remote user stuff below password security stuff 2018-03-22 14:00:35 -07:00
snipe
cb4f1daac1 Fix for a bad merge - the migration was changed between master and develop :( 2018-03-22 13:45:55 -07:00
snipe
96bab5697d Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
#	database/migrations/2018_02_22_160436_add_remote_user_settings.php
2018-03-22 09:46:50 -07:00
snipe
abac3e7758 Bumped hash 2018-03-22 09:44:54 -07:00
snipe
8557cb5305 Added - make accessory manufacturer field searchable 2018-03-22 09:43:53 -07:00
tiagom62
735840aafd Revert removing setting hostname (#5208) 2018-03-21 19:09:37 -07:00
David Kaatz
1c777888d5 Authentication via REMOTE_USER (clean repull) (#5204)
- Implementation in Login
- Configuration
- Database migration
2018-03-21 19:09:37 -07:00
Brady Wetherington
625810cd8a Support \r-terminated files better from the Importer (#5184)
* Hoist the ini_get/ini_set auto-detect line endings code higher

* Placate the irascible Codacy
2018-03-21 19:08:06 -07:00
Anh DAO-DUY
cd63abd72e Add default_label field missing in status_labels table (Travis CI) (#5180) 2018-03-21 19:08:06 -07:00
snipe
d911b776bf Check for associated maintenance asset
This should probably never happen, but triggers on the demo sometimes because of fluctuating data seeding
2018-03-21 19:08:06 -07:00
tiagom62
9e7d1b3ed8 Revert removing setting hostname (#5208) 2018-03-15 10:20:42 -07:00
David Kaatz
53735f2026 Authentication via REMOTE_USER (clean repull) (#5204)
- Implementation in Login
- Configuration
- Database migration
2018-03-14 12:48:07 -07:00
David Kaatz
a43b31400f Authentication via REMOTE_USER (#5142)
* Added authentication via Remote User

* - Removed nullable from remote_user settings fileds and used just default values instead
- Removed german translations
- Removed 401 error page and replaced usage with 403 error page as 401 was actual a duplicate of 403
- Replaced usage of $_SERVER['REMOTE_USER'] with Laravels API Request::server('REMOVE_USER')

* - Fixed request usage
2018-03-13 20:07:52 -07:00
Brady Wetherington
e15f2ac8ab Support \r-terminated files better from the Importer (#5184)
* Hoist the ini_get/ini_set auto-detect line endings code higher

* Placate the irascible Codacy
2018-03-13 20:06:53 -07:00
Anh DAO-DUY
06e760081c Add default_label field missing in status_labels table (Travis CI) (#5180) 2018-03-09 13:05:14 -08:00
snipe
fc8637c81a Check for associated maintenance asset
This should probably never happen, but triggers on the demo sometimes because of fluctuating data seeding
2018-03-07 22:22:36 -08:00
snipe
01eaf48471 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-03-07 18:25:12 -08:00
snipe
238a075c6a Bumped hash 2018-03-07 18:24:09 -08:00
snipe
1d130b4a89 Fixed asset model permission not granted for edit 2018-03-07 18:22:49 -08:00
snipe
95d935d917 Added warning to not edit config files manually 2018-03-07 17:39:13 -08:00
snipe
c4db8d37c2 Fixed #5168 - users without superadmin could not see custom fields UI even if granted 2018-03-07 13:37:37 -08:00
snipe
17e0154995 Fixed #5160 - make field_values readable via custom fields API 2018-03-06 13:11:45 -08:00
snipe
d60c9800c2 Check that the id key exists to prevent any weird edge cases for location 2018-03-05 22:44:05 -08:00
snipe
04d2542b81 Fixed #5078 - check for object or array as location in LDAP sync 2018-03-05 22:42:40 -08:00
snipe
9a25cb3ee7 Set default labels in seeders 2018-03-05 22:16:36 -08:00
snipe
1e22b8e567 Fixed #5138 - added default_label flag to status labels 2018-03-05 22:04:16 -08:00
snipe
d05dfb18a7 Fixed #5150 - added lastname first initial as username format 2018-03-05 21:39:05 -08:00
snipe
a5c6ddb8ac Change gate for updating assets via the API to edit 2018-03-05 21:27:17 -08:00
snipe
90bff709a4 Merge branch 'develop' 2018-03-05 20:58:08 -08:00
snipe
052132ec37 Remove pyrchase cost from accessory search 2018-03-05 20:57:50 -08:00
snipe
d42361bac1 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-03-05 20:27:50 -08:00
snipe
2df19bcbb4 Only pull category matches for accessory category 2018-03-05 20:22:44 -08:00
snipe
0ef4251462 Include new icons in stalebot 2018-03-05 19:06:39 -08:00
snipe
a4af81563a Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-03-05 16:26:49 -08:00
snipe
36cd63836e Fixed #5151 - added asset tag to maintenances 2018-03-05 16:26:40 -08:00
tiagom62
48f9959fcd snipeit.sh improvements (#4708)
* snipeit.sh improvements

* Missing dependency for upgrade.php #4726

* Upgrade noninteractively
2018-03-04 21:48:44 -08:00
snipe
9effa3d2ab Fixed manufacturer restore 2018-03-03 19:14:27 -08:00
snipe
0782222c6b Removed commenting
This restore() is still not working, though not sure why. Seems like it should be pretty straghtforward, and yet…

Additonally, manually setting the deleted)at date to null or blank isn’t working either. I’m sure I’m just missing something obvious.
2018-03-03 18:49:02 -08:00
snipe
f7784b6543 Fixed #2402 - add ability to restore manufacturers 2018-03-03 18:46:19 -08:00
snipe
fabc9e5d1c Bumped hash 2018-03-03 17:15:59 -08:00
snipe
06805d70fe Fixed incorrect settings HTML 2018-03-03 17:07:28 -08:00
snipe
123e317e52 Check for an array (for PHP7.2 support) 2018-03-03 17:07:07 -08:00
fordster78
68a9855506 New First Admin Notification (#5147)
* New First Admin Notification

* Include Last name in Welcome and First admin Notifications
2018-03-03 14:37:42 -08:00
fordster78
688a3251a9 New Welcome Notification (#5146)
* New Test Notification

Created Test Notification.
Updated Vendor Mail message.blade files.
Updated api settings controller to use Notification Façade.

* Add show URL in Emails condition

* New Welcome Notification
2018-03-03 12:44:41 -08:00
snipe
30c5cc1dc4 Additional dark themes 2018-03-02 20:01:41 -08:00
snipe
4ab1d5ca7f Fixed #5110 - crash on accessory checkin missing last_name 2018-03-02 19:26:41 -08:00
snipe
5a808a0f91 Fix for header color in green skin 2018-03-02 19:17:12 -08:00
snipe
a6cc31944a Added package-lock 2018-03-02 19:17:01 -08:00
snipe
41a9e5f710 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-03-02 18:01:41 -08:00
snipe
31790e0bb7 Tweaks to theme settings 2018-03-02 18:01:36 -08:00
fordster78
4e0c8e218d New Test Notification (#5137)
Created Test Notification.
Updated Vendor Mail message.blade files.
Updated api settings controller to use Notification Façade.
2018-03-02 18:01:20 -08:00
Geoff Young
4fe4c0c72a Add viewKeys permission check on Asset page (#5141) 2018-03-02 18:00:22 -08:00
snipe
f171357e36 Removed unused skin files, added skin setting option 2018-03-02 17:50:40 -08:00
snipe
ef91cc992e Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2018-03-02 16:52:00 -08:00
snipe
04b92f2ad2 Added info on third-party libraries 2018-03-02 16:51:56 -08:00
Tim F
4f049112de Patch for dark-green.css to include more in template. (#5122)
* Update dark-green.css

Added several new rules, to include little bits that weren't previously. Mostly centered around the new 'form' page and the various settings pages.

* Update dark-green.css

Fixed buttons for "View All" on main page. Missed those previously. Well, the buttons were included, but not the hover effects.
2018-03-01 20:45:22 -08:00
Tim F
86242b322c Create dark-green.css (#5118) 2018-02-27 14:10:08 -08:00
Tim F
14af95001e Create dark-green.css (#5117) 2018-02-27 13:37:12 -08:00
snipe
2dd56f5bda Backup the .env if BACKUP_ENV is set to true 2018-02-26 18:12:48 -08:00
snipe
c250c632f3 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-26 15:46:49 -08:00
snipe
e3fb4f8799 Bumped hash 2018-02-26 15:44:36 -08:00
snipe
b4f704d7f1 Fixed 2FA reset button 2018-02-26 15:43:49 -08:00
Daniel Meltzer
9ee2c6be57 Api tests2 (#5098)
* Cleanup

* API tests for asset models and related cleanup/improvements

* Api license test.  Tests incomplete because create/update/destroy are not implemented yet in the controller

* API Category tests.

* Manufacturers API Test.

* Implement License Create/Update/Delete Methods for API and enable test.

* Add missing gate for api.  Fixes only superadmins being able to generate Personal Access Toekns
2018-02-25 12:10:02 -08:00
Daniel Meltzer
7de8f71f58 Api tests (#5096)
* Use the formated date helper to clean up verifications.

* Add Checkin/Checkout api tests.

* Accessories api test

* Add Companies API Test.

* Return ModelNotFound as a 404.

* Cleanups/simplficiations/updates.

* Locations api test.

* currency and image should be fillable on location.

* Update components api test.

* Use findOrFail so we return a 404 instead of a 200.  Matches other item types.

* order_number should be fillable in component.

* Add updated_at and permissions to information returned from api for a user.

* Add users test and flesh out factory and fillable fields.

* Add test for assets method

* API status label test.

* Disable php7.2 for now on travis until the count(null) issues are remedied

* Add serial to update.

* API model not found should return a 200
2018-02-24 19:01:34 -08:00
snipe
b6a75093b7 Removed duplicate location_id assignment 2018-02-24 14:06:10 -08:00
Daniel Meltzer
d54dda40d3 Functional Tests Improvements (#5095)
* Rely on laravel transactions instead of refreshing the database dump between functional test runs.  Cuts functional test runtime by 75%.  Also use mysql to seed directly.

* Split functional tests into two groups on travis to reduce overall memory usage.  Any new tests will need to be added to one of these two files before they are run on travis.  running all functional tests simultaneously still works locally.

* Fix name of test in group.
2018-02-24 12:03:33 -08:00
snipe
a705c714ab Merge branch 'develop' 2018-02-23 05:53:44 -08:00
snipe
0e48837eec Fixed assets checked out to assets listing table 2018-02-23 05:53:00 -08:00
snipe
5e5ba54c3e Disallow checkout asset to itself 2018-02-23 05:52:19 -08:00
snipe
4403b139d5 Merge branch 'develop' 2018-02-23 05:12:49 -08:00
snipe
103974cae4 Fixed statuslabel detail view table 2018-02-23 05:09:14 -08:00
snipe
aea37467d8 Attempt to add codeclimate test coverage 2018-02-22 22:34:08 -08:00
snipe
9d2ed7bc5f Merge branch 'develop' 2018-02-22 21:49:37 -08:00
snipe
bfb11d249e Tweaked slack test UI 2018-02-22 21:47:48 -08:00
Daniel Meltzer
f7dbda4ed3 Disable broken tests (#5073)
* Work towards a functional travis.  Step 1: Disable broken unit tests.

* Fix functional tests

This updates the login information and model factories to work with
changes to source.

* Importer name/full name fixes.

Fix a bug where "name" was used ambigously and mapping "item name" to
"name" would confuse the importer into thinking it should also be a user
name.  Now we default to "full name" for the users name, and "item name"
for the item name.  These are still both configurable through the custom
mapping.

Also update sample csvs and remove an outdated sample.

* Max length of supplier notes is 191, not 255, as per default laravel string length.  Might make sense to change this to a text field in the future to match other places.

* Use sqlite/different db setup for unit tests.

* Fix assets api test.

* Fix Components API test.

* increase travis memory limit for functional tests.

* Use travis config for api tests as well.

* Fix memory limit file.

* Disable ApiComponentsAssetsCest until it's fixed.
2018-02-22 21:46:58 -08:00
snipe
7c9c6ea3df Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-22 16:40:19 -08:00
snipe
7d49c4de87 Bumped hash 2018-02-22 16:39:12 -08:00
snipe
e49a36c9fd Fixed #5084 - min supplier name now 1 2018-02-22 16:37:05 -08:00
snipe
236e773438 Fixed incorrect component not found string 2018-02-22 16:36:01 -08:00
snipe
e3144c3093 Added Slack test button 2018-02-22 16:35:34 -08:00
snipe
cbd8409611 Fixed #5067 - account for only one name in generateFormattedNameFromFullName 2018-02-22 14:10:58 -08:00
snipe
a85b38850c Added roave security-advisories to composer
https://packagist.org/packages/roave/security-advisories
2018-02-22 13:22:13 -08:00
snipe
e01f1ae8ed Partial fix for #2892 - Added export for license seat assignments 2018-02-21 17:28:55 -08:00
snipe
d60a4c5f87 Merge branch 'develop' 2018-02-21 16:47:03 -08:00
snipe
9c1fe2c922 Reverting previous change - qty already existed in allowed columns. Derp. 2018-02-21 16:46:37 -08:00
snipe
3131c0d0ac Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 16:45:21 -08:00
snipe
364e9cf3de Bumped version 2018-02-21 16:43:00 -08:00
Daniel Meltzer
5a84863873 Importer name/full name fixes. (#5072)
Fix a bug where "name" was used ambigously and mapping "item name" to
"name" would confuse the importer into thinking it should also be a user
name.  Now we default to "full name" for the users name, and "item name"
for the item name.  These are still both configurable through the custom
mapping.

Also update sample csvs and remove an outdated sample.
2018-02-21 16:42:36 -08:00
snipe
006b2b18b0 Added qty to sorting 2018-02-21 16:21:31 -08:00
snipe
fb262e38a7 Added - make total sortable in components 2018-02-21 16:21:21 -08:00
snipe
f8151284ee Fixed - removed extra components table 2018-02-21 16:20:46 -08:00
snipe
5d8c91b687 Fixed regression - Added sum footer back into accessories 2018-02-21 15:54:16 -08:00
snipe
0f23462607 Merge branch 'develop' 2018-02-21 15:51:33 -08:00
snipe
ca50ea190f Applied master changes to develop
Wrong branch :(
2018-02-21 15:51:04 -08:00
snipe
31192abd2c Fixed ambniguous query in asset maintenance search 2018-02-21 10:28:05 -08:00
snipe
42c7f41d24 Added some alerting in custom fields if it looks like the db_column and real column do not match 2018-02-21 10:06:23 -08:00
snipe
fade03e337 Added nicer gates for auditing 2018-02-21 08:13:18 -08:00
snipe
d511d90a2f Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 06:50:42 -08:00
snipe
54d6cafda9 Bumped hash 2018-02-21 06:49:36 -08:00
snipe
a730cd1c51 Fixed - better handle admin routes when user is not logged in
Redirect to login instead of just showing forbidden
2018-02-21 05:51:49 -08:00
snipe
cccd75fc42 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 05:48:02 -08:00
snipe
dbc16a6c9b Bumped hash 2018-02-21 05:47:12 -08:00
snipe
6ffad6043f Fixed issue where bootstrap tables didn’t use a default page size 2018-02-21 05:44:41 -08:00
snipe
4e398950a0 Merge branch 'develop' 2018-02-21 05:34:02 -08:00
snipe
f5e51897e3 Added - sort by color on status labels 2018-02-21 05:29:50 -08:00
snipe
698ea36cc2 Added - Order location by manager 2018-02-21 05:09:40 -08:00
snipe
0cf9cdd3b1 Make department columns sortable 2018-02-21 05:04:20 -08:00
snipe
42c8de5620 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 04:44:48 -08:00
snipe
ba4f63bca3 Bumped version 2018-02-21 04:43:52 -08:00
snipe
4ed2ef6298 Merge branch 'develop' 2018-02-21 04:42:03 -08:00
snipe
d28c2af7de Fixed bootstrap table issue if per page was set to 10 2018-02-21 04:41:44 -08:00
snipe
1e8c32fbdb Fixed - missing table prefix for location parent search 2018-02-21 04:33:47 -08:00
snipe
97bd87773c Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 02:40:23 -08:00
snipe
b560828173 Bumped hash 2018-02-21 02:18:14 -08:00
snipe
0d7a43ab38 Fixed orderby parent location name 2018-02-21 02:17:18 -08:00
snipe
7865fbea7e Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-21 01:27:11 -08:00
snipe
5fcbf9091e Bumped version 2018-02-21 01:23:19 -08:00
snipe
fedd2b638c Merge branch 'develop' 2018-02-21 01:20:48 -08:00
snipe
22f44e342f Fixed incorrect next audit date if asset override 2018-02-21 01:18:21 -08:00
snipe
f7eb013935 Reverse order on activity report 2018-02-20 15:42:44 -08:00
snipe
1c28f893e7 Fixed #5027 - remove link to non-existant reports page 2018-02-20 14:44:29 -08:00
snipe
195dfd752d Updated translations 2018-02-20 14:41:12 -08:00
snipe
cc788d3b62 Added Slovenian 2018-02-20 14:37:22 -08:00
snipe
e54021e7be Added Venzuelan spanish 2018-02-20 14:37:12 -08:00
snipe
918db39eba Merge branch 'develop' 2018-02-19 18:06:58 -08:00
snipe
213fe2ecea Fixed #5044 - incorrect env values for SSL DB paths 2018-02-19 18:03:50 -08:00
snipe
6e70352d0b Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-17 01:05:20 -08:00
snipe
d2403cdadb Added image preview for uploads 2018-02-17 00:51:22 -08:00
snipe
9a4f5e0ba8 Bumped hash 2018-02-16 22:10:06 -08:00
snipe
502e4d672a Use image upload partial in asset edit/create 2018-02-16 22:08:49 -08:00
snipe
99205bf72e Merge branch 'develop' 2018-02-16 21:39:15 -08:00
snipe
b3d673b0aa Fixed #5037 - use line breaks in notes in table view 2018-02-16 21:38:56 -08:00
snipe
baf51eb430 Merge branch 'develop' 2018-02-16 21:19:03 -08:00
snipe
d8fc50c351 Fixed #5033 - use PHP’s max filesize for image uploads 2018-02-16 21:17:41 -08:00
snipe
ba5057181e Check for model in asset view 2018-02-16 18:04:11 -08:00
snipe
4c4dee0bf1 Use dropifexists for rollback 2018-02-16 17:45:40 -08:00
snipe
589c3a2be3 Fixed #4668 - wrong gate on manufacturer save 2018-02-16 17:45:27 -08:00
snipe
991f182f89 Added drop table to migration 2018-02-16 15:36:33 -08:00
snipe
f44b03f5a9 Updated sticky table header extenion 2018-02-16 15:36:21 -08:00
snipe
5fd50040d3 Fixed weird migration 2018-02-16 15:31:47 -08:00
snipe
5302108556 Update to master 2018-02-16 13:27:03 -08:00
snipe
592c62de75 Merge branch 'develop'
# Conflicts:
#	README.md
#	config/version.php
2018-02-16 13:26:53 -08:00
snipe
27c1bc0271 Bumped hash 2018-02-16 13:24:47 -08:00
snipe
3744a68daf Features/better table options (#5018)
* Added CSS for table toolbar

* Use maintenances API for table listings

* NIcer layout for allowed_columns in maintenances API

* Fixed #5014 - bootstrap cookie issues

* Fixed #5015 - bug when saving settings

* Refactored datatable code to use data attributes

* Updated dashboard with new table code

* Added - Order by group user count

* Updated groups to use new table attributes

* New license listing table code

* More bootstrap table implementations

* More BS table refactoring

* Improved bootstrap assigned assets

* New bootstrap for reports

* Misc BS fixes

* FIxed small issue with asset history display

* Removed multisort option

* JS refactor
2018-02-16 13:22:55 -08:00
dl1l
c209b7bb5d Update edit.blade.php (#5019) 2018-02-15 14:59:54 -08:00
snipe
f04493c1ab Added firstname_lastname as possible username format 2018-02-13 20:31:51 -08:00
snipe
62edf14893 Refactored method to generate usernames from full names 2018-02-13 20:31:11 -08:00
snipe
0d11e32523 Fixed issue where group with no permissions would crash view page 2018-02-13 17:14:30 -08:00
snipe
5484b06df8 Fixed #4923 - invalid check for location object in Ldap Sync 2018-02-13 17:06:42 -08:00
snipe
9f3116e4e3 Added - Deep linking in Bootstrap tabs 2018-02-13 17:04:50 -08:00
snipe
f144d671ff Fixed #4988 - incorrect variable name in asset checkout API method 2018-02-12 19:13:33 -08:00
snipe
b294635e17 Updated packages 2018-02-08 09:29:12 -08:00
snipe
36234cc0e8 Add @seanmcilvenna as a contributor 2018-02-08 09:29:12 -08:00
snipe
5c87003196 Added code triage badge 2018-02-08 09:29:12 -08:00
Sean McIlvenna
cc5d36f8be Addng "Category" column to "view assets" and re-organizing columns (#4970) 2018-02-05 17:36:35 -06:00
README Bot
1359a4f88a Add CodeTriage badge to snipe/snipe-it (#4968)
Adds a badge showing the number of people helping this repo on CodeTriage.

[![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)

## What is CodeTriage?

CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed

[Read more about the CodeTriage project](https://www.codetriage.com/what).

## Why am I getting this PR?

Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 4 months ago, [@imjennyli](https://github.com/imjennyli) added this project to CodeTriage in order to start contributing. Since then, 8 people have subscribed to help this repo.

## What does adding a badge accomplish?

Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project.

You can see an example of a CodeTriage badge on these popular OSS READMEs:

- [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails
- [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal

## Have a question or comment?

While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics.

If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again.

Thanks for making your project Open Source! Any feedback is greatly appreciated.
2018-02-05 15:22:52 -06:00
snipe
1e1362bdbc Merge branch 'develop' 2018-02-02 09:42:50 -06:00
snipe
813106efd3 Default to only changing default location in bulk edit 2018-02-02 09:42:34 -06:00
snipe
08d129c2ea Fixed #4951 - updating asset location in bulk edit 2018-02-02 09:36:40 -06:00
snipe
8491e57258 Merge branch 'develop' 2018-02-02 09:32:00 -06:00
snipe
d86fb098a8 Fixed sort ordering on dashboard 2018-02-02 09:31:43 -06:00
snipe
e21f8e46d4 Merge branch 'develop' 2018-02-01 17:18:16 -06:00
snipe
b3a6ec2804 Fixed #4938 - default sort order 2018-02-01 17:17:56 -06:00
snipe
0034938c04 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-02-01 16:32:13 -06:00
snipe
d2587337e4 Fixed #4952 - ignore show_archived setting when drilling down into specific ID 2018-02-01 16:31:32 -06:00
snipe
8b2045523d Bumped hash 2018-02-01 15:55:33 -06:00
snipe
e7e10c24be Add departments to usr export 2018-02-01 15:54:49 -06:00
snipe
f06852f48d Merge branch 'develop' 2018-01-25 13:17:11 -06:00
snipe
2f9fff7130 Do not try to show depreciation report if no depreciations are set up 2018-01-25 13:16:49 -06:00
snipe
0b788f64f7 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-25 11:07:18 -08:00
snipe
facf1d42f7 Sigh. 2018-01-25 11:06:37 -08:00
snipe
e77aae703b Bumped hash 2018-01-25 11:03:45 -08:00
snipe
76f52dd89b Fixed #4872 - Check for assigned in depreciation table 2018-01-25 11:02:46 -08:00
snipe
0e980f7229 Hotfix: Disabling the event dispatcher broke model validation. (#4912) (#4915)
* Hotfix: Disabling the event dispatcher broke model validation.

This resulted in invalid data being imported.  Reenable the event dispatcher for now--this causes double logs, but at least validates properly.

* Actually disable the event dispatcher.

* Sign in where necessary to fix the importer unit test.  This catches the issues found manually in 4912
2018-01-25 10:29:06 -08:00
Daniel Meltzer
2f5d550df6 Hotfix: Disabling the event dispatcher broke model validation. (#4912)
* Hotfix: Disabling the event dispatcher broke model validation.

This resulted in invalid data being imported.  Reenable the event dispatcher for now--this causes double logs, but at least validates properly.

* Actually disable the event dispatcher.

* Sign in where necessary to fix the importer unit test.  This catches the issues found manually in 4912
2018-01-25 10:10:56 -08:00
snipe
3acf5f76fe Merge branch 'develop' 2018-01-25 03:35:30 -08:00
snipe
4be8ae7f3d Fuckery. 2018-01-25 03:34:52 -08:00
snipe
029dd43ecd Merge branch 'develop' 2018-01-25 03:01:35 -08:00
snipe
cf8feb37e1 Fixed crash if no model associated
This should never be triggered. Bad data is getting in somehow.
2018-01-25 03:00:54 -08:00
snipe
648531f4cc Merge branch 'develop' 2018-01-25 00:26:17 -08:00
snipe
11505d5d06 Fix crash if no asset model
(This should never happen, but shouldn’t crash if/when it does)
2018-01-25 00:25:43 -08:00
snipe
1d04897b32 Order by default location 2018-01-24 14:27:12 -08:00
snipe
eddcc0fbbd Fixed cookie issue on hardware 2018-01-24 14:23:19 -08:00
snipe
bd80a77b78 Added default location to asset listing 2018-01-24 14:23:12 -08:00
snipe
edc30a31c1 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-24 11:02:46 -08:00
snipe
f5636f325d Bumped hash 2018-01-24 11:01:51 -08:00
snipe
426bf3803b Merge branch 'develop' 2018-01-24 11:00:40 -08:00
snipe
739d3c72b7 Fixed #4902 - include last name in checkin email 2018-01-24 10:59:34 -08:00
snipe
60c38a0c47 Added setting to choose what appears in model select list - Fixes #4879 2018-01-24 10:43:46 -08:00
snipe
9566fbd3cd Fixed borked down() migrations 2018-01-24 10:41:49 -08:00
Anh DAO-DUY
e41ee1bcf8 Fix upload images problem with Docker (#4906)
Some folders were missing in this script file (Docker installation).
Missing folders caused errors while uploading some images.
2018-01-24 09:13:03 -08:00
Tim Bishop
bc14f225af Only use location currency if it's actually set. (#4904)
This changes the depreciation report to only use the currency on a
location if it's actually set to something. Previously, if an asset had
a location it would use the currency even if it was blank rather than
falling back to the default.
2018-01-24 08:55:51 -08:00
snipe
07148c1503 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-24 08:29:25 -08:00
snipe
fcb5eea951 Bumped hash 2018-01-24 08:28:53 -08:00
snipe
7a4bf26733 Merge branch 'develop' 2018-01-24 08:25:36 -08:00
snipe
2c2910d1dd Handle shitty edge cases where an asset has no model without crashing 2018-01-24 08:16:05 -08:00
snipe
1dae7a2fe4 Fixed #3636 - removed thread_id (we don’t use this anymore) 2018-01-24 08:00:28 -08:00
snipe
e19ab329bd Add @tdb as a contributor 2018-01-24 07:13:24 -08:00
snipe
922cb90cb3 Add @CronKz as a contributor 2018-01-24 07:12:59 -08:00
snipe
765295136a Added - ability to add custom footer text, hide user’s manual/support links 2018-01-24 07:02:30 -08:00
snipe
7579333ad3 Disallow LDAP settings if in demo mode 2018-01-24 05:50:31 -08:00
snipe
543ea28b72 Added qty to asset components - fixed #4876 2018-01-24 05:25:01 -08:00
snipe
fefd6d60f6 Fixed #4899 - sorting in user history 2018-01-24 04:48:00 -08:00
snipe
a7b8b4bf55 Added: ability to change actual location from bulk edit assets 2018-01-24 04:38:25 -08:00
snipe
7cafa194c1 Fix for counts 2018-01-24 04:26:15 -08:00
snipe
b37f78dbbf Fixed #4858 - wrong params for id count 2018-01-24 04:24:45 -08:00
snipe
e20cd42cc2 More parse error fixes 2018-01-24 03:42:49 -08:00
snipe
7b99f81f72 Fixed parse error on hardware blade 2018-01-24 03:38:29 -08:00
snipe
f1bbdc9e59 Revert PR #4845 2018-01-24 03:32:45 -08:00
Richard Hofman
5219fb63a1 Add --base_dn option to LdapSync command. (#4888) 2018-01-23 18:15:36 -08:00
CronKz
dcc379c3fa Adding missing translations. (#4896) 2018-01-23 18:13:55 -08:00
vcordes79
0fb8dc3418 check if user is allowed to view assets (#4845) 2018-01-23 18:10:05 -08:00
vcordes79
f4e9d245d0 Status labels (#4895)
* fix statuslabels

* fix statuslabels
2018-01-23 18:08:54 -08:00
snipe
c75a2051ff Hide mfg button if no permissions to create 2018-01-22 14:02:23 -08:00
CronKz
691ec164b1 Added Translations (#4880) 2018-01-22 13:14:52 -08:00
vcordes79
7b596c750d API for (dis)associating fields with fieldsets (#4881)
* start work on fields in fieldset api

* revert CustomFieldsetsController

* fieldset associate / disassociate api

* fix variable names and payload

* fix variable name
2018-01-22 13:14:04 -08:00
Tim Bishop
b346556caa Allow manager_id to be fillable. (#4882)
The API UsersController accepts manager_id, but calls the following:

$user->fill($request->all());

This results in manager_id being ignored. I can't see any problem with
allowing a user's manager to be modified using the API, so this change
allows it.
2018-01-22 13:12:02 -08:00
snipe
93ec7068df Removed a few additional unused badges 2018-01-20 08:54:49 -08:00
snipe
5f65f993a0 Small refactor for AssetPresenter method 2018-01-20 08:52:50 -08:00
snipe
a9441521a4 Small refactor for Setting method 2018-01-20 08:52:50 -08:00
snipe
2dede67ae9 Removed unusued badges 2018-01-20 08:52:49 -08:00
Daniel Meltzer
6bf17e7d47 Reset the item between rows of import to avoid stale data. (#4868) 2018-01-20 08:40:01 -08:00
snipe
a3240da707 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-20 07:47:54 -08:00
snipe
a28fee1ff4 Bumped hash 2018-01-20 07:47:07 -08:00
snipe
57e5af2f69 Removed console logging from JS 2018-01-20 07:42:48 -08:00
snipe
8d3a214475 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-20 04:56:36 -08:00
snipe
58eedce6fe Updated language files 2018-01-20 04:53:04 -08:00
snipe
f91704a372 Added Ukrianian to language dropdown 2018-01-20 04:49:40 -08:00
snipe
f53c68e040 Bumped hash 2018-01-20 04:40:26 -08:00
snipe
99e55f84f0 Fixed misc UI permissions elements 2018-01-20 04:39:31 -08:00
snipe
798ea75f3d Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-20 04:00:26 -08:00
snipe
913e6a5709 Bumped hash 2018-01-20 03:59:42 -08:00
snipe
34f6d5ab45 Fixed #4671 - corrected component checkin gate 2018-01-20 03:58:59 -08:00
snipe
86c0194e9a Fixed #4628 and #4590 - Illegal mix of collations for operation error when searching on some languages 2018-01-20 03:08:27 -08:00
Brady Wetherington
dfb2b9b569 Major overhaul for modals, and a fix for #4820 (#4866)
Changes how the various modals work, and allows for specifying
category_type when inline-creating categories.
2018-01-20 00:42:29 -08:00
snipe
7fae380ab6 Fixed #4300 - Fix for illegal character type in action log 2018-01-20 00:27:45 -08:00
snipe
4ca8272efc Merge branch 'develop' 2018-01-20 00:21:03 -08:00
snipe
5d4bbc393e Fixed #4837 - link phone numbers 2018-01-20 00:20:45 -08:00
snipe
984275ff05 Merge branch 'develop' 2018-01-19 23:47:54 -08:00
snipe
e6f70f2ab7 Fixed #4821 - add username to user selection dropdown 2018-01-19 23:47:37 -08:00
snipe
f7de252f67 Wrong branch. :( 2018-01-19 23:46:42 -08:00
snipe
532b299c40 Fixed #4821 - add username to user selection dropdown 2018-01-19 23:46:12 -08:00
snipe
5dcc63dc74 Merge branch 'develop' 2018-01-19 20:49:20 -08:00
snipe
6eb8acf319 Swich to panel box for dashboard message 2018-01-19 20:49:02 -08:00
snipe
862251ab36 Change label to stale 2018-01-19 20:45:25 -08:00
snipe
95a9742571 Fixed #4768 - adds dashboard message option to settings 2018-01-19 20:43:55 -08:00
snipe
81d0921782 Merge branch 'develop' 2018-01-19 19:18:43 -08:00
snipe
0fa556da1d Exempt ready for dev and bounty tags from stale collection 2018-01-19 19:16:35 -08:00
snipe
05bc238c9a Merge branch 'develop' 2018-01-19 18:17:38 -08:00
snipe
782813ab49 Check for valid category before checking acceptance 2018-01-19 18:17:23 -08:00
snipe
cbca126d9d Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-19 17:53:15 -08:00
snipe
a9ffe8d210 Bumped hash 2018-01-19 17:52:40 -08:00
snipe
c242abb42e Added Company policy to fix company deletion issue 2018-01-19 17:51:28 -08:00
snipe
2c722ffc4f Fixed issue template with correct path to storage logs 2018-01-19 16:31:01 -08:00
snipe
484c47cef8 Added stale.yml to auto-close stale issues 2018-01-19 16:23:28 -08:00
snipe
f38dc37152 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-17 19:31:09 -08:00
snipe
6133f09056 Bumped hash 2018-01-17 19:30:31 -08:00
snipe
2a959edeba Added - Setting to allow archived assets in List All 2018-01-17 19:18:48 -08:00
snipe
8995d689b8 Fixed #4817 - [BREAKING API CHANGE] - changed user API response from firstname, lastname to first_name, last_name 2018-01-17 13:25:11 -08:00
snipe
f08bec6c78 Merge branch 'develop' 2018-01-17 13:12:58 -08:00
snipe
d920d91786 Fixed #4593 - do not require model_id to update asset custom fields via API 2018-01-17 13:12:32 -08:00
snipe
e35b3745eb Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-17 12:41:51 -08:00
snipe
e8252d9468 Bumped hash 2018-01-17 12:41:11 -08:00
snipe
8fff6a1a06 Update asset location based on user checkout in bulk checkout 2018-01-17 12:39:49 -08:00
snipe
0eff821928 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-17 12:12:33 -08:00
snipe
9e605e0f79 Bumped hash 2018-01-17 12:11:39 -08:00
snipe
8fb991110e Fixed #4652 - asset not correctly checking out to user on creation 2018-01-17 12:10:28 -08:00
snipe
b383ebee48 Add @fordster78 as a contributor 2018-01-17 10:59:00 -08:00
fordster78
138313dcb9 Setup changes (#4813)
* Add Asset Tag settings to Setup

* Add Locale Settings to Setup

* Add multiple company support to setup

* Changed the locale label name from 'site_name' to locale
Added default value for language to 'en' and multiple companies support
to 0 (false)
Switched out the old Input facade to the preferred $request->input
method for the setup page.
2018-01-17 10:58:03 -08:00
snipe
f254958db9 Check if db_column foield already exists 2018-01-17 06:48:14 -08:00
snipe
09eff88679 Merge branch 'develop' 2018-01-17 06:28:11 -08:00
snipe
24b356dba4 Fixed typos in default blade that cause components to not show up for non-superadmins 2018-01-17 06:27:23 -08:00
snipe
91bca5fcba Fixed #4844 - use input value for ids in user bulk edit 2018-01-17 06:15:52 -08:00
vcordes79
96a469db36 allow changing user activated field via api (#4843) 2018-01-17 05:38:10 -08:00
snipe
9ab05e7037 Add @vcordes79 as a contributor 2018-01-17 05:36:15 -08:00
vcordes79
52a99faf68 fix phone number update in api (#4842) 2018-01-17 05:33:55 -08:00
vcordes79
94cf1f8741 Fixes #4829 - add fields api (#4840)
* add fields api

* change route names
2018-01-17 05:31:57 -08:00
snipe
e2a8f9b790 Merge branch 'develop' 2018-01-17 03:27:03 -08:00
snipe
409f5cc4fd Added - display asset model category on hardware view page 2018-01-16 16:26:24 -08:00
snipe
3b5e4c44eb Fixed depreciation report not showing assigned to 2018-01-16 07:46:34 -08:00
snipe
69a7ea63e2 Pass nopages to dashboard stuff 2018-01-16 07:37:20 -08:00
snipe
aac379daeb If nopages is passed, hide page numbers
This seems weird since we’re checking for a negative, but there are only a few spots where we wouldn’t want page numbers, namely the dashboard sruff
2018-01-16 07:37:06 -08:00
snipe
1d3472b5c4 Truncate the checkout_requests field on seed 2018-01-16 07:36:14 -08:00
snipe
66a590b774 Account for user’s that have been deleted in the requested list 2018-01-16 07:36:03 -08:00
snipe
dc4472e9e9 Misc export table fixes 2018-01-15 21:03:46 -08:00
snipe
6bfd428c2e Use real status label names, even if deployed 2018-01-15 21:03:26 -08:00
snipe
f4623bd277 Add user group membership to user view page 2018-01-15 20:31:10 -08:00
snipe
1f04d7aafd Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-11 15:27:17 -08:00
snipe
78efeec368 Bumped hash 2018-01-11 15:24:37 -08:00
snipe
5b15a2fc0a Merge branch 'develop' 2018-01-11 15:23:27 -08:00
snipe
68f1fa0340 Added - ability to sort by asset count in models 2018-01-11 15:21:51 -08:00
snipe
9293f17707 Moved search scope lower to fix weird (possible Laravel) bug w/prepared statements 2018-01-11 15:17:34 -08:00
snipe
a4b32e2328 Improved - use presenters on model detail 2018-01-11 15:17:10 -08:00
snipe
d109ca30e2 Improved - allow searching on most detail views with additional tables 2018-01-11 15:16:53 -08:00
snipe
3b4a651dd9 Allow counts in mfg sorting 2018-01-11 15:16:23 -08:00
snipe
a4ac53e2e9 Improved - Using presenters for column headers in location detail 2018-01-11 15:16:09 -08:00
snipe
cf1b5a7685 Added- sort by count in manufacturer listing 2018-01-11 15:13:32 -08:00
snipe
0789eb8b07 Improved - using presenters for column headers in manufacturer detail 2018-01-11 15:12:48 -08:00
snipe
7f674fdd35 Require implicit search 2018-01-11 13:18:06 -08:00
snipe
500aa37e3c Fixed switchable attributes on table headers 2018-01-11 13:17:55 -08:00
snipe
efe668d26d Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-01-10 22:59:02 -08:00
snipe
90db97b980 Merge branch 'features/fixes_4792_ajax_tables_on_license_view' into develop 2018-01-10 22:57:44 -08:00
snipe
bc3d27fac6 Bumped hash 2018-01-10 22:57:22 -08:00
snipe
55b9f1207d Updated bootstrap tables 2018-01-10 22:53:54 -08:00
snipe
2bbb6001a8 Added location to seats transformer 2018-01-10 20:36:27 -08:00
snipe
af7b7664c5 Added license seat location method 2018-01-10 20:34:44 -08:00
snipe
c31362655c Refactored BS tables include for clearer separation of simple v not simple 2018-01-10 20:34:36 -08:00
snipe
c6a956382f Fixed #4784 - cookie not always being set correctly for ajax tables 2018-01-10 20:04:41 -08:00
snipe
bab0bda174 Added custom formatter for license seats (WIP) 2018-01-10 18:58:55 -08:00
snipe
bb52a8417c Switch view code to AJAX table 2018-01-10 18:58:41 -08:00
snipe
e3d7be23cb Added new seats controller method 2018-01-10 18:47:57 -08:00
snipe
3d5545494e Added new seats transformer 2018-01-10 18:47:40 -08:00
snipe
7f1a535b30 Added new seats API route 2018-01-10 18:47:27 -08:00
snipe
254234b0dc Fixed #4787 - don’t try to display category if it is invalid
This shouldn’t be needed, but in case data got weird (manual editing, etc)
2018-01-10 06:33:59 -08:00
snipe
3566f37981 Merge branch 'develop' 2018-01-10 06:25:06 -08:00
snipe
e6259eb6e1 Fixed #4774 - show assets assigned to assets in asset view
Todo: Fix text search on the asset to asset tab. It’s currently broken so I’ve disabled it.
2018-01-10 05:44:11 -08:00
snipe
eeb3d1eb42 Use language strings for tab text 2018-01-10 05:42:34 -08:00
snipe
79295f6434 Use dataTableLayout for table 2018-01-10 05:33:45 -08:00
snipe
285e4e2e52 Allow bulk language update in user edit 2018-01-10 05:33:26 -08:00
snipe
5587b64d64 Fixed #4770 - broken licenses, etc on company view, added users and components 2018-01-10 03:52:21 -08:00
snipe
f0f2a5aa67 Workaround for #4784 - make changes tab always visible
Not sure why the cookie isn’t cookie-ing
2018-01-10 01:56:24 -08:00
Brady Wetherington
061317866b Changes to Models in the asset-edit screen maintain chosen values (#4781)
Fixed for #2195
2018-01-09 23:45:20 -08:00
snipe
65ffc01583 Merge branch 'develop' 2018-01-09 20:19:50 -08:00
snipe
f5f7a63a23 Production assets 2018-01-09 20:19:04 -08:00
snipe
b6a7fd1cec Run production assets - because modern web development. Sigh. 2018-01-09 20:18:49 -08:00
snipe
6245f92e16 Merge branch 'develop' 2018-01-09 20:12:44 -08:00
snipe
0abab2107c Fixed #4779 - show selected filename on filepicker 2018-01-09 20:12:07 -08:00
snipe
afc7116260 Fixed #4778 - added notes to asset model view 2018-01-09 20:00:06 -08:00
Geoff Young
0b3d2b46e0 Add attributes to delete asset file button (#4336)
This adds attributes to the delete file buttons on the View Asset page.
The attributes will fill the confirmation modal that prompts users
before deleting. They also activate a tooltip on the button.
2018-01-03 17:25:50 -08:00
Brandon Daniel Bailey
f786e07179 Allow auto increment through the API (#4690)
* Allow auto incrementing asset_id from the API when the setting is enabled

* Cleaned up the if else statement

* Added prefix to the orWhereRaw which causes a database error if the configuration uses a prefix

* Auto incrementing through the API
2018-01-03 17:24:32 -08:00
Daniel Meltzer
b2469bb34f Fix double create log on import. (#4706)
* Fix double create log on import.

* Fix code error causing component importer to implode.

* More component importer oldities
2018-01-03 17:22:02 -08:00
Brandon Daniel Bailey
b721bfcc84 Added prefix to the orWhereRaw which causes a database error if the configuration uses a prefix (#4703) 2017-12-28 20:09:36 -08:00
Daniel Meltzer
f16ce09a7a Importer again (#4702)
* If a user id is provided in the name column of an import, we should assume that it is a user id and check out to it.

* Fix build of vue files.  The location is public/js/build, not public/build

* Ensure a status type is set before allowing submission of an import.

Also expand the status text label to change color based on success/failure.

Fixes #4658

* Use right key to lookup emails when importing users.  Fixes 4619.

* Import serial for components, and make unique matches based on the serial as well as the name.  Fixes #4569

* Set the location_id when importing an item properly.

This moves as well to using the Asset::checkout() method, which should consolidate the logic into a useful spot.
Fixes #4563 (I think)

* Production assets.

* Case insensitive field map guessing and repopulate when changingin import type.
2017-12-28 20:08:45 -08:00
snipe
bbe9df306b Merge branch 'develop' 2017-12-26 17:33:06 -08:00
snipe
1bd7392531 Account for audits on deleted assets 2017-12-26 17:32:45 -08:00
snipe
2002dfca17 Merge branch 'develop' 2017-12-26 17:13:49 -08:00
snipe
38c2ecbd0c Fixed migration use 2017-12-26 17:13:26 -08:00
snipe
ce97faa8d4 Merge branch 'develop' 2017-12-26 17:11:40 -08:00
snipe
31ca4bff8c Break audit date denorm migrations into two separate migrations 2017-12-26 17:11:08 -08:00
snipe
6f9033b2fd Merge branch 'develop' 2017-12-26 16:49:42 -08:00
snipe
8864f81402 Fixed manufacturer error on printable user page
Manufacturer is not required for accessories, so need to account for that
2017-12-26 16:49:01 -08:00
snipe
71ba1af647 Fixed button class on asset view 2017-12-26 16:48:28 -08:00
snipe
b894a4c19a Merge branch 'develop' 2017-12-20 12:36:45 -08:00
snipe
c3a44f25fd Fixed #4663 - duplicate manufacturer name in selectlist 2017-12-20 12:32:55 -08:00
snipe
1f36e7997f Bumped version 2017-12-20 03:31:43 -08:00
snipe
33b5c26da5 Bumped hashcount 2017-12-19 22:16:31 -08:00
snipe
e5a7e6619f Merge branch 'develop' 2017-12-19 22:15:51 -08:00
snipe
d2e2c1c05f Stub and 404 registration routes 2017-12-19 22:14:51 -08:00
snipe
557b8b0ded Merge branch 'develop' 2017-12-19 20:57:46 -08:00
snipe
37d4cf3afb Fixed #4647 - requestable model button not clickable 2017-12-19 20:48:26 -08:00
snipe
b716db225f Added “new” buttons for manufacturer and category in asset model creation 2017-12-19 20:30:46 -08:00
snipe
fbe093705d Fixed #4640 - add username to user detail 2017-12-19 13:42:34 -08:00
snipe
db278e9109 Merge branch 'develop' 2017-12-19 00:48:44 -08:00
snipe
88798435f6 Fixed inefficient query for inventory alerts 2017-12-19 00:32:39 -08:00
snipe
6220f0d8a5 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-15 18:57:21 -08:00
snipe
30716b349b Bumped dev hash 2017-12-15 18:55:56 -08:00
snipe
7a8c8233a2 Fixes #4639 2017-12-15 18:54:38 -08:00
snipe
7a2abcca33 Merge branch 'develop' 2017-12-14 13:00:46 -08:00
Ryan
9a40e5e651 return an error from ldap_search (#4623)
This will return the error from the ldap_search ran in Models/Ldap.php instead of throwing an exception. Users seem to commonly be getting an Exception because of invalid search filters. This will better inform them of that issue without requiring them to enable DEBUG.
2017-12-14 12:57:43 -08:00
snipe
893bcd0533 Merge branch 'develop' 2017-12-12 21:14:41 -08:00
snipe
f1a911d305 Fixed ambiguous query on non-super admins with FCS 2017-12-12 21:14:12 -08:00
snipe
8e8d9118e9 Merge branch 'develop' 2017-12-12 16:35:45 -08:00
snipe
9c108873e9 Trying 5.4.35 2017-12-12 16:32:45 -08:00
snipe
6fe5d00e9b Testing laravel 5.4.3 for PHP7.2 bug 2017-12-12 16:23:30 -08:00
snipe
a85d5cfe92 Merge branch 'develop' 2017-12-12 12:58:52 -08:00
snipe
6b257cc287 Concat search for full name 2017-12-12 12:52:10 -08:00
snipe
2952497a60 Removed bower components directory
We use npm/webpack now
2017-12-12 11:42:26 -08:00
snipe
9018af4f1d Bumped hash 2017-12-12 10:45:20 -08:00
snipe
5204e84703 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-12 09:25:45 -08:00
snipe
55eaea0110 Derp. 2017-12-12 09:25:06 -08:00
snipe
671a125c62 Bumped hash 2017-12-12 09:24:40 -08:00
snipe
eb827cdbe6 Merge branch 'develop' 2017-12-12 09:21:17 -08:00
snipe
5504dd435f Updated transslations 2017-12-12 09:20:57 -08:00
snipe
608bb1b5b1 Fixed #3416 - bulk delete asset models 2017-12-12 09:10:05 -08:00
snipe
88b64034e8 Fixed #3416 - bulk delete asset models 2017-12-12 09:03:37 -08:00
snipe
694c6f3ec6 Merge branch 'develop' 2017-12-12 07:22:30 -08:00
snipe
1d82f80e73 Improved - used “checked out to” string in asset model listing to reflect new polymorphic options 2017-12-12 07:20:06 -08:00
snipe
e21fa37254 Added - bulk actions to model asset listings 2017-12-12 07:15:51 -08:00
snipe
1ef44721f5 Improved - disallow delete if not elgible in UI 2017-12-12 07:03:31 -08:00
snipe
528630a8d3 Improvement - make asset, etc totals in company listing sortable 2017-12-12 07:03:09 -08:00
snipe
856a760d89 Improved - disallow deleting manufactureres if there are associated items
This is enforced on the backend - UI imorovement only
2017-12-12 06:52:50 -08:00
snipe
9179b6d9c4 Improved - disallow delete through the GUI if asset model has assets associated
(We prevent this on the backend anyway, but this makes for nicer UI)
2017-12-12 06:49:04 -08:00
snipe
4ce91a4f5d Fixed namespace gate for components - related to #4282 2017-12-12 06:26:37 -08:00
snipe
aff93d8f2b Fixed #4589 - Licence deployed to asset issue 2017-12-12 05:43:06 -08:00
snipe
e4ab4024c5 Added - changelog searchable in history tab 2017-12-12 05:10:13 -08:00
snipe
612f23f6e0 Added #2893 - track changes on asset edits 2017-12-12 04:59:28 -08:00
snipe
1d543f83d4 Added line spacing between unstyled lists 2017-12-12 04:31:53 -08:00
snipe
88ca852dcf Cleaned up asset observer 2017-12-12 03:40:04 -08:00
snipe
9fe2a7f81d Removed duplicate log entry on asset creation 2017-12-12 03:39:55 -08:00
snipe
a988011f0c Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-12 03:07:43 -08:00
snipe
c816870083 De-norm last audit date so we can display it in the asset listing 2017-12-12 03:03:43 -08:00
snipe
c5c46b7283 Removed duplicate deleted logaction
This is handled via observers now
2017-12-12 02:32:45 -08:00
snipe
8bb8306f80 Added restored string 2017-12-12 02:31:46 -08:00
snipe
d91e8bfee5 Fixed select2 on setup 2017-12-12 02:31:36 -08:00
snipe
2b3e5c8800 Moved “deleted” alert banner higher on page 2017-12-12 02:31:26 -08:00
snipe
5b00a8ae33 Use specific company_id column name in user search 2017-12-11 22:50:55 -08:00
snipe
5ee6e7f94b Fixed #4613 - Added table prefix to user search DB raw 2017-12-11 22:31:07 -08:00
snipe
f90271dae5 Aaaaand one more for #2810. Sigh. 2017-12-08 14:33:12 -08:00
snipe
e1423bd9d9 One more fix for #2810 2017-12-08 14:02:27 -08:00
snipe
557714e7b7 Fixed #2810 - checkin fix for licenses 2017-12-08 13:19:10 -08:00
snipe
3df62a200f Fixed manufacturer gates 2017-12-08 13:16:37 -08:00
snipe
a65ea639ed Added comments to SnipePermissionsPolicy for clarity 2017-12-07 21:00:09 -08:00
snipe
defed52caa Fixed #4596 - manufacturer gate 2017-12-07 20:59:55 -08:00
snipe
035f7a5292 Formatted version 2017-12-07 19:24:03 -08:00
snipe
0ea3100896 Bumped hash 2017-12-07 19:23:34 -08:00
snipe
2e0c2bd190 Add @Gelob as a contributor 2017-12-07 19:20:08 -08:00
snipe
ceca76b344 Merge branch 'develop' 2017-12-07 13:21:45 -08:00
snipe
a062769672 Check for array key before checking selected state
This is necessary for pre-created groups that don’t have all keys present
2017-12-07 13:21:30 -08:00
Ryan
af6a794ae9 fix typo (#4585) 2017-12-06 20:51:37 -08:00
snipe
f4ac087c83 Fixed policy namespace 2017-12-06 14:49:52 -08:00
snipe
4898dd8e23 Use ImageUploadRequest $request on user profile update 2017-12-06 14:42:14 -08:00
snipe
a090b6a9d2 Use ImageUploadRequest 2017-12-06 14:38:01 -08:00
snipe
023910472c Fixes #2415 - only allow gif, png jpg, svg image uploads 2017-12-06 14:33:52 -08:00
snipe
25bf10b125 Merge branch 'develop' 2017-12-06 11:19:45 -08:00
snipe
dfb0c09c51 Fixed #4581 - renamed print method 2017-12-06 11:17:42 -08:00
snipe
41c8d6302a Merge branch 'develop' 2017-12-05 18:17:32 -08:00
snipe
83547e3fed Prepopulate ther groups array on create 2017-12-05 18:17:11 -08:00
snipe
0d92ffc7ed Merge branch 'develop' 2017-12-05 18:13:08 -08:00
snipe
3d0525bc51 Removed debugging info 2017-12-05 18:11:59 -08:00
snipe
808cd0f728 Nicer groups permission edit UI 2017-12-05 18:11:06 -08:00
snipe
f5b3df697c Better select/unselect all javascript for permissions 2017-12-05 18:10:35 -08:00
snipe
29a36b5d1c Do not allow null id on group edit 2017-12-05 16:49:26 -08:00
snipe
ec131a7416 Updated account licenses 2017-12-05 16:27:02 -08:00
snipe
3da49ceb60 Standardize hidden key format 2017-12-05 16:26:39 -08:00
snipe
a19cef07bf Check for gate on license key display 2017-12-05 16:26:23 -08:00
snipe
c39e6d7006 Fixed #4576 - licence checkout gate 2017-12-05 15:04:10 -08:00
snipe
60f6895919 Remove version from asset table to persist column selections 2017-12-05 14:50:38 -08:00
snipe
bfa4812482 Exclude company on filter 2017-12-05 14:23:46 -08:00
snipe
a083cdba18 Merge branch 'develop' 2017-12-05 14:04:55 -08:00
snipe
438f484d68 Removed duplicate purchase cost column 2017-12-05 14:03:08 -08:00
snipe
f95d780fcf Include purchase cost column if depreciation is selected 2017-12-05 14:02:43 -08:00
snipe
79fe092c81 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-05 13:20:39 -08:00
snipe
027f6a7c12 Bumped hash 2017-12-05 13:18:52 -08:00
snipe
76fe2af0af Fixed #4444 - Added EOL back into asset lsiting and custom report 2017-12-05 13:03:11 -08:00
snipe
e490185533 Fixed EOL in custom report 2017-12-05 12:54:19 -08:00
snipe
deba4d2b81 Added consumables tio printable output 2017-12-05 12:37:42 -08:00
snipe
a8cc29f062 Added #2562 - print view of all assigned inventory 2017-12-05 12:34:16 -08:00
snipe
c9e6a75ea8 Make sure there is a valid status label associated withthe asset when checking if deployable 2017-12-05 11:28:48 -08:00
snipe
7efa7ec03f Starter print template for all assets assigned to a user 2017-12-05 01:55:24 -08:00
snipe
9eef7a94ab Fixed #4557 - added missing fields in components/consumables list view 2017-12-05 00:40:30 -08:00
snipe
244eceadec Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-04 23:51:53 -08:00
snipe
28fdbdd5d8 Bumped version 2017-12-04 23:50:45 -08:00
Brady Wetherington
4584990cc3 Cleaner re-implementation of loop-detection for asset-assigned-assets (#4553)
* Cleaner re-implementation of loop-detection for asset-assigned-assets

* Get rid of the other static and pass it along recursively too.
2017-12-04 23:00:55 -08:00
snipe
4f3c932bb1 Merge branch 'develop' 2017-12-04 20:45:46 -08:00
snipe
d7f2bceea2 Fixed incorrect property on supplier 2017-12-04 20:45:20 -08:00
snipe
18c1b2b477 Merge branch 'develop' 2017-12-04 20:43:24 -08:00
snipe
174e3e720a Fixed #4521 - profile location not selected
This is kind of a janky way to handle this - I created a new dropdown select, instead of adding even more convoluted code to the partial. The reasoon for this is because there may be situations where $user is passed in *addition* to $item, and we don’t want to erroneously use that location if we’re not supposed to be.
2017-12-04 20:30:57 -08:00
snipe
fdaa279930 Fixed #4548 - add cateory to model dropdown 2017-12-04 20:19:30 -08:00
snipe
a4323a0308 Fixed #4559 - min width for checkin/checkout buttons 2017-12-04 19:57:15 -08:00
snipe
ec4bed436c Fixed #4565 - show manufacturer in models selectlist 2017-12-04 19:52:18 -08:00
snipe
636c558fe6 Fixed #4566 - search by purchase_date 2017-12-04 15:26:40 -08:00
snipe
29873f9c22 Merge branch 'develop' 2017-12-01 21:58:23 -08:00
snipe
912ee20f3c Better supplier/location flow for suppliers/locations without data 2017-12-01 21:58:00 -08:00
snipe
9f10080243 Fixed parsed error 2017-12-01 21:13:18 -08:00
snipe
b3c386663f Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-12-01 21:11:33 -08:00
snipe
f2d25ff777 Added device image to supplier 2017-12-01 21:06:36 -08:00
snipe
dec9d959db Added additional user/asset fields to location view 2017-12-01 21:06:16 -08:00
snipe
2aafdb1400 Bumped version 2017-12-01 20:49:04 -08:00
snipe
c6b02cdc02 Location layout tweaks 2017-12-01 20:35:15 -08:00
snipe
f0c825a9b3 Improved suppliers view (use ajax tables) 2017-12-01 20:29:51 -08:00
snipe
79c035da11 Added map alt tag 2017-12-01 20:29:04 -08:00
snipe
9deafd771e Fixed bad suppliers error route 2017-12-01 20:28:37 -08:00
snipe
cbc09f3a12 Fixes #4508 - adds image to location view (and google map, if address and API key exist) 2017-12-01 19:12:35 -08:00
snipe
22c4d79cfb Fixed #4345 - Disallow future date for checkin date, disallow past date for expected checkin 2017-12-01 17:52:56 -08:00
snipe
71b9a15c9c Moved User menu down 2017-12-01 17:50:01 -08:00
snipe
db328e1ce5 Fixed granular hash resetting cookie 2017-12-01 17:49:44 -08:00
snipe
8b4c85d69a Removed verbose logging 2017-12-01 16:56:50 -08:00
snipe
78a51d3675 Additional fixes, improvements for custom report 2017-12-01 16:51:38 -08:00
snipe
a5bcf53146 Pass $required variable to display requiredness 2017-12-01 16:51:22 -08:00
snipe
9f97b4aefd Added created_at status, and manufacturer filter to custom report 2017-12-01 16:50:56 -08:00
snipe
62468199af Wider column for checkboxes 2017-12-01 16:50:16 -08:00
snipe
c220315cb0 Check if item is set before trying to look up selected 2017-12-01 16:49:51 -08:00
snipe
5a3233da37 Grr. 2017-12-01 14:54:18 -08:00
snipe
80109071a2 Fixed version number 2017-12-01 14:54:02 -08:00
snipe
c43bb670d2 Bumped hash 2017-12-01 14:50:46 -08:00
snipe
14874d8e8a Fixes #4495 - missing field in Download CSV, added filter options on custom report 2017-12-01 14:47:52 -08:00
snipe
ff793f1cb5 Added variable to determine if “new” modal button should be displayed 2017-12-01 14:37:11 -08:00
snipe
500f6d7baf Set requiredness of select2 ajax fields 2017-12-01 14:36:27 -08:00
snipe
ee9a229c0e Added datepicker range CSS 2017-12-01 14:26:01 -08:00
snipe
8140bdaa88 Fixed repeated header issue in asset export 2017-12-01 14:25:32 -08:00
snipe
5979a18852 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-30 16:43:59 -08:00
snipe
5efb803b60 Auto-bumped hash/version 2017-11-30 16:41:06 -08:00
snipe
fd4a8edae9 Fixed incorrect glyphs for users/locations 2017-11-30 16:40:58 -08:00
snipe
e9fdf06bf6 Improved display of asset status and meta status 2017-11-30 16:40:37 -08:00
snipe
0a5b72e71e Fixed #4517 - order number not visible if no purchase cost given on asset view 2017-11-30 16:39:59 -08:00
snipe
51168e8e10 Auto-bumped hash/version 2017-11-30 13:40:36 -08:00
snipe
cefdca3d22 Fixed incorrect has vs input 2017-11-30 13:40:26 -08:00
snipe
cc5eee1890 Auto-bumped hash/version 2017-11-30 13:32:12 -08:00
snipe
d3864db5e1 Switched to use $request from Input:: facade 2017-11-30 13:32:00 -08:00
snipe
ece8ae3adc Fixed #4542 and #4482 - default asset location not updating on biulk edit 2017-11-30 13:31:46 -08:00
snipe
e25829c759 Removed extra debug logging 2017-11-30 13:30:20 -08:00
snipe
92afd5f232 Removed debugging console code 2017-11-30 11:48:59 -08:00
snipe
b934d2e504 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-27 22:19:24 -08:00
snipe
8cf70e7e20 Auto-bumped hash/version 2017-11-27 22:10:54 -08:00
snipe
cdfd720c65 Fixed custom fields edit behavior with cutom format always selected 2017-11-27 22:10:46 -08:00
snipe
c0f791cf13 Sigh. 2017-11-27 21:35:06 -08:00
snipe
290cf79778 Auto-bumped hash/version 2017-11-27 21:25:21 -08:00
snipe
8af1481749 Auto-bumped hash/version 2017-11-27 21:20:38 -08:00
snipe
d8f404096c Added spacing in js 2017-11-27 21:20:31 -08:00
snipe
ea2f7617df Validate checkout_to_type on asset checkout 2017-11-27 21:20:12 -08:00
snipe
b6c258bb12 Redirect to back on bad checkout 2017-11-27 21:19:50 -08:00
snipe
804b49cefb Make sure the asset is available for checkout before displaying the checkout screen 2017-11-27 21:18:29 -08:00
snipe
305b0d8edb Fixed #4522 - properly check for valid target, throw error otherwise 2017-11-27 21:17:58 -08:00
snipe
05996019e5 Use asset checkout request in API 2017-11-27 21:17:16 -08:00
snipe
31a967e072 Use general order_number string to mean order number 2017-11-27 21:16:51 -08:00
snipe
0b56ebf291 Fixed radio button selector to provide correct default value in checkout 2017-11-27 21:16:25 -08:00
snipe
fcc87b3219 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-24 11:27:54 -08:00
snipe
82fca0c72d Auto-bumped hash/version 2017-11-24 11:25:57 -08:00
snipe
51661b0a21 Fixed #4190 - Added artisan command to regenerate asset tags 2017-11-24 11:25:51 -08:00
snipe
e8670fe591 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-11-24 10:53:20 -08:00
Daniel Meltzer
bee1dfc4a6 More importer fixes (#4516)
* The default locale of en does not include dollar sign in default currency.  Assume if there is no currency symbol set that the dollar sign is a good thing to look for in parsefloat.

* Fix for 4485.  Serial not serial_number

Also fix bug where updating with a csv that does not include custom field columns should not overwrite current values.

* Rename serial_number to serial in default imports to avoid needing to map weirdly.

* Add Test for 4359.  Not reproducable at current though
2017-11-24 10:42:11 -08:00
snipe
83c8449aca Auto-bumped hash/version 2017-11-22 18:43:18 -08:00
snipe
9dba1bb3e5 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-22 18:43:15 -08:00
snipe
76e3398d44 Auto-bumped hash/version 2017-11-22 18:42:33 -08:00
snipe
a7e12931fa Check that assigned is an object in reports controller 2017-11-22 18:42:17 -08:00
snipe
ba04c64567 Auto-bumped hash/version 2017-11-22 18:34:03 -08:00
snipe
8c8352ecc6 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-22 18:33:56 -08:00
snipe
64b670033d Auto-bumped hash/version 2017-11-22 18:30:45 -08:00
snipe
b9d102a5fb Truncate the fieldset table and pivot table on seed as well 2017-11-22 15:57:05 -08:00
snipe
77076e02e8 Fixed - mark supplier as requried in maintenance 2017-11-22 15:22:44 -08:00
snipe
c6a761a5ad Fixed - supplier is not required on asset creation/edit 2017-11-22 15:20:46 -08:00
snipe
6f3a90c48b Check that the assigned data is valid 2017-11-22 15:07:34 -08:00
snipe
8f160a8590 Fixed CSV asset export missing checked out to info 2017-11-22 13:05:48 -08:00
snipe
2278d5bfd8 Fixed restore permission to assets API disable delete/checkin/checkout on deleted assets 2017-11-22 10:35:24 -08:00
snipe
66ac147c9e Auto-bumped hash/version 2017-11-22 07:32:40 -08:00
snipe
05e0d15ae4 Auto-bumped hash/version 2017-11-22 07:31:50 -08:00
snipe
8562f018ed Fixed - conflicting error when a user and asset were both checked out 2017-11-22 07:31:38 -08:00
snipe
0cbdcce3ea Production asset manifest 2017-11-22 07:07:33 -08:00
snipe
980be65193 Added ability to turn items on/off in checkout-selector 2017-11-22 07:07:18 -08:00
snipe
3aaaea37e4 Added - sync locations artican call after seed 2017-11-22 06:21:56 -08:00
snipe
964c594c4c Added ItemFormatter for acrtivity report 2017-11-22 06:21:06 -08:00
snipe
9430c4bf43 Added sanity checks in BS tables formatter 2017-11-22 06:20:51 -08:00
snipe
538757317b Fixed #4411 - broken activity report if purge didn’t remove relationship entries 2017-11-22 06:20:28 -08:00
snipe
fe986f7c51 Auto-bumped hash/version 2017-11-21 22:45:10 -08:00
snipe
09105871d9 Auto-bumped hash/version 2017-11-21 22:43:33 -08:00
snipe
e84a6059f4 Merge branch 'develop' 2017-11-21 22:43:00 -08:00
snipe
09f20873df Auto-bumped hash/version 2017-11-21 22:35:05 -08:00
snipe
2adc1e8ba9 Auto-bumped hash/version 2017-11-21 22:35:04 -08:00
snipe
4a6c18532b Auto-bumped hash/version 2017-11-21 22:35:02 -08:00
snipe
921f882680 Auto-bumped hash/version 2017-11-21 22:35:01 -08:00
snipe
f79e5add58 Auto-bumped hash/version 2017-11-21 22:34:59 -08:00
snipe
d98d06377e Fixed #4098 - autolink URL and email addresses in listing 2017-11-21 22:34:53 -08:00
snipe
22fdd05314 Add UTF charset 2017-11-21 22:34:07 -08:00
snipe
8c15a4e0c6 Auto-bumped hash/version 2017-11-21 21:00:07 -08:00
snipe
9250b45e6e Auto-bumped hash/version 2017-11-21 21:00:05 -08:00
snipe
3a1de3d2a5 Auto-bumped hash/version 2017-11-21 21:00:02 -08:00
snipe
b59dd11304 Auto-bumped hash/version 2017-11-21 21:00:00 -08:00
snipe
b6222abb7c Auto-bumped hash/version 2017-11-21 20:59:58 -08:00
snipe
dcf8e4f5ef Auto-bumped hash/version 2017-11-21 20:59:54 -08:00
snipe
f195073ac3 Auto-bumped hash/version 2017-11-21 20:59:52 -08:00
snipe
452a9d6725 Auto-bumped hash/version 2017-11-21 20:59:50 -08:00
snipe
1d74ddc547 Auto-bumped hash/version 2017-11-21 20:59:45 -08:00
snipe
932b589a14 Auto-bumped hash/version 2017-11-21 20:59:43 -08:00
snipe
671e514785 Auto-bumped hash/version 2017-11-21 20:59:41 -08:00
snipe
1e2ebdb69c Auto-bumped hash/version 2017-11-21 20:59:39 -08:00
snipe
148751d927 Auto-bumped hash/version 2017-11-21 20:59:37 -08:00
snipe
efecdfaea0 Auto-bumped hash/version 2017-11-21 20:59:35 -08:00
snipe
ab9c84a6b6 Auto-bumped hash/version 2017-11-21 20:59:33 -08:00
snipe
8711bc0dbd Added comments for toggle checkout-to JS 2017-11-21 20:55:57 -08:00
snipe
0adebd1ec8 Add sorting and additional category types to dashboard categories
BREAKING CHANGE: Category type now reports as `category_type`, instead of `type`
2017-11-21 20:33:30 -08:00
snipe
43c1e893c0 Fixed #4494 - use audit settings for asset audit pre-populaton 2017-11-21 20:13:51 -08:00
snipe
7ce63e653b Auto-bumped hash/version 2017-11-21 19:39:53 -08:00
snipe
e5129a8b98 Removed next_version 2017-11-21 19:39:33 -08:00
snipe
a922c1a298 Auto-bumped hash/version 2017-11-21 19:38:29 -08:00
snipe
f4aa812d96 Changed twitter handle to @snipeitapp 2017-11-21 19:02:15 -08:00
snipe
0c9e41e1fa Updated versioning script to handle githooks better 2017-11-21 19:01:47 -08:00
snipe
1d6320a88f Merge branch 'develop' 2017-11-21 16:26:41 -08:00
snipe
4696e799ed Fixes #4491 and #4483 - handle pre-selected asset on checkout, better checkout-to selection UI (#4501)
* Added form checkout selector partial

* Stupid stash

* Added radio button checkout selector javascript

* New compiled production assets

* Added $style override in form partials for select2 ajax lists

* Added checkout-to radio button selector

TODO: Fix for accessibility - currently cannot tab-select this radio button

* Added new checkout-to selector to hardware edit

* Added new checkout-to selector to asset checkout form

* Refactored postCheckout to use radio button submission

This defaults to user checkout if nothing is passed for some reason

* Better visual feedback on whether or not an asset is deployable in edit screen
2017-11-21 15:58:31 -08:00
snipe
2e08a91c53 Merge branch 'develop' 2017-11-20 21:48:57 -08:00
snipe
4fd35573ad Bumped version 2017-11-20 21:20:34 -08:00
snipe
caf2cdbf6f Merge branch 'develop' 2017-11-20 20:51:22 -08:00
snipe
c0293a7c1c Add @uknzaeinozpas as a contributor 2017-11-20 19:39:04 -08:00
snipe
17405f5de1 Fixed #4413 - Next license seat not bering assigned correctly 2017-11-20 19:21:05 -08:00
snipe
bfefa10462 Add @TheVakman as a contributor 2017-11-20 18:54:43 -08:00
snipe
55a140045b Fixed upgrade language 2017-11-16 16:50:16 -08:00
snipe
897bd2c56e Fixed location sorting location instead of rtd 2017-11-16 16:49:48 -08:00
snipe
7321c5937f Fixed #4440 - allow username search in asset dropdown 2017-11-16 16:49:16 -08:00
snipe
98700cab8b Merge branch 'develop' 2017-11-16 14:14:49 -08:00
snipe
3d07635820 Fixed sort by model in asset listing 2017-11-16 14:14:30 -08:00
snipe
59b18a6ada Merge branch 'develop' 2017-11-16 13:49:05 -08:00
snipe
ce525c1985 Fixed #4471 - removed gate for categories selectlist 2017-11-16 13:48:38 -08:00
snipe
a0d163a3c4 Merge branch 'develop' 2017-11-16 11:41:18 -08:00
snipe
faab971931 Improved - removed 500 pixel width on 2FA dropdown 2017-11-16 11:09:42 -08:00
snipe
38eb16dfea Fixed #4408 - requestable assets not working 2017-11-16 11:09:24 -08:00
snipe
4494456cad Merge branch 'develop' 2017-11-15 15:51:50 -08:00
snipe
876dde1280 Fixed #4428 - updated translations 2017-11-15 15:31:34 -08:00
snipe
17ee904828 Fixed #4450 - added MAIL_REPLYTO_ADDR to docker.env 2017-11-15 14:48:45 -08:00
snipe
4f1b58cd35 Merge branch 'develop' 2017-11-15 14:27:47 -08:00
snipe
edcd3afc3e Fixes #4457 - use un-escaped CSS for custom CSS styles
We are already escaping the CSS in the show_custom_css() method
2017-11-15 14:27:21 -08:00
snipe
e433b0f9d8 Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-14 16:50:05 -08:00
snipe
f137e516a6 Bumped version 2017-11-14 16:49:18 -08:00
snipe
ba38b841cb Constrain accessory by category ID if one is passed 2017-11-14 16:47:21 -08:00
snipe
f7c6697a69 Merge branch 'develop' 2017-11-14 01:32:43 -08:00
snipe
498fc3762d Fixed #4437 - pagination for maintenances 2017-11-14 01:32:25 -08:00
snipe
eb24eb1fff Merge branch 'develop' 2017-11-14 00:04:27 -08:00
snipe
3e5e6ba99a Use table alias for models join in search/order by models 2017-11-14 00:04:03 -08:00
snipe
0396267388 Fixed #4412 - use select2 ajax list for asset maintenances 2017-11-12 17:22:16 -08:00
snipe
4577ba39d7 Merge branch 'develop' 2017-11-10 15:56:34 -08:00
snipe
7eef1b4bcf Fixed #4418 - order models by name asc in selectlist 2017-11-10 15:56:14 -08:00
snipe
578da128e9 Merge branch 'develop' 2017-11-09 18:59:25 -08:00
snipe
bb4d49690f Add purchase date to dates array so it’s treated as a Carbon date 2017-11-09 18:59:05 -08:00
snipe
7eb94d16a1 Merge branch 'develop' 2017-11-09 14:42:19 -08:00
snipe
65bd33c274 Make sure the user isn’t deleted before trying to display the name 2017-11-09 14:42:03 -08:00
snipe
0f7aa21a59 Merge branch 'develop' 2017-11-09 13:41:17 -08:00
snipe
ee0814716a Only format ecpected checkin date if one exists 2017-11-09 13:40:55 -08:00
snipe
f56e1768b6 Merge branch 'develop' 2017-11-09 13:28:12 -08:00
snipe
012afe99e2 Set purchase date to date type 2017-11-09 13:27:58 -08:00
snipe
0c02b6d24e Use —no-dev flag in upgrade 2017-11-09 13:25:44 -08:00
snipe
2f34336f2b Merge branch 'develop' 2017-11-09 11:18:21 -08:00
snipe
afe6f43a1b Exclude manufacturer on filter sort 2017-11-09 11:18:04 -08:00
snipe
738e33b165 Merge branch 'develop' 2017-11-09 11:06:50 -08:00
snipe
566fd4d2e1 Only format purchase date if one exists 2017-11-09 11:06:37 -08:00
snipe
c61887da21 Merge branch 'develop'
# Conflicts:
#	upgrade.php
2017-11-09 10:52:46 -08:00
snipe
6852b74317 Removed gates from selectlist method 2017-11-09 10:51:55 -08:00
snipe
4c266fdcf4 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-11-08 20:28:23 -08:00
snipe
c26a2f8291 Patching #4402 into develop 2017-11-08 20:28:18 -08:00
uknzaeinozpas
a994e54726 Update upgrade.php (#4402)
posix_* are not supported on Windows platform
2017-11-08 20:13:35 -08:00
madd15
172c7c75a8 Fixed #4374 - Add option to display company names to labels (#4405)
* Migration - Labels Display Company Name setting

* Add Company Name to Labels

Add company name if it is turned on in settings and asset has a valid company

* Add Company Name checkbox to Label settings

* Add Company Name Lang

* Add display company name to postLabels

* Revert Add Company Name Lang

* Fix display company name in postLabels

* Change tinyInt to boolean for display company name

* Simplify checking for company and getting the name

* Change to square brackets for array notation

* Move divs inside if statements on optional fields
2017-11-08 20:05:39 -08:00
snipe
487fd17ce3 Fixed ambiguous query when selecting by model_id 2017-11-08 20:03:26 -08:00
snipe
65353fa422 Nicer styling for purchase cost in edit form
Made it more consistent with the warranty months foeld below it
2017-11-08 18:06:51 -08:00
snipe
4f4920615c Format expected checkin as Y-m-d in form 2017-11-08 18:06:14 -08:00
snipe
c162c02304 Force expected checkin to be formatted as a date (not datetime) since it’s a date field in the DB 2017-11-08 18:03:47 -08:00
snipe
1bb1480f67 Added a comment around protected dates so we know wtf 2017-11-08 18:03:27 -08:00
snipe
8dceacc2b4 Merge branch 'develop' 2017-11-08 17:09:19 -08:00
snipe
ffae537400 Check that the models are valid being trying to return a value in the ajax partials 2017-11-08 17:07:57 -08:00
snipe
5ce7882bf5 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-11-08 17:01:50 -08:00
snipe
b36594f508 Check that the company is valid in select ajax 2017-11-08 17:01:46 -08:00
madd15
59c9c22a59 Small UI Changes (#4404)
* Small ui change to settings nav

* Remove min-height

Removed min-height from Assets by Status

* Add min-height to box-body
2017-11-08 13:12:03 -08:00
snipe
9097681c13 Changed log fomrat in example env to single 2017-11-08 11:08:11 -08:00
snipe
567f741d95 Merge branch 'develop' 2017-11-08 10:59:29 -08:00
snipe
9b1fb90519 Change default log type back to single 2017-11-08 10:58:56 -08:00
snipe
b567ffdcfe Fixed #2855 - checkin for components 2017-11-08 06:06:05 -08:00
snipe
9d44607b8f Added UI fade out when bootstrap alerts are dismissed 2017-11-08 03:13:18 -08:00
snipe
35ee52212f Added ability to disable the alert icon in the top menu 2017-11-08 03:08:17 -08:00
snipe
e9e32fdb00 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-11-08 02:46:40 -08:00
snipe
6c130ce8ac Apply patch #4354 2017-11-08 02:46:33 -08:00
Sorvani
dd7db0de93 Moved firewall commands from CentOS 7 to a routine and called form both CentOS 7 and Fedora. (#4366) 2017-11-08 02:37:34 -08:00
snipe
70efac8fa7 Production js 2017-11-08 02:24:34 -08:00
snipe
6e2556eefd Production assets 2017-11-08 02:24:06 -08:00
snipe
fb6a545cc6 Slightly nicer styling 2017-11-08 02:11:33 -08:00
snipe
e5c1e41966 Nicer icheck for user menu 2017-11-08 01:52:35 -08:00
snipe
61617a2629 Slightly less fugly groups permission styling 2017-11-08 01:19:20 -08:00
snipe
a3e80882c1 Better error handling for qr codes on invalid assets 2017-11-08 01:04:14 -08:00
snipe
31980c55de Merge branch 'develop'
# Conflicts:
#	config/version.php
2017-11-08 00:58:16 -08:00
snipe
d1022e8ff7 Fixed #4390 - results couldn’t be loaded error
The baseUrl in the javascript routes is already appending a trailing slash, so don’t prepend a slash in front of api call
2017-11-08 00:57:43 -08:00
snipe
e6ff447ee8 Bumped hash 2017-11-07 23:12:59 -08:00
snipe
1123272ae8 Bumped hash 2017-11-07 23:12:10 -08:00
snipe
74aa562a3b Merge branch 'develop' 2017-11-07 23:10:49 -08:00
snipe
74773ac912 Fixed incorrect policy reference in consumables listing 2017-11-07 23:05:29 -08:00
snipe
9764d2ad24 Removed commented code 2017-11-07 22:25:32 -08:00
snipe
d03b8c6528 Error handling for when log ID has no match on asset accept 2017-11-07 22:25:24 -08:00
snipe
99a355145e Removed empty comments 2017-11-07 22:24:57 -08:00
snipe
7233e2dded Merge branch 'develop' 2017-11-07 18:21:47 -08:00
snipe
eed50112d5 Better fallback for local that won’t break migrations 2017-11-07 18:18:27 -08:00
snipe
5aee5a3f3d Update location on checkout, error if bad target 2017-11-07 17:37:08 -08:00
snipe
f21c7ba312 Merge branch 'develop' 2017-11-07 11:28:41 -08:00
snipe
9d7455f022 Fixed handling deleting old images better 2017-11-07 11:28:13 -08:00
snipe
b748e7ed5e Fixed transformers to use new singleton upload urls 2017-11-07 11:11:47 -08:00
snipe
84a717c6ad Fixed deleted ordering scope 2017-11-07 11:06:38 -08:00
snipe
a202e1657c Fixed path for singletons 2017-11-06 21:58:28 -08:00
snipe
30ec919048 Remove AWS package
We unfortunately can’t use it right now because it requires a symlink from the storage/app directory. Until we have a better way of checking for configuration issues and/or automatically handling that symlink creation, we’re pulling it for now, since it’s not used anywhere.
2017-11-06 21:45:31 -08:00
snipe
4ae4083b7b Removed unused $matches variable 2017-11-06 21:26:30 -08:00
snipe
dec9ac1ac8 Fixed lowercase false 2017-11-06 21:25:40 -08:00
snipe
8776d28d3b Remove fastclick from package.json
We’re not using it anymore because of conflicts with select2. Select2 is more important for our UX than fastclick is.
2017-11-06 21:22:18 -08:00
snipe
231dea0ebc Break out service providers by responsibility 2017-11-06 21:17:17 -08:00
snipe
cceeb5c8a2 Disable Fastclick - conflicts with Select2, per #4392 2017-11-06 21:16:20 -08:00
snipe
579334b5fc Fixed - name should always be required in custom fields 2017-11-06 20:05:40 -08:00
snipe
3a82fbe714 Switch to rollbar’s official package 2017-11-06 20:04:50 -08:00
snipe
143071fa0c Merge branch 'develop' 2017-11-06 17:18:18 -08:00
snipe
0589652edb Fixed #4392 - select2 + fastclick incompatibility
This is a workaround fix - should upgrade both to latest after testing extensively
2017-11-06 17:17:48 -08:00
snipe
b772d8e527 Fix for IIS+ Chrome not showing webfonts
Thanks, @BrettFagerlund!
2017-11-06 16:51:46 -08:00
snipe
b96d4dcf1f Don’t show ANY in custom regex field if empty 2017-11-06 16:43:27 -08:00
snipe
dc32e4bdb0 Fixed form request ffor custom fields 2017-11-06 16:42:37 -08:00
snipe
43f2a530f5 Merge branch 'develop' 2017-11-06 12:39:00 -08:00
snipe
6ece593629 Fixed #4381 - asset uploads 2017-11-06 12:38:31 -08:00
snipe
33de0ec8a9 Merge branch 'develop' 2017-11-06 12:19:48 -08:00
snipe
8e17714c12 Fixed #4384 - bulk checkout of assets 2017-11-06 12:19:22 -08:00
snipe
32fc052c3b Fixed #4391 - companies now listing correct assets 2017-11-06 12:01:54 -08:00
snipe
17febca466 Merge branch 'develop' 2017-11-06 10:55:42 -08:00
snipe
8572a9771b Only return location name if find is valid 2017-11-06 10:55:20 -08:00
snipe
3ea294c0e6 Merge branch 'develop' 2017-11-06 10:45:14 -08:00
snipe
2b3b2e3197 Removed presenter references in custom report 2017-11-06 10:44:57 -08:00
snipe
a9d9234fb3 One more fix for custom reports 2017-11-06 10:44:18 -08:00
snipe
a11d9b84a4 Merge branch 'develop' 2017-11-06 10:42:48 -08:00
snipe
936ff707c7 Fixed custom report bug if assignedTo has no value 2017-11-06 10:42:34 -08:00
snipe
def69b1aaa Merge branch 'develop' 2017-11-06 10:30:42 -08:00
snipe
4818e1b8ca Fixed sorting for filtered items by location 2017-11-06 10:26:09 -08:00
snipe
623f21e51f Merge branch 'develop' 2017-11-04 18:21:09 -07:00
snipe
bd524ff2f3 Removed buggy migration code switched to artisan command 2017-11-04 18:20:41 -07:00
snipe
04ab522ee3 Fixes #4236 - validate the regex custom validation (#4380)
* More helpful text on how the custom validator works

* Clarified language of custom format, fixed regex example

* Fixed regex example in placeholder

* Added comments to custom fields

* Added regex validation string

* Added valid_regex validator in format requirements

* Removed useles comments

* Fixes #4236 - validate the regex custom validation
2017-11-04 17:06:14 -07:00
snipe
f672b14468 Ignore deleted assets and check for valid location in artisan command 2017-11-04 16:20:31 -07:00
snipe
b0973de1a6 Merge branch 'develop' 2017-11-04 01:24:23 -07:00
snipe
fe06ef10f1 Set the items array variable for no results 2017-11-04 01:23:50 -07:00
snipe
14ca690441 Merge branch 'develop' 2017-11-04 01:11:41 -07:00
snipe
2de0a3669e Check that there is a valid array before unshifting 2017-11-04 01:11:11 -07:00
snipe
c7c63e8432 Merge branch 'develop' 2017-11-04 00:49:19 -07:00
snipe
adf6afbb43 Moved delay back to 250 on ajax menus 2017-11-04 00:48:54 -07:00
snipe
69b90fb65e Merge branch 'develop' 2017-11-04 00:46:22 -07:00
snipe
97ea68b15c Updated translations 2017-11-04 00:45:50 -07:00
snipe
badc763c06 Bumped version 2017-11-04 00:32:33 -07:00
snipe
b6a14d2c9c Production assets 2017-11-04 00:20:06 -07:00
snipe
d59dd0f636 Reduced rate limit from 250 to 100 2017-11-04 00:20:00 -07:00
snipe
d68d95a915 Fixed - Added a “clear selection” option to select2 ajax lists 2017-11-04 00:19:16 -07:00
snipe
15d4344efb Comments in the JS 2017-11-04 00:15:23 -07:00
snipe
dc10f18188 Merge branch 'develop' 2017-11-03 21:53:13 -07:00
snipe
2522bfee9c Only return mnon-archived assets by location 2017-11-03 21:46:11 -07:00
snipe
d7f8615964 Fixed query for location ID 2017-11-03 20:10:36 -07:00
snipe
88dff754b1 Fixed swapped asset total headings 2017-11-03 20:10:16 -07:00
snipe
ecd21074fb Commented out erroneous fixme 2017-11-03 20:10:05 -07:00
snipe
12caa48390 Uncommented info output 2017-11-03 19:42:54 -07:00
snipe
cc7be5f947 Added location to checkout in API 2017-11-03 19:42:45 -07:00
snipe
5b489e003d Set the location_id on save/update to rtd if one is given 2017-11-03 19:41:26 -07:00
snipe
8c1f4b006e Removed comment 2017-11-03 19:40:55 -07:00
snipe
3a52c19428 Updated fieldname in Locations transformer and API 2017-11-03 19:40:40 -07:00
snipe
279ad6d80a Updasted fieldname in locations table 2017-11-03 19:40:04 -07:00
snipe
b786791401 Added location to checkout method 2017-11-03 19:39:48 -07:00
snipe
1c12b6e13b Added artisan command to sync locations 2017-11-03 19:00:36 -07:00
snipe
c06539dee3 Update the location ID if there is an rtd_location given 2017-11-03 17:36:18 -07:00
snipe
3b9544d1f3 Remove commented code 2017-11-03 17:35:46 -07:00
snipe
da9bb07041 Add a default legacy cipher of rijndael-256 2017-11-03 16:27:03 -07:00
snipe
5d9b9ad590 Merge branch 'develop' 2017-11-03 15:05:07 -07:00
snipe
f95502ae35 Fixed search by model on adsvanced search for assets 2017-11-03 15:04:21 -07:00
snipe
877daba096 Merge branch 'develop' 2017-11-03 14:59:38 -07:00
snipe
b3b8ab493e Switch to the reply_to address 2017-11-03 14:58:49 -07:00
snipe
1859c8f5ab Reply to seems to be overwritten 2017-11-03 14:55:03 -07:00
snipe
87ba042b2d Fixed manager name subquery on user search
Self-joins in Laravel make baby jesus cry :(
2017-11-03 14:47:31 -07:00
snipe
6fb0ef908d Fixed issue sorting when viewing users by department 2017-11-03 14:29:04 -07:00
snipe
c4a30cc646 Merge branch 'develop' 2017-11-03 13:29:33 -07:00
snipe
cf56f70b3a Added table alias for sorting 2017-11-03 13:28:57 -07:00
snipe
f84e6a34cc Merge branch 'develop' 2017-11-03 13:04:18 -07:00
snipe
85360a7c7f Removed gates on selectlists
They can’t access the API directly unless they have been granted API accesses anyway
2017-11-03 13:03:57 -07:00
snipe
9e0b3afa91 Merge branch 'develop' 2017-11-03 12:48:17 -07:00
snipe
416455fe01 Fixes weird manager_id validation
This is a shit fix - need to find out what’s happening here.
2017-11-03 12:48:00 -07:00
snipe
53a1511cac No idea why this is needed, but… 2017-11-03 12:20:11 -07:00
snipe
733921f1f9 Added optional required parameter 2017-11-03 12:17:41 -07:00
snipe
3ecaa99990 Fixed only undeployed assets in checkout to list 2017-11-03 11:33:36 -07:00
Kasey
ab9729c39a fix to availCount() (licenseSeatRelation) (#4378)
`license_seats`.`user_id` represents an overall "owner" of the license
2017-11-03 09:51:15 -07:00
snipe
104cc2bf11 Make sure the seat hasn’t been deleted 2017-11-02 21:07:59 -07:00
snipe
1659c3f1a6 Fixed inconsistent color type on checkin/checkout 2017-11-02 20:42:07 -07:00
snipe
caa8ec3178 Fixed checkout on license view page 2017-11-02 20:23:17 -07:00
snipe
0c794c103b Return an integer value for free seats if null 2017-11-02 20:23:04 -07:00
snipe
53175d5035 Fixed sorting issue on company/manufacturer/supplier 2017-11-02 20:01:39 -07:00
snipe
0bd09f9c46 Added sorting on available and total seats 2017-11-02 19:37:30 -07:00
snipe
27d795508d Fixed n+1 query, changed checkout behavior to just ask for a license ID
We’re offloading the freeSeat() to the checkout page now
2017-11-02 19:16:09 -07:00
snipe
368ac5b85d First stab at handling the n+1 issue on licenses 2017-11-02 18:20:42 -07:00
snipe
d5635f32e5 Merge branch 'develop' 2017-11-02 17:23:14 -07:00
snipe
f47075c180 Removed depreciation from Licenses transformer 2017-11-02 17:18:53 -07:00
snipe
0a5e4b9b7b Fixed inconsistent required field indicator 2017-11-02 17:13:06 -07:00
snipe
85624205b4 Updated Lang::get to trans 2017-11-02 17:12:51 -07:00
snipe
2330e5ee57 Merge branch 'develop' 2017-11-02 16:20:48 -07:00
snipe
c9c5ce6ee0 Return Deleted User if the user is invalid for some reason 2017-11-02 16:20:01 -07:00
snipe
fc3a59d193 Merge branch 'develop' 2017-11-02 13:47:52 -07:00
snipe
950519be5d Added a few more table aliases for ordering complex results 2017-11-02 13:47:37 -07:00
snipe
e64cf8b320 Merge branch 'develop' 2017-11-02 13:01:28 -07:00
snipe
87affa40ed Fixes filtering on status label 2017-11-02 13:01:09 -07:00
snipe
513a1b1e3b Bumped version 2017-11-02 12:52:44 -07:00
snipe
cf09908c60 Merge branch 'develop' 2017-11-02 12:50:56 -07:00
snipe
d21c92f91b Changed assetloc to location 2017-11-02 12:50:34 -07:00
snipe
ffd93c59d6 Merge branch 'develop' 2017-11-02 11:26:13 -07:00
snipe
01bb4bf64a Bumped version 2017-11-02 11:24:01 -07:00
snipe
28a4293a0b Fixed #4370 - user’s listing flashing then no results 2017-11-02 11:19:34 -07:00
snipe
f095f1807c Eager load userloc 2017-11-02 11:06:15 -07:00
snipe
e08911ab8f Removed nonce for now
There is a dependency in a package where we can’t edit the script tags to add the nonce
2017-11-02 10:57:05 -07:00
snipe
ca6dc5c2b5 Eager load user location 2017-11-02 10:56:36 -07:00
snipe
27875c2dac Improved to use trans() facade instead of lang 2017-11-02 10:27:53 -07:00
snipe
6f886d3d6e Merge branch 'develop' 2017-11-02 08:44:40 -07:00
snipe
fd74e4308b Don’t show request if the user or the asset/model has been deleted 2017-11-02 08:11:04 -07:00
snipe
3695e118f4 Fixed erroneous call to assets.category in filter scope 2017-11-02 08:06:10 -07:00
snipe
f43692938b Fixed slug call in asset models image 2017-11-02 08:02:51 -07:00
snipe
6008eec205 Merge branch 'develop' 2017-11-02 04:37:27 -07:00
snipe
3343cf16dd Bumped version 2017-11-02 04:37:08 -07:00
snipe
48207fc695 Added model number in box header 2017-11-02 04:36:04 -07:00
snipe
3dae464c34 Added nicer formatting for model details 2017-11-02 04:33:53 -07:00
snipe
0c524e0830 Use model image if it’s a requestable model 2017-11-02 04:29:05 -07:00
snipe
da56a253bc Added checkout requests method 2017-11-02 04:21:57 -07:00
snipe
a844d5b018 Added pagination, nicer formatting for requested assets 2017-11-02 04:17:14 -07:00
snipe
ba9bb470eb Added imageSrc presenter to assets 2017-11-02 04:15:24 -07:00
snipe
41452450b3 Added imageSrc presenter 2017-11-02 04:15:09 -07:00
snipe
a9e5ad0df1 Added link to requested assets in sidenav 2017-11-02 03:12:12 -07:00
snipe
0e2f4f3cfb Added requested route back in 2017-11-02 03:11:09 -07:00
snipe
3bc9d3f3f1 Merge branch 'develop' 2017-11-01 23:47:36 -07:00
snipe
81ca0ac91d Added better styling for user upload 2017-11-01 23:46:21 -07:00
snipe
3ca5d39c66 Hide upload button if app is locked 2017-11-01 23:44:31 -07:00
snipe
4f008e118f Fixed search string on suppliers selectlist 2017-11-01 21:55:17 -07:00
snipe
e11f9313f0 Fixed #4360 - better output if backup fails 2017-11-01 14:12:18 -07:00
snipe
d379c6b61f Merge branch 'develop' 2017-11-01 13:28:19 -07:00
snipe
d36e8cfbd2 Dashbpoard pie fixes 2017-11-01 13:27:59 -07:00
snipe
4cdcbc97ee Fixed varname issue on old image delete for manufacturers 2017-11-01 13:10:56 -07:00
snipe
ba516ac9af Merge branch 'develop' 2017-11-01 11:11:32 -07:00
snipe
feb2f5b076 Fixed #4356 - removed reference to old assetloc 2017-10-31 18:20:03 -07:00
snipe
b5f1e10b45 Merge branch 'develop' 2017-10-31 11:50:47 -07:00
snipe
a1eac967a7 Few more migration fixes 2017-10-31 08:57:57 -07:00
snipe
6186c324b5 Misc assetLoc error checks 2017-10-31 08:47:40 -07:00
snipe
772785f9b5 Few more fixes for big ugly location migration 2017-10-31 08:39:53 -07:00
snipe
2f6c0cee59 Merge branch 'develop' 2017-10-31 07:46:36 -07:00
snipe
e56f1ee6fd Fix for anomolies where asigned_type is not null but asigned_to is 2017-10-31 07:33:43 -07:00
snipe
37868cd70e Added warranty and expiration to list view 2017-10-31 07:05:15 -07:00
snipe
32b2f77ad9 Fixed issue where we tried to call the audit log even if the asset wasn’t valid 2017-10-31 05:41:06 -07:00
snipe
472a5b9f69 Removed extra log on API asset create 2017-10-31 05:38:52 -07:00
snipe
121e158f39 Change method name from userloc to location
This needs to be changed in more places though
2017-10-31 05:22:57 -07:00
snipe
f4e7bfc28d Null custom field if field exists but is empty 2017-10-31 05:22:21 -07:00
snipe
0089f73686 Moved trait to single line 2017-10-31 05:21:55 -07:00
snipe
0f4c05c5d0 Remove commented code 2017-10-31 05:02:46 -07:00
snipe
379274deff Check for multiple variable (for bulk checkout) 2017-10-31 04:48:40 -07:00
snipe
dbf5fec7b0 Fixed language string 2017-10-31 04:48:09 -07:00
snipe
4bb546a882 Pull assigned asets preview into its own blade for re-use 2017-10-31 04:47:59 -07:00
snipe
7f1b7be416 Rolling back prepending models to sort 2017-10-30 21:26:25 -07:00
snipe
02720f225c Fixed sorting issue in asset models when ordering by manufacturer 2017-10-30 20:51:01 -07:00
snipe
e44e573a3c Fixed requestable assets reference to assetloc 2017-10-30 19:40:35 -07:00
snipe
546c3e50fa Fixed updating the assigned assets location if user’s location changes 2017-10-30 19:33:52 -07:00
snipe
7f1b962e56 Improved use of de-normed locations fields 2017-10-30 19:21:35 -07:00
snipe
4eee7f8d97 Added address for users - fixes #4323 2017-10-30 18:57:00 -07:00
snipe
0fd0e3a8b4 Returns null for order number if it’s blank 2017-10-28 15:53:22 -07:00
snipe
1076ec96be Bumped version 2017-10-28 15:46:02 -07:00
snipe
3b498efee1 Fixes indefined item in user select blade 2017-10-28 15:42:55 -07:00
snipe
9687a78981 Fixed a few inconsistencies in the API results (null vs empty string) 2017-10-28 15:17:36 -07:00
snipe
2244a4b3cf Fix peresenter for assigned 2017-10-28 15:17:09 -07:00
snipe
f3f84f1a8c Production assets 2017-10-28 11:22:38 -07:00
snipe
41994c95e0 Bumped version 2017-10-28 11:22:27 -07:00
snipe
39c68214e9 More ajax menu fixes 2017-10-28 11:17:52 -07:00
snipe
9c94e70917 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-28 09:21:49 -07:00
snipe
6a3716a06d Added new ajax dropdown menus for components, consumables, etc editing/creating 2017-10-28 09:21:39 -07:00
Sorvani
feccc55c54 Added support for Fedora to the installer. (#4332) 2017-10-28 09:17:19 -07:00
snipe
fe70792cbd Bumped hash 2017-10-28 08:41:34 -07:00
snipe
95b6e0d2d8 Fixed assetloc to location 2017-10-28 08:40:27 -07:00
snipe
5d890fb139 Added more defaults for selected values 2017-10-28 08:38:19 -07:00
snipe
cd2816b1c7 Suppliers ajax menu API route 2017-10-28 08:38:00 -07:00
snipe
2172e6cc25 Added suppliers ajax list 2017-10-28 08:37:47 -07:00
snipe
04130a568c Fixes check for help_text 2017-10-28 07:41:13 -07:00
snipe
108ac79442 Added update to asset location id on checkout/checkin 2017-10-28 07:38:36 -07:00
snipe
3d7fd5cf04 Fixed references to assetLoc in hardware view 2017-10-28 07:29:32 -07:00
snipe
5737de2e22 Added help text to location partial 2017-10-28 07:29:14 -07:00
snipe
1e21cef218 Set max page size to 500 2017-10-28 07:29:03 -07:00
snipe
ad7a2da9bd Add help text that explains location override 2017-10-28 07:28:49 -07:00
snipe
bd48ae96c2 Update location on checkin if one is given 2017-10-28 07:28:35 -07:00
snipe
0f5e0dcd4f Added nobr for nicer formatting of bs tables status and deployed to 2017-10-28 07:12:47 -07:00
snipe
c37fa44f72 Use ther morphto assigned 2017-10-28 07:12:22 -07:00
snipe
daaf98783f Fixes status display in listing 2017-10-28 07:11:06 -07:00
snipe
1399ebb133 Nicer formatting on 503 2017-10-28 07:01:45 -07:00
snipe
03f6211582 Make notes null if empty 2017-10-28 07:01:06 -07:00
snipe
20bcc73000 Don’t emit to logs on this migration, otherwise we’ll flood the actionlogs 2017-10-28 06:33:15 -07:00
snipe
0058f02e82 Closure wasn’t working, go traditional 2017-10-28 06:23:04 -07:00
snipe
25b8c4438e Check for asset before attempting to cite mismatch 2017-10-28 06:20:44 -07:00
snipe
4f1747023a Seed with demo images 2017-10-28 05:46:43 -07:00
snipe
46fb5c9d40 Remove die() from migration 2017-10-28 03:54:06 -07:00
snipe
52f10232a1 Merge branch 'features/flatten-locations' into develop 2017-10-28 03:50:32 -07:00
snipe
5278dac2b0 Eager loading assignedTo - I have no idea why this works 2017-10-28 03:50:02 -07:00
snipe
890012f6c4 Update references to assetloc to location 2017-10-28 02:58:38 -07:00
snipe
3991f79115 Use new location method for hardware view 2017-10-28 02:37:59 -07:00
snipe
0a114c7daf Log instead of echoing 2017-10-28 02:32:05 -07:00
snipe
3064b3f80e Updated availableForCheckout() method to be clearer 2017-10-28 02:31:54 -07:00
snipe
df430a2263 Removed assignedTo eager load for now - it’s not working 2017-10-28 02:31:36 -07:00
snipe
277e49468b Added deleted at to API 2017-10-28 02:31:13 -07:00
snipe
f687c8db24 Fix custom field seeder to drop old columns 2017-10-28 02:11:10 -07:00
snipe
c616041876 Use form selector 2017-10-28 01:51:10 -07:00
snipe
d76f858dcd Try eager loading assignedTo 2017-10-28 01:50:58 -07:00
snipe
7a543fa6d5 Use new location method on asset API 2017-10-28 01:49:13 -07:00
snipe
49afd325a9 Add more data from seeders to check for 1001 queries 2017-10-28 01:48:50 -07:00
snipe
ce5ccc31f0 Added location method, fixed assetLoc 2017-10-28 01:48:27 -07:00
snipe
c70db75de9 Migration to add location_id de-normed data 2017-10-28 01:47:59 -07:00
snipe
7b76bbfd68 Update seeders with more locations 2017-10-28 01:03:16 -07:00
snipe
cfd1925625 Nicer display of notifications on checkout 2017-10-28 01:00:26 -07:00
snipe
e8b4bdf6f4 Added location_id to assets table to denorm 2017-10-27 20:35:34 -07:00
snipe
7e0c33d535 Speed up user factory by only bcrypting once 2017-10-27 20:35:13 -07:00
tiagom62
24211cb674 Setup firewall rules on CentOS 7. (#4329) 2017-10-27 18:27:44 -07:00
snipe
2d758be0e1 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-27 18:01:46 -07:00
snipe
f49ecbdb61 Code cleanup 2017-10-27 18:01:42 -07:00
Daniel Meltzer
3cea12565b Add missing policies (#4330)
* Add Authorizable trait and interface to our user model so we have access to User::can/User::cant.  We should take a look at where else our user model has diverged from Larvel since it was created...

* Policy cleanup/fixes.

This commit adds policies for the missing backend/"settings" areas.  The
permissions were implemented a while back but the policies did not, so
authorizing actions was failing.

In addition, this condenses a lot of code in the policies into base
classes.  Most of the files were identical except for table names, so we
move all of the checks into a base class and override the table name in
each policy.

* Use a better name and permission for the check in the default layout.
2017-10-27 18:01:11 -07:00
snipe
b1ac024725 Refined upload code 2017-10-27 17:40:10 -07:00
snipe
ec68bd7842 Small refactoring for code quality 2017-10-27 17:38:11 -07:00
snipe
a224904ade Removed uncessary code
This is already handled in the env
2017-10-27 17:00:33 -07:00
snipe
cb3b294baa Clesned up status label model for code quality 2017-10-26 22:54:07 -07:00
snipe
0788347990 Cleaned up status label method 2017-10-26 22:49:57 -07:00
snipe
7496a902bd Removed unused getDataView controller method
This is all API based now
2017-10-26 22:39:41 -07:00
snipe
f2d04be8fe Remove unused methods in Settings API controller (for now) 2017-10-26 22:35:01 -07:00
snipe
f2499fc7d2 Removed else condition on custom fields API for code quality 2017-10-26 22:33:40 -07:00
snipe
00d910ddbc Added manufacturers and categories select lists 2017-10-26 22:09:08 -07:00
snipe
c6d191bcba Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-26 21:52:04 -07:00
snipe
f13836eb55 Use select2 partials 2017-10-26 21:52:00 -07:00
snipe
76c4c19b3e Fixed small issues with select2 partials 2017-10-26 21:51:53 -07:00
snipe
1d212b59bd Added model select2 partial 2017-10-26 21:51:27 -07:00
snipe
1174d37c20 Added model selectlist route 2017-10-26 21:51:09 -07:00
snipe
6bcb55a129 Slight tweak to model name presenter 2017-10-26 21:50:39 -07:00
snipe
a99e09e5e3 Removed extra with() calls, since we load those up via the select2 lists now 2017-10-26 21:50:27 -07:00
snipe
7d11cb0748 Added selectlist transformer for select2 API calls 2017-10-26 21:50:01 -07:00
snipe
9c29ee9c6d Fixed datepicker in asset checkout 2017-10-26 19:51:29 -07:00
snipe
bdb95e4e3d Added companies ajax select2 endpoint 2017-10-26 16:37:41 -07:00
snipe
1fa6228fb7 Add @techincolor as a contributor 2017-10-26 16:32:32 -07:00
Danielle
8b535c1806 Default DB_HOST 127.0.0.1 instead of localhost (#4324) 2017-10-26 16:30:48 -07:00
snipe
b71d0ab484 Fixed order number and warranty bug in importer 2017-10-26 16:13:35 -07:00
snipe
ea07517ad5 Added more ajax select2 boxes to checkouts, remove helper ->with() methods 2017-10-26 03:43:28 -07:00
snipe
82690e1fd7 Integrate ajax select2 menus in all asset checkouts 2017-10-26 02:28:17 -07:00
snipe
75b527ab59 Features/image uploads (#4320)
* Locations API support for image

* Added manufacturers API support for image

* Added manufacturers API support for image

* Added image support for locations add/update

* Added manufacturer image upload support to controller

* General image string

* Added blade support for image uploads/delete image

* Added $request support (from Input::)

* Added image support in API transformers

* Added image to Manufacturers presenter for data table

* Migration to create image fields

* Ignore the contents of the new image directories

* Create new image upload directories

* Created components/consumables uploads directory

* Fixed missing textSearch scope from companies

* Added ignore for companies uploads directory

* Added blade support for image upload

* Fixed path to upload directory on edit

* Added company image upport to transformers, controllers

* Added image support for categories

* Added support for images in Departments

* Added support for image in Consumables

* Added image support for components
2017-10-25 22:35:58 -07:00
snipe
b083541723 Fixed docblock copypasta 2017-10-25 20:23:59 -07:00
snipe
6dbb598616 Merge branch 'develop' 2017-10-25 20:18:24 -07:00
snipe
e8d938e188 Bumped hash 2017-10-25 20:18:06 -07:00
snipe
f7c92f61e1 Merge branch 'develop' 2017-10-25 20:16:16 -07:00
snipe
4f80eac467 Prod assets 2017-10-25 20:15:34 -07:00
snipe
0e5af78cf1 New route for menu state saving 2017-10-25 20:15:30 -07:00
snipe
0d34cc704a Added controller for state-saver for sidenav 2017-10-25 20:10:59 -07:00
snipe
d4bb4d2edd Added state-save for open/cloed sidenav 2017-10-25 20:10:41 -07:00
snipe
d008334f2d Fixed #2857 - better CSS for requestable assets page when no results 2017-10-25 20:07:10 -07:00
snipe
109ea82cb9 Merge branch 'develop' 2017-10-25 18:17:38 -07:00
snipe
e8847753f4 Fixed importer using previous row’s custom field 2017-10-25 18:16:28 -07:00
snipe
7dad71d2b6 Fixed smaller button for import/cancel 2017-10-25 18:14:22 -07:00
snipe
db5968f95a Fixed larger button size 2017-10-25 18:13:55 -07:00
snipe
d97a1edeb4 Moved style sheet call back into page content 2017-10-25 18:13:41 -07:00
snipe
3dd39a46be Fixed large font size on importer 2017-10-25 18:13:21 -07:00
snipe
dc9a908de7 Fixing laravel mix() fuckery, hopefully for the last time 2017-10-25 18:13:03 -07:00
snipe
687cf44d3d Use inline style for logo 2017-10-25 16:15:50 -07:00
snipe
e054504669 lol whoops 2017-10-25 16:08:29 -07:00
snipe
e1ad28aa20 Mix fuckery 2017-10-25 16:05:39 -07:00
snipe
87992b7f71 More mix() fuckery 2017-10-25 16:05:18 -07:00
snipe
32478f1a10 Fixed #4310 - logo not scaling correctly without text 2017-10-25 15:47:06 -07:00
snipe
abc722b9c0 Fix path to overrides 2017-10-25 15:43:57 -07:00
snipe
49ebf4a314 Removed old build dir 2017-10-25 15:04:37 -07:00
snipe
c503729a9a Removed build vue 2017-10-25 15:03:04 -07:00
snipe
0b7864b09c Fixed model number not saving in modal 2017-10-25 13:17:35 -07:00
snipe
ede16eec3c Fixed #4308 - checkin and delete from user page 2017-10-25 11:11:32 -07:00
snipe
21c1ca2336 Bumped hash 2017-10-24 19:26:21 -07:00
snipe
a305b1ea2d Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-24 19:24:39 -07:00
snipe
17d58d9cc5 Added snazzy rich user selection menu
TODO:
- Abstract this out so it can be used by other select2 menus
- Write a select2 transformer to standardize output
2017-10-24 19:24:35 -07:00
snipe
c605984db0 Added nicer table formatting for “assets checked out to this user” table 2017-10-24 19:21:53 -07:00
snipe
d678a0ebff Switched to using JS routes for better subdirectory support, removed debugging console logs 2017-10-24 19:20:00 -07:00
snipe
e0fe383815 Removed debug message in SaveUserRequest 2017-10-24 19:18:53 -07:00
snipe
7140efc561 Use the transformers, Luke 2017-10-24 19:18:20 -07:00
snipe
99be54fd96 Fixed success message on saving new user 2017-10-24 19:17:30 -07:00
snipe
35da7906cc Fix standardized exception formatter for correct payload 2017-10-24 17:57:49 -07:00
snipe
680ad676ca Remove debugging 2017-10-24 17:10:42 -07:00
snipe
4628c15813 Fixed typo in comments 2017-10-24 16:57:04 -07:00
snipe
8a9960f830 Fixed missing break that would override password requirement 2017-10-24 16:56:46 -07:00
snipe
2b45433255 Removed copypasta commented out seeder code 2017-10-24 16:55:59 -07:00
snipe
715da63581 Check that model name exists before trying to display it
I don’t know why this would ever trigger, but a user in Gitter complained earlier today, so… ¯\_(ツ)_/¯
2017-10-24 16:53:46 -07:00
Brady Wetherington
ad32bae62f Fix to bad relation definition in Location. (#4306) 2017-10-24 16:52:45 -07:00
snipe
bbda0dc3b4 Always return an avatar, and set a fun default 2017-10-24 13:02:30 -07:00
snipe
dc805dd9b1 Added user avatars to listing 2017-10-24 09:51:07 -07:00
snipe
167cd4e4a0 Merge branch 'develop' 2017-10-24 05:25:52 -07:00
snipe
8d68bb7a57 Sticky headers for bootstrap tables 2017-10-24 05:22:26 -07:00
snipe
7d64ab3158 Fixes #4294 - pass correct group ID for group user listings 2017-10-24 04:39:47 -07:00
snipe
a41d603e0f Merge branch 'develop' 2017-10-23 21:17:43 -07:00
snipe
c18220069d Bumped version 2017-10-23 21:17:23 -07:00
snipe
fbf516284c One more time… 2017-10-23 21:13:39 -07:00
snipe
3db25dca7a Downgrade doctrine for php5.6 2017-10-23 20:54:52 -07:00
snipe
47c7061af5 Merge branch 'develop' 2017-10-23 20:11:01 -07:00
snipe
3799ab87ed Fixed search query for default sorting 2017-10-23 20:10:37 -07:00
snipe
6232a1f941 Merge branch 'develop' 2017-10-23 19:51:29 -07:00
snipe
f5ce580593 Bumped version 2017-10-23 19:51:14 -07:00
snipe
3afa5f7cda Merge branch 'develop' 2017-10-23 19:50:09 -07:00
snipe
17b271918f Fix date picker for custom fields 2017-10-23 19:47:43 -07:00
snipe
3922569d7f Merge branch 'develop' 2017-10-23 19:14:13 -07:00
Nicolai Essig
3a302fe2d7 ref #2737 prevent assets with "rtd_location_id" null values to be removed on location sort (#4283) 2017-10-23 18:28:06 -07:00
snipe
249f86bb21 Merge branch 'develop' 2017-10-23 17:50:58 -07:00
snipe
0951a756cc Updated passport to 3.0
Re: https://stackoverflow.com/a/45029309/200021
via @robertpearce
2017-10-23 17:35:31 -07:00
snipe
fc644925ea Fixes #4291 - adds phone to user listing 2017-10-23 14:21:51 -07:00
snipe
59c0b63ad0 Merge branch 'develop' 2017-10-20 20:22:42 -07:00
snipe
c0f8b3773c Temp fix for markdown stuff 2017-10-20 20:22:14 -07:00
snipe
fd210c6439 Fixes #4267 - email notifications showing model name as number 2017-10-20 18:58:11 -07:00
snipe
f7e23cf7c8 Fixes #4272 - adds serial to assigned assets view 2017-10-20 18:51:14 -07:00
snipe
387351d7ed Merge branch 'master' of github.com:snipe/snipe-it 2017-10-20 17:44:35 -07:00
snipe
6438d30afc Merge branch 'develop' 2017-10-20 17:44:28 -07:00
snipe
b4e1d37b16 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-20 17:44:04 -07:00
Brady Wetherington
8ac57d0121 Need to prefix status_id with assets. for uniqueness (#4279) 2017-10-20 17:37:46 -07:00
Kasey
d3b51715dc adding --force to php artistan snipeit:legacy-recrypt (#4232) 2017-10-20 17:21:21 -07:00
snipe
b215924b1a Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-20 17:18:13 -07:00
Brady Wetherington
25d3d66880 Add performance-improving indexes (#4278) 2017-10-20 17:17:11 -07:00
Brady Wetherington
189574377a Add 'where' clause to hasManyThrough relationship. (#4276) 2017-10-20 16:58:39 -07:00
snipe
341ddec9d8 Adds built-in mail notification vendor templates 2017-10-20 16:52:12 -07:00
Nicolai Essig
abcce78944 use translation for "All" in sidebar menu (#4268) 2017-10-20 00:20:33 -07:00
snipe
22e13cd4d2 Allow sorting on asset counts, disable delete button if the user has items checked out to them 2017-10-19 17:15:21 -07:00
snipe
5c105b52b6 Merge branch 'develop' 2017-10-19 16:45:22 -07:00
snipe
f757da1a98 Bumped hash 2017-10-19 16:45:04 -07:00
snipe
c1f8db37d9 Added note about saving before testing LDAP 2017-10-19 16:35:59 -07:00
snipe
cfc215a013 Merge branch 'develop' 2017-10-19 16:28:26 -07:00
snipe
4215a3257b Fixes #1044 - adds suppliers and image to accessories (#4266)
* Ignore accesories uploads

* API: Allow searching accessories by supplier id

* Adds suppliers and image upload to accessories

* Allow sorting by counts for suppliers

* Validate supplier image uploads

* Remove purchase_date from protected accessory array, it was converting it to datetime in datepicker
2017-10-19 16:25:24 -07:00
snipe
1f247ff541 Don’t let the user checkout an asset to itself
(We should consolidate that AssetCheckoutRequest for the API)
2017-10-19 15:51:55 -07:00
snipe
2fc46746e2 Adds translation string placeholders for new LDAP functionality 2017-10-19 12:22:54 -07:00
snipe
e185dc68af Fixes #4240 - allows admins to use custom password reset URL 2017-10-19 12:22:27 -07:00
snipe
54000ff69f Allows sorting by number of assets, etc in category 2017-10-19 11:48:09 -07:00
snipe
f6a38e848a Allows sorting by whether or not the category requires acceptance 2017-10-19 11:44:42 -07:00
snipe
7e8d670f81 Disable delete buttons if there are assets, etc 2017-10-19 11:41:35 -07:00
snipe
287b150b7f Show disabled delete button if thing can’t be deleted 2017-10-19 11:29:58 -07:00
snipe
b379656d55 Adds more consistent visual display of status label types 2017-10-19 11:06:55 -07:00
snipe
2e11a983c8 Nicer card display of status type explanations 2017-10-19 10:52:30 -07:00
snipe
a9753eb646 Include asset count in status labels overview 2017-10-19 10:48:15 -07:00
snipe
707c4db881 API: Check there are no assets associated before allowing delete 2017-10-19 10:39:08 -07:00
snipe
d1de34394e Removed stupid count method 2017-10-19 10:37:30 -07:00
snipe
e5719441bc Merge branch 'develop' 2017-10-19 08:36:05 -07:00
snipe
7153013fb0 Fake sending the test email if the app is in demo mode 2017-10-19 08:33:46 -07:00
snipe
55500f77fb Merge branch 'develop' 2017-10-19 08:20:44 -07:00
snipe
2b826c3adc Merge branch 'features/mail_test_button' into develop 2017-10-19 08:19:24 -07:00
snipe
cd193ce8bb Fixes #4036 - adds test email button to general settings 2017-10-19 08:18:56 -07:00
snipe
cb50142ba3 Update @thakilla as a contributor 2017-10-19 06:16:03 -07:00
snipe
81aaed92ce Change default session name 2017-10-19 06:08:58 -07:00
Nicolai Essig
4bc551db82 ref #4042 scale barcode with label size (#4258) 2017-10-19 06:08:01 -07:00
snipe
471d665408 Add @thakilla as a contributor 2017-10-19 06:07:42 -07:00
Robin Temme
068308ef56 Change changepassword menu icon to fixed width (#4262) 2017-10-19 06:04:02 -07:00
Nicolai Essig
1e65c7bf9a load custom css also on login page (#4260) 2017-10-19 06:01:41 -07:00
snipe
ae567c08db Fixes incorrect language reference for consumables on checkout if consumable doesn’t exist 2017-10-19 03:44:01 -07:00
snipe
0d25a4868a Merge branch 'develop' 2017-10-19 03:37:52 -07:00
snipe
13586be6b0 Fixed load error if license is invalid 2017-10-19 03:37:27 -07:00
snipe
44c649c3c8 Fixes #4256 - double encoding on user bulk checkin and delete blade 2017-10-19 03:17:55 -07:00
snipe
1a7bfb75fa Merge branch 'develop' 2017-10-19 02:22:39 -07:00
snipe
2b803a6a6c Fixes #4257 - use admin url when editing groups 2017-10-19 02:22:05 -07:00
snipe
92e4bdc02e Merge branch 'develop' 2017-10-19 02:09:06 -07:00
snipe
4d3d19ca2b Fixes older route reference in consumables 2017-10-19 02:07:31 -07:00
snipe
9c06912efd Small tweaks to prevent Chrome autofill 2017-10-19 01:59:13 -07:00
snipe
c5893b4445 Fixes #4249 - display deployed location in listing 2017-10-19 01:30:40 -07:00
snipe
b90933bb8b Fixes #4139 - fixed route for deleting file 2017-10-19 01:11:24 -07:00
snipe
fe9a90854d Adds d.m.Y as date format, per #2423 2017-10-18 10:30:25 -07:00
snipe
3b012f2827 Some advanced search query tweaks 2017-10-18 10:07:35 -07:00
snipe
e31dadc99a Merge branch 'develop' 2017-10-18 09:28:00 -07:00
snipe
5519e2d4ae Fixes custom fields sorting on asset listings
I need a silkwood shower :(
2017-10-18 09:27:34 -07:00
snipe
6285a8b5b2 Merge branch 'develop' 2017-10-18 08:54:35 -07:00
snipe
a3139c6fc6 Fix accessories route for invalid accessory ID 2017-10-18 08:53:25 -07:00
snipe
389ba9059f Merge branch 'develop' 2017-10-18 08:45:22 -07:00
snipe
c0e50be03e Duh. Helps if you actually assign the array first. 2017-10-18 08:45:05 -07:00
snipe
447833c996 Only try to process model bulk editing if at least one model was selected 2017-10-18 08:15:54 -07:00
snipe
809e310565 Recrypt the LDAP password properly
Older installs should add a line to their .env:

`LEGACY_CIPHER=rijndael-256`
2017-10-18 08:15:23 -07:00
snipe
38dd03b5ad Merge branch 'develop' 2017-10-18 07:16:19 -07:00
snipe
68f6385eba Fixes 500 in bulk checkout if no asset is selected 2017-10-18 07:15:16 -07:00
snipe
bd376a4992 Possible fix for #4227 2017-10-18 07:02:18 -07:00
snipe
97f65ceac0 Merge branch 'develop' 2017-10-18 06:25:01 -07:00
snipe
9e39abcc32 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-18 06:24:41 -07:00
snipe
5cd2857d5d Use footer sumformatter for cost totals 2017-10-18 06:24:36 -07:00
snipe
585fcfb7d4 Use maintenances report API to populate the maintenances report 2017-10-18 05:47:47 -07:00
snipe
d9135a8aac Disallow deleting suppliers with associated assets, licenses or maintenances 2017-10-18 05:47:20 -07:00
snipe
081a64223a Add @TonisOrmisson as a contributor 2017-10-18 05:45:14 -07:00
Tõnis Ormisson
a4eeff01f0 FIXED upgrade Recrypt not working with changed cipher (#4245)
* FIX legacy cipher change

* FIX Recrypt Custom fields column names

* FIX ReCrypt Clean un-needed code
2017-10-18 05:43:54 -07:00
snipe
eedbe468ac Fix licenses not saving supplier id on edit 2017-10-18 05:43:15 -07:00
snipe
463d9513e2 Merge branch 'develop' 2017-10-18 04:49:16 -07:00
snipe
ed4aa7dec2 Account for deleted suppliers in asset maintenances report
This should all be reworked via the API though anyway
2017-10-18 04:48:52 -07:00
snipe
d6bbe15a76 Added upload state back to log test 2017-10-18 04:04:05 -07:00
snipe
ea0a0a4a69 Merge branch 'develop' 2017-10-18 02:35:43 -07:00
snipe
34442362ca Fixes bad route for new groups 2017-10-18 02:35:30 -07:00
snipe
b846bb20c6 Merge branch 'develop' 2017-10-18 01:23:01 -07:00
snipe
ab3f5f4f4d Bumped version 2017-10-18 01:21:50 -07:00
snipe
ea63ced2bd Fixes table alias bug in complex queries for Laravel 2017-10-18 01:21:08 -07:00
snipe
6bd49bfb72 Fixes #4191 - user search 2017-10-18 01:20:50 -07:00
snipe
b80d3ce50d Hopefully fixes #4218 2017-10-18 00:36:52 -07:00
snipe
c069829b33 Fixes #906 - groups view 2017-10-17 21:43:57 -07:00
Geoff Young
665a113ed8 Update account history query (#4237)
This will limit the action_log records displayed when a user is viewing
their own assets and history since both target_type and target_id must
be set for a where condition to be added to the history query.
2017-10-17 20:39:49 -07:00
snipe
aa7091d962 Fixes #4226 - adds log_max_files to app config and sample env 2017-10-17 20:04:07 -07:00
snipe
ceff6a9617 Merge branch 'develop' 2017-10-17 19:27:31 -07:00
snipe
c01850fc73 Corrected Italian to Irish in language selector 2017-10-17 19:19:46 -07:00
snipe
4c04dc7b84 Merge branch 'develop' 2017-10-17 18:55:49 -07:00
snipe
c0103cd878 Bumped version 2017-10-17 18:55:30 -07:00
snipe
1f1d9d5461 Merge branch 'develop' 2017-10-17 18:53:21 -07:00
snipe
c776fa4f7b Updated language strings 2017-10-17 18:52:20 -07:00
snipe
dc91d10395 More possible fixes for #4210 2017-10-17 17:35:48 -07:00
snipe
4df10ad26c Merge branch 'develop' 2017-10-17 17:19:15 -07:00
snipe
668a88bc86 Add autocomplete=off to settings forms for #4210 2017-10-17 17:18:17 -07:00
snipe
d6e0012818 Merge branch 'develop' 2017-10-17 17:05:38 -07:00
snipe
1d5fb52bfc Fix for LDAP where location ou is not null but blank 2017-10-17 16:59:50 -07:00
snipe
8efb02a0ee Merge branch 'develop' 2017-10-17 16:23:59 -07:00
snipe
dea42db18b Reversed order of withTrashed for deleted asset QR codes 2017-10-17 16:21:50 -07:00
snipe
78d5d3947f Merge branch 'develop' 2017-10-17 16:18:14 -07:00
snipe
a1f5e11517 Fix for broken QR code on deleted assets 2017-10-17 16:01:53 -07:00
snipe
99ad096a8a Added a space next to crowdin badge 2017-10-17 15:26:16 -07:00
snipe
65b4ffeed9 Higher res crowdin badge 2017-10-17 15:25:34 -07:00
snipe
0dac88816a Merge branch 'develop' of github.com:snipe/snipe-it into develop
# Conflicts:
#	README.md
2017-10-17 15:20:53 -07:00
snipe
f7626404b7 Add @BlueHatbRit as a contributor 2017-10-17 15:18:27 -07:00
Elliot Blackburn
78f4b08398 Change zenhub markdown shield to higher res (#4235)
The zenhub badge was fuzzy as it was a low resolution for retina display. It's now using a new shield which falls in line with some of the others (img.shield.io).
2017-10-17 15:16:49 -07:00
snipe
160fd1c86a Added setting to let admin decide whether footer text should link back to site 2017-10-17 13:54:03 -07:00
snipe
6ce01487c5 Merge branch 'develop' 2017-10-17 13:31:43 -07:00
snipe
b46cbac911 Fixes #4230 - adds model name and manufacturer to emails 2017-10-17 13:30:32 -07:00
snipe
e9c3d6bfb7 Full text search fixes - addresses laravel bug :( 2017-10-17 12:48:18 -07:00
snipe
9e9a5b7a53 Changed checkin/checkout buttons to different colors for easier visibility 2017-10-17 11:32:09 -07:00
snipe
e7fe91c9d4 Depreciation view 2017-10-17 11:20:05 -07:00
snipe
6d130326aa Merge branch 'develop' 2017-10-16 21:28:23 -07:00
snipe
02db0f9f9d Handle deleted assets in maintenance 2017-10-16 21:28:05 -07:00
snipe
e0668b7507 Handle references to suppliers that have been deleted 2017-10-16 21:19:06 -07:00
snipe
1376f246dc Merge branch 'develop' 2017-10-16 20:38:19 -07:00
snipe
113c55d905 Hopefully fixes #4150 2017-10-16 20:34:31 -07:00
snipe
9cc25bcfd0 Hopefully fixes #4150 2017-10-16 20:34:22 -07:00
snipe
81d3f78263 Production assets 2017-10-16 20:12:42 -07:00
snipe
66596b0b09 Production assets 2017-10-16 20:12:22 -07:00
snipe
7455b1019a Hacky possible fix for subdir issues 2017-10-16 20:12:11 -07:00
snipe
37df934e97 Fixes #4220 - allow nullable for completion date 2017-10-16 18:32:48 -07:00
snipe
6517a95d45 Check if there are any custom fields before trying to loop through them 2017-10-16 15:29:19 -07:00
snipe
4b84a0c916 Tidying some of the LDAP UDN logic 2017-10-16 15:29:06 -07:00
snipe
0f0a61e477 Merge branch 'develop' 2017-10-16 10:10:29 -07:00
snipe
f64382aa00 Nicer error display in LDAP tests 2017-10-16 10:10:11 -07:00
snipe
1743417ec9 Merge branch 'develop' 2017-10-16 09:38:26 -07:00
snipe
c61bed52c8 Removed danger class 2017-10-16 09:38:09 -07:00
snipe
edf75865dc Merge branch 'develop' 2017-10-16 09:27:01 -07:00
snipe
176a26e6c3 Bumped version 2017-10-16 09:26:44 -07:00
snipe
10a4d7e849 Merge branch 'develop' 2017-10-16 09:09:53 -07:00
snipe
cbe008d52f Fix assetLoc to assetloc because reasons? 2017-10-16 09:08:08 -07:00
snipe
aeb5152789 Removed extranneous class for danger text 2017-10-16 09:04:38 -07:00
snipe
938490df16 Merge branch 'develop' 2017-10-16 09:01:09 -07:00
snipe
45c2af80a3 More LDAP testing US refinements 2017-10-16 09:00:51 -07:00
snipe
892c1b04fd Merge branch 'develop' 2017-10-16 07:07:35 -07:00
snipe
1fbf3753bc More small LDAP test improvements 2017-10-16 07:07:21 -07:00
snipe
5ead5a94e3 Merge branch 'develop' 2017-10-16 06:47:01 -07:00
snipe
bcf435f625 Try for better error reporting on LDAP fail
Sorry for all the commits on this - my local LDAP isn’t working and I can’t figure out why, so no easy way to test locally
2017-10-16 06:46:33 -07:00
snipe
8638c46b1d Merge branch 'develop' 2017-10-16 06:40:15 -07:00
snipe
b107280b7b Slightly nicer UI for LDAP login test 2017-10-16 06:39:36 -07:00
snipe
1a60c20117 Merge branch 'develop' 2017-10-16 06:34:23 -07:00
snipe
f1a6926ad9 LDAP test login 2017-10-16 06:34:04 -07:00
snipe
c27a7f09bd Merge branch 'develop' 2017-10-16 05:55:27 -07:00
snipe
ba7b9d8168 Removed stray foo 2017-10-16 05:54:33 -07:00
snipe
5b070ee32f Merge branch 'develop' 2017-10-16 05:52:52 -07:00
snipe
59a126c47c Small tweaks to LDAP test 2017-10-16 05:52:18 -07:00
snipe
322e62418e Merge branch 'develop' 2017-10-16 05:23:26 -07:00
snipe
a98d94ccdc Pass token to LDAPtest 2017-10-16 05:22:37 -07:00
snipe
5addcb517f Merge branch 'develop' 2017-10-16 05:01:37 -07:00
snipe
56cbc005ae Fixes expected checkins name in console kernel 2017-10-16 05:01:15 -07:00
Daniel Meltzer
22e9246031 Fix more old routes. Should fix #4216 (#4217) 2017-10-15 23:32:40 -07:00
snipe
c0b39701cc Fixes #4170 - asset maintenance type not showing 2017-10-14 16:17:14 -07:00
madd15
e2bac62e36 Fix #4205 (#4213)
* Fixing various UI items

* Revert css change

* Dashboard icon CSS up 4px
2017-10-14 00:14:22 -07:00
snipe
c12a23b84a Merge branch 'develop' 2017-10-11 15:37:01 -07:00
snipe
fa95f6d836 Another attempt for #4165 2017-10-11 15:36:47 -07:00
snipe
280e8c7ed1 Revert "Another attempt for #4165"
This reverts commit 7617fda978.
2017-10-11 15:34:30 -07:00
snipe
7617fda978 Another attempt for #4165
(This is terrible and needs to be refactored.)
2017-10-11 15:33:53 -07:00
snipe
af69f7636b Merge branch 'develop' 2017-10-11 14:44:37 -07:00
snipe
6d4574130f Clearer indication of whether or not the user will be emailed a eula 2017-10-11 14:44:25 -07:00
snipe
485b6397d0 Possible (crummy temp) fix for #4165 2017-10-11 14:42:11 -07:00
snipe
93990327de Hopefully fixes #4163 2017-10-11 14:18:08 -07:00
madd15
4ee7765403 Change Save buttons to Checkout and add Cancel (#4202)
Bringing components and consumables checkout page inline with other
checkout pages
2017-10-11 13:29:22 -07:00
snipe
bdbad067b4 Merge branch 'develop' 2017-10-11 13:10:46 -07:00
snipe
13a716310c Bumped hash 2017-10-11 13:10:29 -07:00
snipe
d0c77c228b Merge branch 'develop' 2017-10-11 13:09:30 -07:00
snipe
36cbffa183 Fixes bug where custom fields would not store new name in custom fields table on edit 2017-10-11 13:09:10 -07:00
snipe
5edf9e143f Merge branch 'develop' 2017-10-11 12:46:42 -07:00
snipe
b6a1e0d12f Call migrate before passport install 2017-10-11 12:42:31 -07:00
snipe
2fda3a2d26 Merge branch 'develop' 2017-10-11 12:29:48 -07:00
snipe
f56eb16941 More specific order by clause for drilling down on order number 2017-10-11 12:29:08 -07:00
snipe
c15c082fb4 Merge branch 'develop' 2017-10-11 01:31:59 -07:00
snipe
64e7ab3a12 Fixes #4182 - empty names for assets when checking out to asset 2017-10-11 01:31:37 -07:00
snipe
afbd6c811b Merge branch 'develop' 2017-10-10 23:38:45 -07:00
snipe
f64c02ce12 Fix for ambiguous query on models 2017-10-10 23:12:16 -07:00
snipe
a0d8aa77d3 Merge branch 'develop' 2017-10-10 22:59:50 -07:00
snipe
bed7b29417 Fixes group search 2017-10-10 22:59:32 -07:00
tiagom62
40ed86bfe0 Sudo isnt available on every distro. (#4194) 2017-10-10 22:02:47 -07:00
snipe
33497c9811 Merge branch 'develop' 2017-10-10 12:48:05 -07:00
snipe
52a8597813 Fixes #4136 2017-10-10 12:44:28 -07:00
snipe
eeb07f73e5 Added Redis vairables to example env 2017-10-09 15:44:08 -07:00
Alex Markessinis
57422c9135 Added Redis support. (#4146)
* Fix forgotten password missing route (???)

* Fixes #4056 - check for assets before deleting user

* added predis/predis dependency to composer.json to support redis based cache/queue/session/broadcast
2017-10-09 15:39:20 -07:00
Richard Hofman
adca7cb0c5 Fix LDAP location sync issue in #3993 (#4181)
* Ensure locations with the most specific OUs take precedence during user assignment.

* Save 'ldap_ou' Location attribute during creation.
2017-10-09 13:06:47 -07:00
Richard Schwab
059f8f5bc9 Remove dead macro code (#4164)
The barcode_types macro existed twice in the code, the second occurrence overriding the first one.
This commit removes the first occurrence which is essentially dead code.
2017-10-09 13:06:05 -07:00
tiagom62
c676e9d794 Fix progress spinner. (#4178) 2017-10-09 13:04:48 -07:00
Daniel Meltzer
e9f10dd74e Maybe Fix #4052. Missed an absolute URL. (#4187) 2017-10-09 13:04:38 -07:00
snipe
e29d878d4f Remove unused method arguments 2017-10-07 15:09:50 -07:00
snipe
d4e3ea1412 Derp 2017-10-07 15:07:31 -07:00
snipe
c5462c5f1f Not sure why this isn’t working… commenting it out for now 2017-10-07 14:52:00 -07:00
snipe
d1328c3ba9 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-07 14:49:54 -07:00
snipe
8c406e8e55 Additional auth policies 2017-10-07 14:49:47 -07:00
snipe
6e33f36595 Set snipe-logo as default 2017-10-07 14:49:36 -07:00
tiagom62
48277606de Support Debian 8 installs. Shellchecked. More cleanup. (#4174) 2017-10-07 12:45:25 -07:00
snipe
d7c9fcc8df Small manufacturer display tweaks on license view to make text clearer, link phone 2017-10-07 08:27:56 -07:00
snipe
508576544b Merge branch 'develop' 2017-10-07 08:17:58 -07:00
snipe
9f2fc21649 I guess we don’t need to manually create the license seats 2017-10-07 08:17:38 -07:00
snipe
11f99a963a Removed extra comma in demo warning 2017-10-07 08:03:08 -07:00
snipe
27ab0271a1 Merge branch 'develop' 2017-10-07 08:00:11 -07:00
snipe
f858b2858d Add language about the demo resetting daily to en files 2017-10-07 07:59:57 -07:00
snipe
e2809f7bd0 Merge branch 'develop' 2017-10-07 07:45:58 -07:00
snipe
f468b37f36 Bumped hash 2017-10-07 07:45:43 -07:00
snipe
0b3d3de30f Merge branch 'develop' 2017-10-07 07:44:30 -07:00
snipe
907b909223 Fixed language on settings page 2017-10-07 07:44:08 -07:00
snipe
9dc79f7165 Demo Settings reset artisan command
We’ll set this as a cron job to reset the language back to english
2017-10-07 07:43:57 -07:00
snipe
324d44dbac Small accessory factory tweaks 2017-10-07 07:24:15 -07:00
snipe
7a0e695ea0 Merge branch 'develop' 2017-10-07 07:17:05 -07:00
snipe
a69a939034 Small barcode tweaks 2017-10-07 07:15:28 -07:00
snipe
ed8efbe759 Add dateFormatter to components view 2017-10-07 06:57:02 -07:00
snipe
834c6ad8f9 Removed extra space 2017-10-07 06:56:47 -07:00
snipe
2ce48fbc7e Use components transformer in API method 2017-10-07 06:56:39 -07:00
snipe
5d18937e94 Standardized component API output 2017-10-07 06:56:18 -07:00
snipe
b3186ba5ea Removed old getDataTable methods
These are no longer used because of the API
2017-10-07 06:56:02 -07:00
snipe
f51dc9a1c4 Use recent date range for factory 2017-10-07 06:17:39 -07:00
snipe
f3f9920bd3 Added Crucial as a manufacturer in factory 2017-10-07 06:17:18 -07:00
snipe
ea6140e786 Components factory and seeder 2017-10-07 06:16:53 -07:00
snipe
05c4d6dead Make HDD into HDD/SSD for factory 2017-10-07 06:16:36 -07:00
snipe
e2c6f36c70 Use recent date range for factory 2017-10-07 06:16:21 -07:00
snipe
21b1ecb6b3 Only checkout RTD assets 2017-10-07 06:16:06 -07:00
snipe
a62cf358ee More realistic prices for factories 2017-10-07 05:52:44 -07:00
snipe
20c429b600 Add some archived and pending assets too 2017-10-07 05:52:26 -07:00
snipe
38e25a388c lol whoops 2017-10-07 05:34:13 -07:00
snipe
2c6cedd62c Removed unused factories 2017-10-07 05:32:59 -07:00
snipe
9b0cca4a37 Added licensed to name, email to licenses factory 2017-10-07 05:32:39 -07:00
snipe
f6d198a39c Added back in some unused asset stuff for tests 2017-10-07 05:32:22 -07:00
snipe
d12fc6b13c Add depreciation ID to model factories 2017-10-07 05:32:00 -07:00
snipe
a12c7c83d4 Req acceptance on laptop category 2017-10-07 05:31:46 -07:00
snipe
affd4035c3 Added displays to seeders 2017-10-07 05:31:32 -07:00
snipe
ecfe1a5442 Depreciations factory 2017-10-07 05:30:53 -07:00
snipe
e9c77198d7 Fix duplicate call to licenses seeder 2017-10-07 04:54:16 -07:00
snipe
20be670648 Fixed ids for license 2017-10-07 04:43:55 -07:00
snipe
a03207e5b4 Show license notes 2017-10-07 04:42:53 -07:00
snipe
6555b3a49d No location id in licenses? 2017-10-07 04:36:23 -07:00
snipe
61a634bc1a No idea why this worked locally before, but… 2017-10-07 04:34:51 -07:00
snipe
00696a3668 Commented out slow users factories right now - will revert when finished 2017-10-07 04:26:37 -07:00
snipe
e20271791b Removed unused factories for now
Need to add these back, once the correct logic is applied
2017-10-07 04:26:21 -07:00
snipe
86b9fdbffe License and actionlog seeder tweaks 2017-10-07 04:25:35 -07:00
snipe
7e728094a1 User seeders 2017-10-07 03:41:46 -07:00
snipe
f865c621ef Removed commented code 2017-10-07 03:41:40 -07:00
snipe
e2f4685a55 Added notes back to list view 2017-10-07 03:36:50 -07:00
snipe
2148ea94bb Additional model seeders 2017-10-07 03:19:46 -07:00
snipe
1d2787250b Reference IDs 2017-10-07 03:19:27 -07:00
snipe
7f02ff12cf Asset seeder 2017-10-07 03:19:17 -07:00
snipe
65341a5d8a Added ID reference numbers 2017-10-07 03:19:07 -07:00
snipe
1608edbb28 Added ID reference numbers 2017-10-07 03:18:57 -07:00
snipe
776da1dea4 Add Dept seeder to db seeder 2017-10-07 03:18:46 -07:00
snipe
a6b3e4bbb1 Department seeder/factory 2017-10-07 03:18:36 -07:00
snipe
3b2ecda243 Consumables factory/seeder 2017-10-07 02:46:04 -07:00
snipe
57cbb5c5ce Removed unused factories 2017-10-07 02:45:54 -07:00
snipe
7b27d32121 Added ID numbers 2017-10-07 02:45:36 -07:00
snipe
ccfba324ee Indenting 2017-10-07 02:45:28 -07:00
snipe
9f55a76fcf Added avery 2017-10-07 02:45:21 -07:00
snipe
8fdddc310f More rigid seeders for more realistic data 2017-10-07 02:27:02 -07:00
snipe
064a4ebe33 Ability to skip deleting/generating new users
This will behave unpredictably if there is not a user id 1
2017-10-07 00:02:37 -07:00
snipe
4981077cb1 Merge branch 'develop' 2017-10-06 22:59:01 -07:00
tiagom62
fbea1c0823 Code dedupe, general cleanup and added a verbose option for debugging. (#4173) 2017-10-06 22:58:41 -07:00
snipe
a84da88114 Demo seeder 2017-10-06 22:58:00 -07:00
snipe
282b3b5b0a Remove catch-all “deployed” from pie chart 2017-10-06 18:41:10 -07:00
snipe
130a99c46f Merge branch 'develop' 2017-10-06 18:15:31 -07:00
snipe
df4cb7d351 Don’t reload the page if the API returns a 500 2017-10-06 18:15:13 -07:00
snipe
1dcff8d463 Remove eager loading on pie
This was causing memory issues for large asset sets
2017-10-06 18:15:01 -07:00
snipe
ae4ba6176d Merge branch 'develop' 2017-10-06 17:04:06 -07:00
snipe
e461c25428 Apply model image fix to update method 2017-10-06 17:03:51 -07:00
snipe
ea4bfdc51d Merge branch 'develop' 2017-10-06 16:57:51 -07:00
snipe
554ea8bb95 Fixed asset model image validation 2017-10-06 16:56:43 -07:00
Richard Hofman
f2be409914 LDAP sync improvements and DB query fix. (#4148)
* Set 'ldap_ou' Location field to NULL when an empty string is submitted.

* Consolidate LDAP user import logic in LdapSync.php.
2017-10-06 16:15:14 -07:00
snipe
3fc1bbea73 Apply PR changes to master. Again? 2017-10-06 14:28:19 -07:00
snipe
a97f7d2277 Merge branch 'develop' 2017-10-06 14:27:46 -07:00
snipe
af70cdaeac Adds email address to CoC 2017-10-06 13:44:39 -07:00
snipe
8919c3b52a Merge branch 'develop'
# Conflicts:
#	snipeit.sh
2017-10-05 23:14:13 -07:00
snipe
f580e20bc3 Fixed custom fields filter for advanced search 2017-10-05 23:09:02 -07:00
snipe
a054cec7c9 Supress output if no title is given
This should never happen, but….
2017-10-05 22:51:33 -07:00
tiagom62
1ad7bbdd0c Support configuring smtp settings (#4161)
* Able to configure smtp settings. General cleanup.

* Check if sudo is available.
2017-10-05 21:27:00 -07:00
snipe
cfe6759825 Merge branch 'develop' 2017-10-05 00:41:30 -07:00
snipe
10c13baf2b Add @GeoffYoung as a contributor 2017-10-05 00:40:32 -07:00
snipe
9fe4e11874 Merge branch 'develop'
# Conflicts:
#	snipeit.sh
2017-10-05 00:37:16 -07:00
snipe
f6d8642799 Fix $search variable to $search_var for new filter 2017-10-05 00:35:37 -07:00
snipe
adddc5324b Add @imjennyli as a contributor 2017-10-05 00:34:50 -07:00
snipe
f442b70ae7 Apply PR #4133 to develop 2017-10-05 00:02:14 -07:00
madd15
7b10213b3a Small UI Tweaks to Accessories (#4149)
* Small UI Change

Changing Save button for Checkout button and adding Cancel button

* Small UI Change

Move buttons to match checkout page and remove extra save button
2017-10-04 23:28:13 -07:00
tiagom62
ddc79b9070 Support Ubuntu and Debian installs. (#4133)
* Ubuntu related fixes.

* Further cleanup.

* Support Debian 9 install. More cleanup.
2017-10-04 23:19:38 -07:00
Jenny Li
5e9b04b0f5 update broken link to contributor docs (#4123) 2017-10-04 23:19:19 -07:00
snipe
2562eb2aeb Merge branch 'develop' 2017-10-03 21:23:46 -07:00
snipe
85da30894b Bumped version 2017-10-03 21:23:27 -07:00
snipe
bf344e9322 Merge branch 'develop' 2017-10-03 21:03:19 -07:00
snipe
f66e222f3d Fixes #4132 - associated accessory users 2017-10-03 21:03:00 -07:00
snipe
fb180d74a1 Merge branch 'develop' 2017-10-03 18:12:47 -07:00
snipe
eaf55f5e79 Hide table toolbar id models are deleted 2017-10-03 18:12:30 -07:00
snipe
8edb586576 Merge branch 'develop' 2017-10-03 18:06:59 -07:00
snipe
32b01b8f38 Toggle button deleted/not for models 2017-10-03 18:06:41 -07:00
snipe
340386f282 Merge branch 'develop' 2017-10-03 17:55:21 -07:00
snipe
6dd4282f1f Fixes #4130 - show deleted asset models 2017-10-03 17:53:08 -07:00
snipe
3f44987799 Small logo size tweaks 2017-10-03 14:15:03 -07:00
snipe
b4fec068d0 Use asset url for favicon on login blade 2017-10-03 13:44:50 -07:00
snipe
512632ce60 Make the export button contextual for requested status 2017-10-03 12:50:18 -07:00
snipe
58a9b0d3e8 Normalized route name 2017-10-03 12:50:01 -07:00
snipe
fcb1283a14 Added missing deployed page title 2017-10-03 12:49:53 -07:00
snipe
54671af7f0 Small export assets fix 2017-10-03 11:49:41 -07:00
snipe
aedabb0920 Merge branch 'develop' 2017-10-03 11:29:06 -07:00
snipe
aaf4acef83 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-03 11:28:45 -07:00
snipe
8e73cacf4e Fixes custom report to include assigned to names, etc 2017-10-03 10:38:28 -07:00
Daniel Meltzer
b4b2daebbd Fix Importer tests. (#4122) 2017-10-03 09:14:04 -07:00
snipe
3e2c18cb4d Merge branch 'develop' 2017-10-03 08:47:57 -07:00
snipe
c721fdd793 Merge branch 'develop' of github.com:snipe/snipe-it into develop 2017-10-03 08:46:25 -07:00
Daniel Meltzer
d119372ff0 Fix License Import. (#4121)
The license name is not unique, so keying by license alone was causing issues.  Match using name + serial instead.
2017-10-03 08:46:06 -07:00
snipe
c8bed867da Export PDF as landscape 2017-10-03 07:32:18 -07:00
snipe
3a470ce789 Only report exceptions we want to see 2017-10-03 07:28:00 -07:00
6097 changed files with 137278 additions and 504368 deletions

View File

@@ -755,6 +755,982 @@
"contributions": [
"code"
]
},
{
"login": "imjennyli",
"name": "Jenny Li",
"avatar_url": "https://avatars3.githubusercontent.com/u/404729?v=4",
"profile": "https://github.com/imjennyli",
"contributions": [
"doc"
]
},
{
"login": "GeoffYoung",
"name": "Geoff Young",
"avatar_url": "https://avatars0.githubusercontent.com/u/869227?v=4",
"profile": "https://github.com/GeoffYoung",
"contributions": [
"code"
]
},
{
"login": "BlueHatbRit",
"name": "Elliot Blackburn",
"avatar_url": "https://avatars3.githubusercontent.com/u/1068477?v=4",
"profile": "http://www.elliotblackburn.com",
"contributions": [
"doc"
]
},
{
"login": "TonisOrmisson",
"name": "Tõnis Ormisson",
"avatar_url": "https://avatars1.githubusercontent.com/u/6357451?v=4",
"profile": "http://andmemasin.eu",
"contributions": [
"code"
]
},
{
"login": "thakilla",
"name": "Nicolai Essig",
"avatar_url": "https://avatars0.githubusercontent.com/u/449411?v=4",
"profile": "http://www.nicolai-essig.de",
"contributions": [
"code"
]
},
{
"login": "techincolor",
"name": "Danielle",
"avatar_url": "https://avatars1.githubusercontent.com/u/14809698?v=4",
"profile": "https://github.com/techincolor",
"contributions": [
"doc"
]
},
{
"login": "TheVakman",
"name": "Lawrence",
"avatar_url": "https://avatars1.githubusercontent.com/u/18545156?v=4",
"profile": "https://github.com/TheVakman",
"contributions": [
"test",
"bug"
]
},
{
"login": "uknzaeinozpas",
"name": "uknzaeinozpas",
"avatar_url": "https://avatars1.githubusercontent.com/u/22473767?v=4",
"profile": "https://github.com/uknzaeinozpas",
"contributions": [
"test",
"code"
]
},
{
"login": "Gelob",
"name": "Ryan",
"avatar_url": "https://avatars3.githubusercontent.com/u/422752?v=4",
"profile": "https://github.com/Gelob",
"contributions": [
"doc"
]
},
{
"login": "vcordes79",
"name": "vcordes79",
"avatar_url": "https://avatars1.githubusercontent.com/u/10672546?v=4",
"profile": "https://github.com/vcordes79",
"contributions": [
"code"
]
},
{
"login": "fordster78",
"name": "fordster78",
"avatar_url": "https://avatars3.githubusercontent.com/u/27958330?v=4",
"profile": "https://github.com/fordster78",
"contributions": [
"code"
]
},
{
"login": "CronKz",
"name": "CronKz",
"avatar_url": "https://avatars0.githubusercontent.com/u/34064225?v=4",
"profile": "https://github.com/CronKz",
"contributions": [
"code",
"translation"
]
},
{
"login": "tdb",
"name": "Tim Bishop",
"avatar_url": "https://avatars1.githubusercontent.com/u/585486?v=4",
"profile": "https://github.com/tdb",
"contributions": [
"code"
]
},
{
"login": "seanmcilvenna",
"name": "Sean McIlvenna",
"avatar_url": "https://avatars2.githubusercontent.com/u/5384694?v=4",
"profile": "https://www.seanmcilvenna.com",
"contributions": [
"code"
]
},
{
"login": "cepacs",
"name": "cepacs",
"avatar_url": "https://avatars3.githubusercontent.com/u/36515590?v=4",
"profile": "https://github.com/cepacs",
"contributions": [
"bug",
"doc"
]
},
{
"login": "lea-mink",
"name": "lea-mink",
"avatar_url": "https://avatars2.githubusercontent.com/u/37537300?v=4",
"profile": "https://github.com/lea-mink",
"contributions": [
"code"
]
},
{
"login": "hannahtinkler",
"name": "Hannah Tinkler",
"avatar_url": "https://avatars0.githubusercontent.com/u/7140719?v=4",
"profile": "https://github.com/hannahtinkler",
"contributions": [
"code"
]
},
{
"login": "doekman",
"name": "Doeke Zanstra",
"avatar_url": "https://avatars1.githubusercontent.com/u/1086388?v=4",
"profile": "https://github.com/doekman",
"contributions": [
"code"
]
},
{
"login": "SjamonDaal",
"name": "Djamon Staal",
"avatar_url": "https://avatars1.githubusercontent.com/u/4325936?v=4",
"profile": "https://www.sdhd.nl/",
"contributions": [
"code"
]
},
{
"login": "EarlRamirez",
"name": "Earl Ramirez",
"avatar_url": "https://avatars3.githubusercontent.com/u/12306859?v=4",
"profile": "https://github.com/EarlRamirez",
"contributions": [
"code"
]
},
{
"login": "RichardRay",
"name": "Richard Ray Thomas",
"avatar_url": "https://avatars2.githubusercontent.com/u/8671456?v=4",
"profile": "https://github.com/RichardRay",
"contributions": [
"code"
]
},
{
"login": "thelamer",
"name": "Ryan Kuba",
"avatar_url": "https://avatars3.githubusercontent.com/u/1852688?v=4",
"profile": "https://www.taisun.io/",
"contributions": [
"code"
]
},
{
"login": "ParadoxGuitarist",
"name": "Brian Monroe",
"avatar_url": "https://avatars1.githubusercontent.com/u/6751928?v=4",
"profile": "https://github.com/ParadoxGuitarist",
"contributions": [
"code"
]
},
{
"login": "plexorama",
"name": "plexorama",
"avatar_url": "https://avatars1.githubusercontent.com/u/605167?v=4",
"profile": "https://github.com/plexorama",
"contributions": [
"code"
]
},
{
"login": "tilldeeke",
"name": "Till Deeke",
"avatar_url": "https://avatars2.githubusercontent.com/u/1795149?v=4",
"profile": "https://tilldeeke.de",
"contributions": [
"code"
]
},
{
"login": "5quirrel",
"name": "5quirrel",
"avatar_url": "https://avatars0.githubusercontent.com/u/12634129?v=4",
"profile": "https://github.com/5quirrel",
"contributions": [
"code"
]
},
{
"login": "jasonlshelton",
"name": "Jason",
"avatar_url": "https://avatars1.githubusercontent.com/u/13071957?v=4",
"profile": "https://github.com/jasonlshelton",
"contributions": [
"code"
]
},
{
"login": "chemfy",
"name": "Antti",
"avatar_url": "https://avatars3.githubusercontent.com/u/7128321?v=4",
"profile": "https://github.com/chemfy",
"contributions": [
"code"
]
},
{
"login": "DeusMaximus",
"name": "DeusMaximus",
"avatar_url": "https://avatars3.githubusercontent.com/u/10080364?v=4",
"profile": "https://github.com/DeusMaximus",
"contributions": [
"code"
]
},
{
"login": "A-ROYAL",
"name": "a-royal",
"avatar_url": "https://avatars2.githubusercontent.com/u/16384611?v=4",
"profile": "https://github.com/A-ROYAL",
"contributions": [
"translation"
]
},
{
"login": "albertoaldrigo",
"name": "Alberto Aldrigo",
"avatar_url": "https://avatars0.githubusercontent.com/u/5358208?v=4",
"profile": "https://github.com/albertoaldrigo",
"contributions": [
"translation"
]
},
{
"login": "RealEnder",
"name": "Alex Stanev",
"avatar_url": "https://avatars0.githubusercontent.com/u/1412342?v=4",
"profile": "http://alex.stanev.org/blog",
"contributions": [
"translation"
]
},
{
"login": "sirrus",
"name": "Andreas Rehm",
"avatar_url": "https://avatars0.githubusercontent.com/u/177295?v=4",
"profile": "http://devel.itsolution2.de",
"contributions": [
"translation"
]
},
{
"login": "xelan",
"name": "Andreas Erhard",
"avatar_url": "https://avatars0.githubusercontent.com/u/5080535?v=4",
"profile": "https://github.com/xelan",
"contributions": [
"translation"
]
},
{
"login": "angeldeejay",
"name": "Andrés Vanegas Jiménez",
"avatar_url": "https://avatars2.githubusercontent.com/u/142350?v=4",
"profile": "https://github.com/angeldeejay",
"contributions": [
"translation"
]
},
{
"login": "aschiavon91",
"name": "Antonio Schiavon",
"avatar_url": "https://avatars0.githubusercontent.com/u/3910403?v=4",
"profile": "https://github.com/aschiavon91",
"contributions": [
"translation"
]
},
{
"login": "benunter",
"name": "benunter",
"avatar_url": "https://avatars0.githubusercontent.com/u/10464547?v=4",
"profile": "https://github.com/benunter",
"contributions": [
"translation"
]
},
{
"login": "rudashi",
"name": "Borys Żmuda",
"avatar_url": "https://avatars1.githubusercontent.com/u/5038647?v=4",
"profile": "http://catweb24.pl",
"contributions": [
"translation"
]
},
{
"login": "chibacityblues",
"name": "chibacityblues",
"avatar_url": "https://avatars0.githubusercontent.com/u/5539359?v=4",
"profile": "https://github.com/chibacityblues",
"contributions": [
"translation"
]
},
{
"login": "cwlin0416",
"name": "Chien Wei Lin",
"avatar_url": "https://avatars1.githubusercontent.com/u/1954830?v=4",
"profile": "https://github.com/cwlin0416",
"contributions": [
"translation"
]
},
{
"login": "Againstreality",
"name": "Christian Schuster",
"avatar_url": "https://avatars3.githubusercontent.com/u/11700533?v=4",
"profile": "https://github.com/Againstreality",
"contributions": [
"translation"
]
},
{
"login": "kopi-item",
"name": "Christian Stefanus",
"avatar_url": "https://avatars1.githubusercontent.com/u/4308704?v=4",
"profile": "http://chriss.webhostid.com",
"contributions": [
"translation"
]
},
{
"login": "wxcafe",
"name": "wxcafé",
"avatar_url": "https://avatars3.githubusercontent.com/u/3009327?v=4",
"profile": "http://wxcafe.net",
"contributions": [
"translation"
]
},
{
"login": "dpyroc",
"name": "dpyroc",
"avatar_url": "https://avatars3.githubusercontent.com/u/35761525?v=4",
"profile": "https://github.com/dpyroc",
"contributions": [
"translation"
]
},
{
"login": "da-friedl",
"name": "Daniel Friedlmaier",
"avatar_url": "https://avatars1.githubusercontent.com/u/2153639?v=4",
"profile": "http://www.friedlmaier.net",
"contributions": [
"translation"
]
},
{
"login": "danielheene",
"name": "Daniel Heene",
"avatar_url": "https://avatars1.githubusercontent.com/u/2947640?v=4",
"profile": "https://github.com/danielheene",
"contributions": [
"translation"
]
},
{
"login": "danielcb",
"name": "danielcb",
"avatar_url": "https://avatars3.githubusercontent.com/u/319022?v=4",
"profile": "https://github.com/danielcb",
"contributions": [
"translation"
]
},
{
"login": "dominiksenti",
"name": "Dominik Senti",
"avatar_url": "https://avatars3.githubusercontent.com/u/15846537?v=4",
"profile": "https://github.com/dominiksenti",
"contributions": [
"translation"
]
},
{
"login": "EpixFr",
"name": "Eric Gautheron",
"avatar_url": "https://avatars0.githubusercontent.com/u/25570954?v=4",
"profile": "http://www.konectik.com",
"contributions": [
"translation"
]
},
{
"login": "Erlpil",
"name": "Erlend Pilø",
"avatar_url": "https://avatars1.githubusercontent.com/u/5732623?v=4",
"profile": "https://erlpil.com",
"contributions": [
"translation"
]
},
{
"login": "frapposelli",
"name": "Fabio Rapposelli",
"avatar_url": "https://avatars0.githubusercontent.com/u/541832?v=4",
"profile": "http://fabio.technology",
"contributions": [
"translation"
]
},
{
"login": "fgbs",
"name": "Felipe Barros",
"avatar_url": "https://avatars2.githubusercontent.com/u/3605240?v=4",
"profile": "https://github.com/fgbs",
"contributions": [
"translation"
]
},
{
"login": "possebon",
"name": "Fernando Possebon",
"avatar_url": "https://avatars0.githubusercontent.com/u/257745?v=4",
"profile": "https://github.com/possebon",
"contributions": [
"translation"
]
},
{
"login": "gdraque",
"name": "gdraque",
"avatar_url": "https://avatars3.githubusercontent.com/u/2540832?v=4",
"profile": "https://github.com/gdraque",
"contributions": [
"translation"
]
},
{
"login": "georgwallisch",
"name": "Georg Wallisch",
"avatar_url": "https://avatars0.githubusercontent.com/u/23440381?v=4",
"profile": "https://github.com/georgwallisch",
"contributions": [
"translation"
]
},
{
"login": "jgroblesr85",
"name": "Gerardo Robles",
"avatar_url": "https://avatars1.githubusercontent.com/u/9852832?v=4",
"profile": "https://github.com/jgroblesr85",
"contributions": [
"translation"
]
},
{
"login": "mrgluek",
"name": "Gluek",
"avatar_url": "https://avatars2.githubusercontent.com/u/11082640?v=4",
"profile": "https://t.me/Gluek",
"contributions": [
"translation"
]
},
{
"login": "AdnanAbuShahad",
"name": "AdnanAbuShahad",
"avatar_url": "https://avatars0.githubusercontent.com/u/6847946?v=4",
"profile": "https://github.com/AdnanAbuShahad",
"contributions": [
"translation"
]
},
{
"login": "hafidzi",
"name": "Hafidzi My",
"avatar_url": "https://avatars1.githubusercontent.com/u/3580608?v=4",
"profile": "https://hafidzi.my",
"contributions": [
"translation"
]
},
{
"login": "fofwisdom",
"name": "Harim Park",
"avatar_url": "https://avatars2.githubusercontent.com/u/205521?v=4",
"profile": "https://github.com/fofwisdom",
"contributions": [
"translation"
]
},
{
"login": "Kentsson",
"name": "Henrik Kentsson",
"avatar_url": "https://avatars2.githubusercontent.com/u/3333841?v=4",
"profile": "http://www.kentsson.se",
"contributions": [
"translation"
]
},
{
"login": "husnulyaqien",
"name": "Husnul Yaqien",
"avatar_url": "https://avatars0.githubusercontent.com/u/36551034?v=4",
"profile": "https://github.com/husnulyaqien",
"contributions": [
"translation"
]
},
{
"login": "abaalkh",
"name": "Ibrahim",
"avatar_url": "https://avatars1.githubusercontent.com/u/2372747?v=4",
"profile": "http://abaalkhail.org",
"contributions": [
"translation"
]
},
{
"login": "igolman",
"name": "igolman",
"avatar_url": "https://avatars0.githubusercontent.com/u/1389334?v=4",
"profile": "https://github.com/igolman",
"contributions": [
"translation"
]
},
{
"login": "itangiang",
"name": "itangiang",
"avatar_url": "https://avatars1.githubusercontent.com/u/3257070?v=4",
"profile": "https://github.com/itangiang",
"contributions": [
"translation"
]
},
{
"login": "jarby1211",
"name": "jarby1211",
"avatar_url": "https://avatars2.githubusercontent.com/u/14814254?v=4",
"profile": "https://github.com/jarby1211",
"contributions": [
"translation"
]
},
{
"login": "JohnWillker",
"name": "Jhonn Willker",
"avatar_url": "https://avatars3.githubusercontent.com/u/6719357?v=4",
"profile": "http://jwillker.com",
"contributions": [
"translation"
]
},
{
"login": "joxelito94",
"name": "Jose",
"avatar_url": "https://avatars2.githubusercontent.com/u/10983635?v=4",
"profile": "https://github.com/joxelito94",
"contributions": [
"translation"
]
},
{
"login": "laopangzi",
"name": "laopangzi",
"avatar_url": "https://avatars0.githubusercontent.com/u/5206122?v=4",
"profile": "https://github.com/laopangzi",
"contributions": [
"translation"
]
},
{
"login": "lstrojny",
"name": "Lars Strojny",
"avatar_url": "https://avatars2.githubusercontent.com/u/79707?v=4",
"profile": "http://usrportage.de",
"contributions": [
"translation"
]
},
{
"login": "MarcosBL",
"name": "MarcosBL",
"avatar_url": "https://avatars0.githubusercontent.com/u/389801?v=4",
"profile": "http://twitter.com/marcosbl",
"contributions": [
"translation"
]
},
{
"login": "mariejoyacajes",
"name": "marie joy cajes",
"avatar_url": "https://avatars3.githubusercontent.com/u/35664606?v=4",
"profile": "https://github.com/mariejoyacajes",
"contributions": [
"translation"
]
},
{
"login": "msjohansen",
"name": "Mark S. Johansen",
"avatar_url": "https://avatars2.githubusercontent.com/u/3052816?v=4",
"profile": "http://www.markjohansen.dk",
"contributions": [
"translation"
]
},
{
"login": "stubben",
"name": "Martin Stub",
"avatar_url": "https://avatars2.githubusercontent.com/u/982885?v=4",
"profile": "http://martinstub.dk",
"contributions": [
"translation"
]
},
{
"login": "meyerf99",
"name": "Meyer Flavio",
"avatar_url": "https://avatars2.githubusercontent.com/u/28959963?v=4",
"profile": "https://github.com/meyerf99",
"contributions": [
"translation"
]
},
{
"login": "MicaelRodrigues",
"name": "Micael Rodrigues",
"avatar_url": "https://avatars3.githubusercontent.com/u/796443?v=4",
"profile": "https://github.com/MicaelRodrigues",
"contributions": [
"translation"
]
},
{
"login": "mikaelssen",
"name": "Mikael Rasmussen",
"avatar_url": "https://avatars0.githubusercontent.com/u/10481331?v=4",
"profile": "http://rubixy.com/",
"contributions": [
"translation"
]
},
{
"login": "IxFail",
"name": "IxFail",
"avatar_url": "https://avatars1.githubusercontent.com/u/1544552?v=4",
"profile": "https://github.com/IxFail",
"contributions": [
"translation"
]
},
{
"login": "MohammedFota",
"name": "Mohammed Fota",
"avatar_url": "https://avatars3.githubusercontent.com/u/18483118?v=4",
"profile": "http://www.mohammedfota.com",
"contributions": [
"translation"
]
},
{
"login": "omego",
"name": "Moayad Alserihi",
"avatar_url": "https://avatars0.githubusercontent.com/u/227080?v=4",
"profile": "https://github.com/omego",
"contributions": [
"translation"
]
},
{
"login": "saymd",
"name": "saymd",
"avatar_url": "https://avatars0.githubusercontent.com/u/1680266?v=4",
"profile": "https://github.com/saymd",
"contributions": [
"translation"
]
},
{
"login": "pooot",
"name": "Patrik Larsson",
"avatar_url": "https://avatars0.githubusercontent.com/u/1826808?v=4",
"profile": "https://nordsken.se",
"contributions": [
"translation"
]
},
{
"login": "drcryo",
"name": "drcryo",
"avatar_url": "https://avatars1.githubusercontent.com/u/20584746?v=4",
"profile": "https://github.com/drcryo",
"contributions": [
"translation"
]
},
{
"login": "pawel1615",
"name": "pawel1615",
"avatar_url": "https://avatars1.githubusercontent.com/u/19408004?v=4",
"profile": "https://github.com/pawel1615",
"contributions": [
"translation"
]
},
{
"login": "bodrovics",
"name": "bodrovics",
"avatar_url": "https://avatars2.githubusercontent.com/u/23340468?v=4",
"profile": "https://github.com/bodrovics",
"contributions": [
"translation"
]
},
{
"login": "priatna",
"name": "priatna",
"avatar_url": "https://avatars0.githubusercontent.com/u/3257654?v=4",
"profile": "https://github.com/priatna",
"contributions": [
"translation"
]
},
{
"login": "ProfFan",
"name": "Fan Jiang",
"avatar_url": "https://avatars1.githubusercontent.com/u/5358374?v=4",
"profile": "https://amayume.net",
"contributions": [
"translation"
]
},
{
"login": "ragnarcx",
"name": "ragnarcx",
"avatar_url": "https://avatars1.githubusercontent.com/u/22555451?v=4",
"profile": "https://github.com/ragnarcx",
"contributions": [
"translation"
]
},
{
"login": "reinvanhaaren",
"name": "Rein van Haaren",
"avatar_url": "https://avatars2.githubusercontent.com/u/18654582?v=4",
"profile": "http://www.reinvanhaaren.nl/",
"contributions": [
"translation"
]
},
{
"login": "dheche",
"name": "Teguh Dwicaksana",
"avatar_url": "https://avatars1.githubusercontent.com/u/386672?v=4",
"profile": "http://dheche.songolimo.net",
"contributions": [
"translation"
]
},
{
"login": "FRaccie",
"name": "fraccie",
"avatar_url": "https://avatars2.githubusercontent.com/u/2572552?v=4",
"profile": "https://github.com/FRaccie",
"contributions": [
"translation"
]
},
{
"login": "vinzruzell",
"name": "vinzruzell",
"avatar_url": "https://avatars0.githubusercontent.com/u/35182720?v=4",
"profile": "https://github.com/vinzruzell",
"contributions": [
"translation"
]
},
{
"login": "vipsystem",
"name": "Kevin Austin",
"avatar_url": "https://avatars1.githubusercontent.com/u/7883603?v=4",
"profile": "http://kevinaustin.com",
"contributions": [
"translation"
]
},
{
"login": "wira-sandy",
"name": "Wira Sandy",
"avatar_url": "https://avatars3.githubusercontent.com/u/3861828?v=4",
"profile": "http://azuraweb.xyz",
"contributions": [
"translation"
]
},
{
"login": "GrayHoax",
"name": "Илья",
"avatar_url": "https://avatars2.githubusercontent.com/u/8663789?v=4",
"profile": "https://github.com/GrayHoax",
"contributions": [
"translation"
]
},
{
"login": "godusevpn",
"name": "GodUseVPN",
"avatar_url": "https://avatars3.githubusercontent.com/u/30119111?v=4",
"profile": "https://github.com/godusevpn",
"contributions": [
"translation"
]
},
{
"login": "EngrZhou",
"name": "周周",
"avatar_url": "https://avatars1.githubusercontent.com/u/745576?v=4",
"profile": "https://github.com/EngrZhou",
"contributions": [
"translation"
]
},
{
"login": "takuy",
"name": "Sam",
"avatar_url": "https://avatars3.githubusercontent.com/u/1631095?v=4",
"profile": "https://github.com/takuy",
"contributions": [
"code"
]
},
{
"login": "Azerothian",
"name": "Azerothian",
"avatar_url": "https://avatars1.githubusercontent.com/u/264022?v=4",
"profile": "https://www.illisian.com.au",
"contributions": [
"code"
]
},
{
"login": "timothyfarmer",
"name": "Tim Farmer",
"avatar_url": "https://avatars1.githubusercontent.com/u/7632599?v=4",
"profile": "https://github.com/timothyfarmer",
"contributions": [
"code"
]
},
{
"login": "mskrip",
"name": "Marián Skrip",
"avatar_url": "https://avatars0.githubusercontent.com/u/17459600?v=4",
"profile": "https://github.com/mskrip",
"contributions": [
"code"
]
},
{
"login": "Godmartinz",
"name": "Godfrey Martinez",
"avatar_url": "https://avatars2.githubusercontent.com/u/47435081?v=4",
"profile": "https://github.com/Godmartinz",
"contributions": [
"code"
]
},
{
"login": "bigtreeEdo",
"name": "bigtreeEdo",
"avatar_url": "https://avatars1.githubusercontent.com/u/2075128?v=4",
"profile": "https://github.com/bigtreeEdo",
"contributions": [
"code"
]
},
{
"login": "ColinMcNeil",
"name": "Colin McNeil",
"avatar_url": "https://avatars0.githubusercontent.com/u/5000430?v=4",
"profile": "https://colinmcneil.me/",
"contributions": [
"code"
]
},
{
"login": "JoKneeMo",
"name": "JoKneeMo",
"avatar_url": "https://avatars0.githubusercontent.com/u/421625?v=4",
"profile": "https://github.com/JoKneeMo",
"contributions": [
"code"
]
},
{
"login": "joshi-redbridge",
"name": "Joshi",
"avatar_url": "https://avatars0.githubusercontent.com/u/54849013?v=4",
"profile": "http://www.redbridge.se",
"contributions": [
"code"
]
},
{
"login": "anthonypburns",
"name": "Anthony Burns",
"avatar_url": "https://avatars2.githubusercontent.com/u/15731458?v=4",
"profile": "https://github.com/anthonypburns",
"contributions": [
"code"
]
},
{
"login": "alek13",
"name": "Alexander Chibrikin",
"avatar_url": "https://avatars2.githubusercontent.com/u/1972329?v=4",
"profile": "http://phpprofi.ru/",
"contributions": [
"code"
]
}
]
}

View File

@@ -7,13 +7,13 @@ APP_KEY=ChangeMe
APP_URL=null
APP_TIMEZONE='UTC'
APP_LOCALE=en
MAX_RESULTS=500
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=localhost
DB_HOST=127.0.0.1
DB_DATABASE=null
DB_USERNAME=null
DB_PASSWORD=null
@@ -26,12 +26,12 @@ DB_COLLATION=utf8mb4_unicode_ci
# OPTIONAL: SSL DATABASE SETTINGS
# --------------------------------------------
DB_SSL=false
DB_SSL_IS_PAAS=false
DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
DB_SSL_CA_PATH=null
DB_SSL_CIPHER=null
# --------------------------------------------
# REQUIRED: OUTGOING MAIL SERVER SETTINGS
# --------------------------------------------
@@ -45,7 +45,7 @@ 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
# --------------------------------------------
# REQUIRED: IMAGE LIBRARY
@@ -53,7 +53,6 @@ MAIL_REPLYTO_NAME='Snipe-IT'
# --------------------------------------------
IMAGE_LIB=gd
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------
@@ -64,13 +63,15 @@ COOKIE_NAME=snipeit_session
COOKIE_DOMAIN=null
SECURE_COOKIES=false
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
# --------------------------------------------
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
CORS_ALLOWED_ORIGINS=null
ENABLE_HSTS=false
# --------------------------------------------
# OPTIONAL: CACHE SETTINGS
@@ -78,7 +79,20 @@ ENABLE_CSP=false
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
CACHE_PREFIX=snipeit
# --------------------------------------------
# OPTIONAL: REDIS SETTINGS
# --------------------------------------------
REDIS_HOST=null
REDIS_PASSWORD=null
REDIS_PORT=null
# --------------------------------------------
# OPTIONAL: MEMCACHED SETTINGS
# --------------------------------------------
MEMCACHED_HOST=null
MEMCACHED_PORT=null
# --------------------------------------------
# OPTIONAL: AWS S3 SETTINGS
@@ -98,8 +112,11 @@ LOGIN_LOCKOUT_DURATION=60
# OPTIONAL: MISC
# --------------------------------------------
APP_LOG=single
APP_LOG_MAX_FILES=10
APP_LOCKED=false
FILESYSTEM_DISK=local
APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1
ALLOW_IFRAMING=false
APP_CIPHER=AES-256-CBC
GOOGLE_MAPS_API=
BACKUP_ENV=true
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600

View File

@@ -1,8 +1,8 @@
APP_ENV=testing
APP_DEBUG=true
APP_URL=http://snipe-it.localapp
DB_CONNECTION=sqlite_testing
DB_DEFAULT=sqlite_testing
DB_CONNECTION=mysql
DB_DEFAULT=mysql
DB_HOST=localhost
DB_DATABASE=snipeittests
DB_USERNAME=snipeit

19
.env.unit-tests Normal file
View File

@@ -0,0 +1,19 @@
APP_ENV=testing
APP_DEBUG=true
APP_URL=http://snipe-it.localapp
DB_CONNECTION=sqlite_testing
DB_DEFAULT=sqlite_testing
DB_HOST=localhost
APP_KEY=base64:tu9NRh/a6+dCXBDGvg0Gv/0TcABnFsbT4AKxrr8mwQo=
# --------------------------------------------
# OPTIONAL: LOGIN THROTTLING
# (LOGIN_LOCKOUT_DURATIONin minutes)
# --------------------------------------------
LOGIN_MAX_ATTEMPTS=1000000
LOGIN_LOCKOUT_DURATION=100000000
MAIL_DRIVER=log
MAIL_FROM_ADDR=you@example.com
MAIL_FROM_NAME=Snipe-IT

5
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
# 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

View File

@@ -28,8 +28,8 @@
- What specific Snipe-IT page you're on, and what specific element you're interacting with to trigger the error
- If a stacktrace is provided in the error, include that too.
- Any errors that appear in your browser's error console.
- Confirm whether the error is reproduceable on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `app/storage/logs` and your webserver's logs.
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `storage/logs` and your webserver's logs.
- Include what you've done so far in the installation, and if you got any error messages along the way.
- Indicate whether or not you've manually edited any data directly in the database

61
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@@ -0,0 +1,61 @@
---
name: Bug report
about: Create a report to help us improve
---
#### Please confirm you have done the following before posting your bug report:
- [ ] I have enabled debug mode
- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Error Messages**
- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
- If a stacktrace is provided in the error, include that too.
- Any errors that appear in your browser's error console.
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `storage/logs` and your webserver's logs.
**Additional context**
- Is this a fresh install or an upgrade?
- What OS and web server you're running Snipe-IT on
- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
- Include what you've done so far in the installation, and if you got any error messages along the way.
- Indicate whether or not you've manually edited any data directly in the database
Add any other context about the problem here.
Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.

View File

@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

43
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- :woman_technologist: ready for dev
- :moneybag: bounty
- :hand: bug
- "🔐 security"
- "👩‍💻 ready for dev"
- "💰 bounty"
- "✋ bug"
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: stale
only: issues
# Comment to post when removing the stale label.
unmarkComment: >
Okay, it looks like this issue or feature request might still be important. We'll re-open
it for now. Thank you for letting us know!
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.

1
.github/travis-memory.ini vendored Normal file
View File

@@ -0,0 +1 @@
memory_limit= 2048M

9
.gitignore vendored
View File

@@ -27,8 +27,16 @@ public/uploads/logo.png
public/uploads/logo.svg
public/uploads/models/*
public/uploads/suppliers/*
public/uploads/accessories/*
public/uploads/locations/*
public/uploads/manufacturers/*
public/uploads/components/*
public/uploads/consumables/*
public/uploads/companies/*
public/uploads/categories/*
public/uploads/users/*
storage/app/private_uploads/users/*
public/uploads/departments/*
storage/debugbar/
storage/dumps/*
storage/laravel-backups
@@ -42,3 +50,4 @@ tests/_support/_generated/*
/storage/oauth-public.key
*.cache
/public/storage

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v6.11.3

View File

@@ -1,4 +1,7 @@
addons:
code_climate:
repo_token:
secure: "C/bUAEpwfZB82dkzI2Nxx3PW5w/BzbKkSyCkp6YjT046jD2/QKvz6ngCFlt3tAWV11TXWFI6D8DzkMmdWOrQl3SGlPZXRD8QOvCiz0HiGMDvlxjAaPaQecGaQZdx/H4m6xTUXRNUVaYmxlMgkkFCWhAp+HZDs0iyOEVamp0Jszg="
hosts:
- localhost
sudo: false
@@ -11,11 +14,17 @@ services:
# list any PHP version you want to test against
php:
- 5.6
- 7.0
- 7.1.2
- 7.2
- 7.3
matrix:
allow_failures:
- php: 7.3
# execute any number of scripts before the test run, custom env's are available as variables
before_script:
- phpenv config-add .github/travis-memory.ini
- phantomjs --webdriver=4444 &
- sleep 4
- mysql -e 'CREATE DATABASE snipeit_unit;'
@@ -45,9 +54,12 @@ before_script:
script:
- ./vendor/bin/codecept run unit
# - ./vendor/bin/codecept run acceptance --env=testing-ci
- ./vendor/bin/codecept run functional --env=functional-travis
#script: ./vendor/bin/codecept run
- ./vendor/bin/codecept run api --env=testing-ci
- ./vendor/bin/codecept run functional --env=functional-travis -g func1
- ./vendor/bin/codecept run functional --env=functional-travis -g func2
- ./vendor/bin/codecept run api --env=functional-travis
after_script:
- vendor/bin/test-reporter
after_success:
- codecov

View File

@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
reported by contacting the project team at abuse@snipeitapp.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@@ -1,6 +1,6 @@
### Contributing
Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/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.

View File

@@ -1,24 +1,27 @@
FROM ubuntu:xenial
MAINTAINER Brady Wetherington <uberbrady@gmail.com>
LABEL maintainer="uberbrady, hinchk"
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 \
apache2 \
apache2-bin \
libapache2-mod-php7.0 \
php7.0-curl \
php7.0-ldap \
php7.0-mysql \
php7.0-mcrypt \
php7.0-gd \
php7.0-xml \
php7.0-mbstring \
php7.0-zip \
php7.0-bcmath \
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 \
patch \
curl \
vim \
git \
mysql-client \
supervisor \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -26,8 +29,8 @@ RUN phpenmod mcrypt
RUN phpenmod gd
RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.0/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.0/cli/php.ini
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 useradd -m --uid 1000 --gid 50 docker
@@ -64,7 +67,12 @@ RUN chown -R docker /var/www/html
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"
&& 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" \
&& 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"
############## DEPENDENCIES via COMPOSER ###################
@@ -91,16 +99,11 @@ VOLUME ["/var/lib/snipeit"]
##### START SERVER
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
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
# 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"]
CMD ["/startup.sh"]
EXPOSE 80
EXPOSE 443

View File

@@ -1,16 +1,16 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=develop)](https://travis-ci.org/snipe/snipe-it) [![Stories in Ready](https://badge.waffle.io/snipe/snipe-it.png?label=ready+for+dev&amp;title=Ready+for+development)](http://waffle.io/snipe/snipe-it) [![Maintenance](https://img.shields.io/maintenance/yes/2017.svg)]() [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.png)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeyhead.svg?style=social)](https://twitter.com/snipeyhead) [![Zenhub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-81-orange.svg?style=flat-square)](#contributors)
[![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-189-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.4](http://laravel.com).
It is built on [Laravel 5.5](http://laravel.com).
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/).)
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/).)
__This is web-based software__. This means there 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.
__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.
-----
@@ -50,6 +50,30 @@ Please see the [translations documentation](https://snipe-it.readme.io/docs/tran
-----
### Libraries, Modules & Related Projects
Since the release of the JSON REST API, several third-party developers have been developing modules and libraries to work with Snipe-IT.
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [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
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
-----
### Security
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
-----
### Contributors
Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
@@ -67,7 +91,22 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars0.githubusercontent.com/u/8341172?v=3" width="110px;"/><br /><sub>Jay Richards</sub>](http://www.cordeos.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [<img src="https://avatars2.githubusercontent.com/u/7295127?v=3" width="110px;"/><br /><sub>Alexander Innes</sub>](https://necurity.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [<img src="https://avatars2.githubusercontent.com/u/334485?v=3" width="110px;"/><br /><sub>Danny Garcia</sub>](https://buzzedword.codes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [<img src="https://avatars2.githubusercontent.com/u/366855?v=3" width="110px;"/><br /><sub>archpoint</sub>](https://github.com/archpoint)<br />[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [<img src="https://avatars1.githubusercontent.com/u/67991?v=3" width="110px;"/><br /><sub>Jake McGraw</sub>](http://www.jakemcgraw.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [<img src="https://avatars1.githubusercontent.com/u/1714374?v=3" width="110px;"/><br /><sub>FleischKarussel</sub>](https://github.com/FleischKarussel)<br />[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/319644?v=3" width="110px;"/><br /><sub>Dylan Yi</sub>](https://github.com/feeva)<br />[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/857740?v=3" width="110px;"/><br /><sub>Gil Rutkowski</sub>](http://FlashingCursor.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [<img src="https://avatars3.githubusercontent.com/u/129360?v=3" width="110px;"/><br /><sub>Desmond Morris</sub>](http://www.desmondmorris.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [<img src="https://avatars2.githubusercontent.com/u/52936?v=3" width="110px;"/><br /><sub>Nick Peelman</sub>](http://peelman.us)<br />[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [<img src="https://avatars0.githubusercontent.com/u/53161?v=3" width="110px;"/><br /><sub>Abraham Vegh</sub>](https://abrahamvegh.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [<img src="https://avatars0.githubusercontent.com/u/2818680?v=3" width="110px;"/><br /><sub>Mohamed Rashid</sub>](https://github.com/rashivkp)<br />[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1509456?v=3" width="110px;"/><br /><sub>Kasey</sub>](http://hinchk.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [<img src="https://avatars2.githubusercontent.com/u/10522541?v=3" width="110px;"/><br /><sub>Brett</sub>](https://github.com/BrettFagerlund)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") |
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/37537300?v=4" width="110px;"/><br /><sub>lea-mink</sub>](https://github.com/lea-mink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [<img src="https://avatars0.githubusercontent.com/u/7140719?v=4" width="110px;"/><br /><sub>Hannah Tinkler</sub>](https://github.com/hannahtinkler)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1086388?v=4" width="110px;"/><br /><sub>Doeke Zanstra</sub>](https://github.com/doekman)<br />[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [<img src="https://avatars1.githubusercontent.com/u/4325936?v=4" width="110px;"/><br /><sub>Djamon Staal</sub>](https://www.sdhd.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [<img src="https://avatars3.githubusercontent.com/u/12306859?v=4" width="110px;"/><br /><sub>Earl Ramirez</sub>](https://github.com/EarlRamirez)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [<img src="https://avatars2.githubusercontent.com/u/8671456?v=4" width="110px;"/><br /><sub>Richard Ray Thomas</sub>](https://github.com/RichardRay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [<img src="https://avatars3.githubusercontent.com/u/1852688?v=4" width="110px;"/><br /><sub>Ryan Kuba</sub>](https://www.taisun.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [<img src="https://avatars1.githubusercontent.com/u/6751928?v=4" width="110px;"/><br /><sub>Brian Monroe</sub>](https://github.com/ParadoxGuitarist)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [<img src="https://avatars1.githubusercontent.com/u/605167?v=4" width="110px;"/><br /><sub>plexorama</sub>](https://github.com/plexorama)<br />[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/1795149?v=4" width="110px;"/><br /><sub>Till Deeke</sub>](https://tilldeeke.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [<img src="https://avatars0.githubusercontent.com/u/12634129?v=4" width="110px;"/><br /><sub>5quirrel</sub>](https://github.com/5quirrel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [<img src="https://avatars1.githubusercontent.com/u/13071957?v=4" width="110px;"/><br /><sub>Jason</sub>](https://github.com/jasonlshelton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [<img src="https://avatars3.githubusercontent.com/u/7128321?v=4" width="110px;"/><br /><sub>Antti</sub>](https://github.com/chemfy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [<img src="https://avatars3.githubusercontent.com/u/10080364?v=4" width="110px;"/><br /><sub>DeusMaximus</sub>](https://github.com/DeusMaximus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [<img src="https://avatars2.githubusercontent.com/u/16384611?v=4" width="110px;"/><br /><sub>a-royal</sub>](https://github.com/A-ROYAL)<br />[🌍](#translation-A-ROYAL "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5358208?v=4" width="110px;"/><br /><sub>Alberto Aldrigo</sub>](https://github.com/albertoaldrigo)<br />[🌍](#translation-albertoaldrigo "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1412342?v=4" width="110px;"/><br /><sub>Alex Stanev</sub>](http://alex.stanev.org/blog)<br />[🌍](#translation-RealEnder "Translation") | [<img src="https://avatars0.githubusercontent.com/u/177295?v=4" width="110px;"/><br /><sub>Andreas Rehm</sub>](http://devel.itsolution2.de)<br />[🌍](#translation-sirrus "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5080535?v=4" width="110px;"/><br /><sub>Andreas Erhard</sub>](https://github.com/xelan)<br />[🌍](#translation-xelan "Translation") | [<img src="https://avatars2.githubusercontent.com/u/142350?v=4" width="110px;"/><br /><sub>Andrés Vanegas Jiménez</sub>](https://github.com/angeldeejay)<br />[🌍](#translation-angeldeejay "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3910403?v=4" width="110px;"/><br /><sub>Antonio Schiavon</sub>](https://github.com/aschiavon91)<br />[🌍](#translation-aschiavon91 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10464547?v=4" width="110px;"/><br /><sub>benunter</sub>](https://github.com/benunter)<br />[🌍](#translation-benunter "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5038647?v=4" width="110px;"/><br /><sub>Borys Żmuda</sub>](http://catweb24.pl)<br />[🌍](#translation-rudashi "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/5539359?v=4" width="110px;"/><br /><sub>chibacityblues</sub>](https://github.com/chibacityblues)<br />[🌍](#translation-chibacityblues "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1954830?v=4" width="110px;"/><br /><sub>Chien Wei Lin</sub>](https://github.com/cwlin0416)<br />[🌍](#translation-cwlin0416 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/11700533?v=4" width="110px;"/><br /><sub>Christian Schuster</sub>](https://github.com/Againstreality)<br />[🌍](#translation-Againstreality "Translation") | [<img src="https://avatars1.githubusercontent.com/u/4308704?v=4" width="110px;"/><br /><sub>Christian Stefanus</sub>](http://chriss.webhostid.com)<br />[🌍](#translation-kopi-item "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3009327?v=4" width="110px;"/><br /><sub>wxcafé</sub>](http://wxcafe.net)<br />[🌍](#translation-wxcafe "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35761525?v=4" width="110px;"/><br /><sub>dpyroc</sub>](https://github.com/dpyroc)<br />[🌍](#translation-dpyroc "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2153639?v=4" width="110px;"/><br /><sub>Daniel Friedlmaier</sub>](http://www.friedlmaier.net)<br />[🌍](#translation-da-friedl "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/2947640?v=4" width="110px;"/><br /><sub>Daniel Heene</sub>](https://github.com/danielheene)<br />[🌍](#translation-danielheene "Translation") | [<img src="https://avatars3.githubusercontent.com/u/319022?v=4" width="110px;"/><br /><sub>danielcb</sub>](https://github.com/danielcb)<br />[🌍](#translation-danielcb "Translation") | [<img src="https://avatars3.githubusercontent.com/u/15846537?v=4" width="110px;"/><br /><sub>Dominik Senti</sub>](https://github.com/dominiksenti)<br />[🌍](#translation-dominiksenti "Translation") | [<img src="https://avatars0.githubusercontent.com/u/25570954?v=4" width="110px;"/><br /><sub>Eric Gautheron</sub>](http://www.konectik.com)<br />[🌍](#translation-EpixFr "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5732623?v=4" width="110px;"/><br /><sub>Erlend Pilø</sub>](https://erlpil.com)<br />[🌍](#translation-Erlpil "Translation") | [<img src="https://avatars0.githubusercontent.com/u/541832?v=4" width="110px;"/><br /><sub>Fabio Rapposelli</sub>](http://fabio.technology)<br />[🌍](#translation-frapposelli "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3605240?v=4" width="110px;"/><br /><sub>Felipe Barros</sub>](https://github.com/fgbs)<br />[🌍](#translation-fgbs "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/257745?v=4" width="110px;"/><br /><sub>Fernando Possebon</sub>](https://github.com/possebon)<br />[🌍](#translation-possebon "Translation") | [<img src="https://avatars3.githubusercontent.com/u/2540832?v=4" width="110px;"/><br /><sub>gdraque</sub>](https://github.com/gdraque)<br />[🌍](#translation-gdraque "Translation") | [<img src="https://avatars0.githubusercontent.com/u/23440381?v=4" width="110px;"/><br /><sub>Georg Wallisch</sub>](https://github.com/georgwallisch)<br />[🌍](#translation-georgwallisch "Translation") | [<img src="https://avatars1.githubusercontent.com/u/9852832?v=4" width="110px;"/><br /><sub>Gerardo Robles</sub>](https://github.com/jgroblesr85)<br />[🌍](#translation-jgroblesr85 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/11082640?v=4" width="110px;"/><br /><sub>Gluek</sub>](https://t.me/Gluek)<br />[🌍](#translation-mrgluek "Translation") | [<img src="https://avatars0.githubusercontent.com/u/6847946?v=4" width="110px;"/><br /><sub>AdnanAbuShahad</sub>](https://github.com/AdnanAbuShahad)<br />[🌍](#translation-AdnanAbuShahad "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3580608?v=4" width="110px;"/><br /><sub>Hafidzi My</sub>](https://hafidzi.my)<br />[🌍](#translation-hafidzi "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/205521?v=4" width="110px;"/><br /><sub>Harim Park</sub>](https://github.com/fofwisdom)<br />[🌍](#translation-fofwisdom "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3333841?v=4" width="110px;"/><br /><sub>Henrik Kentsson</sub>](http://www.kentsson.se)<br />[🌍](#translation-Kentsson "Translation") | [<img src="https://avatars0.githubusercontent.com/u/36551034?v=4" width="110px;"/><br /><sub>Husnul Yaqien</sub>](https://github.com/husnulyaqien)<br />[🌍](#translation-husnulyaqien "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2372747?v=4" width="110px;"/><br /><sub>Ibrahim</sub>](http://abaalkhail.org)<br />[🌍](#translation-abaalkh "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1389334?v=4" width="110px;"/><br /><sub>igolman</sub>](https://github.com/igolman)<br />[🌍](#translation-igolman "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3257070?v=4" width="110px;"/><br /><sub>itangiang</sub>](https://github.com/itangiang)<br />[🌍](#translation-itangiang "Translation") | [<img src="https://avatars2.githubusercontent.com/u/14814254?v=4" width="110px;"/><br /><sub>jarby1211</sub>](https://github.com/jarby1211)<br />[🌍](#translation-jarby1211 "Translation") |
| [<img src="https://avatars3.githubusercontent.com/u/6719357?v=4" width="110px;"/><br /><sub>Jhonn Willker</sub>](http://jwillker.com)<br />[🌍](#translation-JohnWillker "Translation") | [<img src="https://avatars2.githubusercontent.com/u/10983635?v=4" width="110px;"/><br /><sub>Jose</sub>](https://github.com/joxelito94)<br />[🌍](#translation-joxelito94 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5206122?v=4" width="110px;"/><br /><sub>laopangzi</sub>](https://github.com/laopangzi)<br />[🌍](#translation-laopangzi "Translation") | [<img src="https://avatars2.githubusercontent.com/u/79707?v=4" width="110px;"/><br /><sub>Lars Strojny</sub>](http://usrportage.de)<br />[🌍](#translation-lstrojny "Translation") | [<img src="https://avatars0.githubusercontent.com/u/389801?v=4" width="110px;"/><br /><sub>MarcosBL</sub>](http://twitter.com/marcosbl)<br />[🌍](#translation-MarcosBL "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35664606?v=4" width="110px;"/><br /><sub>marie joy cajes</sub>](https://github.com/mariejoyacajes)<br />[🌍](#translation-mariejoyacajes "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3052816?v=4" width="110px;"/><br /><sub>Mark S. Johansen</sub>](http://www.markjohansen.dk)<br />[🌍](#translation-msjohansen "Translation") |
| [<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://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars2.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
@@ -80,9 +119,3 @@ Please see the documentation on [contributing and developing for Snipe-IT](https
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.

84
Vagrantfile vendored Normal file
View File

@@ -0,0 +1,84 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
Vagrant.configure("2") do |config|
config.vm.define "bionic" do |bionic|
bionic.vm.box = "ubuntu/bionic64"
bionic.vm.hostname = 'bionic'
bionic.vm.network "public_network", bridge: NETWORK_BRIDGE
bionic.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
bionic.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "xenial" do |xenial|
xenial.vm.box = "ubuntu/xenial64"
xenial.vm.hostname = 'xenial'
xenial.vm.network "public_network", bridge: NETWORK_BRIDGE
xenial.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
xenial.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "trusty" do |trusty|
trusty.vm.box = "ubuntu/trusty32"
trusty.vm.hostname = 'trusty'
trusty.vm.network "public_network", bridge: NETWORK_BRIDGE
trusty.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
trusty.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "centos7" do |centos7|
centos7.vm.box = "centos/7"
centos7.vm.hostname = 'centos7'
centos7.vm.network "public_network", bridge: NETWORK_BRIDGE
centos7.vm.provision :shell, :inline => "sudo yum -y update"
centos7.vm.provision :shell, :inline => "yum install -y wget"
centos7.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
centos7.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "centos6" do |centos6|
centos6.vm.box = "centos/6"
centos6.vm.hostname = 'centos6'
centos6.vm.network "public_network", bridge: NETWORK_BRIDGE
centos6.vm.provision :shell, :inline => "sudo yum -y update"
centos6.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
centos6.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "jessie" do |jessie|
jessie.vm.box = "debian/jessie64"
jessie.vm.hostname = 'debian8'
jessie.vm.network "public_network", bridge: NETWORK_BRIDGE
jessie.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
jessie.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "stretch" do |stretch|
stretch.vm.box = "debian/stretch64"
stretch.vm.hostname = 'debian9'
stretch.vm.network "public_network", bridge: NETWORK_BRIDGE
stretch.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
stretch.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "fedora27" do |fedora27|
fedora27.vm.box = "fedora/27-cloud-base"
fedora27.vm.hostname = 'fedora27'
fedora27.vm.network "public_network", bridge: NETWORK_BRIDGE
fedora27.vm.provision :shell, :inline => "dnf -y install wget"
fedora27.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
fedora27.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "fedora26" do |fedora26|
fedora26.vm.box = "fedora/26-cloud-base"
fedora26.vm.hostname = 'fedora26'
fedora26.vm.network "public_network", bridge: NETWORK_BRIDGE
fedora26.vm.provision :shell, :inline => "dnf -y install wget"
fedora26.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
fedora26.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
end

View File

@@ -0,0 +1,112 @@
<?php
namespace App\Console\Commands;
use App\Models\LicenseSeat;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\License;
use Illuminate\Database\Eloquent\Model;
class CheckoutLicenseToAllUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:checkout-to-all {--license_id=} {--notify}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$license_id = $this->option('license_id');
$notify = $this->option('notify');
if (!$license_id) {
$this->error('ERROR: License ID is required.');
return false;
}
if (!$license = License::where('id','=',$license_id)->with('assignedusers')->first()) {
$this->error('Invalid license ID');
return false;
}
$users = User::whereNull('deleted_at')->with('licenses')->get();
if ($users->count() > $license->getAvailSeatsCountAttribute()) {
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
}
$this->info('Checking out '.$users->count().' of '.$license->getAvailSeatsCountAttribute().' seats for '.$license->name);
if (!$notify) {
$this->info('No mail will be sent.');
}
foreach ($users as $user) {
// Check to make sure this user doesn't already have this license checked out
// to them
if ($user->licenses->where('id', '=', $license_id)->count()) {
$this->info($user->username .' already has this license checked out to them. Skipping... ');
continue;
}
// If the license is valid, check that there is an available seat
if ($license->availCount()->count() < 1) {
$this->error('ERROR: No available seats');
return false;
}
$this->info($license->availCount()->count().' seats left');
// Get the seat ID
$licenseSeat = $license->freeSeat();
// Update the seat with checkout info,
$licenseSeat->assigned_to = $user->id;
if ($licenseSeat->save()) {
// Temporarily null the user's email address so we don't send mail if we're not supposed to
if (!$notify) {
$user->email = null;
}
// Log the checkout
$licenseSeat->logCheckout('Checked out via cli tool', $user);
$this->info('License '.$license_id.' seat '.$licenseSeat->id.' checked out to '.$user->username);
}
}
}
}

View File

@@ -0,0 +1,160 @@
<?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

@@ -16,7 +16,7 @@ class LdapSync extends Command
*
* @var string
*/
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--summary}';
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--summary} {--json_summary}';
/**
* The console command description.
@@ -42,9 +42,8 @@ class LdapSync extends Command
*/
public function handle()
{
ini_set('max_execution_time', 600); //600 seconds = 10 minutes
ini_set('memory_limit', '500M');
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '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;
@@ -55,31 +54,37 @@ class LdapSync extends Command
try {
$ldapconn = Ldap::connectToLdap();
} catch (\Exception $e) {
LOG::error($e);
}
try {
Ldap::bindAdminToLdap($ldapconn);
} catch (\Exception $e) {
LOG::error($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 [];
}
$summary = array();
$results = Ldap::findLdapUsers();
$ldap_ou_locations = Location::whereNotNull('ldap_ou')->get();
if (sizeof($ldap_ou_locations) > 0) {
LOG::debug('Some locations have special OUs set. Locations will be automatically set for users in those OUs.');
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 [];
}
$results = Ldap::findLdapUsers();
for ($i = 0; $i < $results["count"]; $i++) {
$results[$i]["ldap_location_override"] = false;
$results[$i]["location_id"] = 0;
}
/* Determine which location to assign users to by default. */
$location = NULL;
if ($this->option('location')!='') {
$location = Location::where('name', '=', $this->option('location'))->first();
@@ -89,40 +94,67 @@ class LdapSync extends Command
$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.')');
} else {
$location = NULL;
}
}
if (!isset($location)) {
LOG::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
// 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++) {
$location_users[$i]["ldap_location_override"] = true;
$location_users[$i]["location_id"] = $ldap_loc->id;
$usernames[] = $location_users[$i][$ldap_result_username][0];
/* 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"]);
}
// Delete located users from the general group.
foreach ($results as $key => $generic_entry) {
if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
unset($results[$key]);
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];
}
}
}
$global_count = $results['count'];
$results = array_merge($location_users, $results);
$results['count'] = $global_count;
// 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]);
}
}
}
$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") {
@@ -135,32 +167,49 @@ class LdapSync extends Command
$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 exists
$item["createorupdate"] = 'updated';
if (!$user = User::where('username', $item["username"])->first()) {
$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';
}
// Create the user if they don't exist.
$user->first_name = e($item["firstname"]);
$user->last_name = e($item["lastname"]);
$user->username = e($item["username"]);
$user->email = e($item["email"]);
$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"]);
$user->activated = 1;
// Sync activated state for Active Directory.
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
$enabled_accounts = [
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224', '4260352'
];
$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'];
} else if ($location) {
$user->location_id = e($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->notes = 'Imported from LDAP';
$user->ldap_import = 1;
$errors = '';
@@ -184,17 +233,16 @@ class LdapSync extends Command
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']);
$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']).'.');
$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 ];
$this->info(json_encode($json_summary));
} else {
return $summary;
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Carbon\Carbon;
class MergeUsersByUsername extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:merge-users';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command allows you to merge the history of users. It looks for users without an email address as their username and merges them into the version that does have an email username.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Get the list of users who have an email address as their username
$users = User::where('username', 'LIKE', '%@%')->whereNull('deleted_at')->get();
foreach ($users as $user) {
$parts = explode("@", $user->username);
$bad_users = User::where('username', '=', $parts[0])->whereNull('deleted_at')->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations')->get();
foreach ($bad_users as $bad_user) {
$this->info($bad_user->username.' ('.$bad_user->id.') will be merged into '.$user->username.' ('.$user->id.') ');
// Walk the list of assets
foreach ($bad_user->assets as $asset) {
$this->info( 'Updating asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
$asset->assigned_to = $user->id;
$asset->save();
}
// Walk the list of licenses
foreach ($bad_user->licenses as $license) {
$this->info( 'Updating license '.$license->name.' '.$license->id.' to user '.$user->id);
$bad_user->licenses()->updateExistingPivot($license->id, ['assigned_to' => $user->id]);
}
// Walk the list of consumables
foreach ($bad_user->consumables as $consumable) {
$this->info( 'Updating consumable '.$consumable->id.' to user '.$user->id);
$bad_user->consumables()->updateExistingPivot($consumable->id, ['assigned_to' => $user->id]);
}
// Walk the list of accessories
foreach ($bad_user->accessories as $accessory) {
$this->info( 'Updating accessory '.$accessory->id.' to user '.$user->id);
$bad_user->accessories()->updateExistingPivot($accessory->id, ['assigned_to' => $user->id]);
}
// Walk the list of logs
foreach ($bad_user->userlog as $log) {
$this->info( 'Updating action log record '.$log->id.' to user '.$user->id);
$log->target_id = $user->id;
$log->save();
}
// Update any manager IDs
$this->info( 'Updating managed user records to user '.$user->id);
User::where('manager_id', '=', $bad_user->id)->update(['manager_id' => $user->id]);
// Update location manager IDs
foreach ($bad_user->managedLocations as $managedLocation) {
$this->info( 'Updating managed location record '.$managedLocation->name.' to manager '.$user->id);
$managedLocation->manager_id = $user->id;
$managedLocation->save();
}
// Mark the user as deleted
$this->info( 'Marking the user as deleted');
$bad_user->deleted_at = Carbon::now()->timestamp;
$bad_user->save();
}
}
}
}

View File

@@ -65,7 +65,7 @@ class ObjectImportCommand extends Command
*
* @return mixed
*/
public function fire()
public function handle()
{
$filename = $this->argument('filename');
$class = title_case($this->option('item-type'));
@@ -74,6 +74,7 @@ class ObjectImportCommand extends Command
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
->setUserId($this->option('user_id'))
->setUpdating($this->option('update'))
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format'));
$logFile = $this->option('logfile');
@@ -172,6 +173,7 @@ class ObjectImportCommand extends Command
array('web-importer', null, InputOption::VALUE_NONE, 'Internal: packages output for use with the web importer'),
array('user_id', null, InputOption::VALUE_REQUIRED, 'ID of user creating items', 1),
array('update', null, InputOption::VALUE_NONE, 'If a matching item is found, update item information'),
array('send-welcome', null, InputOption::VALUE_NONE, 'Whether to send a welcome email to any new users that are created.'),
);
}

View File

@@ -79,6 +79,7 @@ class PaveIt extends Command
DB::statement('delete from accessories_users');
DB::statement('delete from asset_logs');
DB::statement('delete from asset_maintenances');
DB::statement('delete from login_attempts');
DB::statement('delete from asset_uploads');
DB::statement('delete from action_logs');
DB::statement('delete from checkout_requests');

View File

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

View File

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

View File

@@ -16,7 +16,8 @@ class RecryptFromMcrypt extends Command
*
* @var string
*/
protected $signature = 'snipeit:legacy-recrypt';
protected $signature = 'snipeit:legacy-recrypt
{--force : Force a re-crypt of encrypted data from MCRYPT.}';
/**
* The console command description.
@@ -48,6 +49,7 @@ class RecryptFromMcrypt extends Command
// If not, we can try to use the current APP_KEY if looks like it's old
$legacy_key = env('LEGACY_APP_KEY');
$key_parts = explode(':', $legacy_key);
$legacy_cipher = env('LEGACY_CIPHER', 'rijndael-256');
$errors = array();
if (!$legacy_key) {
@@ -60,6 +62,7 @@ class RecryptFromMcrypt extends Command
if (strlen($legacy_key) == 32) {
$legacy_length_check = true;
} elseif (array_key_exists('1', $key_parts) && (strlen($key_parts[1])==44)) {
$legacy_key = base64_decode($key_parts[1],true);
$legacy_length_check = true;
} else {
$legacy_length_check = false;
@@ -79,7 +82,9 @@ class RecryptFromMcrypt extends Command
$this->error('================================!!!! WARNING !!!!================================');
$this->comment("This tool will attempt to decrypt your old Snipe-IT (mcrypt, now deprecated) encrypted data and re-encrypt it using OpenSSL. \n\nYou should only continue if you have backed up any and all old APP_KEYs and have backed up your data.");
if ($this->confirm("Are you SURE you wish to continue?")) {
$force = ($this->option('force')) ? true : false;
if ($force || ($this->confirm("Are you SURE you wish to continue?"))) {
$backup_file = 'backups/env-backups/'.'app_key-'.date('Y-m-d-gis');
@@ -91,13 +96,21 @@ class RecryptFromMcrypt extends Command
}
$mcrypter = new McryptEncrypter($legacy_key);
if ($legacy_cipher){
$mcrypter = new McryptEncrypter($legacy_key,$legacy_cipher);
}else{
$mcrypter = new McryptEncrypter($legacy_key);
}
$settings = Setting::getSettings();
if ($settings->ldap_password=='') {
if ($settings->ldap_pword=='') {
$this->comment('INFO: No LDAP password found. Skipping... ');
} else {
$decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword);
$settings->ldap_pword = \Crypt::encrypt($decrypted_ldap_pword);
$settings->save();
}
/** @var CustomField[] $custom_fields */
$custom_fields = CustomField::where('field_encrypted','=', 1)->get();
$this->comment('INFO: Retrieving encrypted custom fields...');
@@ -110,32 +123,22 @@ class RecryptFromMcrypt extends Command
// Get all assets with a value in any of the fields that were encrypted
/** @var Asset[] $assets */
$assets = $query->get();
$bar = $this->output->createProgressBar(count($assets));
foreach ($custom_fields as $encrypted_field) {
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($encrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
}
$bar->advance();
}
foreach ($assets as $asset) {
foreach ($custom_fields as $encrypted_field) {
$columnName = $encrypted_field->db_column;
// Make sure the value isn't null
if ($asset->{$encrypted_field}!='') {
if ($asset->{$columnName}!='') {
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($asset->{$encrypted_field});
$asset->{$encrypted_field} = \Crypt::encrypt($decrypted_field);
$decrypted_field = $mcrypter->decrypt($asset->{$columnName});
$asset->{$columnName} = \Crypt::encrypt($decrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();

View File

@@ -0,0 +1,105 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Asset;
use App\Models\Setting;
use DB;
use Artisan;
class RegenerateAssetTags extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:regenerate-tags {--start=} {--output= : info|warn|error|all} ';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This utility will regenerate all asset tags. THIS IS DATA-DESTRUCTIVE AND SHOULD BE USED WITH CAUTION. ';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if ($this->confirm('This will regenerate all of the asset tags within your system. This action is data-destructive and should be used with caution. Do you wish to continue?'))
{
$output['info'] = [];
$output['warn'] = [];
$output['error'] = [];
$settings = Setting::getSettings();
$start_tag = ($this->option('start')) ? $this->option('start') : (($settings->next_auto_tag_base) ? Setting::getSettings()->next_auto_tag_base : 1) ;
$this->info('Starting at '.$start_tag);
$total_assets = Asset::orderBy('id','asc')->get();
$bar = $this->output->createProgressBar(count($total_assets));
try {
Artisan::call('backup:run');
} catch (\Exception $e) {
$output['error'][] = $e;
}
foreach ($total_assets as $asset) {
$start_tag++;
$output['info'][] = 'Asset tag:'.$asset->asset_tag;
$asset->asset_tag = $settings->auto_increment_prefix.$settings->auto_increment_prefix.$start_tag;
if ($settings->zerofill_count > 0) {
$asset->asset_tag = $settings->auto_increment_prefix.Asset::zerofill($start_tag, $settings->zerofill_count);
}
$output['info'][] = 'New Asset tag:'.$asset->asset_tag;
// Use forceSave here to override model level validation
$asset->forceSave();
}
$bar->finish();
$this->info("\n");
if (($this->option('output')=='all') || ($this->option('output')=='info')) {
foreach ($output['info'] as $key => $output_text) {
$this->info($output_text);
}
}
if (($this->option('output')=='all') || ($this->option('output')=='warn')) {
foreach ($output['warn'] as $key => $output_text) {
$this->warn($output_text);
}
}
if (($this->option('output')=='all') || ($this->option('output')=='error')) {
foreach ($output['error'] as $key => $output_text) {
$this->error($output_text);
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Setting;
use App\Models\User;
class ResetDemoSettings extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:demo-settings';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This will reset the Snipe-IT demo settings back to default. ';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('Resetting the demo settings.');
$settings = Setting::first();
$settings->per_page = 20;
$settings->site_name = 'Snipe-IT Asset Management Demo';
$settings->auto_increment_assets = 1;
$settings->logo = 'snipe-logo.png';
$settings->alert_email = 'service@snipe-it.io';
$settings->header_color = null;
$settings->barcode_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 3;
$settings->ldap_enabled = 0;
$settings->full_multiple_companies_support = 1;
$settings->alt_barcode = 'C128';
$settings->skin = '';
$settings->email_domain = 'snipeitapp.com';
$settings->email_format = 'filastname';
$settings->username_format = 'filastname';
$settings->date_display_format = 'D M d, Y';
$settings->time_display_format = 'g:iA';
$settings->thumbnail_max_h = '30';
$settings->locale = 'en';
$settings->version_footer = 'on';
$settings->support_footer = 'on';
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
$user->locale = 'en';
$user->save();
}
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Consumable;
use App\Models\Accessory;
use App\Models\LicenseSeat;
use App\Models\License;
use DB;
use Artisan;
class RestoreDeletedUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:restore-users {--start_date=} {--end_date=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Restore users, and any associated assets and license checkouts.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start_date = $this->option('start_date');
$end_date = $this->option('end_date');
$asset_totals = 0;
$license_totals = 0;
$user_count = 0;
if (($start_date=='') || ($end_date=='')) {
$this->info('ERROR: All fields are required.');
return false;
}
$users = User::whereBetween('deleted_at', [$start_date, $end_date])->withTrashed()->get();
$this->info('There are '.$users->count().' users deleted between '.$start_date.' and '.$end_date);
$this->warn('Making a backup!');
Artisan::call('backup:run');
foreach ($users as $user) {
$user_count++;
$user_logs = Actionlog::where('target_id', $user->id)->where('target_type',User::class)
->where('action_type','checkout')->with('item')->get();
$this->info($user_count.'. '.$user->username.' ('.$user->id.') was deleted at '.$user->deleted_at. ' and has '.$user_logs->count().' checkouts associated.');
foreach ($user_logs as $user_log) {
$this->info(' * '.$user_log->item_type.': '.$user_log->item->name.' - item_id: '.$user_log->item_id);
if ($user_log->item_type==Asset::class) {
$asset_totals++;
DB::table('assets')
->where('id', $user_log->item_id)
->update(['assigned_to' => $user->id, 'assigned_type'=> User::class]);
$this->info(' ** Asset '.$user_log->item->id.' ('.$user_log->item->asset_tag.') restored to user '.$user->id.'');
} elseif ($user_log->item_type==License::class) {
$license_totals++;
$avail_seat = DB::table('license_seats')->where('license_id','=',$user_log->item->id)
->whereNull('assigned_to')->whereNull('asset_id')->whereBetween('updated_at', [$start_date, $end_date])->first();
if ($avail_seat) {
$this->info(' ** Allocating seat '.$avail_seat->id.' for this License');
DB::table('license_seats')
->where('id', $avail_seat->id)
->update(['assigned_to' => $user->id]);
} else {
$this->warn('ERROR: No available seats for '.$user_log->item->name);
}
}
}
$this->warn('Restoring user '.$user->username.'!');
$user->restore();
}
$this->info($asset_totals.' assets affected');
$this->info($license_totals.' licenses affected');
}
}

View File

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

View File

@@ -4,8 +4,10 @@ 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 Carbon\Carbon;
class SendExpectedCheckinAlerts extends Command
@@ -40,22 +42,29 @@ class SendExpectedCheckinAlerts extends Command
*
* @return mixed
*/
public function fire()
public function handle()
{
$settings = Setting::getSettings();
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->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');
foreach ($assets as $asset) {
if ($asset->assignedTo && $asset->checkoutOutToUser()) {
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
//$this->info($asset);
if ($asset->assigned && $asset->checkedOutToUser()) {
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
}
}
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);
});
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}

View File

@@ -6,6 +6,8 @@ use App\Models\Asset;
use App\Models\License;
use App\Models\Setting;
use DB;
use App\Notifications\ExpiringLicenseNotification;
use App\Notifications\ExpiringAssetsNotification;
use Illuminate\Console\Command;
@@ -41,91 +43,44 @@ class SendExpirationAlerts extends Command
*
* @return mixed
*/
public function fire()
public function handle()
{
// Expiring Assets
$expiring_assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
$this->info(count($expiring_assets).' expiring assets');
$asset_data['count'] = count($expiring_assets);
$asset_data['email_content'] ='';
$now = date("Y-m-d");
$settings = Setting::getSettings();
$threshold = $settings->alert_interval;
foreach ($expiring_assets as $asset) {
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
$expires = $asset->present()->warrantee_expires();
$difference = round(abs(strtotime($expires) - strtotime($now))/86400);
if ($difference > 30) {
$asset_data['email_content'] .= '<tr style="background-color: #fcffa3;">';
} else {
$asset_data['email_content'] .= '<tr style="background-color:#d9534f;">';
}
$asset_data['email_content'] .= '<td><a href="'.config('app.url').'/hardware/'.e($asset->id).'/view">';
$asset_data['email_content'] .= $asset->present()->name().'</a></td><td>'.e($asset->asset_tag).'</td>';
$asset_data['email_content'] .= '<td>'.e($asset->present()->warrantee_expires()).'</td>';
$asset_data['email_content'] .= '<td>'.$difference.' '.trans('mail.days').'</td>';
$asset_data['email_content'] .= '<td>'.($asset->supplier ? e($asset->supplier->name) : '').'</td>';
$asset_data['email_content'] .= '<td>'.($asset->assignedTo ? e($asset->assignedTo->present()->name()) : '').'</td>';
$asset_data['email_content'] .= '</tr>';
}
// Expiring licenses
$expiring_licenses = License::getExpiringLicenses(Setting::getSettings()->alert_interval);
$this->info(count($expiring_licenses).' expiring licenses');
$license_data['count'] = count($expiring_licenses);
$license_data['email_content'] = '';
foreach ($expiring_licenses as $license) {
$expires = $license->expiration_date;
$difference = round(abs(strtotime($expires) - strtotime($now))/86400);
if ($difference > 30) {
$license_data['email_content'] .= '<tr style="background-color: #fcffa3;">';
} else {
$license_data['email_content'] .= '<tr style="background-color:#d9534f;">';
}
$license_data['email_content'] .= '<td><a href="'.route('licenses.show', $license->id).'">';
$license_data['email_content'] .= $license->name.'</a></td>';
$license_data['email_content'] .= '<td>'.$license->expiration_date.'</td>';
$license_data['email_content'] .= '<td>'.$difference.' days</td>';
$license_data['email_content'] .= '</tr>';
}
if ((Setting::getSettings()->alert_email!='') && (Setting::getSettings()->alerts_enabled==1)) {
if (count($expiring_assets) > 0) {
$this->info('Report sent to '.Setting::getSettings()->alert_email);
\Mail::send('emails.expiring-assets-report', $asset_data, function ($m) {
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Expiring_Assets_Report'));
});
// 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);
});
// Expiring Assets
$assets = Asset::getExpiringWarrantee(Setting::getSettings()->alert_interval);
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));
}
if (count($expiring_licenses) > 0) {
$this->info('Report sent to '.Setting::getSettings()->alert_email);
\Mail::send('emails.expiring-licenses-report', $license_data, function ($m) {
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Expiring_Licenses_Report'));
});
// 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));
}
} else {
if (Setting::getSettings()->alert_email=='') {
echo "Could not send email. No alert email configured in settings. \n";
} elseif (Setting::getSettings()->alerts_enabled!=1) {
echo "Alerts are disabled in the settings. No mail will be sent. \n";
if ($settings->alert_email=='') {
$this->error('Could not send email. No alert email configured in settings');
} elseif ($settings->alerts_enabled!=1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Setting;
use DB;
use Mail;
use App\Helpers\Helper;
use App\Notifications\InventoryAlert;
use Illuminate\Console\Command;
@@ -42,25 +43,28 @@ class SendInventoryAlerts extends Command
*/
public function handle()
{
if ((Setting::getSettings()->alert_email!='') && (Setting::getSettings()->alerts_enabled==1)) {
$settings = Setting::getSettings();
$data['data'] = Helper::checkLowInventory();
$data['count'] = count($data['data']);
if (($settings->alert_email!='') && ($settings->alerts_enabled==1)) {
if (count($data['data']) > 0) {
\Mail::send('emails.low-inventory', $data, function ($m) {
$m->to(explode(',', Setting::getSettings()->alert_email), Setting::getSettings()->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Low_Inventory_Report'));
$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);
});
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
}
} else {
if (Setting::getSettings()->alert_email=='') {
echo "Could not send email. No alert email configured in settings. \n";
$this->error('Could not send email. No alert email configured in settings');
} elseif (Setting::getSettings()->alerts_enabled!=1) {
echo "Alerts are disabled in the settings. No mail will be sent. \n";
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}

View File

@@ -0,0 +1,91 @@
<?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

@@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Asset;
class SyncAssetCounters extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:counter-sync';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Syncs checkedout, checked in, and requested counters for assets';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start = microtime(true);
$assets = Asset::withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')
->withTrashed()->get();
if ($assets) {
if ($assets->count() > 0) {
$bar = $this->output->createProgressBar($assets->count());
foreach ($assets as $asset) {
$asset->checkin_counter = (int) $asset->checkins_count;
$asset->checkout_counter = (int) $asset->checkouts_count;
$asset->requests_counter = (int) $asset->user_requests_count;
$asset->unsetEventDispatcher();
$asset->save();
$output['info'][] = 'Asset: ' . $asset->id . ' has ' . $asset->checkin_counter . ' checkins, ' . $asset->checkout_counter . ' checkouts, and ' . $asset->requests_counter . ' requests';
$bar->advance();
}
$bar->finish();
foreach ($output['info'] as $key => $output_text) {
$this->info($output_text);
}
$time_elapsed_secs = microtime(true) - $start;
$this->info('Sync executed in ' . $time_elapsed_secs . ' seconds');
} else {
$this->info('No assets to sync');
}
}
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace App\Console\Commands;
use App\Models\CustomField;
use Illuminate\Console\Command;
use App\Models\Asset;
use Illuminate\Support\Facades\Storage;
class SyncAssetLocations extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:sync-asset-locations {--output= : info|warn|error|all} ';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This utility will sync the location_id of assets based on current state. It should not normally be needed, but is a safeguard in case we missed something in the Great Migration when flattening the assets to location relationship.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$output['info'] = [];
$output['warn'] = [];
$output['error'] = [];
$total_assets = Asset::whereNull('deleted_at')->get();
$bar = $this->output->createProgressBar(count($total_assets));
// Unassigned
$rtd_assets = Asset::whereNull('assigned_to')->whereNull('deleted_at')->with('defaultLoc')->get();
$output['info'][] = 'There are '.$rtd_assets->count().' unassigned assets.';
foreach ($rtd_assets as $rtd_asset) {
$output['info'][] = 'Setting Unassigned Asset ' . $rtd_asset->id . ' ('.$rtd_asset->asset_tag.') to location: ' . $rtd_asset->rtd_location_id . " because their default location is: " . $rtd_asset->rtd_location_id;
$rtd_asset->location_id=$rtd_asset->rtd_location_id;
$rtd_asset->unsetEventDispatcher();
$rtd_asset->save();
$bar->advance();
}
$assigned_user_assets = Asset::where('assigned_type','App\Models\User')->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$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;
$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! ';
$new_location = $assigned_user_asset->rtd_location_id;
}
$assigned_user_asset->location_id=$new_location;
$assigned_user_asset->unsetEventDispatcher();
$assigned_user_asset->save();
$bar->advance();
}
$assigned_location_assets = Asset::where('assigned_type','App\Models\Location')
->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$output['info'][] = 'There are '.$assigned_location_assets->count().' assets checked out to locations.';
foreach ($assigned_location_assets as $assigned_location_asset) {
if ($assigned_location_asset->assignedTo) {
$assigned_location_asset->location_id = $assigned_location_asset->assignedTo->id;
$output['info'][] ='Setting Location Assigned asset ' . $assigned_location_asset->id . ' ('.$assigned_location_asset->asset_tag.') that is checked out to '.$assigned_location_asset->assignedTo->name.' (#'.$assigned_location_asset->assignedTo->id.') to location: ' . $assigned_location_asset->assetLoc()->id;
$assigned_location_asset->unsetEventDispatcher();
$assigned_location_asset->save();
} else {
$output['warn'][] ='Asset ' . $assigned_location_asset->id . ' ('.$assigned_location_asset->asset_tag.') did not return a valid associated location - perhaps it was deleted?';
}
$bar->advance();
}
// Assigned to assets
$assigned_asset_assets = Asset::where('assigned_type','App\Models\Asset')
->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$output['info'][] ='Asset-assigned assets: '.$assigned_asset_assets->count();
foreach ($assigned_asset_assets as $assigned_asset_asset) {
// Check to make sure there aren't any invalid relationships
if ($assigned_asset_asset->assetLoc()) {
$assigned_asset_asset->location_id = $assigned_asset_asset->assetLoc()->id;
$output['info'][] ='Setting Asset Assigned asset ' . $assigned_asset_asset->assetLoc()->id. ' ('.$assigned_asset_asset->asset_tag.') location to: ' . $assigned_asset_asset->assetLoc()->id;
$assigned_asset_asset->unsetEventDispatcher();
$assigned_asset_asset->save();
} else {
$output['warn'][] ='Asset Assigned asset ' . $assigned_asset_asset->id. ' ('.$assigned_asset_asset->asset_tag.') does not seem to have a valid location';
}
$bar->advance();
}
$unlocated_assets = Asset::whereNull("location_id")->whereNull('deleted_at')->get();
$output['info'][] ='Assets still without a location: '.$unlocated_assets->count();
foreach($unlocated_assets as $unlocated_asset) {
$output['warn'][] ='Asset: '.$unlocated_asset->id.' still has no location. ';
$bar->advance();
}
$bar->finish();
$this->info("\n");
if (($this->option('output')=='all') || ($this->option('output')=='info')) {
foreach ($output['info'] as $key => $output_text) {
$this->info($output_text);
}
}
if (($this->option('output')=='all') || ($this->option('output')=='warn')) {
foreach ($output['warn'] as $key => $output_text) {
$this->warn($output_text);
}
}
if (($this->option('output')=='all') || ($this->option('output')=='error')) {
foreach ($output['error'] as $key => $output_text) {
$this->error($output_text);
}
}
}
}

View File

@@ -36,7 +36,7 @@ class SystemBackup extends Command
*
* @return mixed
*/
public function fire()
public function handle()
{
//
$this->call('backup:run');

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Version extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'version:update {--branch=master} {--type=patch}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$use_branch = $this->option('branch');
$use_type = $this->option('type');
$git_branch = trim(shell_exec('git rev-parse --abbrev-ref HEAD'));
$build_version = trim(shell_exec('git rev-list --count '.$use_branch));
$versionFile = 'config/version.php';
$full_hash_version = str_replace("\n", '', shell_exec('git describe master --tags'));
$version = explode('-', $full_hash_version);
$app_version = $current_app_version = $version[0];
$hash_version = (array_key_exists('2', $version)) ? $version[2] : '';
$prerelease_version = '';
$this->line('Branch is: '.$use_branch);
$this->line('Type is: '.$use_type);
$this->line('Current version is: '.$full_hash_version);
if (count($version)==3) {
$this->line('This does not look like an alpha/beta release.');
} else {
if (array_key_exists('3',$version)) {
$this->line('The current version looks like a beta release.');
$prerelease_version = $version[1];
$hash_version = $version[3];
}
}
$app_version_raw = explode('.', $app_version);
$maj = str_replace('v', '', $app_version_raw[0]);
$min = $app_version_raw[1];
$patch = '';
// This is a major release that might not have a third .0
if (array_key_exists(2, $app_version_raw)) {
$patch = $app_version_raw[2];
}
if ($use_type=='major') {
$app_version = "v".($maj + 1).".$min.$patch";
} elseif ($use_type=='minor') {
$app_version = "v"."$maj.".($min + 1).".$patch";
} elseif ($use_type=='pre') {
$pre_raw = str_replace('beta','', $prerelease_version);
$pre_raw = str_replace('alpha','', $pre_raw);
$pre_raw = str_ireplace('rc','', $pre_raw);
$pre_raw = $pre_raw++;
$this->line('Setting the pre-release to '. $prerelease_version.'-'.$pre_raw);
$app_version = "v"."$maj.".($min + 1).".$patch";
} elseif ($use_type=='patch') {
$app_version = "v" . "$maj.$min." . ($patch + 1);
// If nothing is passed, leave the version as it is, just increment the build
} else {
$app_version = "v" . "$maj.$min." . $patch;
}
// Determine if this tag already exists, or if this prior to a release
$this->line('Running: git rev-parse master '.$current_app_version);
// $pre_release = trim(shell_exec('git rev-parse '.$use_branch.' '.$current_app_version.' 2>&1 1> /dev/null'));
if ($use_branch=='develop') {
$app_version = $app_version.'-pre';
}
$full_app_version = $app_version.' - build '.$build_version.'-'.$hash_version;
$array = var_export(
array(
'app_version' => $app_version,
'full_app_version' => $full_app_version,
'build_version' => $build_version,
'prerelease_version' => $prerelease_version,
'hash_version' => $hash_version,
'full_hash' => $full_hash_version,
'branch' => $git_branch),
true
);
// Construct our file content
$content = <<<CON
<?php
return $array;
CON;
// And finally write the file and output the current version
\File::put($versionFile, $content);
$this->info('Setting NEW version: '. $full_app_version.' ('.$git_branch.')');
}
}

View File

@@ -1,91 +0,0 @@
<?php
namespace App\Console\Commands;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Console\Command;
class Versioning extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'versioning:update';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate and update app\'s version via git.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$versionFile = 'config/version.php';
$hash_version = str_replace("\n", '', shell_exec('git describe --tags'));
$version = explode('-', $hash_version);
$array = var_export(
array(
'app_version' => $version[0],
'build_version' => $version[1],
'hash_version' => $version[2],
'full_hash' => $hash_version),
true
);
// Construct our file content
$content = <<<CON
<?php
return $array;
CON;
// And finally write the file and output the current version
\File::put($versionFile, $content);
$this->line('Setting version: '. config('version.app_version').' build '.config('version.build_version').' ('.config('version.hash_version').')');
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array(
);
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
);
}
}

View File

@@ -2,31 +2,14 @@
namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
Commands\PaveIt::class,
Commands\CreateAdmin::class,
Commands\SendExpirationAlerts::class,
Commands\SendInventoryAlerts::class,
Commands\SendExpectedCheckinAlerts::class,
Commands\ObjectImportCommand::class,
Commands\Versioning::class,
Commands\SystemBackup::class,
Commands\DisableLDAP::class,
Commands\Purge::class,
Commands\LdapSync::class,
Commands\FixDoubleEscape::class,
Commands\RecryptFromMcrypt::class
];
/**
* Define the application's command schedule.
@@ -39,13 +22,15 @@ class Kernel extends ConsoleKernel
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkins')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
protected function commands()
{
require base_path('routes/console.php');
$this->load(__DIR__.'/Commands');
}
}

View File

@@ -5,8 +5,17 @@ namespace App\Exceptions;
use Exception;
class CheckoutNotAllowed extends Exception
{
private $errorMessage;
function __construct($errorMessage = null)
{
$this->errorMessage = $errorMessage;
parent::__construct($errorMessage);
}
public function __toString()
{
"A checkout is not allowed under these circumstances";
return is_null($this->errorMessage) ? "A checkout is not allowed under these circumstances" : $this->errorMessage;
}
}

View File

@@ -7,6 +7,7 @@ use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Helpers\Helper;
use Illuminate\Validation\ValidationException;
use Log;
class Handler extends ExceptionHandler
{
@@ -22,6 +23,7 @@ class Handler extends ExceptionHandler
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
\Intervention\Image\Exception\NotSupportedException::class,
];
/**
@@ -34,8 +36,10 @@ class Handler extends ExceptionHandler
*/
public function report(Exception $exception)
{
\Log::error($exception); // rollbar
parent::report($exception);
if ($this->shouldReport($exception)) {
Log::error($exception);
return parent::report($exception);
}
}
/**
@@ -63,10 +67,6 @@ class Handler extends ExceptionHandler
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
}
if ($e instanceof \Illuminate\Validation\ValidationException) {
return response()->json(Helper::formatStandardApiResponse('error', $e->response['messages'], $e->getMessage(), 400));
}
if ($this->isHttpException($e)) {
$statusCode = $e->getStatusCode();
@@ -81,11 +81,6 @@ class Handler extends ExceptionHandler
}
}
// Try to parse 500 Errors in a bit nicer way when debug is enabled.
if (config('app.debug')) {
return response()->json(Helper::formatStandardApiResponse('error', null, "An Error has occured! " . $e->getMessage()), 500);
}
}
@@ -114,4 +109,16 @@ 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(Helper::formatStandardApiResponse('error', null, $exception->errors(), 400));
}
}

View File

@@ -127,99 +127,16 @@ class Helper
$floatString = str_replace(",", "", $floatString);
$floatString = str_replace($LocaleInfo["decimal_point"], ".", $floatString);
// Strip Currency symbol
$floatString = str_replace($LocaleInfo['currency_symbol'], '', $floatString);
// If no currency symbol is set, default to $ because Murica
$currencySymbol = $LocaleInfo['currency_symbol'];
if (empty($currencySymbol)) {
$currencySymbol = '$';
}
$floatString = str_replace($currencySymbol, '', $floatString);
return floatval($floatString);
}
/**
* Get the list of models in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function modelList()
{
$models = AssetModel::with('manufacturer')->get();
$model_array[''] = trans('general.select_model');
foreach ($models as $model) {
$model_array[$model->id] = $model->present()->modelName();
}
return $model_array;
}
/**
* Get the list of companies in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function companyList()
{
$company_list = array('' => trans('general.select_company')) + DB::table('companies')
->orderBy('name', 'asc')
->pluck('name', 'id')
->toArray();
return $company_list;
}
/**
* Get the list of categories in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function categoryList($category_type = null)
{
$categories = Category::orderBy('name', 'asc')
->whereNull('deleted_at')
->orderBy('name', 'asc');
if (!empty($category_type)) {
$categories = $categories->where('category_type', '=', $category_type);
}
$category_list = array('' => trans('general.select_category')) + $categories->pluck('name', 'id')->toArray();
return $category_list;
}
/**
* Get the list of categories in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function departmentList()
{
$departments = Department::orderBy('name', 'asc')
->whereNull('deleted_at')
->orderBy('name', 'asc');
return array('' => trans('general.select_department')) + $departments->pluck('name', 'id')->toArray();
}
/**
* Get the list of suppliers in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function suppliersList()
{
$supplier_list = array('' => trans('general.select_supplier')) + Supplier::orderBy('name', 'asc')
->orderBy('name', 'asc')
->pluck('name', 'id')->toArray();
return $supplier_list;
}
/**
* Get the list of status labels in an array to make a dropdown menu
*
@@ -229,42 +146,11 @@ class Helper
*/
public static function statusLabelList()
{
$statuslabel_list = array('' => trans('general.select_statuslabel')) + Statuslabel::orderBy('deployable', 'desc')
$statuslabel_list = array('' => trans('general.select_statuslabel')) + Statuslabel::orderBy('default_label', 'desc')->orderBy('name','asc')->orderBy('deployable','desc')
->pluck('name', 'id')->toArray();
return $statuslabel_list;
}
/**
* Get the list of locations in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function locationsList()
{
$location_list = array('' => trans('general.select_location')) + Location::orderBy('name', 'asc')
->pluck('name', 'id')->toArray();
return $location_list;
}
/**
* Get the list of manufacturers in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function manufacturerList()
{
$manufacturer_list = array('' => trans('general.select_manufacturer')) +
Manufacturer::orderBy('name', 'asc')
->pluck('name', 'id')->toArray();
return $manufacturer_list;
}
/**
* Get the list of status label types in an array to make a dropdown menu
*
@@ -283,24 +169,6 @@ class Helper
return $statuslabel_types;
}
/**
* Get the list of managers in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function managerList()
{
$manager_list = array('' => trans('general.select_user')) +
User::where('deleted_at', '=', null)
->orderBy('last_name', 'asc')
->orderBy('first_name', 'asc')->get()
->pluck('complete_name', 'id')->toArray();
return $manager_list;
}
/**
* Get the list of depreciations in an array to make a dropdown menu
*
@@ -324,58 +192,17 @@ class Helper
*/
public static function categoryTypeList()
{
$category_types = array('' => '','accessory' => 'Accessory', 'asset' => 'Asset', 'consumable' => 'Consumable','component' => 'Component');
$category_types = array(
'' => '',
'accessory' => 'Accessory',
'asset' => 'Asset',
'consumable' => 'Consumable',
'component' => 'Component',
'license' => 'License'
);
return $category_types;
}
/**
* Get the list of users in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function usersList()
{
$users_list = array( '' => trans('general.select_user')) +
Company::scopeCompanyables(User::where('deleted_at', '=', null))
->where('show_in_list', '=', 1)
->orderBy('last_name', 'asc')
->orderBy('first_name', 'asc')->get()
->pluck('complete_name', 'id')->toArray();
return $users_list;
}
/**
* Get the list of assets in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return Array
*/
public static function assetsList()
{
$assets_list = array('' => trans('general.select_asset')) + Asset::orderBy('name', 'asc')
->whereNull('deleted_at')
->pluck('name', 'id')->toArray();
return $assets_list;
}
/**
* Get the detailed list of assets in an array to make a dropdown menu
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.5]
* @return array
*/
public static function detailedAssetList()
{
$assets = array('' => trans('general.select_asset')) + Company::scopeCompanyables(Asset::with('assignedTo', 'model'), 'assets.company_id')->get()->pluck('detailed_name', 'id')->toArray();
return $assets;
}
/**
* Get the list of custom fields in an array to make a dropdown menu
*
@@ -400,7 +227,7 @@ class Helper
{
$keys = array_keys(CustomField::$PredefinedFormats);
$stuff = array_combine($keys, $keys);
return $stuff+["" => trans('admin/custom_fields/general.custom_format')];
return $stuff;
}
/**
@@ -454,9 +281,9 @@ class Helper
*/
public static function checkLowInventory()
{
$consumables = Consumable::with('users')->whereNotNull('min_amt')->get();
$accessories = Accessory::with('users')->whereNotNull('min_amt')->get();
$components = Component::with('assets')->whereNotNull('min_amt')->get();
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$components = Component::withCount('assets as assets_count')->whereNotNull('min_amt')->get();
$avail_consumables = 0;
$items_array = array();
@@ -466,7 +293,7 @@ class Helper
$avail = $consumable->numRemaining();
if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($consumable->qty > 0) {
$percent = number_format((($consumable->numRemaining() / $consumable->qty) * 100), 0);
$percent = number_format((($avail / $consumable->qty) * 100), 0);
} else {
$percent = 100;
}
@@ -475,7 +302,7 @@ class Helper
$items_array[$all_count]['name'] = $consumable->name;
$items_array[$all_count]['type'] = 'consumables';
$items_array[$all_count]['percent'] = $percent;
$items_array[$all_count]['remaining']=$consumable->numRemaining();
$items_array[$all_count]['remaining'] = $avail;
$items_array[$all_count]['min_amt']=$consumable->min_amt;
$all_count++;
}
@@ -484,11 +311,11 @@ class Helper
}
foreach ($accessories as $accessory) {
$avail = $accessory->numRemaining();
$avail = $accessory->qty - $accessory->users_count;
if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($accessory->numRemaining() / $accessory->qty) * 100), 0);
$percent = number_format((($avail / $accessory->qty) * 100), 0);
} else {
$percent = 100;
}
@@ -497,7 +324,7 @@ class Helper
$items_array[$all_count]['name'] = $accessory->name;
$items_array[$all_count]['type'] = 'accessories';
$items_array[$all_count]['percent'] = $percent;
$items_array[$all_count]['remaining']=$accessory->numRemaining();
$items_array[$all_count]['remaining'] = $avail;
$items_array[$all_count]['min_amt']=$accessory->min_amt;
$all_count++;
}
@@ -505,10 +332,10 @@ class Helper
}
foreach ($components as $component) {
$avail = $component->numRemaining();
$avail = $component->qty - $component->assets_count;
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($component->qty > 0) {
$percent = number_format((($component->numRemaining() / $component->qty) * 100), 0);
$percent = number_format((($avail / $component->qty) * 100), 0);
} else {
$percent = 100;
}
@@ -517,7 +344,7 @@ class Helper
$items_array[$all_count]['name'] = $component->name;
$items_array[$all_count]['type'] = 'components';
$items_array[$all_count]['percent'] = $percent;
$items_array[$all_count]['remaining']=$component->numRemaining();
$items_array[$all_count]['remaining'] = $avail;
$items_array[$all_count]['min_amt']=$component->min_amt;
$all_count++;
}
@@ -684,7 +511,7 @@ class Helper
$array['status'] = $status;
$array['messages'] = $messages;
if (($messages) && (count($messages) > 0)) {
if (($messages) && (is_array($messages)) && (count($messages) > 0)) {
$array['messages'] = $messages;
}
($payload) ? $array['payload'] = $payload : $array['payload'] = null;
@@ -725,5 +552,124 @@ class Helper
}
// Nicked from Drupal :)
// Returns a file size limit in bytes based on the PHP upload_max_filesize
// and post_max_size
public static function file_upload_max_size() {
static $max_size = -1;
if ($max_size < 0) {
// Start with post_max_size.
$post_max_size = Helper::parse_size(ini_get('post_max_size'));
if ($post_max_size > 0) {
$max_size = $post_max_size;
}
// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$upload_max = Helper::parse_size(ini_get('upload_max_filesize'));
if ($upload_max > 0 && $upload_max < $max_size) {
$max_size = $upload_max;
}
}
return $max_size;
}
public static function file_upload_max_size_readable() {
static $max_size = -1;
if ($max_size < 0) {
// Start with post_max_size.
$post_max_size = Helper::parse_size(ini_get('post_max_size'));
if ($post_max_size > 0) {
$max_size = ini_get('post_max_size');
}
// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$upload_max = Helper::parse_size(ini_get('upload_max_filesize'));
if ($upload_max > 0 && $upload_max < $max_size) {
$max_size = ini_get('upload_max_filesize');
}
}
return $max_size;
}
public static function parse_size($size) {
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
if ($unit) {
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
}
else {
return round($size);
}
}
public static function filetype_icon($filename) {
$extension = substr(strrchr($filename,'.'),1);
if ($extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
case 'gif':
case 'png':
return "fa fa-file-image-o";
break;
case 'doc':
case 'docx':
return "fa fa-file-word-o";
break;
case 'xls':
case 'xlsx':
return "fa fa-file-excel-o";
break;
case 'zip':
case 'rar':
return "fa fa-file-archive-o";
break;
case 'pdf':
return "fa fa-file-pdf-o";
break;
case 'txt':
return "fa fa-file-text-o";
break;
case 'lic':
return "fa fa-floppy-o";
break;
default:
return "fa fa-file-o";
}
}
return "fa fa-file-o";
}
public static function show_file_inline($filename) {
$extension = substr(strrchr($filename,'.'),1);
if ($extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
case 'gif':
case 'png':
return true;
break;
default:
return false;
}
}
return false;
}
}

View File

@@ -12,12 +12,13 @@ use DB;
use Gate;
use Input;
use Lang;
use Mail;
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.
@@ -52,13 +53,9 @@ class AccessoriesController extends Controller
public function create(Request $request)
{
$this->authorize('create', Accessory::class);
// Show the page
return view('accessories/edit')
->with('item', new Accessory)
->with('category_list', Helper::categoryList('accessory'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList())
->with('manufacturer_list', Helper::manufacturerList());
$category_type = 'accessory';
return view('accessories/edit')->with('category_type', $category_type)
->with('item', new Accessory);
}
@@ -68,7 +65,7 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return Redirect
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize(Accessory::class);
// create a new model instance
@@ -87,6 +84,9 @@ class AccessoriesController extends Controller
$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,600, public_path().'/uploads/accessories');
// Was the accessory created?
if ($accessory->save()) {
@@ -105,18 +105,15 @@ class AccessoriesController extends Controller
*/
public function edit(Request $request, $accessoryId = null)
{
// Check if the accessory exists
if (is_null($item = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
if ($item = Accessory::find($accessoryId)) {
$this->authorize($item);
$category_type = 'accessory';
return view('accessories/edit', compact('item'))->with('category_type', $category_type);
}
$this->authorize($item);
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
return view('accessories/edit', compact('item'))
->with('category_list', Helper::categoryList('accessory'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList())
->with('manufacturer_list', Helper::manufacturerList());
}
@@ -127,7 +124,7 @@ class AccessoriesController extends Controller
* @param int $accessoryId
* @return Redirect
*/
public function update(Request $request, $accessoryId = null)
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'));
@@ -144,11 +141,15 @@ class AccessoriesController extends Controller
$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->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
// Was the accessory updated?
$accessory = $request->handleImages($accessory,600, public_path().'/uploads/accessories');
// Was the accessory updated?
if ($accessory->save()) {
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
}
@@ -197,11 +198,7 @@ class AccessoriesController extends Controller
if (isset($accessory->id)) {
return view('accessories/view', compact('accessory'));
}
// Prepare the error message
$error = trans('admin/accessories/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('accessories')->with('error', $error);
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
@@ -219,10 +216,17 @@ class AccessoriesController extends Controller
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize('checkout', $accessory);
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.');
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'))->with('users_list', Helper::usersList());
}
@@ -247,7 +251,7 @@ class AccessoriesController extends Controller
$this->authorize('checkout', $accessory);
if (!$user = User::find(Input::get('assigned_to'))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
return redirect()->route('checkout/accessory', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
}
// Update the accessory data
@@ -273,15 +277,6 @@ class AccessoriesController extends Controller
$data['expected_checkin'] = '';
$data['note'] = $logaction->note;
$data['require_acceptance'] = $accessory->requireAcceptance();
// TODO: Port this to new mail notifications
if ((($accessory->requireAcceptance()=='1') || ($accessory->getEula())) && ($user->email!='')) {
Mail::send('emails.accept-accessory', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_accessory_delivery'));
});
}
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
@@ -328,7 +323,7 @@ class AccessoriesController extends Controller
// 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'));
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
@@ -346,20 +341,12 @@ class AccessoriesController extends Controller
$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 ((($accessory->checkin_email()=='1')) && ($user->email!='')) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_Accessory_Checkin'));
});
}
if ($backto=='user') {
return redirect()->route("users.show", $return_to)->with('success', trans('admin/accessories/message.checkin.success'));
}
@@ -369,143 +356,5 @@ class AccessoriesController extends Controller
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
}
/**
* Generates the JSON response for accessories listing view.
*
* Example:
* {
* "actions": "(links to available actions)",
* "category": "(link to category)",
* "company": "My Company",
* "location": "My Location",
* "min_amt": 2,
* "name": "(link to accessory),
* "numRemaining": 6,
* "order_number": null,
* "purchase_cost": "0.00",
* "purchase_date": null,
* "qty": 7
* },
*
* The names of the fields in the returns JSON correspond directly to the the
* names of the fields in the bootstrap-tables in the view.
*
* For debugging, see at /api/accessories/list
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @return string JSON containing accessories and their associated atrributes.
* @internal param int $accessoryId
*/
public function getDatatable(Request $request)
{
$this->authorize('index', Accessory::class);
$accessories = Company::scopeCompanyables(
Accessory::select('accessories.*')
->whereNull('accessories.deleted_at')
->with('category', 'company', 'manufacturer', 'users', 'location')
);
if (Input::has('search')) {
$accessories = $accessories->TextSearch(e(Input::get('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['name','min_amt','order_number','purchase_date','purchase_cost','company','category','model_number', 'manufacturer', 'location'];
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array(Input::get('sort'), $allowed_columns) ? e(Input::get('sort')) : 'created_at';
switch ($sort) {
case 'category':
$accessories = $accessories->OrderCategory($order);
break;
case 'company':
$accessories = $accessories->OrderCompany($order);
break;
case 'location':
$accessories = $accessories->OrderLocation($order);
break;
case 'manufacturer':
$accessories = $accessories->OrderManufacturer($order);
break;
default:
$accessories = $accessories->orderBy($sort, $order);
break;
}
$accessCount = $accessories->count();
$accessories = $accessories->skip($offset)->take($limit)->get();
$rows = array();
foreach ($accessories as $accessory) {
$rows[] = $accessory->present()->forDataTable();
}
$data = array('total'=>$accessCount, 'rows'=>$rows);
return $data;
}
/**
* Generates the JSON response for accessory detail view.
*
* Example:
* <code>
* {
* "rows": [
* {
* "actions": "(link to available actions)",
* "name": "(link to user)"
* }
* ],
* "total": 1
* }
* </code>
*
* The names of the fields in the returns JSON correspond directly to the the
* names of the fields in the bootstrap-tables in the view.
*
* For debugging, see at /api/accessories/$accessoryID/view
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return string JSON containing accessories and their associated atrributes.
**/
public function getDataView(Request $request, $accessoryID)
{
$accessory = Accessory::find($accessoryID);
if (!Company::isCurrentUserHasAccess($accessory)) {
return ['total' => 0, 'rows' => []];
}
$accessory_users = $accessory->users;
$count = $accessory_users->count();
$rows = array();
foreach ($accessory_users as $user) {
$actions = '';
if (Gate::allows('checkin', $accessory)) {
$actions .= Helper::generateDatatableButton('checkin', route('checkin/accessory', $user->pivot->id));
}
if (Gate::allows('view', $user)) {
$name = (string) link_to_route('users.show', e($user->present()->fullName()), [$user->id]);
} else {
$name = e($user->present()->fullName());
}
$rows[] = array(
'name' => $name,
'actions' => $actions
);
}
$data = array('total'=>$count, 'rows'=>$rows);
return $data;
}
}

View File

@@ -7,7 +7,11 @@ use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Models\Accessory;
use App\Http\Transformers\AccessoriesTransformer;
use App\Models\Company;
use App\Models\User;
use Carbon\Carbon;
use Auth;
use DB;
class AccessoriesController extends Controller
{
@@ -23,22 +27,36 @@ class AccessoriesController extends Controller
$this->authorize('view', Accessory::class);
$allowed_columns = ['id','name','model_number','eol','notes','created_at','min_amt','company_id'];
$accessories = Accessory::whereNull('accessories.deleted_at')->with('category', 'company', 'manufacturer', 'users', 'location');
$accessories = Accessory::with('category', 'company', 'manufacturer', 'users', 'location');
if ($request->has('search')) {
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
}
if ($request->has('company_id')) {
if ($request->filled('company_id')) {
$accessories->where('company_id','=',$request->input('company_id'));
}
if ($request->has('manufacturer_id')) {
if ($request->filled('category_id')) {
$accessories->where('category_id','=',$request->input('category_id'));
}
if ($request->filled('manufacturer_id')) {
$accessories->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
if ($request->filled('supplier_id')) {
$accessories->where('supplier_id','=',$request->input('supplier_id'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -95,8 +113,6 @@ class AccessoriesController extends Controller
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::findOrFail($id);
$accessory_users = $accessory->users;
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
@@ -128,9 +144,15 @@ class AccessoriesController extends Controller
public function checkedout($id)
{
$this->authorize('view', Accessory::class);
$accessory = Accessory::findOrFail($id)->with('users')->first();
$total = $accessory->users->count();
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $total);
$accessory = Accessory::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);
}
@@ -178,4 +200,94 @@ class AccessoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success')));
}
/**
* Save the Accessory checkout information.
*
* If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return Redirect
*/
public function checkout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$this->authorize('checkout', $accessory);
if ($accessory->numRemaining() > 0) {
if (!$user = User::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
}
// Update the accessory data
$accessory->assigned_to = $request->input('assigned_to');
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to')
]);
$accessory->logCheckout($request->input('note'), $user);
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
}
/**
* Check in the item so that it can be checked out again to someone else
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param integer $accessoryUserId
* @param string $backto
* @return Redirect
* @internal param int $accessoryId
*/
public function checkin(Request $request, $accessoryUserId = null)
{
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
$logaction = $accessory->logCheckin(User::find($accessoryUserId), $request->input('note'));
// Was the accessory updated?
if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
if (!is_null($accessory_user->assigned_to)) {
$user = User::find($accessory_user->assigned_to);
}
$data['log_id'] = $logaction->id;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['item_name'] = $accessory->name;
$data['checkin_date'] = $logaction->created_at;
$data['item_tag'] = '';
$data['note'] = $logaction->note;
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
}
}

View File

@@ -40,10 +40,31 @@ class AssetMaintenancesController extends Controller
$maintenances = $maintenances->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
if ($request->filled('asset_id')) {
$maintenances->where('asset_id', '=', $request->input('asset_id'));
}
$allowed_columns = ['id','title','asset_maintenance_time','asset_maintenance_type','cost','start_date','completion_date','notes','user_id'];
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($maintenances) && ($request->get('offset') > $maintenances->count())) ? $maintenances->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = [
'id',
'title',
'asset_maintenance_time',
'asset_maintenance_type',
'cost',
'start_date',
'completion_date',
'notes',
'asset_tag',
'asset_name',
'user_id'
];
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@@ -51,16 +72,20 @@ class AssetMaintenancesController extends Controller
case 'user_id':
$maintenances = $maintenances->OrderAdmin($order);
break;
case 'asset_tag':
$maintenances = $maintenances->OrderByTag($order);
break;
case 'asset_name':
$maintenances = $maintenances->OrderByAssetName($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
}
$total = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get();
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $maintenances->count());
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total);
}

View File

@@ -8,6 +8,7 @@ use App\Helpers\Helper;
use Illuminate\Http\Request;
use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
/**
@@ -29,19 +30,43 @@ class AssetModelsController extends Controller
public function index(Request $request)
{
$this->authorize('view', AssetModel::class);
$allowed_columns = ['id','image','name','model_number','eol','notes','created_at','manufacturer'];
$allowed_columns = ['id','image','name','model_number','eol','notes','created_at','manufacturer','assets_count'];
$assetmodels = AssetModel::select(['models.id','models.image','models.name','model_number','eol','models.notes','models.created_at','category_id','manufacturer_id','depreciation_id','fieldset_id'])
$assetmodels = AssetModel::select([
'models.id',
'models.image',
'models.name',
'model_number',
'eol',
'models.notes',
'models.created_at',
'category_id',
'manufacturer_id',
'depreciation_id',
'fieldset_id',
'models.deleted_at',
'models.updated_at',
])
->with('category','depreciation', 'manufacturer','fieldset')
->withCount('assets');
->withCount('assets as assets_count');
if ($request->has('search')) {
if ($request->filled('status')) {
$assetmodels->onlyTrashed();
}
if ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($assetmodels) && ($request->get('offset') > $assetmodels->count())) ? $assetmodels->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$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';
@@ -55,6 +80,7 @@ class AssetModelsController extends Controller
}
$total = $assetmodels->count();
$assetmodels = $assetmodels->skip($offset)->take($limit)->get();
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
@@ -93,7 +119,7 @@ class AssetModelsController extends Controller
public function show($id)
{
$this->authorize('view', AssetModel::class);
$assetmodel = AssetModel::withCount('assets')->findOrFail($id);
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
return (new AssetModelsTransformer)->transformAssetModel($assetmodel);
}
@@ -124,13 +150,13 @@ class AssetModelsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', AssetModel::class);
$this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
$assetmodel->fill($request->all());
$assetmodel->fieldset_id = $request->get("custom_fieldset_id");
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/assetmodels/message.update.success')));
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
@@ -154,8 +180,69 @@ class AssetModelsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.assoc_users')));
}
if ($assetmodel->image) {
try {
unlink(public_path().'/uploads/models/'.$assetmodel->image);
} catch (\Exception $e) {
\Log::info($e);
}
}
$assetmodel->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/assetmodels/message.delete.success')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$assetmodels = AssetModel::select([
'models.id',
'models.name',
'models.image',
'models.model_number',
'models.manufacturer_id',
'models.category_id',
])->with('manufacturer','category');
$settings = \App\Models\Setting::getSettings();
if ($request->filled('search')) {
$assetmodels = $assetmodels->SearchByManufacturerOrCat($request->input('search'));
}
$assetmodels = $assetmodels->OrderCategory('ASC')->OrderManufacturer('ASC')->orderby('models.name', 'asc')->orderby('models.model_number', 'asc')->paginate(50);
foreach ($assetmodels as $assetmodel) {
$assetmodel->use_text = '';
if ($settings->modellistCheckedValue('category')) {
$assetmodel->use_text .= (($assetmodel->category) ? e($assetmodel->category->name).' - ' : '');
}
if ($settings->modellistCheckedValue('manufacturer')) {
$assetmodel->use_text .= (($assetmodel->manufacturer) ? e($assetmodel->manufacturer->name).' ' : '');
}
$assetmodel->use_text .= e($assetmodel->name);
if (($settings->modellistCheckedValue('model_number')) && ($assetmodel->model_number!='')) {
$assetmodel->use_text .= ' (#'.e($assetmodel->model_number).')';
}
$assetmodel->use_image = ($settings->modellistCheckedValue('image') && ($assetmodel->image)) ? url('/').'/uploads/models/'.$assetmodel->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($assetmodels);
}
}

View File

@@ -4,6 +4,7 @@ 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\Models\Asset;
use App\Models\AssetModel;
@@ -30,6 +31,7 @@ use Str;
use TCPDF;
use Validator;
use View;
use App\Http\Transformers\SelectlistTransformer;
/**
@@ -50,9 +52,11 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function index(Request $request)
public function index(Request $request, $audit = null)
{
$this->authorize('index', Asset::class);
$settings = Setting::getSettings();
$allowed_columns = [
'id',
@@ -69,100 +73,195 @@ class AssetsController extends Controller
'created_at',
'updated_at',
'purchase_date',
'purchase_cost'
'purchase_cost',
'last_audit_date',
'next_audit_date',
'warranty_months',
'checkout_counter',
'checkin_counter',
'requests_counter',
];
$filter = array();
if ($request->has('filter')) {
$filter = json_decode($request->input('filter'));
}
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
}
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[]=$field->db_column_name();
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'))->with(
'assetloc', 'assetstatus', 'defaultLoc', 'assetlog', 'company',
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
// If we should search on everything
if (($request->has('search')) && (count($filter) == 0)) {
$assets->TextSearch($request->input('search'));
// otherwise loop through the filters and search strictly on them
} else {
if (count($filter) > 0) {
$assets->ByFilter($filter);
}
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
// These are used by the API to query against specific ID numbers.
// They are also used by the individual searches on detail pages like
// locations, etc.
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
}
// These are used by the API to query against specific ID numbers
if ($request->has('status_id')) {
$assets->where('status_id', '=', $request->input('status_id'));
if ($request->input('requestable')=='true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->has('model_id')) {
if ($request->filled('model_id')) {
$assets->InModelList([$request->input('model_id')]);
}
if ($request->has('category_id')) {
if ($request->filled('category_id')) {
$assets->InCategory($request->input('category_id'));
}
if ($request->has('location_id')) {
$assets->ByLocationId($request->input('location_id'));
if ($request->filled('location_id')) {
$assets->where('assets.location_id', '=', $request->input('location_id'));
}
if ($request->has('supplier_id')) {
if ($request->filled('rtd_location_id')) {
$assets->where('assets.rtd_location_id', '=', $request->input('rtd_location_id'));
}
if ($request->filled('supplier_id')) {
$assets->where('assets.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->has('company_id')) {
if (($request->filled('assigned_to')) && ($request->filled('assigned_type'))) {
$assets->where('assets.assigned_to', '=', $request->input('assigned_to'))
->where('assets.assigned_type', '=', $request->input('assigned_type'));
}
if ($request->filled('company_id')) {
$assets->where('assets.company_id', '=', $request->input('company_id'));
}
if ($request->has('manufacturer_id')) {
if ($request->filled('manufacturer_id')) {
$assets->ByManufacturer($request->input('manufacturer_id'));
}
$request->has('order_number') ? $assets = $assets->where('order_number', '=', e($request->get('order_number'))) : '';
if ($request->filled('depreciation_id')) {
$assets->ByDepreciationId($request->input('depreciation_id'));
}
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$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
// related to fulltext searches on complex queries.
// I am sad. :(
switch ($request->input('status')) {
case 'Deleted':
$assets->withTrashed()->Deleted();
break;
case 'Pending':
$assets->Pending();
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',0)
->where('status_alias.pending','=',1)
->where('status_alias.archived', '=', 0);
});
break;
case 'RTD':
$assets->RTD();
$assets->whereNull('assets.assigned_to')
->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',1)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Undeployable':
$assets->Undeployable();
break;
case 'Archived':
$assets->Archived();
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',0)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 1);
});
break;
case 'Requestable':
$assets->RequestableAssets();
$assets->where('assets.requestable', '=', 1)
->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.deployable','=',1)
->where('status_alias.pending','=',0)
->where('status_alias.archived', '=', 0);
});
break;
case 'Deployed':
$assets->Deployed();
// more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->where('assets.assigned_to', '>', '0');
break;
default:
if ((!$request->filled('status_id')) && ($settings->show_archived_in_list!='1')) {
// terrible workaround for complex-query Laravel bug in fulltext
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id")
->where('status_alias.archived', '=', 0);
});
// If there is a status ID, don't take show_archived_in_list into consideration
} else {
$assets->join('status_labels AS status_alias',function ($join) {
$join->on('status_alias.id', "=", "assets.status_id");
});
}
}
if ((!is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// This handles all of the pivot sorting (versus the assets.* fields in the allowed_columns array)
$column_sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets.created_at';
switch ($request->input('sort')) {
// This is kinda gross, but we need to do this because the Bootstrap Tables
// API passes custom field ordering as custom_fields.fieldname, and we have to strip
// that out to let the default sorter below order them correctly on the assets table.
$sort_override = str_replace('custom_fields.','', $request->input('sort')) ;
// This handles all of the pivot sorting (versus the assets.* fields
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
break;
@@ -180,6 +279,8 @@ class AssetsController extends Controller
break;
case 'location':
$assets->OrderLocation($order);
case 'rtd_location':
$assets->OrderRtdLocation($order);
break;
case 'status_label':
$assets->OrderStatus($order);
@@ -194,14 +295,54 @@ class AssetsController extends Controller
$assets->orderBy($column_sort, $order);
break;
}
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformAssets($assets, $total);
}
/**
/**
* Returns JSON with information about an asset (by tag) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $tag
* @since [v4.2.1]
* @return JsonResponse
*/
public function showByTag($tag)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->where('asset_tag',$tag)->first()) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
}
/**
* Returns JSON with information about an asset (by serial) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $serial
* @since [v4.2.1]
* @return JsonResponse
*/
public function showBySerial($serial)
{
$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 response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
}
/**
* Returns JSON with information about an asset for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
@@ -211,12 +352,69 @@ class AssetsController extends Controller
*/
public function show($id)
{
if ($asset = Asset::withTrashed()->find($id)) {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as userRequests_count')->findOrFail($id)) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select([
'assets.id',
'assets.name',
'assets.asset_tag',
'assets.model_id',
'assets.assigned_to',
'assets.assigned_type',
'assets.status_id'
])->with('model', 'assetstatus', 'assignedTo')->NotArchived(),'company_id', 'assets');
if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
}
if ($request->filled('search')) {
$assets = $assets->AssignedSearch($request->input('search'));
}
$assets = $assets->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($assets as $asset) {
$asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
}
if ($asset->assetstatus->getStatuslabelType()=='pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
}
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;
}
return (new SelectlistTransformer)->transformSelectlist($assets);
}
@@ -242,7 +440,7 @@ class AssetsController extends Controller
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
@@ -255,18 +453,25 @@ class AssetsController extends Controller
$asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0);
$asset->rtd_location_id = $request->get('rtd_location_id', null);
$asset->location_id = $request->get('rtd_location_id', null);
// 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->fieldset) {
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($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 ($asset->save()) {
$asset->logCreate();
if ($request->get('assigned_user')) {
$target = User::find(request('assigned_user'));
} elseif ($request->get('assigned_asset')) {
@@ -294,66 +499,51 @@ class AssetsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('create', Asset::class);
$this->authorize('update', Asset::class);
if ($asset = Asset::find($id)) {
($request->has('model_id')) ?
$asset->model()->associate(AssetModel::find($request->get('model_id'))) : '';
($request->has('name')) ?
$asset->name = $request->get('name') : '';
($request->has('serial')) ?
$asset->serial = $request->get('serial') : '';
($request->has('model_id')) ?
$asset->model_id = $request->get('model_id') : '';
($request->has('order_number')) ?
$asset->order_number = $request->get('order_number') : '';
($request->has('notes')) ?
$asset->notes = $request->get('notes') : '';
($request->has('asset_tag')) ?
$asset->asset_tag = $request->get('asset_tag') : '';
($request->has('archived')) ?
$asset->archived = $request->get('archived') : '';
($request->has('status_id')) ?
$asset->status_id = $request->get('status_id') : '';
($request->has('warranty_months')) ?
$asset->warranty_months = $request->get('warranty_months') : '';
($request->has('purchase_cost')) ?
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost')) : '';
($request->has('purchase_date')) ?
$asset->purchase_date = $request->get('purchase_date') : '';
($request->has('assigned_to')) ?
$asset->assigned_to = $request->get('assigned_to') : '';
($request->has('supplier_id')) ?
$asset->supplier_id = $request->get('supplier_id') : '';
($request->has('requestable')) ?
$asset->requestable = $request->get('requestable') : '';
($request->has('rtd_location_id')) ?
$asset->rtd_location_id = $request->get('rtd_location_id') : '';
($request->has('company_id')) ?
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
if ($request->has('model_id')) {
if (($model = AssetModel::find($request->get('model_id'))) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->convertUnicodeDbSlug())) {
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug()));
$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;
// 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 ($asset->save()) {
if ($request->get('assigned_user')) {
$target = User::find(request('assigned_user'));
} elseif ($request->get('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
} elseif ($request->get('assigned_location')) {
$target = Location::find(request('assigned_location'));
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id;
Asset::where('assigned_type', '\\App\\Models\\Asset')->where('assigned_to', $id)
->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')));
$asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
@@ -402,7 +592,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function checkout(Request $request, $asset_id)
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
$this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id);
@@ -413,29 +603,64 @@ class AssetsController extends Controller
$this->authorize('checkout', $asset);
if ($request->has('user_id')) {
$target = User::find($request->input('user_id'));
} elseif ($request->has('asset_id')) {
$target = Asset::find($request->input('asset_id'));
} elseif ($request->has('location_id')) {
$target = Location::find($request->input('location_id'));
$error_payload = [];
$error_payload['asset'] = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
];
// This item is checked out to a location
if (request('checkout_to_type')=='location') {
$target = Location::find(request('assigned_location'));
$asset->location_id = ($target) ? $target->id : '';
$error_payload['target_id'] = $request->input('assigned_location');
$error_payload['target_type'] = 'location';
} elseif (request('checkout_to_type')=='asset') {
$target = Asset::where('id','!=',$asset_id)->find(request('assigned_asset'));
$asset->location_id = $target->rtd_location_id;
// Override with the asset's location_id if it has one
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset');
$error_payload['target_type'] = 'asset';
} elseif (request('checkout_to_type')=='user') {
// Fetch the target and set the asset's new location_id
$target = User::find(request('assigned_user'));
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_user');
$error_payload['target_type'] = 'user';
}
if (!isset($target)) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], 'No valid checkout target specified for asset '.e($asset->asset_tag).'.'));
return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.'));
}
$checkout_at = request('checkout_at', date("Y-m-d H:i:s"));
$expected_checkin = request('expected_checkin', null);
$note = request('note', null);
$asset_name = request('name', null);
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name)) {
// Set the location ID to the RTD location id if there is one
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
// TODO: Follow up here. WTF. Commented out for now.
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
// $asset->location_id = $target->rtd_location_id;
// }
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')))->withErrors($asset->getErrors());
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
}
@@ -447,7 +672,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function checkin($asset_id)
public function checkin(Request $request, $asset_id)
{
$this->authorize('checkin', Asset::class);
$asset = Asset::findOrFail($asset_id);
@@ -464,32 +689,23 @@ class AssetsController extends Controller
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = e(Input::get('name'));
if (Input::has('status_id')) {
$asset->status_id = e(Input::get('status_id'));
if ($request->filled('name')) {
$asset->name = $request->input('name');
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
$asset->location_id = $request->input('location_id');
}
if (Input::has('status_id')) {
$asset->status_id = Input::get('status_id');
}
// Was the asset updated?
if ($asset->save()) {
$logaction = $asset->logCheckin($target, e(request('note')));
$data['log_id'] = $logaction->id;
$data['first_name'] = get_class($target) == User::class ? $target->first_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;
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!config('app.lock_passwords'))) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_Asset_Checkin'));
});
}
$asset->logCheckin($target, e(request('note')));
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
}
@@ -524,7 +740,11 @@ class AssetsController extends Controller
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->last_audit_date = date('Y-m-d h:i:s');
if ($asset->save()) {
$log = $asset->logAudit(request('note'),request('location_id'));
return response()->json(Helper::formatStandardApiResponse('success', [
@@ -541,5 +761,51 @@ class AssetsController extends Controller
}
/**
* Returns JSON listing of all requestable assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier')->where('assets.requestable', '=', '1');
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$assets->TextSearch($request->input('search'));
switch ($request->input('sort')) {
case 'model':
$assets->OrderModels($order);
break;
case 'model_number':
$assets->OrderModelNumber($order);
break;
case 'category':
$assets->OrderCategory($order);
break;
case 'manufacturer':
$assets->OrderManufacturer($order);
break;
default:
$assets->orderBy('assets.created_at', $order);
break;
}
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
}
}

View File

@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Models\Category;
use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
class CategoriesController extends Controller
{
@@ -20,19 +21,24 @@ class CategoriesController extends Controller
public function index(Request $request)
{
$this->authorize('view', Category::class);
$allowed_columns = ['id', 'name','category_type','use_default_eula','require_acceptance','checkin_email'];
$allowed_columns = ['id', 'name','category_type', 'category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count','licenses_count', 'image'];
$categories = Category::select(['id', 'created_at', 'updated_at', 'name','category_type','use_default_eula','require_acceptance','checkin_email'])
->withCount('assets', 'accessories', 'consumables', 'components');
$categories = Category::select(['id', 'created_at', 'updated_at', 'name','category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email','image'])
->withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count','licenses as licenses_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$categories = $categories->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($categories) && ($request->get('offset') > $categories->count())) ? $categories->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
$categories->orderBy($sort, $order);
$total = $categories->count();
@@ -91,7 +97,7 @@ class CategoriesController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Category::class);
$this->authorize('update', Category::class);
$category = Category::findOrFail($id);
$category->fill($request->all());
@@ -128,4 +134,40 @@ class CategoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request, $category_type = 'asset')
{
$categories = Category::select([
'id',
'name',
'image',
]);
if ($request->filled('search')) {
$categories = $categories->where('name', 'LIKE', '%'.$request->get('search').'%');
}
$categories = $categories->where('category_type', $category_type)->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($categories as $category) {
$category->use_image = ($category->image) ? url('/').'/uploads/categories/'.$category->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($categories);
}
}

View File

@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Models\Company;
use App\Http\Transformers\SelectlistTransformer;
class CompaniesController extends Controller
{
@@ -21,19 +22,32 @@ class CompaniesController extends Controller
{
$this->authorize('view', Company::class);
$allowed_columns = ['id','name'];
$allowed_columns = [
'id',
'name',
'created_at',
'updated_at',
'users_count',
'assets_count',
'licenses_count',
'accessories_count',
'consumables_count',
'components_count',
];
$companies = Company::withCount('assets','licenses','accessories','consumables','components','users')
->withCount('users')->withCount('users')->withCount('assets')
->withCount('licenses')->withCount('accessories')
->withCount('consumables')->withCount('components');
$companies = Company::withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count','components as components_count','users as users_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($companies) && ($request->get('offset') > $companies->count())) ? $companies->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$companies->orderBy($sort, $order);
@@ -95,7 +109,7 @@ class CompaniesController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Company::class);
$this->authorize('update', Company::class);
$company = Company::findOrFail($id);
$company->fill($request->all());
@@ -141,4 +155,37 @@ class CompaniesController extends Controller
}
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$companies = Company::select([
'companies.id',
'companies.name',
'companies.image',
]);
if ($request->filled('search')) {
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%');
}
$companies = $companies->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($companies as $company) {
$company->use_image = ($company->image) ? url('/').'/uploads/companies/'.$company->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($companies);
}
}

View File

@@ -24,17 +24,33 @@ class ComponentsController extends Controller
public function index(Request $request)
{
$this->authorize('view', Component::class);
$components = Company::scopeCompanyables(Component::select('components.*')->whereNull('components.deleted_at')
$components = Company::scopeCompanyables(Component::select('components.*')
->with('company', 'location', 'category'));
if ($request->has('search')) {
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
if ($request->filled('company_id')) {
$components->where('company_id','=',$request->input('company_id'));
}
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location'];
if ($request->filled('category_id')) {
$components->where('category_id','=',$request->input('category_id'));
}
if ($request->filled('location_id')) {
$components->where('location_id','=',$request->input('location_id'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($components) && ($request->get('offset') > $components->count())) ? $components->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -89,13 +105,11 @@ class ComponentsController extends Controller
public function show($id)
{
$this->authorize('view', Component::class);
$component = Component::find($id);
$component = Component::findOrFail($id);
if ($component) {
return (new ComponentsTransformer)->transformComponent($component);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist')));
}
@@ -110,7 +124,7 @@ class ComponentsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Component::class);
$this->authorize('update', Component::class);
$component = Component::findOrFail($id);
$component->fill($request->all());
@@ -149,7 +163,7 @@ class ComponentsController extends Controller
*/
public function getAssets(Request $request, $id)
{
$this->authorize('index', Asset::class);
$this->authorize('view', \App\Models\Asset::class);
$component = Component::findOrFail($id);
$assets = $component->assets();
@@ -158,6 +172,6 @@ class ComponentsController extends Controller
$limit = $request->input('limit', 50);
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new ComponentsAssetsTransformer)->transformAssets($assets, $total);
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
}
}

View File

@@ -24,26 +24,34 @@ class ConsumablesController extends Controller
$this->authorize('index', Consumable::class);
$consumables = Company::scopeCompanyables(
Consumable::select('consumables.*')
->whereNull('consumables.deleted_at')
->with('company', 'location', 'category', 'users', 'manufacturer')
);
if ($request->has('search')) {
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
}
if ($request->has('company_id')) {
if ($request->filled('company_id')) {
$consumables->where('company_id','=',$request->input('company_id'));
}
if ($request->has('manufacturer_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 = 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'];
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = ['id','name','order_number','min_amt','purchase_date','purchase_cost','company','category','model_number', 'item_no', 'manufacturer','location','qty','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -121,7 +129,7 @@ class ConsumablesController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Consumable::class);
$this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id);
$consumable->fill($request->all());
@@ -160,10 +168,9 @@ class ConsumablesController extends Controller
*/
public function getDataView($consumableId)
{
//$consumable = Consumable::find($consumableID);
$consumable = Consumable::with(array('consumableAssignments'=>
function ($query) {
$query->orderBy('created_at', 'DESC');
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
},
'consumableAssignments.admin'=> function ($query) {
},
@@ -171,18 +178,16 @@ class ConsumablesController extends Controller
},
))->find($consumableId);
// $consumable->load('consumableAssignments.admin','consumableAssignments.user');
if (!Company::isCurrentUserHasAccess($consumable)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', Component::class);
$this->authorize('view', Consumable::class);
$rows = array();
foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [
'name' => $consumable_assignment->user->present()->nameUrl(),
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}

View File

@@ -2,11 +2,14 @@
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\Request;
use Validator;
use Illuminate\Validation\Rule;
class CustomFieldsController extends Controller
{
@@ -21,15 +24,97 @@ class CustomFieldsController extends Controller
public function index()
{
$this->authorize('index', CustomFields::class);
$this->authorize('index', CustomField::class);
$fields = CustomField::get();
$total = count($fields);
return (new CustomFieldsTransformer)->transformCustomFields($fields, $total);
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
}
/**
* Shows the given field
* @author [V. Cordes] [<volker@fdatek.de>]
* @param int $id
* @since [v4.1.10]
* @return View
*/
public function show($id)
{
$this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) {
return (new CustomFieldsTransformer)->transformCustomField($field);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/custom_fields/message.field.invalid')), 200);
}
/**
* Update the specified field
*
* @author [V. Cordes] [<volker@fdatek.de>]
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id);
/**
* Updated values for the field,
* without the "field_encrypted" flag, preventing the change of encryption status
* @var array
*/
$data = $request->except(['field_encrypted']);
$validator = Validator::make($data, $field->validationRules());
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
$field->fill($data);
if ($field->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $field, trans('admin/custom_fields/message.field.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $field->getErrors()));
}
/**
* Store a newly created field.
*
* @author [V. Cordes] [<volker@fdatek.de>]
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->authorize('create', CustomField::class);
$field = new CustomField;
$data = $request->all();
$validator = Validator::make($data, $field->validationRules());
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
$field->fill($data);
if ($field->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $field, trans('admin/custom_fields/message.field.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $field->getErrors()));
}
public function postReorder(Request $request, $id)
{
$fieldset = CustomFieldset::find($id);
$this->authorize('update', $fieldset);
$fields = array();
$order_array = array();
@@ -47,6 +132,40 @@ class CustomFieldsController extends Controller
}
public function associate(Request $request, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id);
$fieldset_id = $request->input('fieldset_id');
foreach ($field->fieldset as $fieldset) {
if ($fieldset->id == $fieldset_id) {
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
}
$fieldset = CustomFieldset::findOrFail($fieldset_id);
$fieldset->fields()->attach($field->id, ["required" => ($request->input('required') == "on"), "order" => $request->input('order', $fieldset->fields->count())]);
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
public function disassociate(Request $request, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id);
$fieldset_id = $request->input('fieldset_id');
foreach ($field->fieldset as $fieldset) {
if ($fieldset->id == $fieldset_id) {
$fieldset->fields()->detach($field->id);
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
}
$fieldset = CustomFieldset::findOrFail($fieldset_id);
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
/**
* Delete a custom field.
@@ -57,15 +176,17 @@ class CustomFieldsController extends Controller
*/
public function destroy($field_id)
{
$field = CustomField::find($field_id);
$field = CustomField::findOrFail($field_id);
$this->authorize('delete', $field);
if ($field->fieldset->count() >0) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Field is in use.'));
} else {
$field->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/custom_fields/message.field.delete.success')));
}
$field->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/custom_fields/message.field.delete.success')));
}
}

View File

@@ -43,10 +43,8 @@ class CustomFieldsetsController extends Controller
public function index()
{
$this->authorize('index', CustomFieldset::class);
$fieldsets = CustomFieldset::withCount(['fields', 'models'])->get();
$total = count($fieldsets);
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $total);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
}
@@ -60,7 +58,7 @@ class CustomFieldsetsController extends Controller
*/
public function show($id)
{
$this->authorize('show', CustomFieldset::class);
$this->authorize('view', CustomFieldset::class);
if ($fieldset = CustomFieldset::find($id)) {
return (new CustomFieldsetsTransformer)->transformCustomFieldset($fieldset);
}
@@ -81,7 +79,7 @@ class CustomFieldsetsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', CustomFieldset::class);
$this->authorize('update', CustomFieldset::class);
$fieldset = CustomFieldset::findOrFail($id);
$fieldset->fill($request->all());
@@ -126,7 +124,7 @@ class CustomFieldsetsController extends Controller
{
$this->authorize('delete', CustomFieldset::class);
$fieldset = CustomFieldset::findOrFail($id);
$modelsCount = $fieldset->models->count();
$fieldsCount = $fieldset->fields->count();
@@ -141,7 +139,41 @@ class CustomFieldsetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, 'Unspecified error'));
}
/**
* Return JSON containing a list of fields belonging to a fieldset.
*
* @author [V. Cordes] [<volker@fdatek.de>]
* @since [v4.1.10]
* @param $fieldsetId
* @return string JSON
*/
public function fields($id)
{
$this->authorize('view', CustomFieldset::class);
$set = CustomFieldset::findOrFail($id);
$fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
}
/**
* Return JSON containing a list of fields belonging to a fieldset with the
* default values for a given model
*
* @param $modelId
* @param $fieldsetId
* @return string JSON
*/
public function fieldsWithDefaultValues($fieldsetId, $modelId)
{
$this->authorize('view', CustomFieldset::class);
$set = CustomFieldset::findOrFail($fieldsetId);
$fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
}
}

View File

@@ -8,40 +8,58 @@ use App\Models\Department;
use App\Http\Transformers\DepartmentsTransformer;
use App\Helpers\Helper;
use Auth;
use App\Http\Transformers\SelectlistTransformer;
class DepartmentsController extends Controller
{
/**
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Godfrey Martinez] [<snipe@snipe.net>]
* @since [v4.0]
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$this->authorize('view', Department::class);
$allowed_columns = ['id','name'];
$allowed_columns = ['id','name','image','users_count'];
$departments = Department::select([
'id',
'name',
'location_id',
'company_id',
'manager_id',
'created_at',
'updated_at'
])->with('users')->with('location')->with('manager')->with('company')->withCount('users');
'departments.id',
'departments.name',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image'
])->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$departments = $departments->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($departments) && ($request->get('offset') > $departments->count())) ? $departments->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$departments->orderBy($sort, $order);
switch ($request->input('sort')) {
case 'location':
$departments->OrderLocation($order);
break;
case 'manager':
$departments->OrderManager($order);
break;
default:
$departments->orderBy($sort, $order);
break;
}
$total = $departments->count();
$departments = $departments->skip($offset)->take($limit)->get();
@@ -63,7 +81,7 @@ class DepartmentsController extends Controller
$department = new Department;
$department->fill($request->all());
$department->user_id = Auth::user()->id;
$department->manager_id = ($request->has('manager_id' ) ? $request->input('manager_id') : null);
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.create.success')));
@@ -101,6 +119,8 @@ class DepartmentsController extends Controller
{
$department = Department::findOrFail($id);
$this->authorize('delete', $department);
if ($department->users->count() > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/departments/message.assoc_users')));
}
@@ -110,4 +130,61 @@ class DepartmentsController extends Controller
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$departments = Department::select([
'id',
'name',
'image',
]);
if ($request->filled('search')) {
$departments = $departments->where('name', 'LIKE', '%'.$request->get('search').'%');
}
$departments = $departments->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($departments as $department) {
$department->use_image = ($department->image) ? url('/').'/uploads/departments/'.$department->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($departments);
}
/**
* Update the specified resource in storage.
*
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->authorize('update', Department::class);
$departments = Department::findOrFail($id);
$departments->fill($request->all());
if ($departments->save()) {
return response()
->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer())->transformdepartment($departments), trans('admin/departments/message.update.success')));
}
return response()
->json(Helper::formatStandardApiResponse('error', null, $departments->getErrors()));
}
}

View File

@@ -24,12 +24,17 @@ class DepreciationsController extends Controller
$depreciations = Depreciation::select('id','name','months','user_id','created_at','updated_at');
if ($request->has('search')) {
if ($request->filled('search')) {
$depreciations = $depreciations->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($depreciations) && ($request->get('offset') > $depreciations->count())) ? $depreciations->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$depreciations->orderBy($sort, $order);
@@ -88,7 +93,7 @@ class DepreciationsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Depreciation::class);
$this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
$depreciation->fill($request->all());

View File

@@ -20,16 +20,21 @@ class GroupsController extends Controller
public function index(Request $request)
{
$this->authorize('view', Group::class);
$allowed_columns = ['id','name','created_at'];
$allowed_columns = ['id','name','created_at', 'users_count'];
$groups = Group::select('id','name','permissions','created_at','updated_at')->withCount('users');
$groups = Group::select('id','name','permissions','created_at','updated_at')->withCount('users as users_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($groups) && ($request->get('offset') > $groups->count())) ? $groups->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$groups->orderBy($sort, $order);
@@ -88,7 +93,7 @@ class GroupsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Group::class);
$this->authorize('update', Group::class);
$group = Group::findOrFail($id);
$group->fill($request->all());

View File

@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Session;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Artisan;
use App\Models\Asset;
class ImportController extends Controller
{
@@ -24,7 +25,7 @@ class ImportController extends Controller
*/
public function index()
{
//
$this->authorize('import');
$imports = Import::latest()->get();
return (new ImportsTransformer)->transformImports($imports);
@@ -38,10 +39,8 @@ class ImportController extends Controller
*/
public function store()
{
//
if (!Company::isCurrentUserAuthorized()) {
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
} elseif (!config('app.lock_passwords')) {
$this->authorize('import');
if (!config('app.lock_passwords')) {
$files = Input::file('files');
$path = config('app.private_uploads').'/imports';
$results = [];
@@ -57,6 +56,35 @@ class ImportController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 500);
}
//TODO: is there a lighter way to do this?
if (! ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$import->header_row = $reader->fetchOne(0);
//duplicate headers check
$duplicate_headers = [];
for($i = 0; $i<count($import->header_row); $i++) {
$header = $import->header_row[$i];
if(in_array($header, $import->header_row)) {
$found_at = array_search($header, $import->header_row);
if($i > $found_at) {
//avoid reporting duplicates twice, e.g. "1 is same as 17! 17 is same as 1!!!"
//as well as "1 is same as 1!!!" (which is always true)
//has to be > because otherwise the first result of array_search will always be $i itself(!)
array_push($duplicate_headers,"Duplicate header '$header' detected, first at column: ".($found_at+1).", repeats at column: ".($i+1));
}
}
}
if(count($duplicate_headers) > 0) {
return response()->json(Helper::formatStandardApiResponse('error',null, implode("; ",$duplicate_headers)), 500); //should this be '4xx'?
}
// Grab the first row to display via ajax as the user picks fields
$import->first_row = $reader->fetchOne(1);
$date = date('Y-m-d-his');
$fixed_filename = str_slug($file->getClientOriginalName());
try {
@@ -71,11 +99,6 @@ class ImportController extends Controller
$file_name = date('Y-m-d-his').'-'.$fixed_filename;
$import->file_path = $file_name;
$import->filesize = filesize($path.'/'.$file_name);
//TODO: is there a lighter way to do this?
$reader = Reader::createFromPath("{$path}/{$file_name}");
$import->header_row = $reader->fetchOne(0);
// Grab the first row to display via ajax as the user picks fields
$import->first_row = $reader->fetchOne(1);
$import->save();
$results[] = $import;
}
@@ -94,9 +117,16 @@ class ImportController extends Controller
*/
public function process(ItemImportRequest $request, $import_id)
{
$this->authorize('create', Asset::class);
$this->authorize('import');
// Run a backup immediately before processing
Artisan::call('backup:run');
if ($request->has('run-backup')) {
\Log::debug('Backup manually requested via importer');
Artisan::call('backup:run');
} else {
\Log::debug('NO BACKUP requested via importer');
}
$errors = $request->import(Import::find($import_id));
$redirectTo = "hardware.index";
switch ($request->get('import-type')) {
@@ -137,7 +167,7 @@ class ImportController extends Controller
*/
public function destroy($import_id)
{
$this->authorize('create', Asset::class);
$this->authorize('import');
$import = Import::find($import_id);
try {
unlink(config('app.private_uploads').'/imports/'.$import->file_path);

View File

@@ -2,11 +2,15 @@
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\LicenseSeatsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Models\License;
use App\Models\Company;
use App\Models\License;
use App\Models\LicenseSeat;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LicensesController extends Controller
{
@@ -21,80 +25,97 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer', 'supplier'));
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier','category')->withCount('freeSeats as free_seats_count'));
if ($request->has('search')) {
$licenses = $licenses->TextSearch($request->input('search'));
}
if ($request->has('company_id')) {
if ($request->filled('company_id')) {
$licenses->where('company_id','=',$request->input('company_id'));
}
if ($request->has('name')) {
if ($request->filled('name')) {
$licenses->where('licenses.name','=',$request->input('name'));
}
if ($request->has('product_key')) {
if ($request->filled('product_key')) {
$licenses->where('licenses.serial','=',$request->input('product_key'));
}
if ($request->has('order_number')) {
if ($request->filled('order_number')) {
$licenses->where('order_number','=',$request->input('order_number'));
}
if ($request->has('purchase_order')) {
if ($request->filled('purchase_order')) {
$licenses->where('purchase_order','=',$request->input('purchase_order'));
}
if ($request->has('license_name')) {
if ($request->filled('license_name')) {
$licenses->where('license_name','=',$request->input('license_name'));
}
if ($request->has('license_email')) {
if ($request->filled('license_email')) {
$licenses->where('license_email','=',$request->input('license_email'));
}
if ($request->has('manufacturer_id')) {
if ($request->filled('manufacturer_id')) {
$licenses->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
if ($request->has('supplier_id')) {
if ($request->filled('supplier_id')) {
$licenses->where('supplier_id','=',$request->input('supplier_id'));
}
if ($request->has('depreciation_id')) {
if ($request->filled('category_id')) {
$licenses->where('category_id','=',$request->input('category_id'));
}
if ($request->filled('depreciation_id')) {
$licenses->where('depreciation_id','=',$request->input('depreciation_id'));
}
if ($request->has('supplier_id')) {
if ($request->filled('supplier_id')) {
$licenses->where('supplier_id','=',$request->input('supplier_id'));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
if ($request->filled('search')) {
$licenses = $licenses->TextSearch($request->input('search'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($licenses) && ($request->get('offset') > $licenses->count())) ? $licenses->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($request->input('sort')) {
case 'manufacturer':
$licenses = $licenses->OrderManufacturer($order);
case 'manufacturer':
$licenses = $licenses->leftJoin('manufacturers', 'licenses.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
break;
case 'supplier':
$licenses = $licenses->OrderSupplier($order);
$licenses = $licenses->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
break;
case 'category':
$licenses = $licenses->leftJoin('categories', 'licenses.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
break;
case 'company':
$licenses = $licenses->OrderCompany($order);
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break;
default:
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','license_name','license_email'];
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','category','license_name','license_email','free_seats_count','seats'];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$licenses = $licenses->orderBy($sort, $order);
break;
}
$total = $licenses->count();
$licenses = $licenses->skip($offset)->take($limit)->get();
return (new LicensesTransformer)->transformLicenses($licenses, $total);
@@ -114,6 +135,14 @@ class LicensesController extends Controller
public function store(Request $request)
{
//
$this->authorize('create', License::class);
$license = new License;
$license->fill($request->all());
if($license->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $license, trans('admin/licenses/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $license->getErrors()));
}
/**
@@ -125,13 +154,10 @@ class LicensesController extends Controller
*/
public function show($id)
{
$license = License::find($id);
if (isset($license->id)) {
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
$this->authorize('view', $license);
return (new LicensesTransformer)->transformLicense($license);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
$this->authorize('view', License::class);
$license = License::findOrFail($id);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
return (new LicensesTransformer)->transformLicense($license);
}
@@ -147,6 +173,16 @@ class LicensesController extends Controller
public function update(Request $request, $id)
{
//
$this->authorize('update', License::class);
$license = License::findOrFail($id);
$license->fill($request->all());
if ($license->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $license, trans('admin/licenses/message.update.success')));
}
return Helper::formatStandardApiResponse('error', null, $license->getErrors());
}
/**
@@ -160,5 +196,66 @@ class LicensesController extends Controller
public function destroy($id)
{
//
$license = License::findOrFail($id);
$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 response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/licenses/message.delete.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.assoc_users')));
}
/**
* Get license seat listing
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @return \Illuminate\Contracts\View\View
*/
public function seats(Request $request, $licenseId)
{
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
$seats = LicenseSeat::where('license_seats.license_id', $licenseId)
->with('license', 'user', 'asset', 'user.department');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->input('sort')=='department') {
$seats->OrderDepartments($order);
} else {
$seats->orderBy('id', $order);
}
$total = $seats->count();
$offset = (($seats) && (request('offset') > $total)) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$seats = $seats->skip($offset)->take($limit)->get();
if ($seats) {
return (new LicenseSeatsTransformer)->transformLicenseSeats($seats, $total);
}
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.does_not_exist')), 200);
}
}

View File

@@ -7,6 +7,9 @@ use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Models\Location;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class LocationsController extends Controller
{
@@ -20,10 +23,12 @@ class LocationsController extends Controller
public function index(Request $request)
{
$this->authorize('view', Location::class);
$allowed_columns = ['id','name','address','address2','city','state','country','zip','created_at',
'updated_at','parent_id', 'manager_id'];
$allowed_columns = [
'id','name','address','address2','city','state','country','zip','created_at',
'updated_at','manager_id','image',
'assigned_assets_count','users_count','assets_count','currency'];
$locations = Location::with('parent', 'manager', 'childLocations')->select([
$locations = Location::with('parent', 'manager', 'children')->select([
'locations.id',
'locations.name',
'locations.address',
@@ -36,21 +41,39 @@ class LocationsController extends Controller
'locations.manager_id',
'locations.created_at',
'locations.updated_at',
'locations.image',
'locations.currency'
])->withCount('locationAssets')
->withCount('assignedAssets')
->withCount('assets')
->withCount('users');
])->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('users as users_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$locations = $locations->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($locations) && ($request->get('offset') > $locations->count())) ? $locations->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$locations->orderBy($sort, $order);
switch ($request->input('sort')) {
case 'parent':
$locations->OrderParent($order);
break;
case 'manager':
$locations->OrderManager($order);
break;
default:
$locations->orderBy($sort, $order);
break;
}
$total = $locations->count();
$locations = $locations->skip($offset)->take($limit)->get();
@@ -89,7 +112,26 @@ class LocationsController extends Controller
public function show($id)
{
$this->authorize('view', Location::class);
$location = Location::findOrFail($id);
$location = Location::with('parent', 'manager', 'children')
->select([
'locations.id',
'locations.name',
'locations.address',
'locations.address2',
'locations.city',
'locations.state',
'locations.zip',
'locations.country',
'locations.parent_id',
'locations.manager_id',
'locations.created_at',
'locations.updated_at',
'locations.image',
'locations.currency'
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('users as users_count')->findOrFail($id);
return (new LocationsTransformer)->transformLocation($location);
}
@@ -105,8 +147,15 @@ class LocationsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Location::class);
$this->authorize('update', Location::class);
$location = Location::findOrFail($id);
if ($request->input('parent_id') == $id) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'A location cannot be its own parent. Please select a different parent ID.'));
}
$location->fill($request->all());
if ($location->save()) {
@@ -138,4 +187,80 @@ class LocationsController extends Controller
$location->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/locations/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* This is handled slightly differently as of ~4.7.8-pre, as
* we have to do some recursive magic to get the hierarchy to display
* properly when looking at the parent/child relationship in the
* rich menus.
*
* This means we can't use the normal pagination that we use elsewhere
* in our selectlists, since we have to get the full set before we can
* determine which location is parent/child/grandchild, etc.
*
* This also means that hierarchy display gets a little funky when people
* use the Select2 search functionality, but there's not much we can do about
* that right now.
*
* As a result, instead of paginating as part of the query, we have to grab
* the entire data set, and then invoke a paginator manually and pass that
* through to the SelectListTransformer.
*
* Many thanks to @uberbrady for the help getting this working better.
* Recursion still sucks, but I guess he doesn't have to get in the
* sea... this time.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$locations = Location::select([
'locations.id',
'locations.name',
'locations.parent_id',
'locations.image',
]);
$page = 1;
if ($request->filled('page')) {
$page = $request->input('page');
}
if ($request->filled('search')) {
$locations = $locations->where('locations.name', 'LIKE', '%'.$request->input('search').'%');
}
$locations = $locations->orderBy('name', 'ASC')->get();
$locations_with_children = [];
foreach ($locations as $location) {
if (!array_key_exists($location->parent_id, $locations_with_children)) {
$locations_with_children[$location->parent_id] = [];
}
$locations_with_children[$location->parent_id][] = $location;
}
if ($request->filled('search')) {
$locations_formatted = $locations;
} else {
$location_options = Location::indenter($locations_with_children);
$locations_formatted = new Collection($location_options);
}
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
//return [];
return (new SelectlistTransformer)->transformSelectlist($paginated_results);
}
}

View File

@@ -8,6 +8,7 @@ use App\Helpers\Helper;
use App\Models\Manufacturer;
use App\Http\Transformers\DatatablesTransformer;
use App\Http\Transformers\ManufacturersTransformer;
use App\Http\Transformers\SelectlistTransformer;
class ManufacturersController extends Controller
{
@@ -21,19 +22,28 @@ class ManufacturersController extends Controller
public function index(Request $request)
{
$this->authorize('view', Manufacturer::class);
$allowed_columns = ['id','name','url','support_url','support_email','support_phone','created_at','updated_at'];
$allowed_columns = ['id','name','url','support_url','support_email','support_phone','created_at','updated_at','image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
$manufacturers = Manufacturer::select(
array('id','name','url','support_url','support_email','support_phone','created_at','updated_at')
)->withCount('assets')->withCount('licenses')->withCount('consumables')->withCount('accessories');
array('id','name','url','support_url','support_email','support_phone','created_at','updated_at','image', 'deleted_at')
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count');
if ($request->input('deleted')=='true') {
$manufacturers->onlyTrashed();
}
if ($request->has('search')) {
if ($request->filled('search')) {
$manufacturers = $manufacturers->TextSearch($request->input('search'));
}
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($manufacturers) && ($request->get('offset') > $manufacturers->count())) ? $manufacturers->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$manufacturers->orderBy($sort, $order);
@@ -76,7 +86,7 @@ class ManufacturersController extends Controller
public function show($id)
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
return (new ManufacturersTransformer)->transformManufacturer($manufacturer);
}
@@ -92,7 +102,7 @@ class ManufacturersController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Manufacturer::class);
$this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$manufacturer->fill($request->all());
@@ -120,4 +130,39 @@ class ManufacturersController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$manufacturers = Manufacturer::select([
'id',
'name',
'image',
]);
if ($request->filled('search')) {
$manufacturers = $manufacturers->where('name', 'LIKE', '%'.$request->get('search').'%');
}
$manufacturers = $manufacturers->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($manufacturers as $manufacturer) {
$manufacturer->use_text = $manufacturer->name;
$manufacturer->use_image = ($manufacturer->image) ? url('/').'/uploads/manufacturers/'.$manufacturer->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($manufacturers);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\CheckoutRequest;
use App\Http\Controllers\Controller;
use Auth;
use App\Helpers\Helper;
class ProfileController extends Controller
{
/**
* Display a listing of requested assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.3.0]
*
* @return Array
*/
public function requestedAssets()
{
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
$results = [];
$results['total'] = $checkoutRequests->count();
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'),
];
}
}
return $results;
}
}

View File

@@ -18,43 +18,49 @@ class ReportsController extends Controller
*/
public function index(Request $request)
{
$this->authorize('reports.view');
$actionlogs = Actionlog::with('item', 'user', 'target','location');
if ($request->has('search')) {
if ($request->filled('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
}
if (($request->has('target_type')) && ($request->has('target_id'))) {
if (($request->filled('target_type')) && ($request->filled('target_id'))) {
$actionlogs = $actionlogs->where('target_id','=',$request->input('target_id'))
->where('target_type','=',"App\\Models\\".ucwords($request->input('target_type')));
}
if (($request->has('item_type')) && ($request->has('item_id'))) {
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
$actionlogs = $actionlogs->where('item_id','=',$request->input('item_id'))
->where('item_type','=',"App\\Models\\".ucwords($request->input('item_type')));
}
if ($request->has('action_type')) {
if ($request->filled('action_type')) {
$actionlogs = $actionlogs->where('action_type','=',$request->input('action_type'))->orderBy('created_at', 'desc');
}
if ($request->filled('uploads')) {
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
}
$allowed_columns = [
'id',
'created_at'
'created_at',
'target_id',
'user_id',
'action_type',
'note'
];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
$offset = request('offset', 0);
$limit = request('limit', 50);
$total = $actionlogs->count();
$actionlogs = $actionlogs->orderBy($sort, $order);
$actionlogs = $actionlogs->skip($offset)->take($limit)->get();
return (new ActionlogsTransformer)->transformActionlogs($actionlogs, $total);
$actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
}
}

View File

@@ -5,78 +5,25 @@ namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ldap;
use Validator;
use App\Models\Setting;
use Mail;
use App\Notifications\SlackTest;
use Notification;
use App\Notifications\MailTest;
class SettingsController extends Controller
{
/**
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
*
* @return \Illuminate\Http\Response
*/
public function index()
public function ldaptest()
{
//
}
if (Setting::getSettings()->ldap_enabled!='1') {
\Log::debug('LDAP is not enabled so cannot test.');
return response()->json(['message' => 'LDAP is not enabled, so we cannot test LDAP connections.'], 400);
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
public function getLdapTest()
{
\Log::debug('Preparing to test LDAP connection');
try {
@@ -85,6 +32,60 @@ class SettingsController extends Controller
\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('LDAP connected but Bind failed. Please check your LDAP settings and try again.');
return response()->json(['message' => $e->getMessage()], 400);
//return response()->json(['message' => $e->getMessage()], 500);
}
} catch (\Exception $e) {
\Log::info('LDAP connection failed but we cannot debug it any further on our end.');
return response()->json(['message' => 'The LDAP connection failed but we cannot debug it any further on our end. The error from the server is: '.$e->getMessage()], 500);
}
}
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');
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);
@@ -92,10 +93,56 @@ class SettingsController extends Controller
}
} catch (\Exception $e) {
\Log::debug('Connection failed');
return response()->json(['message' => $e->getMessage()], 600);
return response()->json(['message' => $e->getMessage()], 500);
}
}
public function slacktest()
{
if ($settings = Setting::getSettings()->slack_channel=='') {
\Log::debug('Slack is not enabled. Cannot test.');
return response()->json(['message' => 'Slack is not enabled, cannot test.'], 400);
}
\Log::debug('Preparing to test slack connection');
try {
Notification::send($settings = Setting::getSettings(), new SlackTest());
return response()->json(['message' => 'Success'], 200);
} catch (\Exception $e) {
\Log::debug('Slack connection failed');
return response()->json(['message' => $e->getMessage()], 400);
}
}
/**
* Test the email configuration
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
*/
public function ajaxTestEmail()
{
if (!config('app.lock_passwords')) {
try {
Notification::send(Setting::first(), new MailTest());
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
}
}
return response()->json(['message' => 'Mail would have been sent, but this application is in demo mode! '], 200);
}
}

View File

@@ -22,16 +22,21 @@ class StatuslabelsController extends Controller
public function index(Request $request)
{
$this->authorize('view', Statuslabel::class);
$allowed_columns = ['id','name','created_at'];
$allowed_columns = ['id','name','created_at', 'assets_count','color','default_label'];
$statuslabels = Statuslabel::withCount('assets');
$statuslabels = Statuslabel::withCount('assets as assets_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$statuslabels = $statuslabels->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($statuslabels) && ($request->get('offset') > $statuslabels->count())) ? $statuslabels->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$statuslabels->orderBy($sort, $order);
@@ -55,8 +60,8 @@ class StatuslabelsController extends Controller
$this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending','archived');
if (!$request->has('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, ["type" => ["Status label type is required."]]));
if (!$request->filled('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, ["type" => ["Status label type is required."]]),500);
}
$statuslabel = new Statuslabel;
@@ -101,12 +106,12 @@ class StatuslabelsController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Statuslabel::class);
$this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
$request->except('deployable', 'pending','archived');
if (!$request->has('type')) {
if (!$request->filled('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Status label type is required.'));
}
@@ -137,8 +142,14 @@ class StatuslabelsController extends Controller
$this->authorize('delete', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
$this->authorize('delete', $statuslabel);
$statuslabel->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/statuslabels/message.delete.success')));
// Check that there are no assets associated
if ($statuslabel->assets()->count() == 0) {
$statuslabel->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/statuslabels/message.delete.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/statuslabels/message.assoc_assets')));
}
@@ -154,23 +165,25 @@ class StatuslabelsController extends Controller
public function getAssetCountByStatuslabel()
{
$this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::with('assets')->groupBy('id')->withCount('assets as assets_count')->get();
$statusLabels = Statuslabel::with('assets')->get();
$labels=[];
$points=[];
$colors=[];
foreach ($statusLabels as $statusLabel) {
if ($statusLabel->assets()->count() > 0) {
$labels[]=$statusLabel->name;
$points[]=$statusLabel->assets()->whereNull('assigned_to')->count();
if ($statusLabel->color!='') {
$colors[]=$statusLabel->color;
foreach ($statuslabels as $statuslabel) {
if ($statuslabel->assets_count > 0) {
$labels[]=$statuslabel->name. ' ('.number_format($statuslabel->assets_count).')';
$points[]=$statuslabel->assets_count;
if ($statuslabel->color!='') {
$colors[]=$statuslabel->color;
}
}
}
$labels[]='Deployed';
$points[]=Asset::whereNotNull('assigned_to')->count();
$colors_array = array_merge($colors, Helper::chartColors());
$result= [
@@ -230,6 +243,8 @@ 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

@@ -7,6 +7,8 @@ use App\Http\Controllers\Controller;
use App\Helpers\Helper;
use App\Models\Supplier;
use App\Http\Transformers\SuppliersTransformer;
use App\Http\Transformers\SelectlistTransformer;
class SuppliersController extends Controller
{
@@ -20,19 +22,24 @@ class SuppliersController extends Controller
public function index(Request $request)
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['id','name','address','phone','contact','fax','email'];
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count'];
$suppliers = Supplier::select(
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at')
)->withCount('assets')->withCount('licenses')->whereNull('deleted_at');
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes')
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count');
if ($request->has('search')) {
if ($request->filled('search')) {
$suppliers = $suppliers->TextSearch($request->input('search'));
}
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($suppliers) && ($request->get('offset') > $suppliers->count())) ? $suppliers->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$suppliers->orderBy($sort, $order);
@@ -91,7 +98,7 @@ class SuppliersController extends Controller
*/
public function update(Request $request, $id)
{
$this->authorize('edit', Supplier::class);
$this->authorize('update', Supplier::class);
$supplier = Supplier::findOrFail($id);
$supplier->fill($request->all());
@@ -113,10 +120,60 @@ class SuppliersController extends Controller
public function destroy($id)
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::findOrFail($id);
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count','assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
$this->authorize('delete', $supplier);
if ($supplier->assets_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
}
if ($supplier->asset_maintenances_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count])));
}
if ($supplier->licenses_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count])));
}
$supplier->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$suppliers = Supplier::select([
'id',
'name',
'image',
]);
if ($request->filled('search')) {
$suppliers = $suppliers->where('suppliers.name', 'LIKE', '%'.$request->get('search').'%');
}
$suppliers = $suppliers->orderBy('name', 'ASC')->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway
foreach ($suppliers as $supplier) {
$supplier->use_text = $supplier->name;
$supplier->use_image = ($supplier->image) ? url('/').'/uploads/suppliers/'.$supplier->image : null;
}
return (new SelectlistTransformer)->transformSelectlist($suppliers);
}
}

View File

@@ -10,6 +10,11 @@ use App\Models\User;
use App\Helpers\Helper;
use App\Http\Requests\SaveUserRequest;
use App\Models\Asset;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\LicensesTransformer;
use Auth;
class UsersController extends Controller
{
@@ -26,53 +31,80 @@ class UsersController extends Controller
$this->authorize('view', User::class);
$users = User::select([
'users.id',
'users.employee_num',
'users.two_factor_enrolled',
'users.jobtitle',
'users.email',
'users.username',
'users.location_id',
'users.manager_id',
'users.first_name',
'users.last_name',
'users.created_at',
'users.notes',
'users.activated',
'users.address',
'users.avatar',
'users.city',
'users.company_id',
'users.last_login',
'users.country',
'users.created_at',
'users.deleted_at',
'users.department_id',
'users.activated'
])->with('manager', 'groups', 'userloc', 'company', 'department','throttle','assets','licenses','accessories','consumables')
->withCount('assets','licenses','accessories','consumables');
'users.email',
'users.employee_num',
'users.first_name',
'users.id',
'users.jobtitle',
'users.last_login',
'users.last_name',
'users.location_id',
'users.manager_id',
'users.notes',
'users.permissions',
'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');
$users = Company::scopeCompanyables($users);
if ($request->has('search')) {
$users = $users->TextSearch($request->input('search'));
}
if (($request->has('deleted')) && ($request->input('deleted')=='true')) {
if (($request->filled('deleted')) && ($request->input('deleted')=='true')) {
$users = $users->GetDeleted();
}
if ($request->has('company_id')) {
$users = $users->where('company_id', '=', $request->input('company_id'));
if ($request->filled('company_id')) {
$users = $users->where('users.company_id', '=', $request->input('company_id'));
}
if ($request->has('location_id')) {
$users = $users->where('location_id', '=', $request->input('location_id'));
if ($request->filled('location_id')) {
$users = $users->where('users.location_id', '=', $request->input('location_id'));
}
if ($request->has('department_id')) {
$users = $users->where('department_id','=',$request->input('department_id'));
if ($request->filled('email')) {
$users = $users->where('users.email', '=', $request->input('email'));
}
if ($request->filled('username')) {
$users = $users->where('users.username', '=', $request->input('username'));
}
if ($request->filled('group_id')) {
$users = $users->ByGroup($request->get('group_id'));
}
if ($request->filled('department_id')) {
$users = $users->where('users.department_id','=',$request->input('department_id'));
}
if ($request->filled('search')) {
$users = $users->TextSearch($request->input('search'));
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$offset = request('offset', 0);
$limit = request('limit', 50);
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items.
$offset = (($users) && ($request->get('offset') > $users->count())) ? $users->count() : $request->get('offset', 0);
// Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
switch ($request->input('sort')) {
case 'manager':
@@ -84,24 +116,91 @@ class UsersController extends Controller
case 'department':
$users = $users->OrderDepartment($order);
break;
case 'company':
$users = $users->OrderCompany($order);
break;
default:
$allowed_columns =
[
'last_name','first_name','email','jobtitle','username','employee_num',
'assets','accessories', 'consumables','licenses','groups','activated','created_at',
'two_factor_enrolled','two_factor_optin','last_login'
'two_factor_enrolled','two_factor_optin','last_login', 'assets_count', 'licenses_count',
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state',
'country', 'zip', 'id'
];
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
$users = $users->orderBy($sort, $order);
break;
}
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
return (new UsersTransformer)->transformUsers($users, $total);
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$users = User::select(
[
'users.id',
'users.username',
'users.employee_num',
'users.first_name',
'users.last_name',
'users.gravatar',
'users.avatar',
'users.email',
]
)->where('show_in_list', '=', '1');
$users = Company::scopeCompanyables($users);
if ($request->filled('search')) {
$users = $users->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
}
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
$users = $users->paginate(50);
foreach ($users as $user) {
$name_str = '';
if ($user->last_name!='') {
$name_str .= e($user->last_name).', ';
}
$name_str .= e($user->first_name);
if ($user->username!='') {
$name_str .= ' ('.e($user->username).')';
}
if ($user->employee_num!='') {
$name_str .= ' - #'.e($user->employee_num);
}
$user->use_text = $name_str;
$user->use_image = ($user->present()->gravatar) ? $user->present()->gravatar : null;
}
return (new SelectlistTransformer)->transformSelectlist($users);
}
/**
* Store a newly created resource in storage.
*
@@ -112,13 +211,33 @@ class UsersController extends Controller
*/
public function store(SaveUserRequest $request)
{
$this->authorize('view', User::class);
$this->authorize('create', User::class);
$user = new User;
$user->fill($request->all());
$user->password = bcrypt($request->input('password'));
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
if (!Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']);
}
$user->permissions = $permissions_array;
}
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$user->password = bcrypt($request->get('password', $tmp_pass));
if ($user->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.create.success')));
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.create')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
@@ -133,7 +252,7 @@ class UsersController extends Controller
public function show($id)
{
$this->authorize('view', User::class);
$user = User::findOrFail($id);
$user = User::withCount('assets as assets_count','licenses as licenses_count','accessories as accessories_count','consumables as consumables_count')->findOrFail($id);
return (new UsersTransformer)->transformUser($user);
}
@@ -149,16 +268,57 @@ class UsersController extends Controller
*/
public function update(SaveUserRequest $request, $id)
{
$this->authorize('edit', User::class);
$this->authorize('update', User::class);
$user = User::findOrFail($id);
$user->fill($request->all());
if ($request->has('password')) {
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
}
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
// We need to use has() instead of filled()
// here because we need to overwrite permissions
// if someone needs to null them out
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
if (!Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']);
}
$user->permissions = $permissions_array;
}
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
if ($user->save()) {
// Sync group memberships:
// This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5
// which changes the behavior of has vs filled.
// The $request->has method will now return true even if the input value is an empty string or null.
// A new $request->filled method has was added that provides the previous behavior of the has method.
// Check if the request has groups passed and has a value
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
// The groups field has been passed but it is null, so we should blank it out
} elseif ($request->has('groups')) {
$user->groups()->sync(array());
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
@@ -180,10 +340,23 @@ class UsersController extends Controller
$this->authorize('delete', $user);
if ($user->assets()->count() > 0) {
if (($user->assets) && ($user->assets->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete_has_assets')));
}
if (($user->licenses) && ($user->licenses->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->licenses->count() . ' license(s) associated with them and cannot be deleted.'));
}
if (($user->accessories) && ($user->accessories->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->accessories->count() . ' accessories associated with them.'));
}
if (($user->managedLocations()) && ($user->managedLocations()->count() > 0)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'This user still has ' . $user->managedLocations()->count() . ' locations that they manage.'));
}
if ($user->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
}
@@ -201,7 +374,83 @@ class UsersController extends Controller
public function assets($id)
{
$this->authorize('view', User::class);
$assets = Asset::where('assigned_to', '=', $id)->with('model')->get();
return response()->json($assets);
$this->authorize('view', Asset::class);
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
/**
* Return JSON containing a list of accessories assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
* @param $userId
* @return string JSON
*/
public function accessories($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());
}
/**
* Return JSON containing a list of licenses assigned to a user.
*
* @author [N. Mathar] [<snipe@snipe.net>]
* @since [v5.0]
* @param $userId
* @return string JSON
*/
public function licenses($id)
{
$this->authorize('view', User::class);
$this->authorize('view', License::class);
$user = User::where('id', $id)->withTrashed()->first();
$licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
/**
* Reset the user's two-factor status
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @param $userId
* @return string JSON
*/
public function postTwoFactorReset(Request $request)
{
$this->authorize('update', User::class);
if ($request->filled('id')) {
try {
$user = User::find($request->get('id'));
$user->two_factor_secret = null;
$user->two_factor_enrolled = 0;
$user->save();
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
} catch (\Exception $e) {
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_error')], 500);
}
}
return response()->json(['message' => 'No ID provided'], 500);
}
/**
* Get info on the current user.
*
* @author [Juan Font] [<juanfontalonso@gmail.com>]
* @since [v4.4.2]
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function getCurrentUserInfo(Request $request)
{
return (new UsersTransformer)->transformUser($request->user());
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
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
*/
public function create($assetId, $backto = null)
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkin', $asset);
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto);
}
/**
* Validate and process the form data to check an asset back into inventory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetCheckinRequest $request
* @param int $assetId
* @param null $backto
* @return Redirect
* @since [v1.0]
*/
public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkin', $asset);
if ($asset->assignedType() == Asset::USER) {
$user = $asset->assignedTo;
}
if (is_null($target = $asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
$asset->accepted = null;
$asset->name = $request->get('name');
if ($request->filled('status_id')) {
$asset->status_id = e($request->get('status_id'));
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
$asset->location_id = e($request->get('location_id'));
}
// Was the asset updated?
if ($asset->save()) {
$logaction = $asset->logCheckin($target, e(request('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')) {
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'));
}
// Redirect to the asset management page with error
return redirect()->route("hardware.index")->with('error', trans('admin/hardware/message.checkin.error'));
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AssetCheckoutController extends Controller
{
use CheckInOutRequest;
/**
* Returns a view that presents a form to check an asset out to a
* user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v1.0]
* @return View
*/
public function create($assetId)
{
// Check if the asset exists
if (is_null($asset = Asset::find(e($assetId)))) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkout', $asset);
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'));
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
}
/**
* Validate and process the form data to check out an asset to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetCheckoutRequest $request
* @param int $assetId
* @return Redirect
* @since [v1.0]
*/
public function store(AssetCheckoutRequest $request, $assetId)
{
try {
// Check if the asset exists
if (!$asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
} elseif (!$asset->availableForCheckout()) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
}
$this->authorize('checkout', $asset);
$admin = Auth::user();
$target = $this->determineCheckoutTarget($asset);
if ($asset->is($target)) {
throw new CheckoutNotAllowed('You cannot check an asset out to itself.');
}
$asset = $this->updateAssetLocation($asset, $target);
$checkout_at = date("Y-m-d H:i:s");
if (($request->filled('checkout_at')) && ($request->get('checkout_at')!= date("Y-m-d"))) {
$checkout_at = $request->get('checkout_at');
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = $request->get('expected_checkin');
}
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
} catch (ModelNotFoundException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
} catch (CheckoutNotAllowed $e) {
return redirect()->back()->with('error', $e->getMessage());
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetFileRequest $request
* @param int $assetId
* @return Redirect
* @since [v1.0]
*/
public function store(AssetFileRequest $request, $assetId = null)
{
if (!$asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('update', $asset);
$destinationPath = config('app.private_uploads').'/assets';
if ($request->hasFile('file')) {
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')));
}
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
*/
public function show($assetId = null, $fileId = null, $download = true)
{
$asset = Asset::find($assetId);
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
if (!$log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = $log->get_src('assets');
if ($log->action_type =='audit') {
$file = $log->get_src('audits');
}
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', mime_content_type($file));
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Response::download($file);
}
// Prepare the error message
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', $error);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
* @return View
*/
public function destroy($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
$this->authorize('update', $asset);
$destinationPath = config('app.private_uploads').'/imports/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'));
}
return redirect()->back()->with('error', 'Could not find matching upload log.');
}
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
}

View File

@@ -63,85 +63,6 @@ class AssetMaintenancesController extends Controller
}
/**
* Generates the JSON response for asset maintenances listing view.
*
* @see AssetMaintenancesController::getIndex() method that generates view
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return String JSON
*/
public function getDatatable(Request $request)
{
$maintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company', 'admin');
if (Input::has('search')) {
$maintenances = $maintenances->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','title','asset_maintenance_time','asset_maintenance_type','cost','start_date','completion_date','notes','user_id'];
$order = Input::get('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array(Input::get('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
case 'user_id':
$maintenances = $maintenances->OrderAdmin($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
}
$maintenancesCount = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get();
$rows = array();
$settings = Setting::getSettings();
foreach ($maintenances as $maintenance) {
$actions = '';
if (Gate::allows('update', Asset::class)) {
$actions .= Helper::generateDatatableButton('edit', route('maintenances.edit', $maintenance->id));
$actions .= Helper::generateDatatableButton(
'delete',
route('maintenances.destroy', $maintenance->id),
$enabled = true,
trans('admin/asset_maintenances/message.delete.confirm'),
$maintenance->title
);
}
if (($maintenance->cost) && (isset($maintenance->asset)) && ($maintenance->asset->assetloc) && ($maintenance->asset->assetloc->currency!='')) {
$maintenance_cost = $maintenance->asset->assetloc->currency.$maintenance->cost;
} else {
$maintenance_cost = $settings->default_currency.$maintenance->cost;
}
$rows[] = array(
'id' => $maintenance->id,
'asset_name' => ($maintenance->asset) ? (string)link_to_route('maintenances.show', $maintenance->asset->present()->Name(), ['maintenance' => $maintenance->asset->id]) : 'Deleted Asset' ,
'title' => $maintenance->title,
'notes' => $maintenance->notes,
'supplier' => ($maintenance->supplier) ? (string)link_to_route('suppliers.show', $maintenance->supplier->name, ['maintenance'=>$maintenance->supplier->id]) : 'Deleted Supplier',
'cost' => $maintenance_cost,
'asset_maintenance_type' => e($maintenance->asset_maintenance_type),
'start_date' => $maintenance->start_date,
'asset_maintenance_time' => $maintenance->asset_maintenance_time,
'completion_date' => $maintenance->completion_date,
'user_id' => ($maintenance->admin) ? (string)link_to_route('users.show', $maintenance->admin->present()->fullName(), ['user'=>$maintenance->admin->id]) : '',
'actions' => $actions,
'company' => ($maintenance->asset->company) ? $maintenance->asset->company->name : ''
);
}
$data = array('total' => $maintenancesCount, 'rows' => $rows);
return $data;
}
/**
* Returns a form view to create a new asset maintenance.
@@ -154,16 +75,21 @@ class AssetMaintenancesController extends Controller
*/
public function create()
{
$asset = null;
if ($asset = Asset::find(request('asset_id'))) {
// We have to set this so that the correct property is set in the select2 ajax dropdown
$asset->asset_id = $asset->id;
}
// Prepare Asset Maintenance Type List
$assetMaintenanceType = [
'' => 'Select an asset maintenance type',
] + AssetMaintenance::getImprovementOptions();
// Mark the selected asset, if it came in
// Render the view
return view('asset_maintenances/edit')
->with('asset_list', Helper::detailedAssetList())
->with('selectedAsset', request('asset_id'))
->with('supplier_list', Helper::suppliersList())
->with('asset', $asset)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', new AssetMaintenance);
}
@@ -187,7 +113,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(e($request->input('asset_id')));
if (!Company::isCurrentUserHasAccess($asset)) {
if ((!Company::isCurrentUserHasAccess($asset)) && ($asset!=null)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -236,6 +162,9 @@ class AssetMaintenancesController extends Controller
// Redirect to the improvement management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} 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();
}
@@ -260,9 +189,7 @@ class AssetMaintenancesController extends Controller
// Get Supplier List
// Render the view
return view('asset_maintenances/edit')
->with('asset_list', Helper::detailedAssetList())
->with('selectedAsset', null)
->with('supplier_list', Helper::suppliersList())
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', $assetMaintenance);

View File

@@ -17,6 +17,7 @@ use App\Models\Company;
use Config;
use App\Helpers\Helper;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -34,12 +35,12 @@ class AssetModelsController extends Controller
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AssetModelsController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return View
*/
public function index()
{
$this->authorize('index', AssetModel::class);
return view('models/index');
}
@@ -52,11 +53,10 @@ class AssetModelsController extends Controller
*/
public function create()
{
// Show the page
return view('models/edit')
->with('category_list', Helper::categoryList('asset'))
$this->authorize('create', AssetModel::class);
$category_type = 'asset';
return view('models/edit')->with('category_type',$category_type)
->with('depreciation_list', Helper::depreciationList())
->with('manufacturer_list', Helper::manufacturerList())
->with('item', new AssetModel);
}
@@ -68,9 +68,10 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @return Redirect
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize('create', AssetModel::class);
// Create a new asset model
$model = new AssetModel;
@@ -89,64 +90,20 @@ class AssetModelsController extends Controller
$model->fieldset_id = e($request->input('custom_fieldset'));
}
if (Input::file('image')) {
$image = Input::file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/models/'.$file_name);
Image::make($image->getRealPath())->resize(500, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$model->image = $file_name;
}
$model = $request->handleImages($model,600, public_path().'/uploads/models');
// Was it created?
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
}
// Redirect to the new model page
return redirect()->route("models.index")->with('success', trans('admin/models/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($model->getErrors());
}
/**
* Validates and stores new Asset Model data created from the
* modal form on the Asset Creation view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @param Request $request
* @return String JSON
*/
public function apiStore(Request $request)
{
//COPYPASTA!!!! FIXME
$model = new AssetModel;
$settings=Input::all();
$settings['eol']= null;
$model->name=$request->input('name');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->model_number = $request->input('model_number');
$model->user_id = Auth::id();
$model->notes = $request->input('notes');
$model->eol= null;
if ($request->input('fieldset_id')=='') {
$model->fieldset_id = null;
} else {
$model->fieldset_id = e($request->input('fieldset_id'));
}
if ($model->save()) {
return JsonResponse::create($model);
} else {
return JsonResponse::create(["error" => "Failed validation: ".print_r($model->getErrors()->all('<li>:message</li>'), true)], 500);
}
}
/**
* Returns a view containing the asset model edit form.
*
@@ -157,17 +114,16 @@ class AssetModelsController extends Controller
*/
public function edit($modelId = null)
{
// Check if the model exists
if (is_null($item = AssetModel::find($modelId))) {
// Redirect to the model management page
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
$this->authorize('update', AssetModel::class);
if ($item = AssetModel::find($modelId)) {
$category_type = 'asset';
$view = View::make('models/edit', compact('item','category_type'));
$view->with('depreciation_list', Helper::depreciationList());
return $view;
}
$view = View::make('models/edit', compact('item'));
$view->with('category_list', Helper::categoryList('asset'));
$view->with('depreciation_list', Helper::depreciationList());
$view->with('manufacturer_list', Helper::manufacturerList());
return $view;
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
@@ -180,44 +136,37 @@ class AssetModelsController extends Controller
* @param int $modelId
* @return Redirect
*/
public function update(Request $request, $modelId = null)
public function update(ImageUploadRequest $request, $modelId = null)
{
$this->authorize('update', AssetModel::class);
// Check if the model exists
if (is_null($model = AssetModel::find($modelId))) {
// Redirect to the models management page
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
$model->depreciation_id = $request->input('depreciation_id');
$model->eol = $request->input('eol');
$model->depreciation_id = $request->input('depreciation_id');
$model->eol = $request->input('eol');
$model->name = $request->input('name');
$model->model_number = $request->input('model_number');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0');
$model->requestable = Input::has('requestable');
$this->removeCustomFieldsDefaultValues($model);
if ($request->input('custom_fieldset')=='') {
$model->fieldset_id = null;
} else {
$model->fieldset_id = $request->input('custom_fieldset');
if ($this->shouldAddDefaultValues($request->input())) {
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
}
}
if (Input::file('image')) {
$image = Input::file('image');
$file_name = str_random(25).".".$image->getClientOriginalExtension();
$path = public_path('uploads/models/'.$file_name);
Image::make($image->getRealPath())->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path);
$model->image = $file_name;
}
if ($request->input('image_delete') == 1 && Input::file('image') == "") {
$model->image = null;
}
$model = $request->handleImages($model,600, public_path().'/uploads/models');
if ($model->save()) {
return redirect()->route("models.index")->with('success', trans('admin/models/message.update.success'));
@@ -236,6 +185,7 @@ class AssetModelsController extends Controller
*/
public function destroy($modelId)
{
$this->authorize('delete', AssetModel::class);
// Check if the model exists
if (is_null($model = AssetModel::find($modelId))) {
return redirect()->route('models.index')->with('error', trans('admin/models/message.not_found'));
@@ -245,6 +195,15 @@ class AssetModelsController extends Controller
// Throw an error that this model is associated with assets
return redirect()->route('models.index')->with('error', trans('admin/models/message.assoc_users'));
}
if ($model->image) {
try {
unlink(public_path().'/uploads/models/'.$model->image);
} catch (\Exception $e) {
\Log::info($e);
}
}
// Delete the model
$model->delete();
@@ -263,7 +222,7 @@ class AssetModelsController extends Controller
*/
public function getRestore($modelId = null)
{
$this->authorize('create', AssetModel::class);
// Get user information
$model = AssetModel::withTrashed()->find($modelId);
@@ -294,16 +253,14 @@ class AssetModelsController extends Controller
*/
public function show($modelId = null)
{
$this->authorize('view', AssetModel::class);
$model = AssetModel::withTrashed()->find($modelId);
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'));
}
/**
@@ -326,9 +283,7 @@ class AssetModelsController extends Controller
// Show the page
$view = View::make('models/edit');
$view->with('category_list', Helper::categoryList('asset'));
$view->with('depreciation_list', Helper::depreciationList());
$view->with('manufacturer_list', Helper::manufacturerList());
$view->with('item', $model);
$view->with('clone_model', $model_to_clone);
return $view;
@@ -352,49 +307,6 @@ class AssetModelsController extends Controller
/**
* Get the asset information to present to the model view detail page
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @param Request $request
* @param $modelID
* @return String JSON
* @internal param int $modelId
*/
public function getDataView(Request $request, $modelID)
{
$assets = Asset::where('model_id', '=', $modelID)->with('company', 'assetstatus');
if (Input::has('search')) {
$assets = $assets->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['name', 'serial','asset_tag'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$assets = $assets->orderBy($sort, $order);
$assetsCount = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
$rows = array();
$all_custom_fields = CustomField::all();
foreach ($assets as $asset) {
$rows[] = $asset->present()->forDataTable($all_custom_fields);
}
$data = array('total' => $assetsCount, 'rows' => $rows);
return $data;
}
/**
* Returns a view that allows the user to bulk edit model attrbutes
@@ -405,20 +317,41 @@ class AssetModelsController extends Controller
*/
public function postBulkEdit(Request $request)
{
$models_raw_array = Input::get('ids');
$models = AssetModel::whereIn('id', $models_raw_array)->get();
$nochange = ['NC' => 'No Change'];
$fieldset_list = $nochange + Helper::customFieldsetList();
$depreciation_list = $nochange + Helper::depreciationList();
$category_list = $nochange + Helper::categoryList('asset');
$manufacturer_list = $nochange + Helper::manufacturerList();
return view('models/bulk-edit', compact('models'))
->with('manufacturer_list', $manufacturer_list)
->with('category_list', $category_list)
->with('fieldset_list', $fieldset_list)
->with('depreciation_list', $depreciation_list);
$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.');
}
@@ -437,10 +370,11 @@ class AssetModelsController extends Controller
$models_raw_array = Input::get('ids');
$update_array = array();
if (($request->has('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id')!='NC'))) {
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
}
if (($request->has('category_id') && ($request->input('category_id')!='NC'))) {
if (($request->filled('category_id') && ($request->input('category_id')!='NC'))) {
$update_array['category_id'] = $request->input('category_id');
}
if ($request->input('fieldset_id')!='NC') {
@@ -463,4 +397,91 @@ class AssetModelsController extends Controller
}
/**
* 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.
*
* @param array $input
* @return boolean
*/
private function shouldAddDefaultValues(array $input)
{
return !empty($input['add_default_values'])
&& !empty($input['default_values'])
&& !empty($input['custom_fieldset']);
}
/**
* Adds default values to a model (as long as they are truthy)
*
* @param AssetModel $model
* @param array $defaultValues
* @return void
*/
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
{
foreach ($defaultValues as $customFieldId => $defaultValue) {
if ($defaultValue) {
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
}
}
}
/**
* Removes all default values
*
* @return void
*/
private function removeCustomFieldsDefaultValues(AssetModel $model)
{
$model->defaultValues()->detach();
}
}

File diff suppressed because it is too large Load Diff

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 Illuminate\Support\Facades\Validator;
class ForgotPasswordController extends Controller
{
@@ -41,6 +42,8 @@ class ForgotPasswordController extends Controller
return property_exists($this, 'subject') ? $this->subject : \Lang::get('mail.reset_link');
}
/**
* Send a reset link to the given user.
*
@@ -49,22 +52,51 @@ class ForgotPasswordController extends Controller
*/
public function sendResetLinkEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
// 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.
/**
* Let's set a max character count here to prevent potential
* buffer overflow issues with attackers sending very large
* payloads through.
*/
$this->validate($request, ['email' => 'required|email|max:250']);
/**
* If we find a matching email with an activated user, we will
* send the password reset link to the 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.
*/
$response = $this->broker()->sendResetLink(
$request->only('email')
array_merge(
$request->only('email'),
['activated' => '1']
)
);
if ($response === \Password::RESET_LINK_SENT) {
return redirect()->route('login')->with('status', trans($response));
}
// 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.
/**
* 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.
*
* HOWEVER, we do not want to translate the message if the user isn't found
* or isn't active, since that would allow an attacker to walk through
* a dictionary attack and figure out registered user email addresses.
*
* Instead we tell the user we've sent an email even though we haven't.
* It's bad UX, but better security. The compromises we sometimes have to make.
*/
if ($response == 'passwords.user') {
\Log::debug('User with email '.$request->input('email').' attempted a password reset request but was not found. No email was sent.');
return redirect()->route('login')->with('success', trans('passwords.user_inactive'));
}
return back()->withErrors(
['email' => trans($response)]
);

View File

@@ -50,47 +50,72 @@ class LoginController extends Controller
\Session::put('backUrl', \URL::previous());
}
function showLoginForm()
function showLoginForm(Request $request)
{
$this->loginViaRemoteUser($request);
if (Auth::check()) {
return redirect()->intended('dashboard');
}
if (Setting::getSettings()->login_common_disabled == "1") {
return view('errors.403');
}
return view('auth.login');
}
private function login_via_ldap(Request $request)
private function loginViaRemoteUser(Request $request)
{
LOG::debug("Binding user to LDAP.");
$remote_user = $request->server('REMOTE_USER');
if (Setting::getSettings()->login_remote_user_enabled == "1" && isset($remote_user) && !empty($remote_user)) {
Log::debug("Authenticatiing via REMOTE_USER.");
$pos = strpos($remote_user, '\\');
if ($pos > 0) {
$remote_user = substr($remote_user, $pos + 1);
};
try {
$user = User::where('username', '=', $remote_user)->whereNull('deleted_at')->where('activated', '=', '1')->first();
Log::debug("Remote user auth lookup complete");
if(!is_null($user)) Auth::login($user, true);
} catch(Exception $e) {
Log::debug("There was an error authenticating the Remote user: " . $e->getMessage());
}
}
}
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");
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");
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)->first();
LOG::debug("Local auth lookup complete");
$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'));
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.");
Log::debug("Local user created.");
} else {
LOG::debug("Could not create local user.");
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.");
Log::debug("Local user ".$request->input('username')." exists in database. Updating existing user against LDAP.");
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
@@ -114,6 +139,10 @@ class LoginController extends Controller
*/
public function login(Request $request)
{
if (Setting::getSettings()->login_common_disabled == "1") {
return view('errors.403');
}
$validator = $this->validator(Input::all());
if ($validator->fails()) {
@@ -132,29 +161,29 @@ class LoginController extends Controller
// Should we even check for LDAP users?
if (Setting::getSettings()->ldap_enabled=='1') {
LOG::debug("LDAP is enabled.");
Log::debug("LDAP is enabled.");
try {
$user = $this->login_via_ldap($request);
$user = $this->loginViaLdap($request);
Auth::login($user, true);
// If the user was unable to login via LDAP, log the error and let them fall through to
// local authentication.
} catch (\Exception $e) {
LOG::error("There was an error authenticating the LDAP user: ".$e->getMessage());
Log::debug("There was an error authenticating the LDAP user: ".$e->getMessage());
}
}
// If the user wasn't authenticated via LDAP, skip to local auth
if (!$user) {
LOG::debug("Authenticating user against database.");
Log::debug("Authenticating user against database.");
// Try to log the user in
if (!Auth::attempt(Input::only('username', 'password'), Input::get('remember-me', 0))) {
if (!Auth::attempt(['username' => $request->input('username'), 'password' => $request->input('password'), 'activated' => 1], $request->input('remember'))) {
if (!$lockedOut) {
$this->incrementLoginAttempts($request);
}
LOG::debug("Local authentication failed.");
Log::debug("Local authentication failed.");
return redirect()->back()->withInput()->with('error', trans('auth/message.account_not_found'));
} else {
@@ -164,7 +193,6 @@ class LoginController extends Controller
if ($user = Auth::user()) {
$user->last_login = \Carbon::now();
\Log::debug('Last login:'.$user->last_login);
$user->save();
}
// Redirect to the users page
@@ -180,26 +208,33 @@ class LoginController extends Controller
public function getTwoFactorEnroll()
{
// Make sure the user is logged in
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'You must be logged in.');
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
$settings = Setting::getSettings();
$user = Auth::user();
$google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA');
if ($user->two_factor_secret=='') {
$user->two_factor_secret = $google2fa->generateSecretKey(32);
$user->save();
// 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'));
}
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$user->two_factor_secret = $secret;
$user->save();
$google2fa_url = $google2fa->getQRCodeGoogleUrl(
urlencode(Setting::getSettings()->site_name),
urlencode($user->username),
$user->two_factor_secret
);
return view('auth.two_factor_enroll')->with('google2fa_url', $google2fa_url);
$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);
}
@@ -211,6 +246,20 @@ 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');
}
@@ -223,22 +272,25 @@ class LoginController extends Controller
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'You must be logged in.');
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'));
}
$user = Auth::user();
$secret = $request->get('two_factor_secret');
$google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA');
$valid = $google2fa->verifyKey($user->two_factor_secret, $secret);
$google2fa = new Google2FA();
$secret = $request->input('two_factor_secret');
if ($valid) {
if ($google2fa->verifyKey($user->two_factor_secret, $secret)) {
$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', 'Invalid two-factor code');
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
}
@@ -251,9 +303,17 @@ class LoginController extends Controller
*/
public function logout(Request $request)
{
$request->session()->forget('2fa_authed');
$request->session()->regenerate(true);
Auth::logout();
return redirect()->route('login')->with('success', 'You have successfully logged out!');
$settings = Setting::getSettings();
$customLogoutUrl = $settings->login_remote_user_custom_logout_url ;
if ($settings->login_remote_user_enabled == '1' && $customLogoutUrl != '') {
return redirect()->away($customLogoutUrl);
}
return redirect()->route('login')->with('success', trans('auth/message.logout.success'));
}
@@ -278,11 +338,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(
@@ -293,18 +353,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 \Illuminate\Http\RedirectResponse
*/
* 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

@@ -12,4 +12,12 @@ class RegisterController extends Controller
{
$this->middleware('guest');
}
public function showRegistrationForm() {
abort(404,'Page not found');
}
public function register() {
abort(404,'Page not found');
}
}

View File

@@ -4,6 +4,8 @@ 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
{
@@ -36,4 +38,8 @@ class ResetPasswordController extends Controller
{
$this->middleware('guest');
}
}

View File

@@ -0,0 +1,249 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class BulkAssetsController extends Controller
{
use CheckInOutRequest;
/**
* Display the bulk edit page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @internal param int $assetId
* @since [v2.0]
*/
public function edit(Request $request)
{
$this->authorize('update', Asset::class);
if (!$request->filled('ids')) {
return redirect()->back()->with('error', 'No assets selected');
}
$asset_ids = array_keys($request->input('ids'));
if ($request->filled('bulk_actions')) {
switch($request->input('bulk_actions')) {
case 'labels':
return view('hardware/labels')
->with('assets', Asset::find($asset_ids))
->with('settings', Setting::getSettings())
->with('count', 0);
case 'delete':
$assets = Asset::with('assignedTo', 'location')->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-delete')->with('assets', $assets);
case 'edit':
return view('hardware/bulk')
->with('assets', request('ids'))
->with('statuslabel_list', Helper::statusLabelList());
}
}
return redirect()->back()->with('error', 'No action selected');
}
/**
* Save bulk edits
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return Redirect
* @internal param array $assets
* @since [v2.0]
*/
public function update(Request $request)
{
$this->authorize('update', Asset::class);
\Log::debug($request->input('ids'));
if(!$request->filled('ids') || count($request->input('ids')) <= 0) {
return redirect()->route("hardware.index")->with('warning', trans('No assets selected, so nothing was updated.'));
}
$assets = array_keys($request->input('ids'));
if (($request->filled('purchase_date'))
|| ($request->filled('purchase_cost'))
|| ($request->filled('supplier_id'))
|| ($request->filled('order_number'))
|| ($request->filled('warranty_months'))
|| ($request->filled('rtd_location_id'))
|| ($request->filled('requestable'))
|| ($request->filled('company_id'))
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
) {
foreach ($assets as $assetId) {
$this->update_array = [];
$this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('model_id')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months');
if ($request->filled('purchase_cost')) {
$this->update_array['purchase_cost'] = Helper::ParseFloat($request->input('purchase_cost'));
}
if ($request->filled('company_id')) {
$this->update_array['company_id'] = $request->input('company_id');
if ($request->input('company_id')=="clear") {
$this->update_array['company_id'] = null;
}
}
if ($request->filled('rtd_location_id')) {
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
$this->update_array['location_id'] = $request->input('rtd_location_id');
}
}
DB::table('assets')
->where('id', $assetId)
->update($this->update_array);
} // endforeach
return redirect()->route("hardware.index")->with('success', trans('admin/hardware/message.update.success'));
// no values given, nothing to update
}
return redirect()->route("hardware.index")->with('warning', trans('admin/hardware/message.update.nothing_updated'));
}
/**
* 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 this Model for Chaining
*/
protected function conditionallyAddItem($field)
{
if(request()->filled($field)) {
$this->update_array[$field] = request()->input($field);
}
return $this;
}
/**
* Save bulk deleted.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @internal param array $assets
* @since [v2.0]
*/
public function destroy(Request $request)
{
$this->authorize('delete', Asset::class);
if ($request->filled('ids')) {
$assets = Asset::find($request->get('ids'));
foreach ($assets as $asset) {
$update_array['deleted_at'] = date('Y-m-d H:i:s');
$update_array['assigned_to'] = null;
DB::table('assets')
->where('id', $asset->id)
->update($update_array);
} // endforeach
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.delete.success'));
// no values given, nothing to update
}
return redirect()->to("hardware")->with('info', trans('admin/hardware/message.delete.nothing_updated'));
}
/**
* Show Bulk Checkout Page
* @return View View to checkout multiple assets
*/
public function showCheckout()
{
$this->authorize('checkout', Asset::class);
// Filter out assets that are not deployable.
return view('hardware/bulk-checkout');
}
/**
* Process Multiple Checkout Request
* @return View
*/
public function storeCheckout(Request $request)
{
try {
$admin = Auth::user();
$target = $this->determineCheckoutTarget();
if (!is_array($request->get('selected_assets'))) {
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
}
$asset_ids = array_filter($request->get('selected_assets'));
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id && request('checkout_to_type') =='asset') {
return redirect()->back()->with('error', 'You cannot check an asset out to itself.');
}
}
$checkout_at = date("Y-m-d H:i:s");
if (($request->filled('checkout_at')) && ($request->get('checkout_at')!= date("Y-m-d"))) {
$checkout_at = e($request->get('checkout_at'));
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = e($request->get('expected_checkin'));
}
$errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) {
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), null);
if ($target->location_id!='') {
$asset->location_id = $target->location_id;
$asset->unsetEventDispatcher();
$asset->save();
}
if ($error) {
array_merge_recursive($errors, $asset->getErrors()->toArray());
}
}
});
if (!$errors) {
// Redirect to the new asset page
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error
return redirect()->to("hardware/bulk-checkout")->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
} catch (ModelNotFoundException $e) {
return redirect()->to("hardware/bulk-checkout")->with('error', $e->getErrors());
}
}
}

View File

@@ -15,6 +15,8 @@ use Lang;
use Redirect;
use Str;
use View;
use Image;
use App\Http\Requests\ImageUploadRequest;
/**
* This class controls all actions related to Categories for
@@ -38,6 +40,7 @@ class CategoriesController extends Controller
public function index()
{
// Show the page
$this->authorize('view', Category::class);
return view('categories/index');
}
@@ -53,6 +56,7 @@ class CategoriesController extends Controller
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);
@@ -67,11 +71,10 @@ class CategoriesController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
// create a new model instance
$this->authorize('create', Category::class);
$category = new Category();
// Update the category data
$category->name = $request->input('name');
$category->category_type = $request->input('category_type');
$category->eula_text = $request->input('eula_text');
@@ -80,6 +83,8 @@ class CategoriesController extends Controller
$category->checkin_email = $request->input('checkin_email', '0');
$category->user_id = Auth::id();
$category = $request->handleImages($category,600, public_path().'/uploads/categories');
if ($category->save()) {
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.create.success'));
}
@@ -98,10 +103,9 @@ class CategoriesController extends Controller
*/
public function edit($categoryId = null)
{
// Check if the category exists
$this->authorize('edit', Category::class);
if (is_null($item = Category::find($categoryId))) {
// Redirect to the blogs management page
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.does_not_exist'));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
$category_types= Helper::categoryTypeList();
@@ -120,9 +124,9 @@ class CategoriesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
*/
public function update(Request $request, $categoryId = null)
public function update(ImageUploadRequest $request, $categoryId = null)
{
// Check if the blog post exists
$this->authorize('edit', Category::class);
if (is_null($category = Category::find($categoryId))) {
// Redirect to the categories management page
return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist'));
@@ -138,6 +142,13 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->checkin_email = $request->input('checkin_email', '0');
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$category->image = null;
}
$category = $request->handleImages($category,600, public_path().'/uploads/categories');
if ($category->save()) {
// Redirect to the new category page
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.update.success'));
@@ -156,24 +167,25 @@ class CategoriesController extends Controller
*/
public function destroy($categoryId)
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::find($categoryId))) {
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.not_found'));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
if ($category->has_models() > 0) {
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'model']));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'model']));
} elseif ($category->accessories()->count() > 0) {
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory']));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'accessory']));
} elseif ($category->consumables()->count() > 0) {
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable']));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'consumable']));
} elseif ($category->components()->count() > 0) {
return redirect()->to('admin/settings/categories')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component']));
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=>'component']));
}
$category->delete();
// Redirect to the locations management page
return redirect()->to(route('categories.index'))->with('success', trans('admin/categories/message.delete.success'));
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}
@@ -189,6 +201,7 @@ class CategoriesController extends Controller
*/
public function show($id)
{
$this->authorize('view', Category::class);
if ($category = Category::find($id)) {
if ($category->category_type=='asset') {
@@ -206,10 +219,7 @@ class CategoriesController extends Controller
->with('category_type_route',$category_type_route);
}
// 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

@@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers;
use App\Exceptions\CheckoutNotAllowed;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
trait CheckInOutRequest
{
/**
* Find target for checkout
* @return SnipeModel Target asset is being checked out to.
*/
protected function determineCheckoutTarget()
{
// This item is checked out to a location
switch(request('checkout_to_type'))
{
case 'location':
return Location::findOrFail(request('assigned_location'));
case 'asset':
return Asset::findOrFail(request('assigned_asset'));
case 'user':
return User::findOrFail(request('assigned_user'));
}
return null;
}
/**
* Update the location of the asset passed in.
* @param Asset $asset Asset being updated
* @param SnipeModel $target Target with location
* @return Asset Asset being updated
*/
protected function updateAssetLocation($asset, $target)
{
switch(request('checkout_to_type'))
{
case 'location':
$asset->location_id = $target->id;
break;
case 'asset':
$asset->location_id = $target->rtd_location_id;
// Override with the asset's location_id if it has one
if ($target->location_id!='') {
$asset->location_id = $target->location_id;
}
break;
case 'user':
$asset->location_id = $target->location_id;
break;
}
return $asset;
}
}

View File

@@ -7,6 +7,8 @@ use Lang;
use Redirect;
use View;
use Illuminate\Http\Request;
use Image;
use App\Http\Requests\ImageUploadRequest;
/**
* This controller handles all actions related to Companies for
@@ -27,7 +29,9 @@ final class CompaniesController extends Controller
*/
public function index()
{
return view('companies/index')->with('companies', Company::all());
$this->authorize('view', Company::class);
return view('companies/index');
}
/**
@@ -39,6 +43,8 @@ final class CompaniesController extends Controller
*/
public function create()
{
$this->authorize('create', Company::class);
return view('companies/edit')->with('item', new Company);
}
@@ -50,11 +56,15 @@ final class CompaniesController extends Controller
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Company::class);
$company = new Company;
$company->name = $request->input('name');
$company = $request->handleImages($company,600, public_path().'/uploads/companies');
if ($company->save()) {
return redirect()->route('companies.index')
->with('success', trans('admin/companies/message.create.success'));
@@ -77,6 +87,9 @@ final class CompaniesController extends Controller
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.does_not_exist'));
}
$this->authorize('update', $item);
return view('companies/edit')->with('item', $item);
}
@@ -89,14 +102,24 @@ final class CompaniesController extends Controller
* @param int $companyId
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, $companyId)
public function update(ImageUploadRequest $request, $companyId)
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist'));
}
$this->authorize('update', $company);
$company->name = $request->input('name');
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$company->image = null;
}
$company = $request->handleImages($company,600, public_path().'/uploads/companies');
if ($company->save()) {
return redirect()->route('companies.index')
->with('success', trans('admin/companies/message.update.success'));
@@ -119,6 +142,9 @@ final class CompaniesController extends Controller
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.not_found'));
} else {
$this->authorize('delete', $company);
try {
$company->delete();
return redirect()->route('companies.index')

View File

@@ -2,6 +2,8 @@
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;
@@ -21,6 +23,7 @@ use View;
use Validator;
use Illuminate\Http\Request;
use Gate;
use Image;
/**
* This class controls all actions related to Components for
@@ -57,12 +60,9 @@ class ComponentsController extends Controller
public function create()
{
$this->authorize('create', Component::class);
// Show the page
return view('components/edit')
->with('item', new Component)
->with('category_list', Helper::categoryList('component'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList());
$category_type = 'component';
return view('components/edit')->with('category_type',$category_type)
->with('item', new Component);
}
@@ -74,7 +74,7 @@ class ComponentsController extends Controller
* @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Component::class);
$component = new Component();
@@ -90,6 +90,9 @@ class ComponentsController extends Controller
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
$component = $request->handleImages($component,600, public_path().'/uploads/components');
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
}
@@ -107,16 +110,18 @@ class ComponentsController extends Controller
*/
public function edit($componentId = null)
{
if (is_null($item = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
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'));
$this->authorize('update', $item);
return view('components/edit', compact('item'))
->with('category_list', Helper::categoryList('component'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList());
}
@@ -129,7 +134,7 @@ class ComponentsController extends Controller
* @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function update($componentId = null)
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'));
@@ -150,6 +155,8 @@ class ComponentsController extends Controller
$component->purchase_cost = request('purchase_cost');
$component->qty = Input::get('qty');
$component = $request->handleImages($component,600, public_path().'/uploads/components');
if ($component->save()) {
return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
}
@@ -167,7 +174,7 @@ class ComponentsController extends Controller
public function destroy($componentId)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$this->authorize('delete', $component);
@@ -175,19 +182,6 @@ class ComponentsController extends Controller
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
}
public function postBulk($componentId = null)
{
//$this->authorize('checkout', $component)
echo 'Stubbed - not yet complete';
}
public function postBulkSave($componentId = null)
{
//$this->authorize('edit', Component::class);
echo 'Stubbed - not yet complete';
}
/**
* Return a view to display component information.
*
@@ -205,10 +199,8 @@ class ComponentsController extends Controller
$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);
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
/**
@@ -228,7 +220,7 @@ class ComponentsController extends Controller
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
return view('components/checkout', compact('component'))->with('assets_list', Helper::detailedAssetList());
return view('components/checkout', compact('component'));
}
/**
@@ -287,36 +279,97 @@ class ComponentsController extends Controller
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'));
}
/**
* Return JSON data to populate the components view,
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentsController::getView() method that returns the view.
* @since [v3.0]
* @param int $componentId
* @return string JSON
*/
public function getDataView($componentId)
* 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 (is_null($component = Component::with('assets')->find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
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'));
}
if (!Company::isCurrentUserHasAccess($component)) {
return ['total' => 0, 'rows' => []];
}
$this->authorize('view', $component);
$rows = array();
$all_custom_fields = CustomField::all(); // Cached for table;
foreach ($component->assets as $component_assignment) {
$rows[] = $component_assignment->present()->forDataTable($all_custom_fields);
}
$this->authorize('checkin', $component);
$componentCount = $component->assets->count();
$data = array('total' => $componentCount, 'rows' => $rows);
return $data;
$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

@@ -7,18 +7,18 @@ use App\Models\Company;
use App\Models\Consumable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutNotification;
use Auth;
use Config;
use DB;
use Input;
use Lang;
use Mail;
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
@@ -54,13 +54,9 @@ class ConsumablesController extends Controller
public function create()
{
$this->authorize('create', Consumable::class);
// Show the page
return view('consumables/edit')
->with('item', new Consumable)
->with('category_list', Helper::categoryList('consumable'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList())
->with('manufacturer_list', Helper::manufacturerList());
$category_type = 'consumable';
return view('consumables/edit')->with('category_type', $category_type)
->with('item', new Consumable);
}
@@ -72,24 +68,28 @@ class ConsumablesController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store()
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable();
$consumable->name = Input::get('name');
$consumable->category_id = Input::get('category_id');
$consumable->location_id = Input::get('location_id');
$consumable->company_id = Company::getIdForCurrentUser(Input::get('company_id'));
$consumable->order_number = Input::get('order_number');
$consumable->min_amt = Input::get('min_amt');
$consumable->manufacturer_id = Input::get('manufacturer_id');
$consumable->model_number = Input::get('model_number');
$consumable->item_no = Input::get('item_no');
$consumable->purchase_date = Input::get('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat(Input::get('purchase_cost'));
$consumable->qty = Input::get('qty');
$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,600, public_path().'/uploads/components');
if ($consumable->save()) {
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
}
@@ -109,17 +109,14 @@ class ConsumablesController extends Controller
*/
public function edit($consumableId = null)
{
if (is_null($item = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
if ($item = Consumable::find($consumableId)) {
$this->authorize($item);
$category_type = 'consumable';
return view('consumables/edit', compact('item'))->with('category_type', $category_type);
}
$this->authorize($item);
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
return view('consumables/edit', compact('item'))
->with('category_list', Helper::categoryList('consumable'))
->with('company_list', Helper::companyList())
->with('location_list', Helper::locationsList())
->with('manufacturer_list', Helper::manufacturerList());
}
@@ -132,7 +129,7 @@ class ConsumablesController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function update($consumableId = null)
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'));
@@ -140,19 +137,32 @@ class ConsumablesController extends Controller
$this->authorize($consumable);
$consumable->name = Input::get('name');
$consumable->category_id = Input::get('category_id');
$consumable->location_id = Input::get('location_id');
$consumable->company_id = Company::getIdForCurrentUser(Input::get('company_id'));
$consumable->order_number = Input::get('order_number');
$consumable->min_amt = Input::get('min_amt');
$consumable->manufacturer_id = Input::get('manufacturer_id');
$consumable->model_number = Input::get('model_number');
$consumable->item_no = Input::get('item_no');
$consumable->purchase_date = Input::get('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat(Input::get('purchase_cost'));
$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'));
}
@@ -194,7 +204,7 @@ class ConsumablesController extends Controller
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
}
return redirect()->route('consumables')->with('error', trans('admin/consumables/message.does_not_exist', compact('id')));
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
/**
@@ -209,10 +219,10 @@ class ConsumablesController extends Controller
public function getCheckout($consumableId)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable'))->with('users_list', Helper::usersList());
return view('consumables/checkout', compact('consumable'));
}
/**
@@ -238,7 +248,7 @@ class ConsumablesController extends Controller
// Check if the user exists
if (is_null($user = User::find($assigned_to))) {
// Redirect to the consumable management page with error
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.user_does_not_exist'));
return redirect()->route('checkout/consumable', $consumable)->with('error', trans('admin/consumables/message.checkout.user_does_not_exist'));
}
// Update the consumable data
@@ -259,14 +269,6 @@ class ConsumablesController extends Controller
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
if (($consumable->requireAcceptance()=='1') || ($consumable->getEula())) {
Mail::send('emails.accept-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_consumable_delivery'));
});
}
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CustomFieldRequest;
use View;
use App\Models\CustomFieldset;
use App\Models\CustomField;
@@ -36,6 +37,7 @@ class CustomFieldsController extends Controller
*/
public function index()
{
$this->authorize('view', CustomField::class);
$fieldsets = CustomFieldset::with("fields", "models")->get();
$fields = CustomField::with("fieldset")->get();
@@ -56,6 +58,7 @@ class CustomFieldsController extends Controller
*/
public function create()
{
$this->authorize('create', CustomField::class);
return view("custom_fields.fields.edit")->with('field', new CustomField());
}
@@ -69,39 +72,33 @@ class CustomFieldsController extends Controller
* @since [v1.8]
* @return Redirect
*/
public function store(Request $request)
public function store(CustomFieldRequest $request)
{
$this->authorize('create', CustomField::class);
$field = new CustomField([
"name" => $request->get("name"),
"element" => $request->get("element"),
"help_text" => $request->get("help_text"),
"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
]);
if (!in_array(Input::get('format'), array_keys(CustomField::$PredefinedFormats))) {
if ($request->has("custom_format")) {
$field->format = e($request->get("custom_format"));
} else {
$field->format = e($request->get("format"));
}
$validator = Validator::make(Input::all(), $field->rules);
if ($validator->passes()) {
$results = $field->save();
if ($results) {
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.create.success'));
} else {
dd($field);
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.create.error'));
}
if ($field->save()) {
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.create.success'));
} else {
return redirect()->back()->withInput()->withErrors($validator);
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.create.error'));
}
}
@@ -116,6 +113,8 @@ class CustomFieldsController extends Controller
{
$field = CustomField::find($field_id);
$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'));
}
@@ -134,6 +133,8 @@ class CustomFieldsController extends Controller
{
$field = CustomField::find($field_id);
$this->authorize('delete', $field);
if ($field->fieldset->count()>0) {
return redirect()->back()->withErrors(['message' => "Field is in-use"]);
} else {
@@ -155,6 +156,9 @@ class CustomFieldsController extends Controller
public function edit($id)
{
$field = CustomField::find($id);
$this->authorize('update', $field);
return view("custom_fields.fields.edit")->with('field', $field);
}
@@ -169,16 +173,18 @@ class CustomFieldsController extends Controller
* @since [v4.0]
* @return Redirect
*/
public function update(Request $request, $id)
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->field_encrypted = e($request->get("field_encrypted", 0));
$field->user_id = Auth::user()->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))) {
$field->format = e($request->get("custom_format"));
@@ -186,13 +192,11 @@ class CustomFieldsController extends Controller
$field->format = e($request->get("format"));
}
$validator = Validator::make(Input::all(), $field->rules);
if ($field->save()) {
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.update.success'));
}
return redirect()->back()->withInput()->withErrors($validator);
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.update.error'));
}

View File

@@ -38,11 +38,13 @@ class CustomFieldsetsController extends Controller
{
$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) {
foreach ($cfset->fields as $field) {
if ($field->pivot->order > $maxid) {
$maxid=$field->pivot->order;
}
@@ -68,6 +70,8 @@ class CustomFieldsetsController extends Controller
*/
public function create()
{
$this->authorize('create', CustomFieldset::class);
return view("custom_fields.fieldsets.edit");
}
@@ -81,6 +85,8 @@ class CustomFieldsetsController extends Controller
*/
public function store(Request $request)
{
$this->authorize('create', CustomFieldset::class);
$cfset = new CustomFieldset(
[
"name" => e($request->get("name")),
@@ -141,6 +147,8 @@ class CustomFieldsetsController extends Controller
{
$fieldset = CustomFieldset::find($id);
$this->authorize('delete', $fieldset);
if ($fieldset) {
$models = AssetModel::where("fieldset_id", "=", $id);
if ($models->count() == 0) {
@@ -164,19 +172,66 @@ class CustomFieldsetsController extends Controller
* @since [v1.8]
* @return View
*/
public function associate($id)
public function associate(Request $request, $id)
{
$set = CustomFieldset::find($id);
foreach ($set->fields as $field) {
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')]);
$this->authorize('update', $set);
if ($request->filled('field_id')) {
foreach ($set->fields as $field) {
if ($field->id == $request->input('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)]);
return redirect()->route("fieldsets.show", [$id])->with("success", trans('admin/custom_fields/message.field.create.assoc_success'));
}
return redirect()->route("fieldsets.show", [$id])->with("error", 'No field selected.');
$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'));
}
/**
* Set the field in a fieldset to required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
*/
public function makeFieldRequired($fieldset_id, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 1];
$fieldset->fields()->syncWithoutDetaching($fields);
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
->with("success", trans('Field successfully set to required'));
}
/**
* Set the field in a fieldset to optional
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0]
*/
public function makeFieldOptional($fieldset_id, $field_id)
{
$this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id);
$fieldset = CustomFieldset::findOrFail($fieldset_id);
$fields[$field->id] = ['required' => 0];
$fieldset->fields()->syncWithoutDetaching($fields);
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
->with("success", trans('Field successfully set to optional'));
}
}

View File

@@ -39,8 +39,8 @@ class DashboardController extends Controller
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
if ((!file_exists(storage_path().'/oauth-private.key')) || (!file_exists(storage_path().'/oauth-public.key'))) {
\Artisan::call('passport:install');
\Artisan::call('migrate', ['--force' => true]);
\Artisan::call('passport:install');
}
return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts);

View File

@@ -6,6 +6,8 @@ use Illuminate\Http\Request;
use App\Models\Department;
use App\Helpers\Helper;
use Auth;
use Image;
use App\Http\Requests\ImageUploadRequest;
class DepartmentsController extends Controller
{
@@ -27,13 +29,11 @@ class DepartmentsController extends Controller
public function index(Request $request)
{
$this->authorize('index', Department::class);
if ($request->has('company_id')) {
$company = null;
if ($request->filled('company_id')) {
$company = Company::find($request->input('company_id'));
} else {
$company = null;
}
return view('departments/index')->with('company',$company);
return view('departments/index')->with('company', $company);
}
@@ -45,20 +45,20 @@ class DepartmentsController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
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->has('manager_id' ) ? $request->input('manager_id') : null);
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
$department = $request->handleImages($department,600, public_path().'/uploads/departments');
if ($department->save()) {
return redirect()->route("departments.index")->with('success', trans('admin/departments/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($department->getErrors());
}
/**
@@ -74,10 +74,12 @@ class DepartmentsController extends Controller
{
$department = Department::find($id);
$this->authorize('view', $department);
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'));
}
@@ -91,10 +93,9 @@ class DepartmentsController extends Controller
*/
public function create()
{
return view('departments/edit')->with('item', new Department)
->with('manager_list', Helper::managerList())
->with('location_list', Helper::locationsList())
->with('company_list', Helper::companyList());
$this->authorize('create', Department::class);
return view('departments/edit')->with('item', new Department);
}
@@ -112,6 +113,8 @@ class DepartmentsController extends Controller
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found'));
}
$this->authorize('delete', $department);
if ($department->users->count() > 0) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.assoc_users'));
}
@@ -135,30 +138,28 @@ class DepartmentsController extends Controller
if (is_null($item = Department::find($id))) {
return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
}
return view('departments/edit', compact('item'))
->with('manager_list', Helper::managerList())
->with('location_list', Helper::locationsList())
->with('company_list', Helper::companyList());
}
public function update(Request $request, $id) {
$this->authorize('create', Department::class);
$this->authorize('update', $item);
return view('departments/edit', compact('item'));
}
public function update(ImageUploadRequest $request, $id) {
if (is_null($department = Department::find($id))) {
return redirect()->to('admin/settings/departments')->with('error', trans('admin/departments/message.does_not_exist'));
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
}
$this->authorize('update', $department);
$department->fill($request->all());
$department->manager_id = ($request->has('manager_id' ) ? $request->input('manager_id') : null);
$department->manager_id = ($request->filled('manager_id' ) ? $request->input('manager_id') : null);
$department = $request->handleImages($department,600, public_path().'/uploads/departments');
if ($department->save()) {
return redirect()->route("departments.index")->with('success', trans('admin/departments/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($department->getErrors());
}
}

View File

@@ -31,8 +31,10 @@ class DepreciationsController extends Controller
*/
public function index()
{
$this->authorize('view', Depreciation::class);
// Show the page
return view('depreciations/index', compact('depreciations'));
return view('depreciations/index');
}
@@ -46,6 +48,8 @@ class DepreciationsController extends Controller
*/
public function create()
{
$this->authorize('create', Depreciation::class);
// Show the page
return view('depreciations/edit')->with('item', new Depreciation);
}
@@ -62,6 +66,8 @@ class DepreciationsController extends Controller
*/
public function store(Request $request)
{
$this->authorize('create', Depreciation::class);
// create a new instance
$depreciation = new Depreciation();
// Depreciation data
@@ -94,6 +100,8 @@ class DepreciationsController extends Controller
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$this->authorize('update', $item);
return view('depreciations/edit', compact('item'));
}
@@ -116,6 +124,8 @@ class DepreciationsController extends Controller
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$this->authorize('update', $depreciation);
// Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
@@ -145,6 +155,8 @@ class DepreciationsController extends Controller
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.not_found'));
}
$this->authorize('delete', $depreciation);
if ($depreciation->has_models() > 0) {
// Redirect to the asset management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.assoc_users'));
@@ -155,5 +167,26 @@ class DepreciationsController extends Controller
return redirect()->route('depreciations.index')->with('success', trans('admin/depreciations/message.delete.success'));
}
/**
* Returns a view that displays a form to display depreciation listing
*
* @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postEdit()
* @param int $depreciationId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function show($id)
{
if (is_null($depreciation = Depreciation::find($id))) {
// Redirect to the blogs management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$this->authorize('view', $depreciation);
return view('depreciations/view', compact('depreciation'));
}
}

View File

@@ -31,7 +31,7 @@ class GroupsController extends Controller
public function index()
{
// Show the page
return view('groups/index', compact('groups'));
return view('groups/index');
}
/**
@@ -47,7 +47,7 @@ class GroupsController extends Controller
$group = new Group;
// Get all the available permissions
$permissions = config('permissions');
$groupPermissions = array();
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$selectedPermissions = Input::old('permissions', $groupPermissions);
// Show the page
@@ -72,7 +72,7 @@ class GroupsController extends Controller
if ($group->save()) {
return redirect()->route("groups.index")->with('success', trans('admin/groups/message.success.create'));
}
return redirect(route('groups.create'))->withInput()->withErrors($group->getErrors());
return redirect()->back()->withInput()->withErrors($group->getErrors());
}
/**
@@ -84,13 +84,18 @@ class GroupsController extends Controller
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function edit($id = null)
public function edit($id)
{
$group = Group::find($id);
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
if ($group) {
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found'));
}
/**
@@ -106,7 +111,7 @@ class GroupsController extends Controller
{
$permissions = config('permissions');
if (!$group = Group::find($id)) {
return redirect()->route('groups')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
}
$group->name = e(Input::get('name'));
$group->permissions = json_encode(Input::get('permission'));
@@ -133,7 +138,7 @@ class GroupsController extends Controller
{
if (!config('app.lock_passwords')) {
if (!$group = Group::find($id)) {
return redirect()->route('groups')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
}
$group->delete();
// Redirect to the group management page
@@ -142,4 +147,24 @@ class GroupsController extends Controller
return redirect()->route('groups.index')->with('error', trans('general.feature_disabled'));
}
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the group detail page.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v4.0.11]
* @return \Illuminate\Contracts\View\View
*/
public function show($id)
{
$group = Group::find($id);
if ($group) {
return view('groups/view', compact('group'));
}
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', compact('id')));
}
}

View File

@@ -5,12 +5,14 @@ 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
{
public function index()
{
$this->authorize('create', Asset::class);
$this->authorize('import');
$imports = Import::latest()->get();
$imports = (new ImportsTransformer)->transformImports($imports);
return view('importer/import')->with('imports', $imports);

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\AssetFileRequest;
use Assets;
use Illuminate\Support\Facades\Session;
use Input;
@@ -67,10 +68,7 @@ class LicensesController extends Controller
return view('licenses/edit')
//->with('license_options',$license_options)
->with('depreciation_list', Helper::depreciationList())
->with('supplier_list', Helper::suppliersList())
->with('maintained_list', $maintained_list)
->with('company_list', Helper::companyList())
->with('manufacturer_list', Helper::manufacturerList())
->with('item', new License);
}
@@ -110,6 +108,7 @@ class LicensesController extends Controller
$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();
@@ -144,10 +143,7 @@ class LicensesController extends Controller
return view('licenses/edit', compact('item'))
->with('depreciation_list', Helper::depreciationList())
->with('supplier_list', Helper::suppliersList())
->with('company_list', Helper::companyList())
->with('maintained_list', $maintained_list)
->with('manufacturer_list', Helper::manufacturerList());
->with('maintained_list', $maintained_list);
}
@@ -183,10 +179,14 @@ class LicensesController extends Controller
$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');
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'));
@@ -244,18 +244,25 @@ class LicensesController extends Controller
* @param int $seatId
* @return \Illuminate\Contracts\View\View
*/
public function getCheckout($seatId)
public function getCheckout($licenceId)
{
// Check if the license seat 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'));
// Check that the license is valid
if ($license = License::where('id',$licenceId)->first()) {
$this->authorize('checkout', $license);
// 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');
}
return view('licenses/checkout', compact('license'));
}
$this->authorize('checkout', $licenseSeat);
return view('licenses/checkout', compact('licenseSeat'))
->with('users_list', Helper::usersList())
->with('asset_list', Helper::detailedAssetList());
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
@@ -266,81 +273,95 @@ class LicensesController extends Controller
* @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, $seatId)
public function postCheckout(Request $request, $licenseId, $seatId = null)
{
$licenseSeat = LicenseSeat::find($seatId);
$assigned_to = e($request->input('assigned_to'));
$asset_id = e($request->input('asset_id'));
$this->authorize('checkout', $licenseSeat);
// 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;
if ($assigned_to!='') {
// Check if the user exists
if (is_null($target = User::find($assigned_to))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.user_does_not_exist'));
// 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');
}
}
if ($asset_id!='') {
if (is_null($target = Asset::find($asset_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.asset_does_not_exist'));
$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');
}
if (($request->has('assigned_to')) && ($request->has('asset_id'))) {
return redirect()->back()->withInput()->with('error', trans('admin/licenses/message.select_asset_or_person'));
$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'));
// Check if the asset exists
if (is_null($licenseSeat)) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
if ($request->input('asset_id') == '') {
$licenseSeat->asset_id = null;
} else {
$licenseSeat->asset_id = $request->input('asset_id');
}
// Update the asset data
if ($request->input('assigned_to') == '') {
$licenseSeat->assigned_to = null;
} else {
$licenseSeat->assigned_to = $request->input('assigned_to');
}
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckout($request->input('note'), $target);
$data['license_id'] = $licenseSeat->license_id;
$data['note'] = $request->input('note');
// Redirect to the new asset page
return redirect()->route("licenses.index")->with('success', trans('admin/licenses/message.checkout.success'));
}
return redirect()->route("licenses.index")->with('error', trans('admin/licenses/message.checkout.error'));
}
@@ -361,7 +382,14 @@ class LicensesController extends Controller
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$this->authorize('checkin', $licenseSeat);
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);
}
@@ -385,8 +413,7 @@ class LicensesController extends Controller
}
$license = License::find($licenseSeat->license_id);
$this->authorize('checkin', $licenseSeat);
$this->authorize('checkout', $license);
if (!$license->reassignable) {
// Not allowed to checkin
@@ -394,31 +421,19 @@ class LicensesController extends Controller
return redirect()->back()->withInput();
}
// Declare the rules for the form validation
$rules = array(
'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);
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($return_to, e(request('note')));
$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'));
}
@@ -440,17 +455,15 @@ class LicensesController extends Controller
public function show($licenseId = null)
{
$license = License::find($licenseId);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
$license = License::with('assignedusers', 'licenseSeats.user', 'licenseSeats.asset')->find($licenseId);
if (isset($license->id)) {
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
if ($license) {
$this->authorize('view', $license);
return view('licenses/view', compact('license'));
}
$error = trans('admin/licenses/message.does_not_exist', compact('id'));
return redirect()->route('licenses.index')->with('error', $error);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
public function getClone($licenseId = null)
{
@@ -473,11 +486,8 @@ class LicensesController extends Controller
// Show the page
return view('licenses/edit')
->with('depreciation_list', Helper::depreciationList())
->with('supplier_list', Helper::suppliersList())
->with('item', $license)
->with('maintained_list', $maintained_list)
->with('company_list', Helper::companyList())
->with('manufacturer_list', Helper::manufacturerList());
->with('maintained_list', $maintained_list);
}
@@ -490,7 +500,7 @@ class LicensesController extends Controller
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
*/
public function postUpload(Request $request, $licenseId = null)
public function postUpload(AssetFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
// the license is valid
@@ -499,21 +509,11 @@ class LicensesController extends Controller
if (isset($license->id)) {
$this->authorize('update', $license);
if (Input::hasFile('licensefile')) {
if (Input::hasFile('file')) {
foreach (Input::file('licensefile') as $file) {
$rules = array(
'licensefile' => 'required|mimes:png,gif,jpg,jpeg,doc,docx,pdf,txt,zip,rar,rtf,xml,lic|max:2000'
);
$validator = Validator::make(array('licensefile'=> $file), $rules);
if ($validator->fails()) {
return redirect()->back()->with('error', trans('admin/licenses/message.upload.invalidfiles'));
}
foreach (Input::file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$filename = 'license-'.$license->id.'-'.str_random(8);
$filename .= '-'.str_slug($file->getClientOriginalName()).'.'.$extension;
$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
@@ -530,9 +530,8 @@ class LicensesController extends Controller
}
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);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
@@ -551,21 +550,24 @@ class LicensesController extends Controller
$destinationPath = config('app.private_uploads').'/licenses';
// the license is valid
if (isset($license->id)) {
if ($license) {
$this->authorize('edit', $license);
$log = Actionlog::find($fileId);
$full_filename = $destinationPath.'/'.$log->filename;
if (file_exists($full_filename)) {
unlink($destinationPath.'/'.$log->filename);
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'));
}
$log->delete();
return redirect()->back()->with('success', trans('admin/licenses/message.deletefile.success'));
return redirect()->back()->with('error', 'Could not locate that file.');
}
// Prepare the error message
$error = trans('admin/licenses/message.does_not_exist', compact('id'));
// Redirect to the licence management page
return redirect()->route('licenses.index')->with('error', $error);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
@@ -579,7 +581,7 @@ class LicensesController extends Controller
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function displayFile($licenseId = null, $fileId = null)
public function displayFile($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
@@ -588,64 +590,38 @@ class LicensesController extends Controller
if (isset($license->id)) {
$this->authorize('view', $license);
$log = Actionlog::find($fileId);
$file = $log->get_src('licenses');
return Response::download($file);
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);
}
}
// Prepare the error message
$error = trans('admin/licenses/message.does_not_exist', compact('id'));
// Redirect to the licence management page
return redirect()->route('licenses.index')->with('error', $error);
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Generates a JSON response to populate the licence index datatables.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LicensesController::getIndex() method that provides the view
* @since [v1.0]
* @return String JSON
*/
public function getDatatable(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'licenseSeatsRelation', 'manufacturer'));
if (Input::has('search')) {
$licenses = $licenses->TextSearch($request->input('search'));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','manufacturer','company'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) {
case 'manufacturer':
$licenses = $licenses->OrderManufacturer($order);
break;
case 'company':
$licenses = $licenses->OrderCompany($order);
break;
default:
$licenses = $licenses->orderBy($sort, $order);
break;
}
$licenseCount = $licenses->count();
$licenses = $licenses->skip($offset)->take($limit)->get();
$rows = array();
foreach ($licenses as $license) {
$rows[] = $license->present()->forDataTable();
}
$data = array('total' => $licenseCount, 'rows' => $rows);
return $data;
}
/**
* Generates the next free seat ID for checkout.

View File

@@ -16,6 +16,8 @@ use Validator;
use View;
use Auth;
use Symfony\Component\HttpFoundation\JsonResponse;
use Image;
use App\Http\Requests\ImageUploadRequest;
/**
* This controller handles all actions related to Locations for
@@ -38,10 +40,9 @@ class LocationsController extends Controller
public function index()
{
// Grab all the locations
$locations = Location::orderBy('created_at', 'DESC')->with('parent', 'assets', 'assignedassets')->get();
$this->authorize('view', Location::class);
// Show the page
return view('locations/index', compact('locations'));
return view('locations/index');
}
@@ -55,16 +56,9 @@ class LocationsController extends Controller
*/
public function create()
{
$locations = Location::orderBy('name', 'ASC')->get();
$location_options_array = Location::getLocationHierarchy($locations);
$location_options = Location::flattenLocationsArray($location_options_array);
$location_options = array('' => 'Top Level') + $location_options;
$this->authorize('create', Location::class);
return view('locations/edit')
->with('location_options', $location_options)
->with('item', new Location)
->with('manager_list', Helper::managerList());
->with('item', new Location);
}
@@ -77,62 +71,31 @@ class LocationsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function store()
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Location::class);
$location = new Location();
$location->name = Input::get('name');
$location->parent_id = Input::get('parent_id', null);
$location->currency = Input::get('currency', '$');
$location->address = Input::get('address');
$location->address2 = Input::get('address2');
$location->city = Input::get('city');
$location->state = Input::get('state');
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->manager_id = Input::get('manager_id');
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
$location->currency = $request->input('currency', '$');
$location->address = $request->input('address');
$location->address2 = $request->input('address2');
$location->city = $request->input('city');
$location->state = $request->input('state');
$location->country = $request->input('country');
$location->zip = $request->input('zip');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->user_id = Auth::id();
$location = $request->handleImages($location,600, public_path().'/uploads/locations');
if ($location->save()) {
return redirect()->route("locations.index")->with('success', trans('admin/locations/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($location->getErrors());
}
/**
* Validates and stores a new location created via the Create Asset form modal.
*
* @todo Check if a Form Request would work better here.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see AssetsController::getCreate() method that makes the form
* @since [v1.0]
* @return String JSON
*/
public function apiStore()
{
$new['currency']=Setting::first()->default_currency;
// create a new location instance
$location = new Location();
// Save the location data
$location->name = Input::get('name');
$location->currency = Setting::first()->default_currency; //e(Input::get('currency'));
$location->address = ''; //e(Input::get('address'));
// $location->address2 = e(Input::get('address2'));
$location->city = Input::get('city');
$location->state = '';//e(Input::get('state'));
$location->country = Input::get('country');
// $location->zip = e(Input::get('zip'));
$location->user_id = Auth::id();
// Was the location created?
if ($location->save()) {
return JsonResponse::create($location);
}
// failure
return JsonResponse::create(["error" => "Failed validation: ".print_r($location->getErrors(), true)], 500);
}
/**
* Makes a form view to edit location information.
@@ -145,21 +108,14 @@ class LocationsController extends Controller
*/
public function edit($locationId = null)
{
$this->authorize('update', Location::class);
// Check if the location exists
if (is_null($item = Location::find($locationId))) {
return redirect()->to('admin/settings/locations')->with('error', trans('admin/locations/message.does_not_exist'));
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
// Show the page
$locations = Location::orderBy('name', 'ASC')->get();
$location_options_array = Location::getLocationHierarchy($locations);
$location_options = Location::flattenLocationsArray($location_options_array);
$location_options = array('' => 'Top Level') + $location_options;
return view('locations/edit', compact('item'))
->with('location_options', $location_options)
->with('manager_list', Helper::managerList());
return view('locations/edit', compact('item'));
}
@@ -172,32 +128,37 @@ class LocationsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function update($locationId = null)
public function update(ImageUploadRequest $request, $locationId = null)
{
$this->authorize('update', Location::class);
// Check if the location exists
if (is_null($location = Location::find($locationId))) {
return redirect()->to('admin/settings/locations')->with('error', trans('admin/locations/message.does_not_exist'));
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($request->input('parent_id') == $locationId) {
return redirect()->back()->withInput()->with('error', 'A location cannot be its own parent. Please select a different parent location.');
}
// Update the location data
$location->name = Input::get('name');
$location->parent_id = Input::get('parent_id', null);
$location->currency = Input::get('currency', '$');
$location->address = Input::get('address');
$location->address2 = Input::get('address2');
$location->city = Input::get('city');
$location->state = Input::get('state');
$location->country = Input::get('country');
$location->zip = Input::get('zip');
$location->ldap_ou = Input::get('ldap_ou');
$location->manager_id = Input::get('manager_id');
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
$location->currency = $request->input('currency', '$');
$location->address = $request->input('address');
$location->address2 = $request->input('address2');
$location->city = $request->input('city');
$location->state = $request->input('state');
$location->country = $request->input('country');
$location->zip = $request->input('zip');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location = $request->handleImages($location,600, public_path().'/uploads/locations');
// Was the location updated?
if ($location->save()) {
// Redirect to the saved location page
return redirect()->route("locations.index")->with('success', trans('admin/locations/message.update.success'));
}
// Redirect to the location management page
return redirect()->back()->withInput()->withInput()->withErrors($location->getErrors());
}
@@ -211,21 +172,23 @@ class LocationsController extends Controller
*/
public function destroy($locationId)
{
// Check if the location exists
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
// Redirect to the blogs management page
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
}
if ($location->users->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_users'));
} elseif ($location->childLocations->count() > 0) {
} elseif ($location->children->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_child_loc'));
} elseif ($location->assets->count() > 0) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.assoc_assets'));
} 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'));
@@ -249,11 +212,8 @@ class LocationsController extends Controller
if (isset($location->id)) {
return view('locations/view', compact('location'));
}
// Prepare the error message
$error = trans('admin/locations/message.does_not_exist', compact('id'));
// Redirect to the user management page
return redirect()->route('locations.index')->with('error', $error);
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\CustomField;
use App\Models\Manufacturer;
use Auth;
@@ -13,6 +14,7 @@ use Redirect;
use Str;
use View;
use Illuminate\Http\Request;
use Image;
/**
* This controller handles all actions related to Manufacturers for
@@ -33,7 +35,8 @@ class ManufacturersController extends Controller
*/
public function index()
{
return view('manufacturers/index', compact('manufacturers'));
$this->authorize('index', Manufacturer::class);
return view('manufacturers/index');
}
@@ -47,6 +50,7 @@ class ManufacturersController extends Controller
*/
public function create()
{
$this->authorize('create', Manufacturer::class);
return view('manufacturers/edit')->with('item', new Manufacturer);
}
@@ -60,9 +64,10 @@ class ManufacturersController extends Controller
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
$manufacturer->name = $request->input('name');
$manufacturer->user_id = Auth::user()->id;
@@ -70,6 +75,7 @@ class ManufacturersController extends Controller
$manufacturer->support_url = $request->input('support_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
$manufacturer = $request->handleImages($manufacturer,600, public_path().'/uploads/manufacturers');
@@ -90,10 +96,14 @@ class ManufacturersController extends Controller
*/
public function edit($id = null)
{
// Handles manufacturer checks and permissions.
$this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists
if (is_null($item = Manufacturer::find($id))) {
if (!$item = Manufacturer::find($id)) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
// Show the page
return view('manufacturers/edit', compact('item'));
}
@@ -109,8 +119,9 @@ class ManufacturersController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0]
*/
public function update(Request $request, $manufacturerId = null)
public function update(ImageUploadRequest $request, $manufacturerId = null)
{
$this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists
if (is_null($manufacturer = Manufacturer::find($manufacturerId))) {
// Redirect to the manufacturer page
@@ -123,6 +134,15 @@ class ManufacturersController extends Controller
$manufacturer->support_url = $request->input('support_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
// Set the model's image property to null if the image is being deleted
if ($request->input('image_delete') == 1) {
$manufacturer->image = null;
}
$manufacturer = $request->handleImages($manufacturer,600, public_path().'/uploads/manufacturers');
if ($manufacturer->save()) {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.update.success'));
@@ -140,6 +160,7 @@ class ManufacturersController extends Controller
*/
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
@@ -150,6 +171,16 @@ class ManufacturersController extends Controller
// Redirect to the asset management page
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
}
if ($manufacturer->image) {
try {
unlink(public_path().'/uploads/manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
\Log::info($e);
}
}
// Delete the manufacturer
$manufacturer->delete();
// Redirect to the manufacturers management page
@@ -158,170 +189,55 @@ 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 in getDatatable.
* 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>]
* @see ManufacturersController::getDataView()
* @param int $manufacturerId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function show($manufacturerId = null)
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::find($manufacturerId);
if (isset($manufacturer->id)) {
return view('manufacturers/view', compact('manufacturer'));
}
// Prepare the error message
$error = trans('admin/manufacturers/message.does_not_exist', compact('id'));
$error = trans('admin/manufacturers/message.does_not_exist');
// Redirect to the user management page
return redirect()->route('manufacturers')->with('error', $error);
return redirect()->route('manufacturers.index')->with('error', $error);
}
/**
* Restore a given Manufacturer (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.1.15]
* @param int $manufacturers_id
* @return Redirect
*/
public function restore($manufacturers_id)
{
$this->authorize('create', Manufacturer::class);
$manufacturer = Manufacturer::onlyTrashed()->where('id',$manufacturers_id)->first();
if ($manufacturer) {
// Not sure why this is necessary - it shouldn't fail validation here, but it fails without this, so....
$manufacturer->setValidating(false);
if ($manufacturer->restore()) {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
}
return redirect()->back()->with('error', 'Could not restore.');
}
return redirect()->back()->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
/**
* Generates the JSON used to display the manufacturer detail.
* This JSON returns data on all of the assets with the specified
* manufacturer ID number.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::getView()
* @param int $manufacturerId
* @param string $itemType
* @param Request $request
* @return String JSON* @since [v1.0]
*/
public function getDataView($manufacturerId, $itemType = null, Request $request)
{
$manufacturer = Manufacturer::find($manufacturerId);
switch ($itemType) {
case "assets":
return $this->getDataAssetsView($manufacturer, $request);
case "licenses":
return $this->getDataLicensesView($manufacturer, $request);
case "accessories":
return $this->getDataAccessoriesView($manufacturer, $request);
case "consumables":
return $this->getDataConsumablesView($manufacturer, $request);
}
return "We shouldn't be here";
}
protected function getDataAssetsView(Manufacturer $manufacturer, Request $request)
{
$manufacturer = $manufacturer->load('assets.model', 'assets.assignedTo', 'assets.assetstatus', 'assets.company');
$manufacturer_assets = $manufacturer->assets();
if ($request->has('search')) {
$manufacturer_assets = $manufacturer_assets->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$allowed_columns = ['id','name','serial','asset_tag'];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$count = $manufacturer_assets->count();
$manufacturer_assets = $manufacturer_assets->skip($offset)->take($limit)->get();
$rows = array();
$all_custom_fields = CustomField::all(); // cached;
foreach ($manufacturer_assets as $asset) {
$rows[] = $asset->present()->forDataTable($all_custom_fields);
}
$data = array('total' => $count, 'rows' => $rows);
return $data;
}
protected function getDataLicensesView(Manufacturer $manufacturer, Request $request)
{
$manufacturer = $manufacturer->load('licenses.company', 'licenses.manufacturer', 'licenses.licenseSeatsRelation');
$licenses = $manufacturer->licenses;
if ($request->has('search')) {
$licenses = $licenses->TextSearch($request->input('search'));
}
$licenseCount = $licenses->count();
$rows = array();
foreach ($licenses as $license) {
$rows[] = $license->present()->forDataTable();
}
$data = array('total' => $licenseCount, 'rows' => $rows);
return $data;
}
public function getDataAccessoriesView(Manufacturer $manufacturer, Request $request)
{
$manufacturer = $manufacturer->load(
'accessories.location',
'accessories.company',
'accessories.category',
'accessories.manufacturer',
'accessories.users'
);
$accessories = $manufacturer->accessories();
if ($request->has('search')) {
$accessories = $accessories->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$accessCount = $accessories->count();
$accessories = $accessories->skip($offset)->take($limit)->get();
$rows = array();
foreach ($accessories as $accessory) {
$rows[] = $accessory->present()->forDataTable();
}
$data = array('total'=>$accessCount, 'rows'=>$rows);
return $data;
}
public function getDataConsumablesView($manufacturer, Request $request)
{
$manufacturer = $manufacturer->load(
'consumables.location',
'consumables.company',
'consumables.category',
'consumables.manufacturer',
'consumables.users'
);
$consumables = $manufacturer->consumables();
if ($request->has('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
$consumCount = $consumables->count();
$consumables = $consumables->skip($offset)->take($limit)->get();
$rows = array();
foreach ($consumables as $consumable) {
$rows[] = $consumable->present()->forDataTable();
}
$data = array('total' => $consumCount, 'rows' => $rows);
return $data;
}
}

View File

@@ -13,9 +13,7 @@ class ModalController extends Controller
}
function model() {
return view('modals.model')
->with('manufacturer', Helper::manufacturerList())
->with('category', Helper::categoryList('asset'));
return view('modals.model');
}
function statuslabel() {
@@ -29,4 +27,13 @@ class ModalController extends Controller
function user() {
return view('modals.user');
}
function category() {
return view('modals.category');
}
function manufacturer() {
return view('modals.manufacturer');
}
}

View File

@@ -11,6 +11,7 @@ use App\Models\Setting;
use Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Http\Requests\ImageUploadRequest;
/**
* This controller handles all actions related to User Profiles for
@@ -30,8 +31,7 @@ class ProfileController extends Controller
public function getIndex()
{
$user = Auth::user();
$location_list = Helper::locationsList();
return view('account/profile', compact('user'))->with('location_list', $location_list);
return view('account/profile', compact('user'));
}
/**
@@ -41,19 +41,28 @@ class ProfileController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function postIndex()
public function postIndex(ImageUploadRequest $request)
{
$user = Auth::user();
$user->first_name = Input::get('first_name');
$user->last_name = Input::get('last_name');
$user->website = Input::get('website');
$user->location_id = Input::get('location_id');
$user->gravatar = Input::get('gravatar');
$user->locale = Input::get('locale');
$user->first_name = $request->input('first_name');
$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');
}
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled=='1') && (!config('app.lock_passwords')))) {
$user->two_factor_optin = Input::get('two_factor_optin', '0');
$user->two_factor_optin = $request->input('two_factor_optin', '0');
}
if (Gate::allows('self.edit_location') && (!config('app.lock_passwords'))) {
$user->location_id = $request->input('location_id');
}
if (Input::file('avatar')) {
@@ -109,18 +118,17 @@ class ProfileController extends Controller
{
if (config('app.lock_passwords')) {
return redirect()->route('account.password.index')->with('error', Lang::get('admin/users/table.lock_passwords'));
return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords'));
}
$user = Auth::user();
if ($user->ldap_import=='1') {
return redirect()->route('account.password.index')->with('error', Lang::get('admin/users/message.error.password_ldap'));
return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap'));
}
$rules = array(
'current_password' => 'required',
'password' => Setting::passwordComplexityRulesSaving('store'),
'password_confirm' => 'required|same:password',
'password' => Setting::passwordComplexityRulesSaving('store').'|confirmed',
);
$validator = \Validator::make($request->all(), $rules);
@@ -143,5 +151,25 @@ class ProfileController extends Controller
}
/**
* Save the menu state of open/closed when the user clicks on the hamburger
* menu.
*
* This URL is triggered via jquery in
* resources/views/layouts/default.blade.php
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return View
*/
public function getMenuState(Request $request) {
if ($request->input('state')=='open') {
$request->session()->put('menu_state', 'open');
} else {
$request->session()->put('menu_state', 'closed');
}
}
}

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\Depreciation;
use App\Models\License;
use App\Models\Setting;
use Carbon\Carbon;
@@ -15,6 +16,7 @@ use Illuminate\Support\Facades\View;
use Input;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Reports for
@@ -24,6 +26,14 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
*/
class ReportsController extends Controller
{
/**
* Checks for correct permissions
*/
public function __construct() {
parent::__construct();
$this->authorize('reports.view');
}
/**
* Returns a view that displays the accessories report.
@@ -80,101 +90,6 @@ class ReportsController extends Controller
return $response;
}
/**
* Display asset report view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
public function getAssetsReport()
{
$settings = \App\Models\Setting::first();
return view('reports/asset', compact('assets'))->with('settings', $settings);
}
/**
* Exports the assets to CSV
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function exportAssetReport()
{
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields) {
// Open output stream
$handle = fopen('php://output', 'w');
Asset::with('assignedTo', 'assetLoc','defaultLoc','assignedTo','model','supplier','assetstatus','model.manufacturer')->orderBy('created_at', 'DESC')->chunk(500, function($assets) use($handle, $customfields) {
$headers=[
trans('general.company'),
trans('admin/hardware/table.asset_tag'),
trans('admin/hardware/form.manufacturer'),
trans('admin/hardware/form.model'),
trans('general.model_no'),
trans('general.name'),
trans('admin/hardware/table.serial'),
trans('general.status'),
trans('admin/hardware/table.purchase_date'),
trans('admin/hardware/table.purchase_cost'),
trans('admin/hardware/form.order'),
trans('admin/hardware/form.supplier'),
trans('admin/hardware/table.checkoutto'),
trans('admin/hardware/table.checkout_date'),
trans('admin/hardware/table.location'),
trans('general.notes'),
];
foreach ($customfields as $field) {
$headers[]=$field->name;
}
fputcsv($handle, $headers);
foreach ($assets as $asset) {
// Add a new row with data
$values=[
($asset->company) ? $asset->company->name : '',
$asset->asset_tag,
($asset->model->manufacturer) ? $asset->model->manufacturer->name : '',
($asset->model) ? $asset->model->name : '',
($asset->model->model_number) ? $asset->model->model_number : '',
($asset->name) ? $asset->name : '',
($asset->serial) ? $asset->serial : '',
($asset->assetstatus) ? e($asset->assetstatus->name) : '',
($asset->purchase_date) ? e($asset->purchase_date) : '',
($asset->purchase_cost > 0) ? Helper::formatCurrencyOutput($asset->purchase_cost) : '',
($asset->order_number) ? e($asset->order_number) : '',
($asset->supplier) ? e($asset->supplier->name) : '',
($asset->assignedTo) ? e($asset->assignedTo->present()->name()) : '',
($asset->last_checkout!='') ? e($asset->last_checkout) : '',
e($asset->assetLoc->present()->name()),
($asset->notes) ? e($asset->notes) : '',
];
foreach ($customfields as $field) {
$values[]=$asset->{$field->db_column_name()};
}
fputcsv($handle, $values);
}
});
// Close the output stream
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="assets-'.date('Y-m-d-his').'.csv"',
]);
return $response;
}
/**
* Show depreciation report for assets.
*
@@ -185,11 +100,12 @@ class ReportsController extends Controller
public function getDeprecationReport()
{
$depreciations = Depreciation::get();
// Grab all the assets
$assets = Asset::with( 'assignedTo', 'assetstatus', 'defaultLoc', 'assetloc', 'assetlog', 'company', 'model.category', 'model.depreciation')
$assets = Asset::with( 'assignedTo', 'assetstatus', 'defaultLoc', 'location', 'assetlog', 'company', 'model.category', 'model.depreciation')
->orderBy('created_at', 'DESC')->get();
return view('reports/depreciation', compact('assets'));
return view('reports/depreciation', compact('assets'))->with('depreciations',$depreciations);
}
/**
@@ -241,7 +157,7 @@ class ReportsController extends Controller
$row[] = ''; // Empty string if unassigned
}
if (( $asset->assigned_to > 0 ) && ( $location = $asset->assetLoc )) {
if (( $asset->assigned_to > 0 ) && ( $location = $asset->location )) {
if ($location->city) {
$row[] = e($location->city) . ', ' . e($location->state);
} elseif ($location->name) {
@@ -253,8 +169,8 @@ class ReportsController extends Controller
$row[] = ''; // Empty string if location is not set
}
if ($asset->assetloc) {
$currency = e($asset->assetloc->currency);
if ($asset->location) {
$currency = e($asset->location->currency);
} else {
$currency = e(Setting::first()->default_currency);
}
@@ -388,251 +304,432 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function postCustom()
public function postCustom(Request $request)
{
$assets = Asset::orderBy('created_at', 'DESC')->with('company', 'assignedTo', 'assetloc', 'defaultLoc', 'model', 'supplier', 'assetstatus', 'model.manufacturer')->get();
ini_set('max_execution_time', 12000);
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
$rows = [ ];
$header = [ ];
\Log::debug('Starting streamed response');
if (e(Input::get('company')) == '1') {
$header[] = 'Company Name';
}
if (e(Input::get('asset_name')) == '1') {
$header[] = 'Asset Name';
}
if (e(Input::get('asset_tag')) == '1') {
$header[] = 'Asset Tag';
}
if (e(Input::get('manufacturer')) == '1') {
$header[] = 'Manufacturer';
}
if (e(Input::get('model')) == '1') {
$header[] = 'Model';
$header[] = 'Model Number';
}
if (e(Input::get('category')) == '1') {
$header[] = 'Category';
}
if (e(Input::get('serial')) == '1') {
$header[] = 'Serial';
}
if (e(Input::get('purchase_date')) == '1') {
$header[] = 'Purchase Date';
}
if (( e(Input::get('purchase_cost')) == '1' ) && ( e(Input::get('depreciation')) != '1' )) {
$header[] = 'Purchase Cost';
}
if (e(Input::get('eol')) == '1') {
$header[] = 'EOL';
}
if (e(Input::get('order')) == '1') {
$header[] = 'Order Number';
}
if (e(Input::get('supplier')) == '1') {
$header[] = 'Supplier';
}
if (e(Input::get('location')) == '1') {
$header[] = 'Location';
}
if (e(Input::get('assigned_to')) == '1') {
$header[] = 'Assigned To';
}
if (e(Input::get('username')) == '1') {
$header[] = 'Username';
}
if (e(Input::get('employee_num')) == '1') {
$header[] = 'Employee No.';
}
if (e(Input::get('status')) == '1') {
$header[] = 'Status';
}
if (e(Input::get('warranty')) == '1') {
$header[] = 'Warranty';
$header[] = 'Warranty Expires';
}
if (e(Input::get('depreciation')) == '1') {
$header[] = 'Purchase Cost';
$header[] = 'Value';
$header[] = 'Diff';
}
if (e(Input::get('expected_checkin')) == '1') {
$header[] = trans('admin/hardware/form.expected_checkin');
}
if (e(Input::get('notes')) == '1') {
$header[] = trans('general.notes');
}
foreach ($customfields as $customfield) {
if (e(Input::get($customfield->db_column_name())) == '1') {
$header[] = $customfield->name;
}
}
$header = array_map('trim', $header);
$rows[] = implode($header, ',');
foreach ($assets as $asset) {
$row = [ ];
if (e(Input::get('company')) == '1') {
$row[] = is_null($asset->company) ? '' : '"'.$asset->company->name.'"';
// Open output stream
$handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
}
if (e(Input::get('asset_name')) == '1') {
$row[] = '"' .e($asset->name) . '"';
}
if (e(Input::get('asset_tag')) == '1') {
$row[] = e($asset->asset_tag);
}
if (e(Input::get('manufacturer')) == '1') {
if ($asset->model->manufacturer) {
$row[] = '"' .e($asset->model->manufacturer->name) . '"';
} else {
$row[] = '';
}
}
if (e(Input::get('model')) == '1') {
$row[] = '"' . e($asset->model->name) . '"';
$row[] = '"' . e($asset->model->model_number) . '"';
}
if (e(Input::get('category')) == '1') {
$row[] = '"' .e($asset->model->category->name) . '"';
$header = [];
if ($request->filled('company')) {
$header[] = trans('general.company');
}
if (e(Input::get('serial')) == '1') {
$row[] = e($asset->serial);
}
if (e(Input::get('purchase_date')) == '1') {
$row[] = e($asset->purchase_date);
}
if (e(Input::get('purchase_cost')) == '1' && ( e(Input::get('depreciation')) != '1' )) {
$row[] = '"' . Helper::formatCurrencyOutput($asset->purchase_cost) . '"';
}
if (e(Input::get('eol')) == '1') {
$row[] = '"' .($asset->present()->eol_date()) ? $asset->present()->eol_date() : ''. '"';
}
if (e(Input::get('order')) == '1') {
if ($asset->order_number) {
$row[] = e($asset->order_number);
} else {
$row[] = '';
}
}
if (e(Input::get('supplier')) == '1') {
if ($asset->supplier) {
$row[] = '"' .e($asset->supplier->name) . '"';
} else {
$row[] = '';
}
if ($request->filled('asset_name')) {
$header[] = trans('admin/hardware/form.name');
}
if (e(Input::get('location')) == '1') {
if($asset->assetLoc) {
$show_loc = $asset->assetLoc->present()->name();
} else {
$show_loc = 'Default location '.$asset->rtd_location_id.' is invalid';
}
$row[] = $show_loc;
if ($request->filled('asset_tag')) {
$header[] = trans('admin/hardware/table.asset_tag');
}
if ($request->filled('model')) {
$header[] = trans('admin/hardware/form.model');
$header[] = trans('general.model_no');
}
if ($request->filled('category')) {
$header[] = trans('general.category');
}
if ($request->filled('manufacturer')) {
$header[] = trans('admin/hardware/form.manufacturer');
}
if ($request->filled('serial')) {
$header[] = trans('admin/hardware/table.serial');
}
if ($request->filled('purchase_date')) {
$header[] = trans('admin/hardware/table.purchase_date');
}
if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) {
$header[] = trans('admin/hardware/table.purchase_cost');
}
if ($request->filled('eol')) {
$header[] = trans('admin/hardware/table.eol');
}
if ($request->filled('order')) {
$header[] = trans('admin/hardware/form.order');
}
if ($request->filled('supplier')) {
$header[] = trans('general.supplier');
}
if ($request->filled('location')) {
$header[] = trans('admin/hardware/table.location');
}
if ($request->filled('location_address')) {
$header[] = trans('general.address');
$header[] = trans('general.address');
$header[] = trans('general.city');
$header[] = trans('general.state');
$header[] = trans('general.country');
$header[] = trans('general.zip');
}
if ($request->filled('rtd_location')) {
$header[] = trans('admin/hardware/form.default_location');
}
if ($request->filled('rtd_location_address')) {
$header[] = trans('general.address');
$header[] = trans('general.address');
$header[] = trans('general.city');
$header[] = trans('general.state');
$header[] = trans('general.country');
$header[] = trans('general.zip');
}
if (e(Input::get('assigned_to')) == '1') {
if ($asset->assignedTo) {
$row[] = '"' .e($asset->assignedTo->present()->name()). '"';
} else {
$row[] = ''; // Empty string if unassigned
}
if ($request->filled('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto');
$header[] = trans('general.type');
}
if (e(Input::get('username')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->username). '"';
} else {
$row[] = ''; // Empty string if unassigned
}
if ($request->filled('username')) {
$header[] = 'Username';
}
if (e(Input::get('employee_num')) == '1') {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
$row[] = '"' .e($asset->assignedTo->employee_num). '"';
} else {
$row[] = ''; // Empty string if unassigned
}
if ($request->filled('employee_num')) {
$header[] = 'Employee No.';
}
if (e(Input::get('status')) == '1') {
if (( $asset->status_id == '0' ) && ( $asset->assigned_to == '0' )) {
$row[] = trans('general.ready_to_deploy');
} elseif (( $asset->status_id == '' ) && ( $asset->assigned_to == '0' )) {
$row[] = trans('general.pending');
} elseif ($asset->assetstatus) {
$row[] = '"' .e($asset->assetstatus->name). '"';
} else {
$row[] = '';
}
}
if (e(Input::get('warranty')) == '1') {
if ($asset->warranty_months) {
$row[] = $asset->warranty_months;
$row[] = $asset->present()->warrantee_expires();
} else {
$row[] = '';
$row[] = '';
}
}
if (e(Input::get('depreciation')) == '1') {
$depreciation = $asset->getDepreciatedValue();
$row[] = '"' . Helper::formatCurrencyOutput($asset->purchase_cost) . '"';
$row[] = '"' . Helper::formatCurrencyOutput($depreciation) . '"';
$row[] = '"' . Helper::formatCurrencyOutput($asset->purchase_cost) . '"';
}
if (e(Input::get('expected_checkin')) == '1') {
if ($asset->expected_checkin) {
$row[] = '"' .e($asset->expected_checkin). '"';
} else {
$row[] = ''; // Empty string if blankd
}
if ($request->filled('manager')) {
$header[] = trans('admin/users/table.manager');
}
if (e(Input::get('notes')) == '1') {
if ($asset->notes) {
$row[] = '"' .$asset->notes . '"';
} else {
$row[] = '';
}
if ($request->filled('department')) {
$header[] = trans('general.department');
}
if ($request->filled('status')) {
$header[] = trans('general.status');
}
if ($request->filled('warranty')) {
$header[] = 'Warranty';
$header[] = 'Warranty Expires';
}
if ($request->filled('depreciation')) {
$header[] = 'Value';
$header[] = 'Diff';
}
if ($request->filled('checkout_date')) {
$header[] = trans('admin/hardware/table.checkout_date');
}
if ($request->filled('expected_checkin')) {
$header[] = trans('admin/hardware/form.expected_checkin');
}
if ($request->filled('created_at')) {
$header[] = trans('general.created_at');
}
if ($request->filled('updated_at')) {
$header[] = trans('general.updated_at');
}
if ($request->filled('last_audit_date')) {
$header[] = trans('general.last_audit');
}
if ($request->filled('next_audit_date')) {
$header[] = trans('general.next_audit_date');
}
if ($request->filled('notes')) {
$header[] = trans('general.notes');
}
foreach ($customfields as $customfield) {
$column_name = $customfield->db_column_name();
if (e(Input::get($customfield->db_column_name())) == '1') {
$row[] = str_replace(",", "\,", $asset->$column_name);
$header[] = $customfield->name;
}
}
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Starting headers: '.$executionTime);
fputcsv($handle, $header);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Added headers: '.$executionTime);
$rows[] = implode($row, ',');
}
// spit out a csv
if (array_filter($rows)) {
$csv = implode($rows, "\n");
$response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename='.date('Y-m-d-His').'-custom-asset-report.csv');
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer','supplier');
if ($request->filled('by_location_id')) {
$assets->where('assets.location_id', $request->input('by_location_id'));
}
if ($request->filled('by_rtd_location_id')) {
\Log::debug('RTD location should match: '.$request->input('by_rtd_location_id'));
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
}
if ($request->filled('by_supplier_id')) {
$assets->where('assets.supplier_id', $request->input('by_supplier_id'));
}
if ($request->filled('by_company_id')) {
$assets->where('assets.company_id', $request->input('by_company_id'));
}
if ($request->filled('by_model_id')) {
$assets->where('assets.model_id', $request->input('by_model_id'));
}
if ($request->filled('by_category_id')) {
$assets->InCategory($request->input('by_category_id'));
}
if ($request->filled('by_manufacturer_id')) {
$assets->ByManufacturer($request->input('by_manufacturer_id'));
}
if ($request->filled('by_order_number')) {
$assets->where('assets.order_number', $request->input('by_order_number'));
}
if ($request->filled('by_status_id')) {
$assets->where('assets.status_id', $request->input('by_status_id'));
}
if (($request->filled('purchase_start')) && ($request->filled('purchase_end'))) {
$assets->whereBetween('assets.purchase_date', [$request->input('purchase_start'), $request->input('purchase_end')]);
}
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
$assets->whereBetween('assets.created_at', [$request->input('created_start'), $request->input('created_end')]);
}
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
$assets->orderBy('assets.created_at', 'ASC')->chunk(20, function($assets) use($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('Walking results: '.$executionTime);
$count = 0;
foreach ($assets as $asset) {
$count++;
$row = [];
if ($request->filled('company')) {
$row[] = ($asset->company) ? $asset->company->name : '';
}
if ($request->filled('asset_name')) {
$row[] = ($asset->name) ? $asset->name : '';
}
if ($request->filled('asset_tag')) {
$row[] = ($asset->asset_tag) ? $asset->asset_tag : '';
}
if ($request->filled('model')) {
$row[] = ($asset->model) ? $asset->model->name : '';
$row[] = ($asset->model) ? $asset->model->model_number : '';
}
if ($request->filled('category')) {
$row[] = (($asset->model) && ($asset->model->category)) ? $asset->model->category->name : '';
}
if ($request->filled('manufacturer')) {
$row[] = ($asset->model && $asset->model->manufacturer) ? $asset->model->manufacturer->name : '';
}
if ($request->filled('serial')) {
$row[] = ($asset->serial) ? $asset->serial : '';
}
if ($request->filled('purchase_date')) {
$row[] = ($asset->purchase_date) ? $asset->purchase_date : '';
}
if ($request->filled('purchase_cost')) {
$row[] = ($asset->purchase_cost) ? Helper::formatCurrencyOutput($asset->purchase_cost) : '';
}
if ($request->filled('eol')) {
$row[] = ($asset->purchase_date!='') ? $asset->present()->eol_date() : '';
}
if ($request->filled('order')) {
$row[] = ($asset->order_number) ? $asset->order_number : '';
}
if ($request->filled('supplier')) {
$row[] = ($asset->supplier) ? $asset->supplier->name : '';
}
if ($request->filled('location')) {
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
}
if ($request->filled('location_address')) {
$row[] = ($asset->location) ? $asset->location->address : '';
$row[] = ($asset->location) ? $asset->location->address2 : '';
$row[] = ($asset->location) ? $asset->location->city : '';
$row[] = ($asset->location) ? $asset->location->state : '';
$row[] = ($asset->location) ? $asset->location->country : '';
$row[] = ($asset->location) ? $asset->location->zip : '';
}
if ($request->filled('rtd_location')) {
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : '';
}
if ($request->filled('rtd_location_address')) {
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->address : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->address2 : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->city : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->state : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->country : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->zip : '';
}
if ($request->filled('assigned_to')) {
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : '');
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
}
if ($request->filled('username')) {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->username : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('employee_num')) {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->employee_num : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('manager')) {
if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedto) && ($asset->assignedto->manager)) ? $asset->assignedto->manager->present()->fullName : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('department')) {
if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedto) && ($asset->assignedto->department)) ? $asset->assignedto->department->name : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warrantee_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
}
if ($request->filled('checkout_date')) {
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}
if ($request->filled('expected_checkin')) {
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
}
if ($request->filled('created_at')) {
$row[] = ($asset->created_at) ? $asset->created_at : '';
}
if ($request->filled('updated_at')) {
$row[] = ($asset->updated_at) ? $asset->updated_at : '';
}
if ($request->filled('last_audit_date')) {
$row[] = ($asset->last_audit_date) ? $asset->last_audit_date : '';
}
if ($request->filled('next_audit_date')) {
$row[] = ($asset->next_audit_date) ? $asset->next_audit_date : '';
}
if ($request->filled('notes')) {
$row[] = ($asset->notes) ? $asset->notes : '';
}
foreach ($customfields as $customfield) {
$column_name = $customfield->db_column_name();
if ($request->filled($customfield->db_column_name())) {
$row[] = $asset->$column_name;
}
}
fputcsv($handle, $row);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('-- Record '.$count.' Asset ID:' .$asset->id. ' in '. $executionTime);
}
});
// Close the output stream
fclose($handle);
$executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
\Log::debug('-- SCRIPT COMPLETED IN '. $executionTime);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition'
=> 'attachment; filename="custom-assets-report-'.date('Y-m-d-his').'.csv"',
]);
return $response;
return $response;
} else {
return redirect()->to("reports/custom")
->with('error', trans('admin/reports/message.error'));
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Http\Controllers;
use enshrined\svgSanitize\Sanitizer;
use Input;
use Lang;
use Illuminate\Http\Request;
@@ -21,6 +22,8 @@ use App\Models\User;
use App\Http\Requests\SetupUserRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsLdapRequest;
use App\Helpers\Helper;
use App\Notifications\FirstAdminNotification;
/**
* This controller handles all actions related to Settings for
@@ -56,9 +59,10 @@ class SettingsController extends Controller
$protocol = array_key_exists('HTTPS', $_SERVER) && ( $_SERVER['HTTPS'] == "on") ? 'https://' : 'http://';
$host = $_SERVER['SERVER_NAME'];
if (($protocol === 'http://' && $_SERVER['SERVER_PORT'] != '80') || ($protocol === 'https://' && $_SERVER['SERVER_PORT'] != '443')) {
$host .= ':' . $_SERVER['SERVER_PORT'];
$host = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : null;
$port = array_key_exists('SERVER_PORT', $_SERVER) ? $_SERVER['SERVER_PORT'] : null;
if (($protocol === 'http://' && $port != '80') || ($protocol === 'https://' && $port != '443')) {
$host .= ':' . $port;
}
$pageURL = $protocol . $host . $_SERVER['REQUEST_URI'];
@@ -66,7 +70,7 @@ class SettingsController extends Controller
$start_settings['url_config'] = url('/');
$start_settings['real_url'] = $pageURL;
// Curl the .env file to make sure it's not accessible via a browser
$ch = curl_init($protocol . $host.'/.env');
curl_setopt($ch, CURLOPT_HEADER, true); // we want headers
@@ -136,28 +140,6 @@ class SettingsController extends Controller
->with('section', 'Pre-Flight Check');
}
/**
* Test the email configuration
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
*/
public function ajaxTestEmail()
{
try {
Mail::send('emails.test', [], function ($m) {
$m->to(config('mail.from.address'), config('mail.from.name'));
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.test_email'));
});
return 'success';
} catch (Exception $e) {
return 'error';
}
}
/**
* Save the first admin user from Setup.
@@ -171,28 +153,31 @@ class SettingsController extends Controller
$user = new User;
$user->first_name = $data['first_name']= e(Input::get('first_name'));
$user->last_name = e(Input::get('last_name'));
$user->email = $data['email'] = e(Input::get('email'));
$user->first_name = $data['first_name']= $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->email = $data['email'] = $request->input('email');
$user->activated = 1;
$permissions = array('superuser' => 1);
$user->permissions = json_encode($permissions);
$user->username = $data['username'] = e(Input::get('username'));
$user->password = bcrypt(Input::get('password'));
$data['password'] = Input::get('password');
$user->username = $data['username'] = $request->input('username');
$user->password = bcrypt($request->input('password'));
$data['password'] = $request->input('password');
$settings = new Setting;
$settings->site_name = e(Input::get('site_name'));
$settings->alert_email = e(Input::get('email'));
$settings->full_multiple_companies_support = $request->input('full_multiple_companies_support', 0);
$settings->site_name = $request->input('site_name');
$settings->alert_email = $request->input('email');
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
$settings->locale = 'en';
$settings->default_currency = 'USD';
$settings->locale = $request->input('locale', 'en');
$settings->default_currency = $request->input('default_currency', "USD");
$settings->user_id = 1;
$settings->email_domain = e(Input::get('email_domain'));
$settings->email_format = e(Input::get('email_format'));
$settings->email_domain = $request->input('email_domain');
$settings->email_format = $request->input('email_format');
$settings->next_auto_tag_base = 1;
$settings->auto_increment_assets = $request->input('auto_increment_assets', 0);
$settings->auto_increment_prefix = $request->input('auto_increment_prefix');
if ((!$user->isValid()) || (!$settings->isValid())) {
@@ -203,11 +188,19 @@ class SettingsController extends Controller
$settings->save();
if (Input::get('email_creds')=='1') {
Mail::send(['text' => 'emails.firstadmin'], $data, function ($m) use ($data) {
$data = array();
$data['email'] = $user->email;
$data['username'] = $user->username;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['password'] = $request->input('password');
$user->notify(new FirstAdminNotification($data));
/*Mail::send(['text' => 'emails.firstadmin'], $data, function ($m) use ($data) {
$m->to($data['email'], $data['first_name']);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.your_credentials'));
});
});*/
}
@@ -263,8 +256,8 @@ class SettingsController extends Controller
$output = Artisan::output();
if ((!file_exists(storage_path().'/oauth-private.key')) || (!file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('passport:install');
Artisan::call('migrate', ['--force' => true]);
Artisan::call('passport:install');
}
@@ -332,8 +325,20 @@ class SettingsController extends Controller
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->modellist_displays = '';
if (($request->filled('show_in_model_list')) && (count($request->input('show_in_model_list')) > 0))
{
$setting->modellist_displays = implode(',', $request->input('show_in_model_list'));
}
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->load_remote = $request->input('load_remote', '0');
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
$setting->show_archived_in_list = $request->input('show_archived_in_list', '0');
$setting->dashboard_message = $request->input('dashboard_message');
$setting->email_domain = $request->input('email_domain');
$setting->email_format = $request->input('email_format');
$setting->username_format = $request->input('username_format');
@@ -344,6 +349,7 @@ class SettingsController extends Controller
$setting->default_eula_text = $request->input('default_eula_text');
$setting->thumbnail_max_h = $request->input('thumbnail_max_h');
$setting->privacy_policy_link = $request->input('privacy_policy_link');
if (Input::get('per_page')!='') {
$setting->per_page = $request->input('per_page');
@@ -391,6 +397,13 @@ class SettingsController extends Controller
$setting->brand = $request->input('brand', '1');
$setting->header_color = $request->input('header_color');
$setting->support_footer = $request->input('support_footer');
$setting->version_footer = $request->input('version_footer');
$setting->footer_text = $request->input('footer_text');
$setting->skin = $request->input('skin');
$setting->show_url_in_emails = $request->input('show_url_in_emails', '0');
$setting->logo_print_assets = $request->input('logo_print_assets', '0');
// Only allow the site name and CSS to be changed if lock_passwords is false
@@ -414,12 +427,23 @@ class SettingsController extends Controller
$file_name = "logo.".$image->getClientOriginalExtension();
$path = public_path('uploads');
if ($image->getClientOriginalExtension()!='svg') {
Image::make($image->getRealPath())->resize(null, 40, function ($constraint) {
Image::make($image->getRealPath())->resize(null, 150, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->save($path.'/'.$file_name);
} else {
$image->move($path, $file_name);
// This is kinda copypasta from the ImageUploadRequest - should refactor the ImageUploadRequest to better handle maybe
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($image->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
file_put_contents($path.'/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$setting->logo = $file_name;
}
@@ -472,13 +496,18 @@ class SettingsController extends Controller
$setting->two_factor_enabled = $request->input('two_factor_enabled');
}
# remote user login
$setting->login_remote_user_enabled = (int)$request->input('login_remote_user_enabled');
$setting->login_common_disabled = (int)$request->input('login_common_disabled');
$setting->login_remote_user_custom_logout_url = $request->input('login_remote_user_custom_logout_url');
}
$setting->pwd_secure_uncommon = (int) $request->input('pwd_secure_uncommon');
$setting->pwd_secure_min = (int) $request->input('pwd_secure_min');
$setting->pwd_secure_complexity = '';
if ($request->has('pwd_secure_complexity')) {
if ($request->filled('pwd_secure_complexity')) {
$setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity'));
}
@@ -521,7 +550,9 @@ class SettingsController extends Controller
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->locale = $request->input('locale', 'en');
if (!config('app.lock_passwords')) {
$setting->locale = $request->input('locale', 'en');
}
$setting->default_currency = $request->input('default_currency', '$');
$setting->date_display_format = $request->input('date_display_format');
$setting->time_display_format = $request->input('time_display_format');
@@ -565,13 +596,17 @@ class SettingsController extends Controller
$alert_email = rtrim($request->input('alert_email'), ',');
$alert_email = trim($alert_email);
$admin_cc_email = rtrim($request->input('admin_cc_email'), ',');
$admin_cc_email = trim($admin_cc_email);
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
$setting->audit_interval = $request->input('audit_interval');
$setting->audit_warning_days = $request->input('audit_warning_days');
$setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0');
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -610,14 +645,24 @@ class SettingsController extends Controller
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
$setting->slack_endpoint = $request->input('slack_endpoint');
$setting->slack_channel = $request->input('slack_channel');
$setting->slack_botname = $request->input('slack_botname');
$validatedData = $request->validate([
'slack_endpoint' => 'url|required_with:slack_channel|nullable',
'slack_channel' => 'regex:/(?<!\w)#\w+/|required_with:slack_endpoint|nullable',
'slack_botname' => 'string|nullable',
]);
if ($setting->save()) {
if ($validatedData) {
$setting->slack_endpoint = $request->input('slack_endpoint');
$setting->slack_channel = $request->input('slack_channel');
$setting->slack_botname = $request->input('slack_botname');
$setting->save();
return redirect()->route('settings.index')
->with('success', trans('admin/settings/message.update.success'));
}
return redirect()->back()->withInput()->withErrors($setting->getErrors());
}
@@ -767,25 +812,38 @@ class SettingsController extends Controller
$setting->labels_fontsize = $request->input('labels_fontsize');
$setting->labels_pagewidth = $request->input('labels_pagewidth');
$setting->labels_pageheight = $request->input('labels_pageheight');
$setting->labels_display_company_name = $request->input('labels_display_company_name', '0');
if (Input::has('labels_display_name')) {
if ($request->filled('labels_display_name')) {
$setting->labels_display_name = 1;
} else {
$setting->labels_display_name = 0;
}
if (Input::has('labels_display_serial')) {
if ($request->filled('labels_display_serial')) {
$setting->labels_display_serial = 1;
} else {
$setting->labels_display_serial = 0;
}
if (Input::has('labels_display_tag')) {
if ($request->filled('labels_display_tag')) {
$setting->labels_display_tag = 1;
} else {
$setting->labels_display_tag = 0;
}
if ($request->filled('labels_display_tag')) {
$setting->labels_display_tag = 1;
} else {
$setting->labels_display_tag = 0;
}
if ($request->filled('labels_display_model')) {
$setting->labels_display_model = 1;
} else {
$setting->labels_display_model = 0;
}
if ($setting->save()) {
@@ -829,7 +887,7 @@ class SettingsController extends Controller
$setting->ldap_server = $request->input('ldap_server');
$setting->ldap_server_cert_ignore = $request->input('ldap_server_cert_ignore', false);
$setting->ldap_uname = $request->input('ldap_uname');
if (Input::has('ldap_pword')) {
if (Input::filled('ldap_pword')) {
$setting->ldap_pword = Crypt::encrypt($request->input('ldap_pword'));
}
$setting->ldap_basedn = $request->input('ldap_basedn');
@@ -846,6 +904,7 @@ class SettingsController extends Controller
$setting->is_ad = $request->input('is_ad', '0');
$setting->ldap_tls = $request->input('ldap_tls', '0');
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -870,7 +929,7 @@ class SettingsController extends Controller
public function getBackups()
{
$path = storage_path().'/app/'.config('laravel-backup.backup.name');
$path = storage_path().'/app/'.config('backup.backup.name');
$files = array();
@@ -907,12 +966,30 @@ class SettingsController extends Controller
public function postBackups()
{
if (!config('app.lock_passwords')) {
Artisan::call('backup:run');
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.generated'));
} else {
return redirect()->to("settings.backups.index")->with('error', trans('general.feature_disabled'));
}
$output = Artisan::output();
// Backup completed
if (!preg_match('/failed/', $output)) {
return redirect()->route('settings.backups.index')
->with('success', trans('admin/settings/message.backup.generated'));
}
$formatted_output = str_replace('Backup completed!', '', $output);
$output_split = explode('...', $formatted_output);
if (array_key_exists(2, $output_split)) {
return redirect()->route("settings.backups.index")->with('error', $output_split[2]);
}
return redirect()->route("settings.backups.index")->with('error', $formatted_output);
}
return redirect()->route("settings.backups.index")->with('error', trans('general.feature_disabled'));
}
@@ -928,7 +1005,7 @@ class SettingsController extends Controller
public function downloadFile($filename = null)
{
if (!config('app.lock_passwords')) {
$path = storage_path().'/app/'.config('laravel-backup.backup.name');
$path = storage_path().'/app/'.config('backup.backup.name');
$file = $path.'/'.$filename;
if (file_exists($file)) {
return Response::download($file);
@@ -957,7 +1034,7 @@ class SettingsController extends Controller
if (!config('app.lock_passwords')) {
$path = storage_path().'/app/'.config('laravel-backup.backup.name');
$path = storage_path().'/app/'.config('backup.backup.name');
$file = $path.'/'.$filename;
if (file_exists($file)) {
unlink($file);
@@ -1023,4 +1100,28 @@ class SettingsController extends Controller
public function api() {
return view('settings.api');
}
/**
* Test the email configuration
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
*/
public function ajaxTestEmail()
{
try {
Mail::send('emails.test', [], function ($m) {
$m->to(config('mail.from.address'), config('mail.from.name'));
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.test_email'));
});
return response()->json(Helper::formatStandardApiResponse('success', null, 'Maiol sent!'));
} catch (Exception $e) {
return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage()));
}
}
}

View File

@@ -32,18 +32,18 @@ class StatuslabelsController extends Controller
public function index()
{
return view('statuslabels/index', compact('statuslabels'));
$this->authorize('view', Statuslabel::class);
return view('statuslabels.index');
}
public function show($id)
{
$statuslabel = Statuslabel::find($id);
if (isset($statuslabel->id)) {
return view('statuslabels/view', compact('statuslabel'));
$this->authorize('view', Statuslabel::class);
if ($statuslabel = Statuslabel::find($id)) {
return view('statuslabels.view')->with('statuslabel', $statuslabel);
}
return redirect()->route('statuslabels.index')->with('error', trans('admin/locations/message.does_not_exist', compact('id')));
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
@@ -56,6 +56,7 @@ class StatuslabelsController extends Controller
public function create()
{
// Show the page
$this->authorize('create', Statuslabel::class);
$item = new Statuslabel;
$use_statuslabel_type = $item->getStatuslabelType();
$statuslabel_types = Helper::statusTypeList();
@@ -73,10 +74,11 @@ class StatuslabelsController extends Controller
public function store(Request $request)
{
$this->authorize('create', Statuslabel::class);
// create a new model instance
$statusLabel = new Statuslabel();
if (!$request->has('statuslabel_types')) {
if (!$request->filled('statuslabel_types')) {
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);
}
@@ -91,9 +93,9 @@ class StatuslabelsController extends Controller
$statusLabel->archived = $statusType['archived'];
$statusLabel->color = Input::get('color');
$statusLabel->show_in_nav = Input::get('show_in_nav', 0);
$statusLabel->default_label = Input::get('default_label', 0);
// Was the asset created?
if ($statusLabel->save()) {
// Redirect to the new Statuslabel page
return redirect()->route('statuslabels.index')->with('success', trans('admin/statuslabels/message.create.success'));
@@ -101,35 +103,6 @@ class StatuslabelsController extends Controller
return redirect()->back()->withInput()->withErrors($statusLabel->getErrors());
}
/**
* @param Request $request
* @return JsonResponse
*/
public function apiStore(Request $request)
{
$statuslabel = new Statuslabel();
if (!$request->has('statuslabel_types')) {
return JsonResponse::create(["error" => trans('validation.statuslabel_type')], 500);
}
$statustype = Statuslabel::getStatuslabelTypesForDB(Input::get('statuslabel_types'));
$statuslabel->name = Input::get('name');
$statuslabel->user_id = Auth::id();
$statuslabel->notes = '';
$statuslabel->deployable = $statustype['deployable'];
$statuslabel->pending = $statustype['pending'];
$statuslabel->archived = $statustype['archived'];
if ($statuslabel->isValid()) {
$statuslabel->save();
// Redirect to the new Statuslabel page
return JsonResponse::create($statuslabel);
}
return JsonResponse::create(["error" => $statuslabel->getErrors()->first()], 500);
}
/**
* Statuslabel update.
*
@@ -138,6 +111,7 @@ class StatuslabelsController extends Controller
*/
public function edit($statuslabelId = null)
{
$this->authorize('update', Statuslabel::class);
// Check if the Statuslabel exists
if (is_null($item = Statuslabel::find($statuslabelId))) {
// Redirect to the blogs management page
@@ -160,13 +134,14 @@ class StatuslabelsController extends Controller
*/
public function update(Request $request, $statuslabelId = null)
{
$this->authorize('update', Statuslabel::class);
// Check if the Statuslabel exists
if (is_null($statuslabel = Statuslabel::find($statuslabelId))) {
// Redirect to the blogs management page
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
if (!$request->has('statuslabel_types')) {
if (!$request->filled('statuslabel_types')) {
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);
}
@@ -180,6 +155,7 @@ class StatuslabelsController extends Controller
$statuslabel->archived = $statustype['archived'];
$statuslabel->color = Input::get('color');
$statuslabel->show_in_nav = Input::get('show_in_nav', 0);
$statuslabel->default_label = Input::get('default_label', 0);
// Was the asset created?
@@ -198,19 +174,18 @@ class StatuslabelsController extends Controller
*/
public function destroy($statuslabelId)
{
$this->authorize('delete', Statuslabel::class);
// Check if the Statuslabel exists
if (is_null($statuslabel = Statuslabel::find($statuslabelId))) {
// Redirect to the blogs management page
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.not_found'));
}
if ($statuslabel->has_assets() == 0) {
// Check that there are no assets associated
if ($statuslabel->assets()->count() == 0) {
$statuslabel->delete();
// Redirect to the statuslabels management page
return redirect()->route('statuslabels.index')->with('success', trans('admin/statuslabels/message.delete.success'));
}
// Redirect to the asset management page
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.assoc_assets'));
}

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