Compare commits

..

836 Commits

Author SHA1 Message Date
snipe 046ce19dbb Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-06-30 11:46:24 +01:00
snipe ba94f1b920 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-06-30 11:45:43 +01:00
snipe edcd46dd67 Updated language strings
Signed-off-by: snipe <snipe@snipe.net>
2025-06-30 11:44:46 +01:00
snipe 5cf6c89dde Merge pull request #17322 from grokability/fixes-#8484-add-supplier-to-license-relationship
Fixed #8484 - added supplier to license relationship
2025-06-30 11:29:07 +01:00
snipe 58676b1f83 Fixed #8484 - added supplier to license relationship
Signed-off-by: snipe <snipe@snipe.net>
2025-06-30 11:27:30 +01:00
snipe 8ff7c30e5a Merge pull request #17315 from grokability/traitify-uploads
Added HasUploads trait and remove uploads method for models
2025-06-27 20:00:28 +01:00
snipe cd989768d4 Added HasUploads trait and remove uploads method for models
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 19:32:22 +01:00
snipe 33263f5a93 Merge remote-tracking branch 'origin/develop' 2025-06-27 13:52:09 +01:00
snipe 6cbdefe3d9 Small regressions
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 13:04:02 +01:00
snipe 8ef8e76300 Merge remote-tracking branch 'origin/develop' 2025-06-27 12:48:54 +01:00
snipe c6ecc0d8e8 Merge pull request #17311 from grokability/rwork-bulk-api-to-smaller-pr
Fixed #9413 and rework upload API for bulk and better responses (refactor of #16964)
2025-06-27 12:43:34 +01:00
snipe e0f5663bf4 Requested changes
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 12:37:11 +01:00
snipe aafc8996c1 phpcbf fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 12:18:54 +01:00
snipe 128da40cbf Added comments
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 12:03:12 +01:00
snipe ea0460e97e Remove unused API files controllers
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 11:51:36 +01:00
snipe d8e7123576 Added uploaded files API controllers and presenters
Signed-off-by: snipe <snipe@snipe.net>
2025-06-27 11:37:31 +01:00
snipe 73cfdae9e7 Merge remote-tracking branch 'origin/develop' 2025-06-25 15:00:46 +01:00
snipe 6f45ec655f Merge pull request #16857 from realchrisolin/generic_tape
Generic tape
2025-06-25 14:59:57 +01:00
snipe 54a3e41281 Merge remote-tracking branch 'origin/develop' 2025-06-25 14:58:46 +01:00
snipe ec17c168ea Merge pull request #17222 from grokability/fixes-#17221-move-table-featueres-into-js
Fixed #17221 - Moved common table elements to partial
2025-06-25 14:52:17 +01:00
snipe 119b097521 More cleanup
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:43:09 +01:00
snipe c731633a84 Fixed weird formatting
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:40:26 +01:00
snipe 6a4d6ade39 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:39:46 +01:00
snipe b3b4697fc9 More whitespace cleanup
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:38:25 +01:00
snipe 72c706d697 Aaaand one more
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:30:34 +01:00
snipe 8a7af24bd4 More whitespace
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:29:33 +01:00
snipe dd01bd3e5f Cleaned up whitespace
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:29:01 +01:00
snipe 59cade9f82 Cleaned up whitespace
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:28:20 +01:00
snipe 6bb9b79832 Tweaked dashboard
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:11:29 +01:00
snipe f8fe7b5803 Removed whitespace
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:11:19 +01:00
snipe 4d6279d61c Added JS to handle data-dash attribute overrides
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:11:04 +01:00
snipe 5ef581f328 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 14:10:51 +01:00
snipe d59ba6da84 Merge remote-tracking branch 'origin/develop' 2025-06-25 13:11:39 +01:00
snipe 20a59c343e Merge pull request #17296 from uberbrady/improve_fmcs_locations_test
Fixed #17190 and [FD-49375]: Do FMCS testing 'async' to keep from blowing out the whole settings page
2025-06-25 13:10:54 +01:00
snipe d6feb522b7 Merge pull request #17297 from grokability/fixes-#17259-#15239-external-avatar
Fixed #15239 and #17259 - better handle external avatars
2025-06-25 13:09:33 +01:00
snipe 951aee8292 Fixes #15239 and #17259 - better handle external avatars
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 13:03:51 +01:00
Brady Wetherington 6e2d7912b5 Do the FMCS testing 'async' to keep from blowing out the whole page 2025-06-25 12:59:37 +01:00
snipe 1b28b06934 Merge remote-tracking branch 'origin/develop' 2025-06-25 11:04:15 +01:00
snipe b20925b550 Fixed #17282 - removed erroneous update gate for user-license endpoint
Signed-off-by: snipe <snipe@snipe.net>
2025-06-25 11:03:06 +01:00
snipe 26774b4193 Merge pull request #17287 from uberbrady/limit_license_seat_increments
Fixed [FD-45786] - Limit changing of asset seat count to no more than 10k at a time
2025-06-24 17:43:02 +01:00
Brady Wetherington a7a597d609 Added some tests around license seat changes 2025-06-24 17:35:49 +01:00
snipe 1c12b9278a Merge pull request #17116 from marcusmoore/17065-send-alert-to-assigner-upon-response
Fixed #17065 - allow sending acceptance alert to initiator.
2025-06-24 17:30:47 +01:00
Brady Wetherington de4764bd05 Limit changing of asset seat count to no more than 10k at a time 2025-06-24 13:53:11 +01:00
snipe ff3e69a56c Merge remote-tracking branch 'origin/develop' 2025-06-23 20:51:24 +01:00
snipe c1e7a78d23 Merge pull request #17284 from Godmartinz/fix-depreciation-choice-in-transformer
FIXED: #14869 changes the depreciation method selected for Assets index table
2025-06-23 20:49:25 +01:00
Godfrey M 8894bb91cc fix method choice in asset transformer for depreciations 2025-06-23 12:37:29 -07:00
snipe c955126f01 Merge pull request #17283 from Godmartinz/multiclick_checkout_bug
Fixed #14077: Disables checkout button after submitting
2025-06-23 20:30:38 +01:00
Godfrey M ce53b48d04 disable checkout button after submitting 2025-06-23 12:18:24 -07:00
snipe 6b975a5fb4 Merge remote-tracking branch 'origin/develop' 2025-06-23 16:41:42 +01:00
snipe 6015aeddee Merge pull request #17209 from Godmartinz/saml_xml_update_bug
Fixed #17089: SAML metadata now updating with new XML uploads
2025-06-23 16:40:35 +01:00
snipe 7b04e30964 Merge pull request #17280 from grokability/fixes/#7246-manager-id-in-import
Fixed #7246 - added manager employee/username number to importer
2025-06-23 16:07:14 +01:00
snipe 6794f5e783 Added logging for manager import
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 15:46:04 +01:00
snipe 6e41ceff39 Fixed parameter order
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 15:41:56 +01:00
snipe 7ab47ff0de Fixed #7246 - added manager employee number to importer
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 15:11:57 +01:00
snipe 3a02b15124 Merge remote-tracking branch 'origin/develop' 2025-06-23 12:38:40 +01:00
snipe 92d24d8702 Merge pull request #17277 from grokability/#17264-add-notes-to-bulk
Fixed #17264: add notes to bulk asset edit
2025-06-23 12:36:50 +01:00
snipe bcbfd46682 Update controller
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 12:32:33 +01:00
snipe bfd96a695f Add notes field and nulling checkbox
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 12:32:28 +01:00
snipe f27e8534dc Make sure $item exists
Signed-off-by: snipe <snipe@snipe.net>
2025-06-23 12:32:15 +01:00
snipe 040cd7ddbf Updated string to use shorter version
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 20:56:37 +01:00
snipe 5f73d81935 Merge remote-tracking branch 'origin/develop' 2025-06-22 20:14:59 +01:00
snipe 8d6b21a076 Merge pull request #17241 from spencerrlongg/bug/17239-possible-500-when-checking-in-license-assigned-to-a-soft-deleted-user
Fixed #17239 - Add `withTrashed` User Search On License Checkin
2025-06-22 20:14:03 +01:00
snipe 2d36b25017 Merge pull request #17258 from Robert-Azelis/patch-13
Company info - user print assets
2025-06-22 20:13:24 +01:00
snipe 002b5c0f6f Merge remote-tracking branch 'origin/develop' 2025-06-22 20:07:52 +01:00
snipe 1f9e4306ae Fixed #17261 - check for either old or new label engine QR
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 20:07:30 +01:00
snipe 8086842570 Merge remote-tracking branch 'origin/develop' 2025-06-22 19:42:03 +01:00
snipe 5242e0b36e Merge pull request #17270 from grokability/fixes/#17249-order-by-location-in-maintenances
Fixed #17249 - sort by location in asset maintenances
2025-06-22 19:41:14 +01:00
snipe e50505532e Fixes #17249 - sort by location in asset maintenances
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:38:39 +01:00
snipe 51f2d5a664 Merge remote-tracking branch 'origin/develop' 2025-06-22 19:30:42 +01:00
snipe f05ef18d55 Merge pull request #17269 from grokability/fixes/#17256-user-eula-view
Fixed #17256: fixed permissions for non-super-admins to view their own EULAs
2025-06-22 19:29:41 +01:00
snipe f6eccd7277 Added profile routes
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:25:30 +01:00
snipe 4d1258c64b Fixed downloadformatter
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:25:17 +01:00
snipe 103cbfd038 Use profile eula API view
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:25:05 +01:00
snipe 47069ad3f4 Added stored eula method on profile
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:24:45 +01:00
snipe 317f620992 Added profile controller
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:24:19 +01:00
snipe 8f43694582 Added eula download with user check in profile controller
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:24:06 +01:00
snipe df30076ffd Update gate for user
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 19:23:50 +01:00
snipe f81750617e Fixed mismatched html header tag
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 17:30:13 +01:00
Robert-Azelis e4534c4319 Company info - user print assets 2025-06-20 10:33:09 +02:00
snipe d6c09aae6b Updated paveit command to delete by user_id instead of ID
Signed-off-by: snipe <snipe@snipe.net>
2025-06-18 21:15:04 +01:00
spencerrlongg d3d5230d0c add with trashed to user search 2025-06-17 19:15:13 -05:00
Marcus Moore 6f3c5c44a5 Remove assertion 2025-06-17 14:31:14 -07:00
Marcus Moore 67b32ca14d Mail content improvements 2025-06-17 14:22:54 -07:00
Marcus Moore bffaf477ea Method order 2025-06-17 14:22:44 -07:00
Marcus Moore cba45ece12 Add image 2025-06-17 13:57:40 -07:00
Marcus Moore 3290d7f401 Add translations 2025-06-17 13:27:05 -07:00
Marcus Moore ef3827376d Add todo 2025-06-17 13:17:42 -07:00
Marcus Moore 4ae8a91051 Remove unused method 2025-06-17 12:34:43 -07:00
Marcus Moore ff4819ac68 Get licenses working 2025-06-17 12:34:16 -07:00
Marcus Moore 58af133853 Re-add some test cases 2025-06-17 11:51:33 -07:00
Godfrey M 8199cd2118 comment merge methods for now 2025-06-17 10:59:57 -07:00
snipe b74b76de75 Merge remote-tracking branch 'origin/develop' 2025-06-17 18:17:10 +01:00
snipe 9f02b80cf1 Merge pull request #17233 from grokability/fixes-#17232-duplicate-expected-checkin-field
Fixed #17232 - removed duplicate expected_checkin field on asset edit/create
2025-06-17 18:14:36 +01:00
snipe d3e4e81168 Fixed #17232 - removed duplicate expected_checkin field
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 18:08:21 +01:00
snipe a1b1498106 Merge remote-tracking branch 'origin/develop' 2025-06-17 17:51:37 +01:00
snipe 38195c0a8f Merge pull request #17225 from grokability/fixes-ui-issues-in-manager-view
Fixes #17227 and #17228 - UI issues in manager view
2025-06-17 17:28:52 +01:00
snipe 5fa11e4278 Updated language strings
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 16:57:26 +01:00
snipe c39b52fcb5 Move select list closer to the table, removed awkward icon
This makes the view more consistent with the normal layout and removes the CSRF that was being passed in the GET on the form.submit

Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 16:57:17 +01:00
snipe ec65fc1e65 Move manager view option to misc section
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 16:56:56 +01:00
snipe 32d8646d96 Add @lukaskraic as a contributor 2025-06-17 16:26:17 +01:00
snipe e8835fc2b1 Merge pull request #17096 from lukaskraic/feature/manager-view-v2
Manager View Feature
2025-06-17 16:24:40 +01:00
snipe 1158851ea7 Merge remote-tracking branch 'origin/develop' 2025-06-17 15:57:12 +01:00
snipe 054a06c5dc Update resources/lang/en-US/admin/settings/general.php
Co-authored-by: Marcus Moore <contact@marcusmoore.io>
2025-06-17 15:39:26 +01:00
snipe 9c61d2eb22 Removed common elements in tables
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 14:25:02 +01:00
snipe d66b6cfee6 Merge pull request #17220 from grokability/added-advanced-search-to-activity-log
Added advanced search to activity log
2025-06-17 13:24:31 +01:00
snipe 89c0427b2f Added fullscreen option
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 13:24:02 +01:00
snipe 3fec10d447 Added advanced search to activity log
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 13:20:39 +01:00
snipe e1ab9e959e Merge remote-tracking branch 'origin/develop' 2025-06-17 13:03:27 +01:00
snipe f8b4981bfe Merge pull request #17219 from grokability/fixes-#17213-search-by-admin
Fixes #17213:  search by admin (created_by) in activity report
2025-06-17 13:02:51 +01:00
snipe 130669a2f9 Added adminuser relationship in Searchable to concat first name and last name
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 13:01:57 +01:00
snipe c2c79ee231 Make created_by searchable
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 12:56:39 +01:00
snipe 86f10bd702 Added employee number to searchable relation
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 12:56:30 +01:00
snipe 2bbac3ae9d Merge remote-tracking branch 'origin/develop' 2025-06-17 12:38:52 +01:00
snipe b496a06fc0 Merge pull request #17218 from grokability/fixed-#17212-hide-columns-on-print
Fixed #17212 - hide columns from print view
2025-06-17 12:37:43 +01:00
snipe f865a6cb37 Added 'printIgnore' => true, to presenters to hide checkbox and action column from print view
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 12:35:05 +01:00
snipe 89186ea4f8 Merge pull request #17195 from grokability/#17192-fixes-wonky-layout-on-bulk-edit
Fixed #17192 - wonky layout on bulk edit screens
2025-06-17 11:55:31 +01:00
snipe fb19985186 Use the same variable for fieldset closing tag
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 11:52:47 +01:00
snipe e889b1d5e5 Merge remote-tracking branch 'origin/develop' 2025-06-17 11:42:00 +01:00
snipe ebc6e1221a Merge pull request #17198 from akemidx/ghissue-16241
Fixed #16241 - menu items not getting class="active" on click.
2025-06-17 11:40:56 +01:00
snipe 233bf856f4 Merge remote-tracking branch 'origin/develop' 2025-06-17 11:39:12 +01:00
snipe 2b91dcb700 Merge pull request #17216 from grokability/#17215-wrong-logo-check-in-print
Fixed #17215: Check for acceptance logo vs regular logo
2025-06-17 11:38:19 +01:00
snipe 9d2e333fd6 Check for acceptance logo vs regular logo
Signed-off-by: snipe <snipe@snipe.net>
2025-06-17 11:36:10 +01:00
Godfrey M 013ad1069c saving certs sooner in the stack 2025-06-16 16:30:00 -07:00
Godfrey M ec059717f6 adds id to XML textbox, now updates 2025-06-16 16:01:11 -07:00
akemidx 418566db3f fixes the easy ones 2025-06-16 18:41:36 -04:00
snipe bca843e06c Merge remote-tracking branch 'origin/develop' 2025-06-16 20:32:40 +01:00
snipe 7be9463be6 Merge pull request #17180 from grokability/#12653-added-jobtitle-to-asset-listing
Fixed #12653 - added jobtitle to asset listing
2025-06-16 20:29:01 +01:00
snipe 51712bc7d6 Check for whether any of the models have a fieldset
Signed-off-by: snipe <snipe@snipe.net>
2025-06-16 16:40:07 +01:00
snipe 7b889d22d2 Fixed HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-06-16 16:14:01 +01:00
snipe 30a79a1278 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-06-16 14:45:53 +01:00
snipe e8aad989ec Updated translations
Signed-off-by: snipe <snipe@snipe.net>
2025-06-16 14:27:47 +01:00
snipe 4006d64d60 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-06-16 14:25:59 +01:00
snipe d792d99375 Removed stray space for favicon
Signed-off-by: snipe <snipe@snipe.net>
2025-06-15 03:51:50 +01:00
snipe 0f92dee2c4 Merge remote-tracking branch 'origin/develop' 2025-06-15 03:41:23 +01:00
snipe b11036a2e5 Fixed #11977 - changed path in backups language
Signed-off-by: snipe <snipe@snipe.net>
2025-06-15 03:40:54 +01:00
snipe f04efede15 Merge remote-tracking branch 'origin/develop' 2025-06-15 02:59:47 +01:00
snipe 01de69a250 Merge pull request #17181 from grokability/fixed-#14542-missing-fullscreen-in-locations
Fixed #14542 - added fullscreen option for location view tabs
2025-06-15 02:57:51 +01:00
snipe 5e1c2e7feb Fixed #14542 - added fullscreen option for location view tabs
Signed-off-by: snipe <snipe@snipe.net>
2025-06-15 02:56:07 +01:00
snipe b842aa11e5 Remove debugging
Signed-off-by: snipe <snipe@snipe.net>
2025-06-15 02:25:25 +01:00
snipe ff01078b60 Fixed #12653 - adds job title to asset listing
Signed-off-by: snipe <snipe@snipe.net>
2025-06-15 02:23:53 +01:00
snipe f0dfdf6720 Merge remote-tracking branch 'origin/develop' 2025-06-14 23:56:06 +01:00
snipe b1e92293fc Merge pull request #14998 from timoschwarzer/feature/department-manager-in-table
Added #14997: Display department manager in user view and list
2025-06-14 23:51:41 +01:00
snipe 443f69bd82 Merge branch 'develop' into feature/department-manager-in-table 2025-06-14 23:48:52 +01:00
snipe e26d731382 Merge remote-tracking branch 'origin/develop' 2025-06-14 23:08:48 +01:00
snipe f4decbf52e Fixed #15480 - adds location to acceptance email
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 23:08:37 +01:00
snipe d684d3e559 Merge remote-tracking branch 'origin/develop' 2025-06-14 22:22:54 +01:00
snipe bd2c311e4f One more fix to bulk menu
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 22:22:42 +01:00
snipe 2dcab6d0b3 Fixed logic on bulk menu
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 22:21:00 +01:00
snipe c68a97198f Fixes #8212 - force date format for purchase date
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 20:40:15 +01:00
snipe 47c54cb998 Merge remote-tracking branch 'origin/develop' 2025-06-14 17:45:44 +01:00
snipe 2702c3da2b Merge pull request #17178 from grokability/fixes-maintenance-name-on-delete
Fixed #17177 - use maintenance title for delete confirmation
2025-06-14 17:44:21 +01:00
snipe da06e9afd5 Fixes #17177 - undefined title on asset maintenance
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 17:40:14 +01:00
snipe 1623f13539 Merge pull request #17147 from chrisnovakovic/php_upload_limit-hardening
Docker: harden updating of `php.ini` in entrypoint
2025-06-14 17:11:15 +01:00
snipe 592cb2b3ec Merge remote-tracking branch 'origin/develop' 2025-06-14 17:08:11 +01:00
snipe 5910982a4f Merge pull request #17176 from grokability/nicer-bulk-options-for-assets
Add logic around menu options
2025-06-14 17:06:49 +01:00
snipe 74630b36b0 Merge pull request #17175 from grokability/fixes/17172-checkin-date
Fixed #17172 - Better handle checkin date overrides
2025-06-14 17:06:30 +01:00
snipe ace4a5d614 Add logic around menu options
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 17:02:09 +01:00
snipe 0d41947f64 Better handle checkin date overrides
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 16:54:25 +01:00
snipe f5a7871a2e Merge remote-tracking branch 'origin/develop' 2025-06-14 16:44:48 +01:00
snipe 78de3b3591 Merge pull request #17174 from grokability/fixes-17163-permissions-on-maintenances
Fixed #17163 - insufficient permissions on editing an asset maintenance
2025-06-14 16:43:27 +01:00
snipe 0a4a6e7ba3 Removed unusued relationship
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 16:37:44 +01:00
snipe 090399b336 Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 16:34:12 +01:00
snipe 47afb15970 Updated HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 16:24:05 +01:00
snipe b1ba3376aa Validation for completion date
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 16:13:57 +01:00
snipe 8c1e19e77c Fixed breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 15:53:05 +01:00
snipe 0801d1473c Use forem group div
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:57:55 +01:00
snipe 6d98878c72 Removed redirect
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:31:15 +01:00
snipe 2d404fdadc Fixed breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:30:52 +01:00
snipe b264fde165 Show title if it’s an edit
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:30:43 +01:00
snipe 970ff25e5e Added model and company to manufacturer view
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:30:27 +01:00
snipe 9dd3eee65c Pull incorrect check for company scoping
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:04:09 +01:00
snipe 957faa6651 Fixed datepicker prepopulating with start_date
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 14:03:52 +01:00
snipe cc7dcc6e81 Disallow multiple on editing a maintenance
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 13:39:18 +01:00
snipe ec411fa0db Merge remote-tracking branch 'origin/develop' 2025-06-14 12:17:43 +01:00
snipe f666cba104 Fixed RB-3991 - override route for non-existent fieldset
Signed-off-by: snipe <snipe@snipe.net>
2025-06-14 12:17:31 +01:00
Marcus Moore 7f35498919 Remove some test cases 2025-06-12 15:56:24 -07:00
Marcus Moore 297205ff91 Tests 2025-06-12 15:55:25 -07:00
Marcus Moore 6f99381d13 Handle accessories 2025-06-12 15:49:38 -07:00
Marcus Moore 45a42b00ad Add test 2025-06-12 15:48:48 -07:00
Marcus Moore 6586858284 Scaffold some test cases 2025-06-12 15:31:32 -07:00
Marcus Moore 19cce15e54 Revert "Method signature change"
This reverts commit a60ffc0702.
2025-06-12 13:54:53 -07:00
Marcus Moore 62b8e4c46f Revert "Inline some values"
This reverts commit 74b7d27408.
2025-06-12 13:54:46 -07:00
Marcus Moore 74b7d27408 Inline some values 2025-06-12 13:49:19 -07:00
Marcus Moore a60ffc0702 Method signature change 2025-06-12 13:46:05 -07:00
snipe 9b8524ba27 Merge pull request #17109 from akemidx/bug/sc-28718-2
FIXED: Adding Total to Consumable View Page
2025-06-12 21:44:08 +01:00
Marcus Moore 32526d77b8 Add todo 2025-06-12 13:35:22 -07:00
snipe a850a9bb83 Merge remote-tracking branch 'origin/develop' 2025-06-12 21:34:59 +01:00
snipe ba7db8f7b3 Merge pull request #17092 from Godmartinz/fix-bulk_checkout_undeployable
Adds `hasUndeplyableStatus` check to bulk checkout
2025-06-12 21:29:26 +01:00
snipe 04712ad252 Merge pull request #17151 from Godmartinz/bulk-checkout-add-pending-status
Fixed #17028: Allows bulk editing assets with a status type of pending
2025-06-12 21:27:44 +01:00
Marcus Moore 5711a9e148 Fix test 2025-06-12 13:02:26 -07:00
Marcus Moore a0f40c2dfb Tests 2025-06-12 12:56:24 -07:00
Marcus Moore 34636016eb Improve test 2025-06-12 12:47:03 -07:00
Marcus Moore 95027e329c Formatting 2025-06-12 12:44:29 -07:00
Marcus Moore c9778a73c7 Wire up category controller 2025-06-12 12:32:34 -07:00
Marcus Moore 628d2a0a0a Flesh out mail contents 2025-06-12 12:12:38 -07:00
Marcus Moore cc0ff1ec1f Handle missing recipient 2025-06-12 11:53:30 -07:00
snipe 6543540509 Merge pull request #17166 from Godmartinz/item_name_fix_from_account_eula_table
FIXED: #17136 loads item relation properly in account EULA tab
2025-06-12 11:53:51 +01:00
Marcus Moore cd53fc6318 Scaffold email contents 2025-06-11 16:56:40 -07:00
Godfrey M 4934b6c4da fix query for item relationship to load properly 2025-06-11 11:42:16 -07:00
snipe 479b7a3f94 Merge remote-tracking branch 'origin/develop' 2025-06-11 10:32:49 +01:00
snipe 59db38524b Merge pull request #17094 from spencerrlongg/snipe-it-17051
Fixes #17051 - Nulling Custom Fields In Bulk Asset Updates
2025-06-11 10:31:28 +01:00
snipe 3f5cfc3a4b Merge pull request #17142 from marcusmoore/fixes/present-on-null-in-expected-checkins-notification
Fixed Expected checkin notification erroring on unknown users
2025-06-11 10:27:49 +01:00
snipe 3443f02c0a Merge pull request #17141 from marcusmoore/fixes/undefined-manages_users_count-on-user
Fixed bad method calls in user index api call
2025-06-11 10:26:13 +01:00
snipe 9a012ca01e Merge pull request #17154 from Robert-Azelis/patch-10
Update user print assigned assets
2025-06-11 10:24:50 +01:00
snipe 509ef34cca Merge pull request #17155 from Robert-Azelis/patch-11
Update location print assigned assets
2025-06-11 10:24:29 +01:00
snipe f7cfee77c9 Merge remote-tracking branch 'origin/develop' 2025-06-11 10:20:22 +01:00
snipe 49c289a094 Merge pull request #17158 from grokability/changes-default-history-column-visibility
Changed default visibility on history views
2025-06-11 10:19:33 +01:00
snipe 10e5d88fb6 Changed default visibility on history views
Signed-off-by: snipe <snipe@snipe.net>
2025-06-11 10:16:26 +01:00
snipe ae64fb3fdb Merge pull request #17157 from grokability/fixed-#17138-category-type-case-sensitive
Fixed #17138 - category type was case-sensitive
2025-06-11 10:12:05 +01:00
snipe cac2fde504 Fixed #17138 - category type in category importer is case sensitive
Signed-off-by: snipe <snipe@snipe.net>
2025-06-11 10:08:59 +01:00
snipe bb38a96fd1 Merge pull request #17152 from marcusmoore/fixes/handle-category-missing-upon-checkin
Handle potentially missing category upon checkin
2025-06-11 09:36:50 +01:00
Robert-Azelis 79dbcb10c9 Update location print assigned assets
Lets allow user to select print layout: portrait or landscape
Use dedicated logo for PDF documents: acceptance_pdf_logo
2025-06-11 09:22:13 +02:00
Robert-Azelis 7c80fdea58 Update user print assigned assets
Lets allow user to select print layout: portrait or landscape
Use dedicated logo for PDF documents: acceptance_pdf_logo
2025-06-11 09:19:51 +02:00
Godfrey M 5500a42744 change message from error to warning 2025-06-10 15:10:10 -07:00
Godfrey M ae46264707 removed comment 2025-06-10 15:07:42 -07:00
Godfrey M f2d8665e54 adds tests 2025-06-10 15:07:04 -07:00
Marcus Moore db7110d6b2 Remove test 2025-06-10 10:43:23 -07:00
Marcus Moore 4e06b597fe Handle category missing in Consumable 2025-06-10 10:43:02 -07:00
Godfrey M fe006d05d3 allows to change the status to pending in bulk edit while deployed 2025-06-10 10:41:26 -07:00
Marcus Moore 358b70e280 Handle category missing in Accessory 2025-06-10 10:36:59 -07:00
Marcus Moore a060dde625 Add failing test 2025-06-10 10:34:25 -07:00
Godfrey M 3cfed72af4 removes undeployables from asset_id array 2025-06-10 10:11:00 -07:00
Chris Novakovic 4c59989236 Docker: harden updating of php.ini in entrypoint
The Docker entrypoint scripts set values for the `upload_max_filesize`
and `post_max_size` directives in `php.ini` based on the value of the
`PHP_UPLOAD_LIMIT` environment variable, subject to the following
restrictions:

* Exactly one file matches `/etc/php/*/apache2/php.ini` (on Ubuntu) or
  `/etc/php*/php.ini` (on Alpine) - if, for example, more than one PHP
  package is installed in the base image, `PHP_UPLOAD_LIMIT` will not be
  honoured.
* The `php.ini` file already sets a non-default value for the
  `upload_max_filesize` or `post_max_size` directives - this is
  currently the case for the configurations inherited from upstream, but
  is not guaranteed. If the default values are relied upon,
  `PHP_UPLOAD_LIMIT` will silently not be honoured (although the script
  output will claim that it is).

Iterate over the lines outputted by `file(1)` so `PHP_UPLOAD_LIMIT` is
honoured in all available `php.ini` files, and set `upload_max_filesize`
and `post_max_size` regardless of whether they already have a value set.
2025-06-10 13:39:46 +01:00
Marcus Moore 6c1adff5c8 Extract translation string 2025-06-09 15:33:39 -07:00
Marcus Moore 9293bdca06 Don't render link for Unknown User 2025-06-09 15:32:07 -07:00
Marcus Moore beeccbfb44 Handle unknown users gracefully 2025-06-09 15:30:03 -07:00
Marcus Moore 0d3d2e2e78 Fix keyes 2025-06-09 12:55:38 -07:00
Marcus Moore 2af7605451 Add failing tests 2025-06-09 12:55:09 -07:00
snipe 976cc1c86f Fixed second incorrect string
Signed-off-by: snipe <snipe@snipe.net>
2025-06-09 14:00:47 +01:00
snipe 65a8126a13 Merge remote-tracking branch 'origin/develop' 2025-06-09 13:40:44 +01:00
snipe cbbf3aa6c8 Fixed incorrect translation string
Signed-off-by: snipe <snipe@snipe.net>
2025-06-09 13:40:34 +01:00
snipe a39bc102d5 Merge remote-tracking branch 'origin/develop' 2025-06-08 15:30:32 +01:00
snipe 4f8ff98d5b Merge pull request #17129 from grokability/fixes#17127-added-note-to-eula-api
Fixed #17127 - added note to EULA info
2025-06-08 15:29:47 +01:00
snipe d4fe81c290 Fixed #17127 - added note to EULA info
Signed-off-by: snipe <snipe@snipe.net>
2025-06-08 15:27:50 +01:00
snipe 81d930c4d2 Merge remote-tracking branch 'origin/develop' 2025-06-08 15:19:19 +01:00
snipe cbdf03aa66 Nicer code formatting
Signed-off-by: snipe <snipe@snipe.net>
2025-06-08 15:18:59 +01:00
snipe d756670c56 Check for supplier before trying to show
Signed-off-by: snipe <snipe@snipe.net>
2025-06-08 15:17:24 +01:00
snipe 9ef7b0e64a Fixed missing translation string
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 20:41:36 +01:00
snipe 2d7c0f7e5f Merge pull request #16469 from grokability/fix_action_date
Fixed `action_date` in `action_logs`
2025-06-06 17:18:39 +01:00
snipe ec8ddc197f Merge branch 'develop' into fix_action_date 2025-06-06 17:18:27 +01:00
snipe b48e56bd46 Merge pull request #17110 from Godmartinz/allow-users-to-dl-eula-from-account
Fixed #17084 - Adds Eula table to User account area with download option
2025-06-06 17:07:19 +01:00
snipe 6839623061 Merge remote-tracking branch 'origin/develop' 2025-06-06 17:03:37 +01:00
snipe 753ca93371 Merge pull request #17126 from grokability/fixes-#17023-multi-maintenances
Fixes #17023 - added ability to bulk add maintenances
2025-06-06 17:01:50 +01:00
snipe 04f71e7f6a Shorter syntax
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 16:58:10 +01:00
snipe 5d129dd420 Small form fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 16:56:45 +01:00
snipe 1c37c630aa Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 16:26:37 +01:00
snipe d329d6104e Make supplier_id nullable
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:55:54 +01:00
snipe 482723f3bc Updated blade to use multiple
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:47:07 +01:00
snipe cbc025b1ff Updated save method to save multiple
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:46:55 +01:00
snipe bf8ceceabe Add bulk maintenance handler
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:42:14 +01:00
snipe fba4bba132 Make supplier not required anymore
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:41:41 +01:00
snipe 0ded40c037 Add multiselect to maintenance form
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:41:30 +01:00
snipe 6f486a37ff Added maintenance option to bulk menu
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 15:41:02 +01:00
snipe 7de2809d42 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2025-06-06 14:05:47 +01:00
snipe 1ef822997b Merge pull request #17125 from grokability/fixed-17117-added-translation
Fixed #17117 - use translation for “site default”
2025-06-06 14:04:08 +01:00
snipe ea66629e98 Fixed #17117 - use translation for “site default”
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 14:03:17 +01:00
snipe 84c9979fe3 Merge pull request #17124 from grokability/add-print-button-to-tables
Added print button to tables
2025-06-06 13:52:22 +01:00
snipe 867a992183 Added print button to tables
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 13:51:12 +01:00
snipe 98ec6b6886 Merge remote-tracking branch 'origin/develop' 2025-06-06 12:48:54 +01:00
snipe c3ad7d649c Merge pull request #17122 from grokability/added_highlighting_to_search
Added highlighting on table search
2025-06-06 12:47:31 +01:00
snipe fc250e228d Added highlighting on table search
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 12:43:47 +01:00
snipe 04827f00cc Merge remote-tracking branch 'origin/develop' 2025-06-06 11:54:10 +01:00
snipe 37e81568ea Merge pull request #17120 from grokability/fixes-11807-datepicker-blade-component
Fixed #11807 - Standardize date-picker
2025-06-06 11:44:54 +01:00
snipe 048d910d5b Use partial
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 11:33:17 +01:00
snipe b162aba445 Added localization for datepicker
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 11:33:02 +01:00
snipe 974627849b Added datepicker partial
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 11:32:47 +01:00
snipe e18e9f699e Use blade component in datepicker partial
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 11:32:37 +01:00
snipe cc9209d2de Removed EOL date and purchase date partials
Signed-off-by: snipe <snipe@snipe.net>
2025-06-06 11:32:13 +01:00
snipe 660bfc6578 Merge remote-tracking branch 'origin/develop' 2025-06-06 08:31:09 +01:00
snipe dd3d264e63 Merge pull request #16675 from Godmartinz/null_location_id_fix
Adds a check for assigned target not being null when creating an asset
2025-06-06 08:30:31 +01:00
snipe 1152cd5537 Merge remote-tracking branch 'origin/develop' 2025-06-06 08:26:52 +01:00
snipe 246f1373a8 Merge pull request #16948 from marcusmoore/bug/sc-29181
Require assigned_x to be integer on asset model
2025-06-06 07:57:20 +01:00
snipe b993f4270e Merge pull request #17114 from grokability/fixes-16934-update-asset-by-id
Fixed #16934 and #17068 - update asset by ID in importer
2025-06-06 07:55:03 +01:00
snipe 6c51ef11b1 Merge pull request #17115 from marcusmoore/17067-limit-cc-if-acceptance-required
Fixed #17067: Allow only sending cc email when acceptance required
2025-06-06 07:41:26 +01:00
Marcus Moore ae98f6276e Replace reject with declined 2025-06-05 17:24:57 -07:00
Marcus Moore 7424a5987b Add subject 2025-06-05 17:22:31 -07:00
Marcus Moore 2d08749207 Improve readability 2025-06-05 16:56:11 -07:00
Marcus Moore db50e98ae3 Populate tests 2025-06-05 16:51:44 -07:00
Marcus Moore 7ec0925c69 Scaffold out tests 2025-06-05 16:44:44 -07:00
Marcus Moore df1361aa43 Scaffold test 2025-06-05 16:42:31 -07:00
akemidx 6db04c86df making fields active. first pass 2025-06-05 18:46:33 -04:00
Marcus Moore bec80b443c WIP: begin to send email 2025-06-05 15:15:22 -07:00
Marcus Moore 333501fe55 WIP: create mail class 2025-06-05 15:07:19 -07:00
Marcus Moore 063553d4f7 Scaffold scenarios 2025-06-05 14:57:15 -07:00
Marcus Moore 19b9e50281 Update livewire component for alert_on_response 2025-06-05 14:40:19 -07:00
Marcus Moore 2a68b4aeff Update test for updating category with alert_on_response 2025-06-05 14:36:06 -07:00
Marcus Moore 5e25150521 Add another test case 2025-06-05 14:26:56 -07:00
Marcus Moore cb183d3645 Store alert_on_response_id on CheckoutAcceptance 2025-06-05 14:06:22 -07:00
Marcus Moore 96bce301a0 Add alert_on_response to Category 2025-06-05 13:43:20 -07:00
Marcus Moore 360f5b7538 Add alert_on_response_id to CheckoutAcceptance 2025-06-05 13:10:18 -07:00
Marcus Moore 77234f6580 Extract translation strings 2025-06-05 12:24:46 -07:00
Marcus Moore 088e6af0b5 Remove admin_cc_email validation for admin_cc_always 2025-06-05 12:07:14 -07:00
Lukas Kraic 16fb1018a2 List users code refactoring 2025-06-05 20:05:38 +02:00
Godfrey M e2e54677ee add auth to api call, gave more specificity to the relationship 2025-06-05 10:59:16 -07:00
Lukas Kraic ad6fe855a9 Fix code style: Change comments 2025-06-05 18:53:36 +02:00
Lukas Kraic c50c97d149 Fix code style: Change comments 2025-06-05 18:18:07 +02:00
Lukas Kraic 8b98ae15f0 Fix code style: Add comment 2025-06-05 18:08:34 +02:00
Lukas Kraic 261f84d5f5 Fix code style: Remove comment 2025-06-05 17:55:46 +02:00
Lukas Kraic 29989ac24e Fix code style: Remove empty line after end comment 2025-06-05 13:50:57 +02:00
snipe 7a93e94fa6 Add ID to field list
Signed-off-by: snipe <snipe@snipe.net>
2025-06-05 12:35:30 +01:00
snipe e33f73fe9f Fixed comment text
Signed-off-by: snipe <snipe@snipe.net>
2025-06-05 11:56:14 +01:00
snipe 6291389df5 Fixed #16934 - update asset by ID in importer
Signed-off-by: snipe <snipe@snipe.net>
2025-06-05 11:53:57 +01:00
snipe ed817dc414 Merge branch 'develop' into snipe-it-17051 2025-06-05 11:37:17 +01:00
Lukas Kraic 7494fa6bc9 Fix code style: Remove unnecessary end comment from short conditional block 2025-06-05 12:03:42 +02:00
Lukas Kraic 62e50dbe52 Fix code style: Add semantically correct end comments 2025-06-05 11:56:13 +02:00
Lukas Kraic 30c090ba2d Fix code style: Add descriptive end comment for conditional block 2025-06-05 11:45:05 +02:00
Lukas Kraic 7ff82e6043 Delete comments 2025-06-05 11:07:42 +02:00
snipe 2950fb1041 Merge pull request #17108 from Godmartinz/clear-buttons-for-custom-fields
Adds a clear button to custom radio buttons in bulk edit
2025-06-05 10:03:27 +01:00
Lukas Kraic 61d3e2fb49 Fix code style: Add required whitespace in end block comments 2025-06-05 09:23:58 +02:00
Lukas Kraic fb18c1a0be Fix code style: Remove whitespace in end block comments 2025-06-05 08:59:07 +02:00
Godfrey M 68c082e0dc fix doc blocks 2025-06-04 15:48:35 -07:00
Godfrey M ee3deb9c63 made eula api route, formatted table, cleaned up code 2025-06-04 15:04:59 -07:00
Marcus Moore c1505de8d6 Update wording 2025-06-04 13:02:04 -07:00
Marcus Moore 10be434c13 Populate tests 2025-06-04 12:58:49 -07:00
Marcus Moore 92e22eead5 Formatting 2025-06-04 12:51:59 -07:00
Marcus Moore 31db86abd3 Simplify test 2025-06-04 12:51:48 -07:00
Marcus Moore 8e70ff135a Scaffold tests 2025-06-04 12:44:55 -07:00
akemidx 5ec52f7471 beginning of everything. tried some stuff but yea. something is up 2025-06-04 15:10:16 -04:00
Marcus Moore 8bc57f98a5 Split test case 2025-06-04 11:51:29 -07:00
Godfrey M 6f4cee6334 adds Eula tab and count to user account 2025-06-04 11:38:50 -07:00
Godfrey M 6beccc5e60 adds clear ability to custom radio buttons 2025-06-04 10:24:45 -07:00
Godfrey M fed8e10644 Merge remote-tracking branch 'upstream/develop' into clear-buttons-for-custom-fields 2025-06-04 10:06:59 -07:00
snipe f30e8497b2 Merge remote-tracking branch 'origin/develop' 2025-06-04 16:17:26 +01:00
snipe 3c0121c1d0 Merge pull request #17107 from grokability/fixes-16218-add-centos
Fixed #16218 - added centos to the switch case
2025-06-04 15:33:23 +01:00
snipe 02021e3fb9 Fixed #16218 - added centos to the switch case
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 15:27:19 +01:00
Lukas Kraic ea447365fa Fix Codacy warnings II 2025-06-04 15:40:17 +02:00
Lukas Kraic 60989d6766 Fix Codacy warnings 2025-06-04 15:17:55 +02:00
snipe 06495bc45d Merge remote-tracking branch 'origin/develop' 2025-06-04 12:13:30 +01:00
snipe a2960dc653 Merge pull request #17103 from grokability/fixes-17102-add-cc-as-search-string-in-settings
Fixed #17102 - added keywords to admin settings search for notifications
2025-06-04 12:12:17 +01:00
snipe 702499dd79 Fixed #17102 - added keywords to admin settings search for notifications
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 12:09:57 +01:00
snipe 26067916b3 Merge remote-tracking branch 'origin/develop' 2025-06-04 11:49:03 +01:00
snipe 482c427e34 Merge pull request #17100 from uberbrady/add_deleted_at_index_to_action_logs
Fixed #16205 - Add an index to deleted_at for those with many deleted action_logs
2025-06-04 11:48:45 +01:00
snipe 67910490bd Merge pull request #17101 from grokability/fixes-16157-added-advanced-search-to
Fixed #16157 - Added advanced search to users
2025-06-04 11:48:24 +01:00
snipe fdb5ab2293 Added advanced search to users
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 11:46:38 +01:00
Brady Wetherington 092d9d1e42 Add deleted_at index to action_logs for people with many deleted action_logs 2025-06-04 11:23:09 +01:00
snipe c36ee4852b Merge remote-tracking branch 'origin/develop' 2025-06-04 10:40:56 +01:00
snipe e657f11531 Merge pull request #17099 from grokability/fixes-16240-localization-strings
Fixed #16240 - made additional strings translatable
2025-06-04 10:40:21 +01:00
snipe 9aac183318 Added aria tag for accessibility
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 10:40:05 +01:00
snipe c8c2867305 Remove unused translation
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 10:39:08 +01:00
snipe 3e1f71026c Fixed #16240 - made additional strings translatable
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 10:37:04 +01:00
snipe 2cb992ad44 Merge remote-tracking branch 'origin/develop' 2025-06-04 08:43:52 +01:00
snipe dc562d8c20 Remove error log
Signed-off-by: snipe <snipe@snipe.net>
2025-06-04 08:43:40 +01:00
Lukas Kraic 84ec5aea26 Manager View Feature 2025-06-04 09:07:26 +02:00
Marcus Moore 444c13c6ea Scaffold template 2025-06-03 17:10:15 -07:00
spencerrlongg 12d5e4f7d2 cleanup and test 2025-06-03 19:07:02 -05:00
spencerrlongg 0fb1639915 this works! 2025-06-03 18:06:41 -05:00
Marcus Moore d01f7cf317 Adhere to admin_cc_always setting 2025-06-03 15:32:11 -07:00
spencerrlongg 03725c8e0c custom field null and filtering 2025-06-03 17:21:07 -05:00
Marcus Moore 3942489d21 Add Test suffix and scaffold test 2025-06-03 14:13:52 -07:00
Marcus Moore 51479c8bbc Scaffold failing test 2025-06-03 13:28:40 -07:00
Marcus Moore ea3364ab68 Split test case 2025-06-03 13:19:44 -07:00
Godfrey M cb608d7fd1 testing buttons out 2025-06-03 11:58:57 -07:00
Godfrey M 7129008428 remove testing changes 2025-06-03 11:44:38 -07:00
Godfrey M 3b832f507f fixes status check for bulk checkout 2025-06-03 11:38:59 -07:00
Godfrey M b5849500f9 add isDeplyable check 2025-06-03 10:49:17 -07:00
snipe 083b7be6c0 Make version number match tagged version :(
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 11:03:33 +01:00
snipe e24854558f Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-06-03 11:03:05 +01:00
snipe 9eaabf95a0 Make version number match tagged version :(
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 11:02:41 +01:00
snipe 724f38abc2 Merge pull request #16998 from kovacs-andras/develop
Bumped container image versions
2025-06-03 06:22:08 +01:00
snipe e4314cf426 Merge remote-tracking branch 'origin/develop' 2025-06-03 06:07:20 +01:00
snipe 0dfc083a91 Removed log error
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 06:07:06 +01:00
snipe 16432f503a Merge pull request #17087 from grokability/#17085-checkin-and-delete-not-nulling-assigned_type
Fixed #17085 - assigned_type not being nulled on asset delete+checkin
2025-06-03 05:53:24 +01:00
snipe 7c9433be5d Added migration to fix existing wonky data
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:37:37 +01:00
snipe e4ce71ff14 Added time on action date
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:30:53 +01:00
snipe 45c6406ff4 Added console command for fixup
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:30:43 +01:00
snipe 550e2b6bb8 Null both assigned to and assigned type on delete
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:30:30 +01:00
snipe a7bb890729 Removed action date from array of things to log
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:29:49 +01:00
snipe 3d8f8faf01 Added action_date
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:28:58 +01:00
snipe 55cf5877c4 Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-06-03 05:28:45 +01:00
Marcus Moore dec3c4aff3 Improve wording 2025-06-02 17:45:08 -07:00
Marcus Moore 79e00c1191 Require admin_cc_email if admin_cc_always is true 2025-06-02 17:21:58 -07:00
Marcus Moore 12dc33244d Start storing admin_cc_always 2025-06-02 17:20:01 -07:00
Marcus Moore 6e37f945ac Add test helpers 2025-06-02 17:13:37 -07:00
Marcus Moore d75120000a Add failing tests 2025-06-02 17:11:18 -07:00
Marcus Moore 9e4aab7165 Scaffold tests 2025-06-02 17:05:18 -07:00
Marcus Moore 6bc3209333 Use @checked for inputs 2025-06-02 17:05:00 -07:00
Marcus Moore 054ff42547 Add migration for admin_cc_always 2025-06-02 17:03:14 -07:00
spencerrlongg 11b47b308b front end done, sloppy 2025-06-02 18:39:08 -05:00
Marcus Moore 367ab8ddd5 Add help text 2025-06-02 16:27:04 -07:00
Marcus Moore 4f5d4a0984 Scaffold settings page changes 2025-06-02 16:25:06 -07:00
snipe 4106e4e45c Merge remote-tracking branch 'origin/develop' 2025-06-02 22:29:09 +01:00
snipe 40489c53d6 Merge pull request #17078 from grokability/fixes-#17076-validation-on-bulk-submit
Fixed #17076 - Disable optional status ID form field if value is blank/Do Not Change
2025-06-02 22:28:31 +01:00
snipe 93b760d53b Disable form fields if the value is blank
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 22:16:36 +01:00
snipe 05f143db2b Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-06-02 18:15:54 +01:00
snipe e86996bc7e Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 18:15:21 +01:00
snipe 14244f45b6 Duplicates PR #16957
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 18:13:16 +01:00
snipe 1b9d90a322 Added over sixty test
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 17:36:50 +01:00
snipe 64aeaeeeea Merge remote-tracking branch 'origin/develop' 2025-06-02 17:29:52 +01:00
snipe 736f74d083 Merge pull request #17074 from uberbrady/improve_api_rate_limiting
Fix to rate-limiter on higher rate-limits
2025-06-02 17:25:02 +01:00
Brady Wetherington 8194c6efdb Fix to rate-limiter on higher rate-limits 2025-06-02 17:21:14 +01:00
snipe 61db37ab0d Merge remote-tracking branch 'origin/develop' 2025-06-02 15:28:08 +01:00
snipe aae2a17ad1 Add @amedranogil as a contributor 2025-06-02 15:27:55 +01:00
snipe f44150668c Merge pull request #17038 from amedranogil/develop
more robust php.ini update.
2025-06-02 15:27:28 +01:00
snipe 6eed2deb09 Merge pull request #17013 from Robert-Azelis/patch-9
API Models - added requestable for API request
2025-06-02 15:26:08 +01:00
snipe f9c4d921e7 Merge remote-tracking branch 'origin/develop' 2025-06-02 15:07:15 +01:00
snipe 878c6e7031 Merge pull request #17019 from grokability/#15320-status_to_bulk_checkout
Fixed #15320 - added status label to bulk checkout
2025-06-02 15:05:44 +01:00
snipe ca099df573 Merge remote-tracking branch 'origin/develop' 2025-06-02 15:04:00 +01:00
snipe 1f4a73fab6 Merge pull request #17062 from grokability/add_category_importer
Added category importer
2025-06-02 15:03:31 +01:00
snipe 7a315523fe Improved CSV
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 15:01:16 +01:00
snipe 6f082e662b Fixed weird layout
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 14:52:41 +01:00
snipe 018c981c5a Merge pull request #17042 from marcusmoore/chore/replace-customfield-elements-macro-take-two
Replace customfield_elements form macro take two
2025-06-02 10:18:19 +01:00
snipe 0149773a03 Fixed variable name
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 04:14:51 +01:00
snipe 5d46d90725 Added category importer
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 04:11:54 +01:00
snipe 0544e05f32 Merge pull request #17061 from grokability/add_manufacturer_importer
Added manufacturer importer
2025-06-02 03:05:25 +01:00
snipe 80ff42a41f Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 03:01:56 +01:00
snipe 90b7df45b9 Added tests and support helper
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:55:11 +01:00
snipe 32858b973a Added sample CSV
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:55:00 +01:00
snipe 40ba8d0de1 Fixed “send welcome email” detection
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:54:34 +01:00
snipe 8ddbb4e64f Added manufacturer factory
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:54:21 +01:00
snipe cc40c48aac Added manufacturers import fields
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:54:03 +01:00
snipe 522ab9e0f5 Added manufacturer importer
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:53:46 +01:00
snipe 97187aa7eb Skip manufacturers on checkout import type
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:53:37 +01:00
snipe d93a5aa623 Added redirect after import
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:53:09 +01:00
snipe a5b88982bf Added manufacturer icon
Signed-off-by: snipe <snipe@snipe.net>
2025-06-02 02:52:55 +01:00
snipe df71bdcada Merge pull request #17044 from marcusmoore/bug/sc-29302
Handle missing location when rendering labels
2025-06-02 02:21:19 +01:00
snipe 28b584b8bc Merge remote-tracking branch 'origin/develop' 2025-06-02 02:08:19 +01:00
snipe 51bab2dd26 Merge pull request #17045 from grokability/docker-laravel-log-permissions
Docker: Ensure permissions on Laravel log file
2025-05-30 06:52:31 +01:00
Jeremy Price ed8da6ad1b Docker: Ensure permissions on Laravel log file
FIXES: https://github.com/grokability/snipe-it/issues/12725

In some of our Docker startups, it was possible for the Laravel log file
to be created with root permissions, causing future errors when the
non-root webapp tries to write to it.

We'll now always chown (and create, if necessary) the log file to the proper
user after running any artisan commands (as root)

We _could_ run them as the proper user via su, but IMO not doing so keeps the
script easier to read, but I'm not married to the approach. I'd still
want to keep the chown command(s) in, because it will also fix the
permissions for anyone who already has this issue.
2025-05-29 17:45:14 -07:00
Marcus Moore 18d0a04efc Avoid dumping pdf contents to test results 2025-05-29 15:05:08 -07:00
Marcus Moore bb68ed3ad9 Handle asset not having location 2025-05-29 14:49:53 -07:00
Marcus Moore 402ca07aa2 Add failing test 2025-05-29 14:20:15 -07:00
snipe 70449e694d Merge remote-tracking branch 'origin/develop' 2025-05-29 22:18:50 +01:00
snipe 28dc358df1 Merge pull request #17041 from grokability/improve_locations_and_supplier_api
Small refinements for suppliers and locations API and list view
2025-05-29 21:30:54 +01:00
Marcus Moore 3cf1e9d55d Remove customfield_elements macro 2025-05-29 13:30:23 -07:00
Marcus Moore 82b001ab5f Extract translation strings 2025-05-29 13:29:13 -07:00
Marcus Moore 7b272226ce Inline customfield_elements select 2025-05-29 13:29:13 -07:00
snipe 78d26fb7f6 Removed stray character
Typing is hard :(

Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 21:23:50 +01:00
snipe 930842e685 Removed unused method
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 21:18:19 +01:00
snipe b938cb42d8 Merge pull request #17040 from marcusmoore/improve-acceptance-reminder-output
Avoid displaying empty table in `SendAcceptanceReminder` command
2025-05-29 21:14:09 +01:00
snipe 4c7b6d130f Added additional search and display fields for suppliers and locations
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 21:13:08 +01:00
Marcus Moore af57ca4983 Avoid displaying empty table 2025-05-29 11:55:43 -07:00
snipe 40c31a1ad7 Eager load adminuser method
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 17:13:16 +01:00
snipe 7ae4a4177f Added created_by to transformer
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 17:13:05 +01:00
snipe 6efd323fbf Added adminuser method
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 17:12:48 +01:00
snipe ed9dbcc777 Added created_by to location presenter
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 17:12:33 +01:00
snipe c2cf7de41b Use presenter for suppliers
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 17:12:16 +01:00
Alejandro M. Medrano Gil 32bd14bd2d more robust php.ini update.
could solve #10830 when setting PHP_UPLOAD_LIMIT environment variable in docker command and/or docker-compose.
2025-05-29 17:46:23 +02:00
snipe 8395ea552d Merge remote-tracking branch 'origin/develop' 2025-05-29 16:17:58 +01:00
snipe f9cbecdb17 Merge pull request #17037 from grokability/supplier_importer
Added #17036 - suppliers importer
2025-05-29 16:15:59 +01:00
snipe 7bb29a0277 Added sample import CSVs
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:08:17 +01:00
snipe d5f7579e9f Added columns to suppliers
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:08:01 +01:00
snipe 13fd43c45c Added tests and test support
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:07:51 +01:00
snipe c08ce901cc Added strings
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:07:19 +01:00
snipe 94bd11d3c9 Added locations and supplier import types
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:07:11 +01:00
snipe 59c6e26b29 Fixed mapping
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 16:06:51 +01:00
snipe bf7cc404f8 Set correct redirect
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 15:54:37 +01:00
snipe 12a2c71b90 Added icon
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 14:47:59 +01:00
snipe 6e2eeba0f6 Added supplier importer
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 14:47:54 +01:00
snipe dc66452633 Merge remote-tracking branch 'origin/develop' 2025-05-29 13:32:41 +01:00
snipe 99a739fae3 Merge pull request #17035 from grokability/settings_style_improvements
Fixed #17034 - larger header color box on small views
2025-05-29 13:30:31 +01:00
snipe 0185f61c11 Fixed #17034 - larger header color box on small views
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 13:23:07 +01:00
snipe 783f0c113d Merge pull request #17009 from marcusmoore/chore/replace-user-skin-macro
Replace `skin` and `user_skin` macros with blade component
2025-05-29 13:07:47 +01:00
snipe 75a839cc21 Merge pull request #17010 from marcusmoore/chore/replace-two-factor-select-macro
Replace `two_factor_options` macro
2025-05-29 13:07:23 +01:00
snipe 53ce44ac91 Merge remote-tracking branch 'origin/develop' 2025-05-29 12:38:40 +01:00
snipe 577b5586b4 Merge pull request #17025 from akemidx/clear_button_on_date_picker
FIXED: Clear Button actually Clearing Dates on Date Picker
2025-05-29 12:37:56 +01:00
snipe 1474a16148 Merge pull request #17024 from akemidx/created_at_date_picker
ADDED: Created At date picker on Custom Reports
2025-05-29 12:37:17 +01:00
snipe 9baa2000e1 Merge pull request #17026 from marcusmoore/bug/translate-email-format
Reference correct translation string
2025-05-29 12:36:08 +01:00
snipe c7c3243bbc Merge remote-tracking branch 'origin/develop' 2025-05-29 12:35:21 +01:00
snipe d0624dbefe Merge pull request #17027 from akemidx/bug/sc-29295
FIXED: Translation strings in Username/Email formats
2025-05-29 12:34:47 +01:00
snipe 8bdd77d33d Merge remote-tracking branch 'origin/develop' 2025-05-29 12:24:51 +01:00
snipe ecb6e8d9a9 Fixed route for custom fields index
Signed-off-by: snipe <snipe@snipe.net>
2025-05-29 12:24:22 +01:00
snipe acd7d0db3a Merge remote-tracking branch 'origin/develop' 2025-05-29 12:09:08 +01:00
Robert-Azelis bbb299faf2 Update AssetModelsController.php 2025-05-29 08:42:29 +02:00
snipe ba3b55cab0 Merge pull request #17022 from marcusmoore/bug/sc-29281
Avoid dividing by zero in DefaultLabel
2025-05-29 02:42:41 +01:00
akemidx 67acca7bc8 fixing two translation strings. 2025-05-28 20:21:05 -04:00
Marcus Moore e4b33c3b56 Reference correct translation string 2025-05-28 16:36:39 -07:00
akemidx ed43c73cec clearing the date pickers 2025-05-28 18:48:22 -04:00
akemidx a6fa795b41 clearing the date pickers 2025-05-28 18:48:08 -04:00
akemidx 3a6bac2e63 date picker 2025-05-28 18:43:24 -04:00
akemidx 8e01c05e42 date picker 2025-05-28 18:43:14 -04:00
akemidx fabf9281e9 date picker 2025-05-28 18:35:43 -04:00
Marcus Moore 6588d409b8 Add validation 2025-05-28 14:45:03 -07:00
snipe 92a3421a4e Merge pull request #17020 from akemidx/column_persist_on_assigned_assets
FIXED: Column persist on User's Self View of Assigned Assets
2025-05-28 22:44:36 +01:00
Marcus Moore a42147ff34 Enforce min of .1 for label width and height 2025-05-28 14:05:29 -07:00
Marcus Moore 0df1bc6894 Scaffold test 2025-05-28 14:04:12 -07:00
akemidx 9317076c5e adding cookie for Assigned Assets 2025-05-28 16:39:37 -04:00
snipe 1b5525c51f Added status label to view blade, variable to controller method
Signed-off-by: snipe <snipe@snipe.net>
2025-05-28 20:51:21 +01:00
snipe 6019c80c7b Added blade element
Signed-off-by: snipe <snipe@snipe.net>
2025-05-28 20:50:59 +01:00
Marcus Moore aaa6cb24d4 Scaffold test 2025-05-28 11:28:23 -07:00
snipe 1ef5ad500a Merge pull request #17017 from grokability/localization/translations-2025-05-28
Updated translations
2025-05-28 18:34:15 +01:00
snipe 9468acedfa Updated languages
Signed-off-by: snipe <snipe@snipe.net>
2025-05-28 18:20:02 +01:00
snipe 6a951b6357 Merge pull request #17012 from Robert-Azelis/patch-8
API Locations - added company_id for API request
2025-05-28 15:23:08 +01:00
snipe 2bfadb8a3c Merge remote-tracking branch 'origin/develop' 2025-05-28 15:21:34 +01:00
snipe 95f7742259 Removed extra a href
Signed-off-by: snipe <snipe@snipe.net>
2025-05-28 15:21:23 +01:00
snipe 9f795306e5 Merge pull request #17014 from grokability/fix_breadcrumb_crash
Manually add API headers
2025-05-28 15:19:05 +01:00
snipe 6feaff1e7b Removed blade::render
Signed-off-by: snipe <snipe@snipe.net>
2025-05-28 15:12:14 +01:00
Robert-Azelis 9a168354ae Update AssetModelsController.php - added requestable for API request 2025-05-28 15:50:19 +02:00
Robert-Azelis 309d242c4d Update LocationsController.php - added company_id for API request 2025-05-28 15:45:22 +02:00
snipe 5c174f829e Merge pull request #16986 from grokability/api_throttle_headers
Fixed  #16961 - Manually add API headers
2025-05-28 13:47:16 +01:00
Marcus Moore 3c428f2d7b Inline two_factor_options macro 2025-05-27 16:40:08 -07:00
Marcus Moore 6833716576 Remove skin macro 2025-05-27 16:22:43 -07:00
Marcus Moore bd374d031a Improve component name 2025-05-27 16:22:28 -07:00
Marcus Moore ef26f48f60 Adapt for regular "skin" macro 2025-05-27 16:21:34 -07:00
Marcus Moore b07f8525db Fix swapped yellow/yellow-dark 2025-05-27 16:13:05 -07:00
Marcus Moore 9f062701fa Add semi-colon 2025-05-27 16:03:22 -07:00
Marcus Moore 2c452daddf Replace user_skin macro with blade component 2025-05-27 16:00:47 -07:00
snipe 53a82d3f4d Merge pull request #17007 from marcusmoore/bug/sc-29278
Ensure boolean returned from method with boolean return type
2025-05-27 22:00:04 +02:00
Marcus Moore b5b8816279 Avoid returning null from method that should return a boolean 2025-05-27 11:37:48 -07:00
snipe 7bc4127e8c Removed dupe header
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 15:01:54 +01:00
snipe 06158cc413 Add timestamp header
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 14:58:57 +01:00
snipe cb49e7c9a6 Updated comments
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 14:32:47 +01:00
snipe 1822027a8f Extend the built-in ThrottleRequests middleware from Laravel
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 14:04:24 +01:00
snipe c8dabc25e3 Added comment
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 14:03:56 +01:00
snipe f2b10eeee8 Re-do the initial change :(
Signed-off-by: snipe <snipe@snipe.net>
2025-05-27 13:00:32 +01:00
snipe 4b52e1471c Remove unused use statement after refactor
Signed-off-by: snipe <snipe@snipe.net>
2025-05-26 18:45:21 +01:00
snipe f6bba03375 Fixed dupe semicolon
Signed-off-by: snipe <snipe@snipe.net>
2025-05-26 13:50:19 +01:00
snipe b3813a7121 Refactorered limiting headers
Signed-off-by: snipe <snipe@snipe.net>
2025-05-26 13:48:50 +01:00
snipe 0912e4af7b Merge remote-tracking branch 'origin/develop' 2025-05-26 13:17:38 +01:00
snipe eb2a1396ca Merge pull request #16999 from grokability/api_audt_fix
Better messaging when an asset fails validation on quick scan
2025-05-26 14:17:14 +02:00
snipe 0fae18c4ba Better handle missing asset payload because of RMB
Signed-off-by: snipe <snipe@snipe.net>
2025-05-26 13:14:28 +01:00
Andras Kovacs 25ac83e944 Bumped container image versions 2025-05-26 13:07:51 +02:00
snipe a82e65e190 Add @Tinyblargon as a contributor 2025-05-26 11:09:12 +01:00
snipe 187bb90de0 Merge pull request #16993 from Tinyblargon/fix-16992
fix: `PHP_UPLOAD_LIMIT` not set for PHP 8.3
2025-05-26 12:08:36 +02:00
snipe 293648582a Improvements to API headers
Signed-off-by: snipe <snipe@snipe.net>
2025-05-26 10:52:14 +01:00
Tinyblargon 51a306993c fix: PHP_UPLOAD_LIMIT not set for PHP 8.3 2025-05-25 16:23:17 +02:00
snipe 5aa5c48018 Merge remote-tracking branch 'origin/develop' 2025-05-23 19:37:42 +01:00
snipe ec1851fa84 More small carbon fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 19:27:58 +01:00
snipe bbe748dbd3 Removed noisy log
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 19:05:39 +01:00
snipe 406e8c5874 Added test
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 18:25:36 +01:00
snipe a4f71a9f0a Manually add API headers
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 18:25:29 +01:00
snipe 8cdd998f79 Merge remote-tracking branch 'origin/develop' 2025-05-23 17:35:08 +01:00
snipe 3748498523 Merge pull request #16985 from grokability/move_faker_take_2
Moved faker out of dev reqs for seeding
2025-05-23 18:34:45 +02:00
snipe 49d11103f7 Fixed test namespace
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 17:28:40 +01:00
snipe dce9060820 Moved faker out of dev reqs for seeding
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 17:28:00 +01:00
snipe 050d4d6b25 Merge remote-tracking branch 'origin/develop' 2025-05-23 14:51:48 +01:00
snipe de6206ce78 Merge pull request #16982 from grokability/fixes#16958-expiring-assets-unarchived
Fixed #16958 - exclude archived assets from expiring assets report
2025-05-23 15:51:27 +02:00
snipe a181ba308a Fixed #16958 - exclude archived assets from expiring assets report
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 14:48:54 +01:00
snipe 366cd11238 Merge remote-tracking branch 'origin/develop' 2025-05-23 14:31:10 +01:00
snipe 5a9ac01cf8 Set audit warnings as int
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 14:30:56 +01:00
snipe 58d6443331 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-05-23 13:27:44 +01:00
snipe a5cd306b1d Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 13:12:30 +01:00
snipe 101b8afb56 Merge remote-tracking branch 'origin/develop' 2025-05-23 13:07:47 +01:00
snipe 57d3abab5f Merge pull request #16979 from marcusmoore/bug/sc-29178
Improved creator on accessory show page
2025-05-23 14:05:28 +02:00
snipe b6eb3185d5 Merge pull request #16968 from marcusmoore/bug/sc-29233
Fixed potential slack webhook setting inconsistencies
2025-05-23 14:05:01 +02:00
snipe 286f78778c Merge pull request #16980 from uberbrady/add_new_checkin_checkout_counters_tests_rebased
New tests for checkin/checkout counters
2025-05-23 13:55:38 +02:00
Brady Wetherington 4b95790e2f WIP: new tests for checkin/checkout counters
note that the test isnt going to work

More WIP for checkout counters

Got all tests passing
2025-05-23 12:37:29 +01:00
snipe 5df5c47945 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-05-23 10:43:58 +01:00
snipe 97883971f7 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 10:43:32 +01:00
snipe a04740ba86 Merge remote-tracking branch 'origin/develop' 2025-05-23 10:42:44 +01:00
snipe b3c12d4ee6 Wider text fields
Signed-off-by: snipe <snipe@snipe.net>
2025-05-23 10:42:24 +01:00
snipe 425ad93ac5 Merge remote-tracking branch 'origin/develop' 2025-05-23 10:17:10 +01:00
Marcus Moore bea426d8e6 Add test 2025-05-22 16:54:52 -07:00
Marcus Moore be60370eae Add test 2025-05-22 16:33:45 -07:00
Marcus Moore 7cc216eb38 Extract variable 2025-05-22 15:05:24 -07:00
Marcus Moore 8c90efe745 Add authorization check 2025-05-22 15:02:14 -07:00
snipe 4b9f4423f6 Merge pull request #16932 from marcusmoore/fixes/webhook-checkin-checkout-fix
Improve notifications
2025-05-22 10:53:43 +01:00
Marcus Moore 932b589bd9 Do not show section if user doesn't exist 2025-05-21 17:28:41 -07:00
Marcus Moore ce18b91b03 Add translation 2025-05-21 16:49:15 -07:00
Marcus Moore 5f883310b5 Improve display of user on accessory show page 2025-05-21 16:48:26 -07:00
Marcus Moore 69cc46c2b8 Improve method name 2025-05-20 16:41:52 -07:00
Marcus Moore dc6951f341 Improve method name 2025-05-20 16:28:49 -07:00
Marcus Moore f7ed336f99 Remove typehint 2025-05-20 16:26:05 -07:00
Marcus Moore 47f287c031 Fix assertion 2025-05-20 16:25:22 -07:00
Marcus Moore 5739393f8d Check to make sure settings exist before attempting to update them 2025-05-20 12:45:23 -07:00
Marcus Moore 13956254ce Add migration to fix webhook settings 2025-05-20 12:27:19 -07:00
Marcus Moore d6b69c8cc2 Fix webhook selected value in mount method 2025-05-20 12:00:58 -07:00
Marcus Moore 7a79fd18f0 Bind the selected webhook and avoid clearing from database 2025-05-20 11:40:44 -07:00
snipe e3ffe79c4c Merge pull request #16962 from Godmartinz/audit_notifications_fix
Added dynamic properties to audit notifications
2025-05-20 12:34:33 +02:00
Marcus Moore 043325b966 Merge branch 'develop' into fixes/webhook-checkin-checkout-fix
# Conflicts:
#	app/Listeners/CheckoutableListener.php
2025-05-19 14:41:05 -07:00
Marcus Moore a2696b799f Standardize test names 2025-05-19 14:33:18 -07:00
snipe eb8ef37808 Merge pull request #16963 from Godmartinz/fix-double-scroll-bar-permission-groups
Removes double scrollbar from groups
2025-05-19 20:32:30 +02:00
Godfrey M 8416c6df05 removes double scrollbar 2025-05-19 11:21:52 -07:00
Godfrey M 3aa4814342 allow dynamic assignment to audit notifications 2025-05-19 11:08:03 -07:00
snipe 8a44144c20 Merge pull request #16954 from grokability/develop
Merge dev into master
2025-05-16 11:16:14 +02:00
snipe 186f322bb5 Merge pull request #16953 from uberbrady/fix_checkin_emails
Make checkin emails not send when not configured to be
2025-05-16 11:14:31 +02:00
Brady Wetherington 28e2e7c924 Get rid of more editorialization 2025-05-16 10:57:34 +02:00
Brady Wetherington 97351028b5 Get rid of editorializing in comments 2025-05-16 10:56:36 +02:00
Brady Wetherington 0ef0863b59 Make checkin emails not send unless the send-emails attribute is set on the category 2025-05-16 10:48:17 +02:00
Marcus Moore 2e80b4ace8 WIP 2025-05-15 17:50:07 -07:00
Marcus Moore 0d896c2ef6 Extract method 2025-05-15 17:44:27 -07:00
Marcus Moore dc9df04237 WIP 2025-05-15 17:40:51 -07:00
Marcus Moore b469a64db3 WIP 2025-05-15 17:35:28 -07:00
Marcus Moore 73f19ff4e7 Scaffold tests 2025-05-15 17:30:06 -07:00
Marcus Moore 22b4fac3ee Formatting 2025-05-15 17:24:56 -07:00
Marcus Moore c97884c8b0 WIP 2025-05-15 16:59:10 -07:00
Marcus Moore b972fb514a Remove type hint 2025-05-15 16:54:46 -07:00
Marcus Moore a3871bd1f2 WIP 2025-05-15 16:47:29 -07:00
Marcus Moore 2069f99b2b WIP 2025-05-15 16:42:20 -07:00
Marcus Moore 882d55fd09 Improve tests 2025-05-15 16:31:07 -07:00
Marcus Moore 68ef975726 WIP 2025-05-15 16:22:31 -07:00
Marcus Moore 0685ff3818 WIP 2025-05-15 16:12:51 -07:00
Marcus Moore b28839d907 Scaffold tests 2025-05-15 15:57:52 -07:00
Marcus Moore 7f0a947de4 WIP 2025-05-15 15:43:57 -07:00
Marcus Moore c8fc4afe65 Fix method name 2025-05-15 15:31:18 -07:00
Marcus Moore 3b7162cb02 Improve test setup 2025-05-15 15:30:16 -07:00
Marcus Moore 4245456382 Scaffold more tests 2025-05-15 15:27:15 -07:00
Marcus Moore cdf43e31e2 Remove randomness from factory 2025-05-15 15:14:01 -07:00
Marcus Moore f19d6b3c52 Scaffold tests 2025-05-15 15:13:50 -07:00
snipe ca66e29072 Merge pull request #16946 from marcusmoore/bug/sc-29166
Gracefully handle error when editing of soft deleted users
2025-05-15 22:46:48 +02:00
Marcus Moore 6ff76a12f8 Strengthen test scenario 2025-05-15 13:39:27 -07:00
Marcus Moore 8b13997597 Merge branch 'develop' into fixes/webhook-checkin-checkout-fix
# Conflicts:
#	app/Listeners/CheckoutableListener.php
2025-05-15 13:38:20 -07:00
Marcus Moore 9600adee6b Don't allow viewing edit page if user soft deleted 2025-05-15 11:14:15 -07:00
snipe ee82c70582 Merge remote-tracking branch 'origin/develop' 2025-05-15 18:32:05 +02:00
snipe 6eb3819492 Apply correct unique custom field validation for audit API
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 18:31:49 +02:00
snipe c87e8e606b Merge remote-tracking branch 'origin/develop' 2025-05-15 18:02:47 +02:00
snipe 95226f87bc Use same logic for auditStore for validating unique fields
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 18:02:23 +02:00
snipe 37a50dd953 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-05-15 17:51:54 +02:00
snipe ee3ae803b9 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 17:51:28 +02:00
snipe a2669a3084 Removed temp code
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 17:31:16 +02:00
snipe 77da22f4dd Print errors if they exist (temp)
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 17:26:04 +02:00
snipe 7830ffe202 Temp echo errors
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 17:24:08 +02:00
snipe 1c9e20d59f Merge remote-tracking branch 'origin/develop' 2025-05-15 17:10:57 +02:00
snipe 057667c425 Removed duplicate unique display in table
Signed-off-by: snipe <snipe@snipe.net>
2025-05-15 17:10:47 +02:00
Marcus Moore 02fa7daa1d Require assigned_x to be integer on asset model 2025-05-14 14:13:07 -07:00
Marcus Moore 2a2acf6d1c Add failing test 2025-05-14 14:09:48 -07:00
Marcus Moore f644642fac Add comment 2025-05-14 12:48:32 -07:00
Marcus Moore 3374a9e5a9 Add assertion 2025-05-14 12:48:11 -07:00
Marcus Moore d3a74a5740 Use route bound user instead of re-querying 2025-05-14 12:46:34 -07:00
Marcus Moore c458cc904a Add failing test 2025-05-14 12:45:55 -07:00
snipe 320edac286 Merge remote-tracking branch 'origin/develop' 2025-05-14 17:39:30 +02:00
snipe 5c7b74e17e Merge pull request #16943 from grokability/small-tweaks-to-status-label-text
Clearer text on status label types
2025-05-14 17:35:40 +02:00
snipe e07b0f65a1 Clearer text on status label types
Signed-off-by: snipe <snipe@snipe.net>
2025-05-14 17:33:12 +02:00
snipe d49878371d Merge remote-tracking branch 'origin/develop' 2025-05-14 16:23:38 +02:00
snipe b06fd5bbca Merge pull request #16942 from uberbrady/quick_temp_fix_notifications
A quick check to make sure that webhooks still fire when email is off
2025-05-14 16:21:37 +02:00
Brady Wetherington 6306f78fe0 A quick check to make sure that webhooks still fire when email is off 2025-05-14 16:14:03 +02:00
snipe d2575a5d9b Add @JassonCordones as a contributor 2025-05-14 15:03:04 +02:00
Marcus Moore ea6cf72580 Formatting 2025-05-14 15:03:04 +02:00
Marcus Moore 2118155b37 Fix bug in getImageUrl method 2025-05-14 15:03:04 +02:00
Marcus Moore ba4f5bb71f Add test for existing functionality 2025-05-14 15:03:04 +02:00
Marcus Moore d5a74a5a8b Remove unneeded div 2025-05-14 15:03:04 +02:00
Marcus Moore 23be1df360 Remove the replaced locales form macro 2025-05-14 15:03:04 +02:00
Marcus Moore b5c1a1da4c Replace Form::locales on user setup 2025-05-14 15:03:04 +02:00
Marcus Moore c11e784f51 Replace Form::locales on bulk edit users page 2025-05-14 15:03:04 +02:00
Marcus Moore 06f51c8f9c Replace Form::locales on user edit page 2025-05-14 15:03:04 +02:00
Marcus Moore 181bcbbda6 Replace Form::locales on localization page 2025-05-14 15:03:04 +02:00
Marcus Moore d008ead6a4 Fix input name 2025-05-14 15:03:04 +02:00
Marcus Moore 75924be958 Introduce locale select component and make replacement on profile page 2025-05-14 15:03:04 +02:00
snipe da4bce0c89 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-05-14 15:02:43 +02:00
snipe 053e815206 Add @JassonCordones as a contributor 2025-05-14 15:02:43 +02:00
snipe b1a6e3f8a2 Merge pull request #16930 from JassonCordones/master
fix typo in snipeit.sh
2025-05-14 15:01:41 +02:00
snipe ab1053ecda Merge pull request #16916 from marcusmoore/bug/sc-29153
Handle potential hard exception in Asset@getImageUrl method
2025-05-14 14:52:33 +02:00
snipe 9a61a3391b Merge pull request #16921 from marcusmoore/chore/locale-select
Replace locales macro
2025-05-14 14:52:02 +02:00
snipe 06712a6041 Merge remote-tracking branch 'origin/develop' 2025-05-14 13:42:51 +02:00
snipe d3c19e28ec Merge pull request #16918 from marcusmoore/bug/sc-29151
Handle displaying deleted creator of an accessory
2025-05-14 13:36:08 +02:00
snipe 87115f2e50 Merge branch 'develop' into bug/sc-29151 2025-05-14 13:35:38 +02:00
Marcus Moore 5f7aadfba0 Ensure CC emails are always sent for assets 2025-05-13 17:56:35 -07:00
Marcus Moore e954e066b4 Scaffold tests 2025-05-13 17:41:19 -07:00
Marcus Moore e336182d79 Formatting 2025-05-13 14:45:09 -07:00
Marcus Moore 3eb0743446 Separate checking for sending email and webhook notifications 2025-05-13 14:43:16 -07:00
Marcus Moore 95c1c37ab1 Invert method 2025-05-13 14:25:03 -07:00
Marcus Moore a7054f0b1e Improve method name 2025-05-13 14:20:42 -07:00
Marcus Moore e4bfabfabe Add failing tests 2025-05-13 14:12:51 -07:00
snipe 62b16339a9 Merge remote-tracking branch 'origin/develop' 2025-05-13 20:44:34 +02:00
snipe 6b56929a06 Null operator for missing created_by record
Signed-off-by: snipe <snipe@snipe.net>
2025-05-13 20:44:25 +02:00
Jasson 9a2f1a36ba fix typo in snipeit.sh
fix redirect output to stderr
2025-05-13 14:31:37 -04:00
Marcus Moore e3642bb513 Remove unneeded div 2025-05-12 17:07:17 -07:00
Marcus Moore ca7c416e19 Remove the replaced locales form macro 2025-05-12 16:35:40 -07:00
snipe ac6d964e28 Merge pull request #16920 from marcusmoore/fixes/remove-outline-from-label
Remove logo outline from L7162_B
2025-05-12 23:04:49 +01:00
Marcus Moore e2772c816d Remove label logo outline from L7162_B label 2025-05-12 13:58:13 -07:00
Marcus Moore 916f7401f3 Handle creator being deleted 2025-05-12 12:30:32 -07:00
Marcus Moore b53a71d523 Add failing test 2025-05-12 12:30:06 -07:00
Marcus Moore 510a2b0889 Formatting 2025-05-12 12:03:15 -07:00
Marcus Moore 73057454c6 Fix bug in getImageUrl method 2025-05-12 11:56:40 -07:00
Marcus Moore 5a6cf2a296 Add test for existing functionality 2025-05-12 11:44:25 -07:00
snipe 9e3e04521e Merge pull request #16900 from marcusmoore/fixes/user-full-name-accessor
Handle settings not being available in full name accessor
2025-05-10 12:26:19 +01:00
snipe 95cc4d3a73 Merge remote-tracking branch 'origin/develop' 2025-05-09 21:16:58 +01:00
snipe 65dfbd02fe Use develop branch
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 21:00:55 +01:00
snipe 649ab53320 Updated codacy link
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 21:00:18 +01:00
snipe 497eeeb2e0 Merge remote-tracking branch 'origin/develop' 2025-05-09 19:29:01 +01:00
snipe 9250624f79 Merge pull request #16909 from grokability/fixes-#16554-category-delete
Fixed #16554 - Added models to deletable check
2025-05-09 19:23:35 +01:00
snipe 995e2090f5 Added/updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 19:17:53 +01:00
snipe 9b91584776 Added models to deletable check
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 19:17:48 +01:00
snipe 4be21ca249 Merge remote-tracking branch 'origin/develop' 2025-05-09 18:03:38 +01:00
snipe 8fd97ea501 Merge pull request #16908 from grokability/bug/sc-28724
Fixed #16535 - more info to side rail in accessories
2025-05-09 18:03:05 +01:00
snipe a80b9ab362 Fixed #16535 - more info to side rail in accessories
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 17:48:02 +01:00
snipe e8598e214e Merge remote-tracking branch 'origin/develop' 2025-05-09 17:23:22 +01:00
snipe 556e1081b3 Added two more selectors for byod
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 17:23:08 +01:00
snipe b070916f0b Merge pull request #16907 from grokability/add_ids_to_menus
Fixed #16456 - added ids to sidenav options and bod
2025-05-09 17:22:19 +01:00
snipe 940caf14b0 Added ids to menu items
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 17:09:59 +01:00
snipe 76da1d6663 Added class to checkbox
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 17:04:19 +01:00
snipe bafff9020a Merge pull request #16611 from Godmartinz/MS_teams_deprecation_update
Reworked MS Teams deprecation warnings and notifications visibility
2025-05-09 16:38:25 +01:00
snipe 54b1d65e3c Merge remote-tracking branch 'origin/develop' 2025-05-09 14:46:37 +01:00
snipe 0d5dca6456 Fixed #16690 - fallback to category image if no model image is present
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 14:46:27 +01:00
snipe 0f9b7119c0 Merge pull request #16905 from grokability/fixes_#16901
Fixed #16901 - use default currency for asset maintenance cost
2025-05-09 12:45:46 +01:00
snipe d4181549e8 Fixes #16901 - use default currency for maintenance cost display
Signed-off-by: snipe <snipe@snipe.net>
2025-05-09 12:43:49 +01:00
Marcus Moore d57f56e44f Handle settings not being available 2025-05-08 16:20:26 -07:00
Marcus Moore f7777ca8a5 Replace Form::locales on user setup 2025-05-08 15:38:49 -07:00
Marcus Moore fce5530bc7 Replace Form::locales on bulk edit users page 2025-05-08 15:31:41 -07:00
Marcus Moore ad1530e9ff Replace Form::locales on user edit page 2025-05-08 15:17:15 -07:00
Marcus Moore abb2dcbbe4 Replace Form::locales on localization page 2025-05-08 15:05:48 -07:00
Marcus Moore 437499c5df Fix input name 2025-05-08 15:04:45 -07:00
Marcus Moore f739c2c84a Introduce locale select component and make replacement on profile page 2025-05-08 15:02:35 -07:00
snipe f7648496d3 Merge remote-tracking branch 'origin/develop' 2025-05-08 16:26:02 +01:00
snipe 59a57c7197 Merge remote-tracking branch 'origin/develop' 2025-05-08 15:43:53 +01:00
snipe 5659b26827 Merge remote-tracking branch 'origin/develop' 2025-05-08 15:22:43 +01:00
snipe ee4443aaf0 Merge remote-tracking branch 'origin/develop' 2025-05-08 15:09:26 +01:00
snipe 839dcad358 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-black-dark.css
#	public/css/dist/skins/skin-black-dark.min.css
#	public/css/dist/skins/skin-blue-dark.css
#	public/css/dist/skins/skin-blue-dark.min.css
#	public/css/dist/skins/skin-green-dark.css
#	public/css/dist/skins/skin-green-dark.min.css
#	public/css/dist/skins/skin-orange-dark.css
#	public/css/dist/skins/skin-orange-dark.min.css
#	public/css/dist/skins/skin-purple-dark.css
#	public/css/dist/skins/skin-purple-dark.min.css
#	public/css/dist/skins/skin-red-dark.css
#	public/css/dist/skins/skin-red-dark.min.css
#	public/css/dist/skins/skin-yellow-dark.css
#	public/css/dist/skins/skin-yellow-dark.min.css
#	public/mix-manifest.json
2025-05-07 11:41:31 +01:00
snipe d67933ab49 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	.all-contributorsrc
#	CONTRIBUTORS.md
2025-05-06 16:46:41 +01:00
Chris Olin 248a05a916 adds support for continuous 53mm and 0.59in printers 2025-05-06 11:26:52 -04:00
Chris Olin 2c141579dd adds support for GenericTape label printers, includes class for 53mm tape printer 2025-05-06 11:26:40 -04:00
Godfrey M e3a2397b74 removed hiding the notifications icon 2025-05-05 10:28:08 -07:00
Godfrey M 3b34654dd7 Merge branch 'develop' into MS_teams_deprecation_update
# Conflicts:
#	resources/lang/en-US/admin/settings/message.php
2025-05-05 09:33:32 -07:00
Godfrey M 4b6437854c swapped out hard coded text with translation 2025-05-05 09:31:23 -07:00
Godfrey M 0eb3f6b952 set max to 5 2025-05-05 14:42:06 +01:00
Godfrey M 68b0f80fce fix input max, and help block position 2025-05-05 14:42:06 +01:00
Godfrey M 93489529a3 adds Field offset option to labels 2025-05-05 14:42:06 +01:00
snipe 511be74e74 Add @chfsx as a contributor 2025-05-05 14:42:06 +01:00
Fabian Schmid bdee067803 [FIX] set upload-limit 2025-05-05 14:42:06 +01:00
snipe 32156cace3 Merge pull request #16847 from realchrisolin/issue16214
add barcode support for Avery 3490
2025-05-05 14:40:54 +01:00
snipe 30688114be Merge remote-tracking branch 'origin/develop' 2025-05-05 14:04:52 +01:00
snipe 34088bcc17 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-05-05 11:17:01 +01:00
Calvin Schwartz 07835766cc adds support for Avery 3490 2025-05-01 21:11:42 -04:00
Brady Wetherington 251851ec6a Try to get Docker images to build for both architectures 2025-04-30 18:00:39 +01:00
snipe 049a669186 Merge remote-tracking branch 'origin/develop' 2025-04-30 16:46:16 +01:00
snipe d29f13bae9 Merge remote-tracking branch 'origin/develop' 2025-04-30 16:26:38 +01:00
snipe c758355df9 Merge remote-tracking branch 'origin/develop' 2025-04-30 16:14:34 +01:00
snipe 79d97a83af Merge remote-tracking branch 'origin/develop' 2025-04-30 15:48:38 +01:00
snipe 85bd47c240 Merge remote-tracking branch 'origin/develop' 2025-04-30 15:35:10 +01:00
snipe 473ead9616 Merge remote-tracking branch 'origin/develop' 2025-04-30 13:57:27 +01:00
snipe cf2850933c Merge remote-tracking branch 'origin/develop' 2025-04-30 10:19:43 +01:00
snipe ff2564c57a Merge remote-tracking branch 'origin/develop' 2025-04-30 10:12:05 +01:00
snipe 91d3848246 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-04-29 23:17:51 +01:00
snipe c031f0b45e Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-blue-dark.css
#	public/css/dist/skins/skin-blue-dark.min.css
#	public/css/dist/skins/skin-green-dark.css
#	public/css/dist/skins/skin-green-dark.min.css
#	public/css/dist/skins/skin-orange-dark.css
#	public/css/dist/skins/skin-orange-dark.min.css
#	public/css/dist/skins/skin-purple-dark.css
#	public/css/dist/skins/skin-purple-dark.min.css
#	public/css/dist/skins/skin-red-dark.css
#	public/css/dist/skins/skin-red-dark.min.css
#	public/css/dist/skins/skin-yellow-dark.css
#	public/css/dist/skins/skin-yellow-dark.min.css
#	public/mix-manifest.json
2025-04-29 20:38:41 +01:00
snipe fdbb9568ae Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-29 12:39:31 +01:00
snipe d817883459 Merge remote-tracking branch 'origin/develop' 2025-04-29 10:25:42 +01:00
snipe 12255979ac Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-29 10:22:21 +01:00
snipe 366b61850b Merge remote-tracking branch 'origin/develop' 2025-04-26 17:54:02 +01:00
snipe 89be6bd183 Merge remote-tracking branch 'origin/develop' 2025-04-24 14:06:00 +01:00
snipe e120331a2c Merge remote-tracking branch 'origin/develop' 2025-04-24 12:16:53 +01:00
snipe cb8a212d96 Merge remote-tracking branch 'origin/develop' 2025-04-23 21:57:20 +01:00
snipe 7aec431ac5 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>
2025-04-23 21:30:16 +01:00
snipe d19681dea1 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>
2025-04-23 21:05:38 +01:00
snipe bf2299daf8 Merge remote-tracking branch 'origin/develop' 2025-04-22 17:51:48 +01:00
snipe 164930d0dd Merge remote-tracking branch 'origin/develop' 2025-04-22 16:35:23 +01:00
snipe 387dbac809 Merge remote-tracking branch 'origin/develop' 2025-04-22 14:36:14 +01:00
snipe 3b661e5a99 Merge remote-tracking branch 'origin/develop' 2025-04-22 12:39:35 +01:00
snipe 90c1c0e655 Merge remote-tracking branch 'origin/develop' 2025-04-22 11:42:49 +01:00
snipe 21d8e7695b Merge remote-tracking branch 'origin/develop' 2025-04-21 20:19:54 +01:00
snipe 1acc452cfe Merge remote-tracking branch 'origin/develop' 2025-04-21 14:59:26 +01:00
snipe 1375e1feee Merge remote-tracking branch 'origin/develop' 2025-04-21 12:21:08 +01:00
snipe 2187adf59a Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>
2025-04-19 15:34:58 +01:00
snipe 0dcb315d9d Merge remote-tracking branch 'origin/develop' 2025-04-18 00:56:26 +01:00
snipe 327ccbd0c9 Merge remote-tracking branch 'origin/develop' 2025-04-18 00:37:11 +01:00
snipe f571d400e6 Merge remote-tracking branch 'origin/develop' 2025-04-17 23:29:12 +01:00
snipe 293aa52335 Merge remote-tracking branch 'origin/develop' 2025-04-17 23:13:04 +01:00
snipe ca178ae9a7 Merge remote-tracking branch 'origin/develop' 2025-04-17 22:48:35 +01:00
snipe d0c810e418 Merge remote-tracking branch 'origin/develop' 2025-04-17 16:42:37 +01:00
snipe d496d2caeb Merge remote-tracking branch 'origin/develop' 2025-04-17 11:59:24 +01:00
snipe e70b75c350 Merge remote-tracking branch 'origin/develop' 2025-04-17 01:00:34 +01:00
snipe a50befeda5 Merge remote-tracking branch 'origin/develop' 2025-04-16 20:16:18 +01:00
snipe e2616e8039 Merge remote-tracking branch 'origin/develop' 2025-04-16 15:52:18 +01:00
snipe 904266debe Merge remote-tracking branch 'origin/develop' 2025-04-16 09:47:18 +01:00
snipe 72d5783795 Merge remote-tracking branch 'origin/develop' 2025-04-16 09:19:05 +01:00
snipe d699fb1473 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:46:43 +01:00
snipe fab1a6c33a Merge remote-tracking branch 'origin/develop' 2025-04-15 20:30:02 +01:00
snipe be73c30194 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2025-04-15 20:01:20 +01:00
snipe 451646fe4f Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 19:42:26 +01:00
snipe decc919991 Merge remote-tracking branch 'origin/develop' 2025-04-15 19:33:23 +01:00
snipe e0b4005921 Merge remote-tracking branch 'origin/develop' 2025-04-15 16:48:17 +01:00
snipe 3ef36e7534 Merge remote-tracking branch 'origin/develop' 2025-04-14 11:02:06 +01:00
snipe 1949e1e1e9 Merge remote-tracking branch 'origin/develop' 2025-04-14 09:26:25 +01:00
snipe 3358382358 Comment out location scoping option for now
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 21:44:38 +01:00
snipe 3eca3ecd75 Merge remote-tracking branch 'origin/develop' 2025-04-09 21:31:41 +01:00
Godfrey M c385b4a082 remove testing lines 2025-04-09 12:11:46 -07:00
Godfrey M 1b961346f0 added withInput to the redirects 2025-04-09 12:11:31 -07:00
Godfrey M 100db23210 add checks that the target is not null, and redirects back with error messages 2025-04-09 11:54:59 -07:00
Godfrey M 3e980a4c57 set location if target is set 2025-04-09 11:11:59 -07:00
Godfrey M 9a3ac41370 add default location as a fallback to asset validation 2025-04-09 11:02:45 -07:00
snipe 140c6b91b0 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-09 02:40:28 +01:00
snipe a5315ec240 Merge remote-tracking branch 'origin/develop' 2025-04-08 08:32:25 +01:00
snipe 93f1656e0b Merge remote-tracking branch 'origin/develop' 2025-04-08 06:51:21 +01:00
snipe f1d006c236 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-08 05:58:51 +01:00
snipe b0b5a96694 Merge remote-tracking branch 'origin/develop' 2025-04-07 13:54:23 +01:00
snipe 7dbe9a85f4 Merge remote-tracking branch 'origin/develop' 2025-04-05 21:02:33 +01:00
snipe d2c39528d5 Merge remote-tracking branch 'origin/develop' 2025-04-05 15:44:22 +01:00
snipe 0420543c94 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-05 14:11:07 +01:00
snipe aae0db902b Merge remote-tracking branch 'origin/develop' 2025-04-03 15:41:08 +01:00
snipe 88dae7cef7 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-04-03 15:28:10 +01:00
snipe e5cb17e934 Merge remote-tracking branch 'origin/develop' 2025-04-03 10:16:18 +01:00
snipe 9d609805f2 Merge remote-tracking branch 'origin/develop' 2025-04-02 18:33:48 +01:00
snipe e2b9ca8254 Merge remote-tracking branch 'origin/develop' 2025-04-02 14:03:22 +01:00
snipe e16a2fe8af Merge remote-tracking branch 'origin/develop' 2025-04-02 13:51:09 +01:00
snipe 22d61a533d Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-04-02 12:50:35 +01:00
snipe af408bb45f Merge remote-tracking branch 'origin/develop' 2025-04-01 21:33:07 +01:00
Godfrey M 4ef161214d notification icon only appears when there are notifications 2025-04-01 12:01:35 -07:00
Godfrey M 29d0380db3 reword warning messages, remove warning if webhook cleared and saved, deprecations only for superadmins 2025-04-01 11:53:32 -07:00
snipe 24bfbc06f0 Merge remote-tracking branch 'origin/develop' 2025-04-01 19:40:03 +01:00
snipe 5eb9f353b5 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/all.css
#	public/css/dist/bootstrap-table.css
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2025-04-01 11:25:33 +01:00
snipe 473ce15f47 Merge pull request #16526 from snipe/develop
Merge #16486  and #16519 into master
2025-03-19 15:31:50 -01:00
snipe eb9cfbaed6 Merge pull request #16498 from snipe/develop
Merge develop into master
2025-03-12 23:23:44 +00:00
snipe faeb037ff9 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/mix-manifest.json
2025-03-12 21:26:34 +00:00
snipe 07602f697d Merge remote-tracking branch 'origin/develop' 2025-03-12 18:13:22 +00:00
snipe 11abb0fdb1 Merge remote-tracking branch 'origin/develop' 2025-03-11 22:13:34 +00:00
snipe deeb2fa543 Merge remote-tracking branch 'origin/develop' 2025-03-11 21:14:12 +00:00
snipe 0b48fd1465 Removed extra headers
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 13:05:31 +00:00
snipe 220537fbfb Updated presenter name
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 12:59:57 +00:00
snipe df5437647b Add optional serial value in presenter
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 12:43:38 +00:00
snipe 92b2da9b1b Added history tab to components
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 11:48:38 +00:00
snipe ef56177372 Use presenter
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 11:48:31 +00:00
snipe cb7822576f Use new presenters
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 11:48:19 +00:00
snipe 7ba361b10d Use date formatter for filestable
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 10:57:54 +00:00
snipe 55694fa2fc Added strings
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 10:57:40 +00:00
snipe c825878c46 Added history presenter
Signed-off-by: snipe <snipe@snipe.net>
2025-03-10 10:57:33 +00:00
snipe 80a69bfe90 Revert datetime to date
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 18:09:27 +00:00
snipe d4dc8d2b79 Remove action_date from loggable as a changed field
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 17:43:07 +00:00
snipe 4e3df93349 Change action_date display to date from datetime
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 16:16:17 +00:00
snipe 38efc62900 Add index on action_date, copy from created_at
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 16:01:46 +00:00
snipe ef8d5ff11e Merge remote-tracking branch 'origin/develop' 2025-03-06 12:06:10 +00:00
snipe 91f3e07b83 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-03-05 17:05:28 +00:00
snipe c29bdbdacb Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-blue.css
#	public/css/dist/skins/skin-blue.min.css
#	public/mix-manifest.json
2025-03-05 13:46:29 +00:00
snipe a20d104d2f Merge remote-tracking branch 'origin/develop' 2025-03-05 11:59:47 +00:00
snipe a61dd8ac17 Merge remote-tracking branch 'origin/develop' 2025-03-05 10:52:42 +00:00
snipe 7ee9a690ea Merge remote-tracking branch 'origin/develop' 2025-03-05 01:12:22 +00:00
snipe 5ba94c6c41 Merge remote-tracking branch 'origin/develop' 2025-03-05 00:12:09 +00:00
snipe 9fa855c837 Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 23:30:45 +00:00
snipe 9251007574 Merge remote-tracking branch 'origin/develop' 2025-03-04 23:29:31 +00:00
snipe cc73b984cb Merge remote-tracking branch 'origin/develop' 2025-03-04 21:13:43 +00:00
snipe 548ef97c32 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-03-04 19:57:33 +00:00
snipe ed8a486726 Merge remote-tracking branch 'origin/develop' 2025-03-04 19:54:08 +00:00
snipe 1ab0911fc8 Merge remote-tracking branch 'origin/develop' 2025-03-04 19:52:16 +00:00
snipe bdbaea7294 Merge remote-tracking branch 'origin/develop' 2025-03-04 19:43:28 +00:00
snipe 5cfd1f6fb2 Merge remote-tracking branch 'origin/develop' 2025-03-04 17:16:26 +00:00
snipe 5eda67381f Merge remote-tracking branch 'origin/develop' 2025-03-04 17:07:13 +00:00
snipe 2c8b8bfaf2 Merge remote-tracking branch 'origin/develop' 2025-03-04 17:05:55 +00:00
snipe 8f3159751a Merge remote-tracking branch 'origin/develop' 2025-03-04 17:01:07 +00:00
snipe 4b05e55b29 Merge remote-tracking branch 'origin/develop' 2025-03-04 15:56:05 +00:00
snipe 3d3c13fcd0 Merge remote-tracking branch 'origin/develop' 2025-03-04 15:38:58 +00:00
snipe 88e1d8a8cf Merge remote-tracking branch 'origin/develop' 2025-03-04 15:28:53 +00:00
snipe e007db34e2 Merge remote-tracking branch 'origin/develop' 2025-03-03 22:12:26 +00:00
snipe f9f06d2c02 Merge remote-tracking branch 'origin/develop' 2025-02-27 19:08:45 +00:00
snipe 234f7d00c8 Merge remote-tracking branch 'origin/develop' 2025-02-27 16:18:18 +00:00
snipe 9924553da5 Merge remote-tracking branch 'origin/develop' 2025-02-27 15:45:57 +00:00
snipe df38d7e3ed Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2025-02-27 12:22:30 +00:00
snipe 44dd061619 Merge remote-tracking branch 'origin/develop' 2025-02-26 20:55:57 +00:00
snipe 7603a932b1 Merge remote-tracking branch 'origin/develop' 2025-02-26 20:29:42 +00:00
snipe 138e7acc13 Merge remote-tracking branch 'origin/develop' 2025-02-26 12:47:54 +00:00
snipe e863d3e7e5 Merge remote-tracking branch 'origin/develop' 2025-02-26 12:02:00 +00:00
snipe c8e401f5ed Merge remote-tracking branch 'origin/develop' 2025-02-26 11:59:53 +00:00
snipe 3ba20a8e28 Merge remote-tracking branch 'origin/develop' 2025-02-26 11:39:10 +00:00
snipe ebae63752f Merge remote-tracking branch 'origin/develop' 2025-02-26 10:25:18 +00:00
snipe 8bc73901cf Merge remote-tracking branch 'origin/develop' 2025-02-26 08:25:35 +00:00
snipe b4f70d9244 Merge remote-tracking branch 'origin/develop' 2025-02-26 07:16:59 +00:00
snipe 21e9f2bba3 Merge remote-tracking branch 'origin/develop' 2025-02-26 07:11:22 +00:00
snipe 881f4e3d6a Merge remote-tracking branch 'origin/develop' 2025-02-25 14:43:57 +00:00
snipe b141945add Updated branch
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 12:18:52 +00:00
Timo Schwarzer a2ff8f9609 Add Department Manager to single and multiple user views 2024-07-15 12:27:29 +02:00
1465 changed files with 26180 additions and 281249 deletions
+36
View File
@@ -4153,6 +4153,42 @@
"contributions": [
"code"
]
},
{
"login": "JassonCordones",
"name": "Jasson",
"avatar_url": "https://avatars.githubusercontent.com/u/4875039?v=4",
"profile": "http://jassoncordones.github.io",
"contributions": [
"code"
]
},
{
"login": "Tinyblargon",
"name": "Okean",
"avatar_url": "https://avatars.githubusercontent.com/u/76069640?v=4",
"profile": "https://github.com/Tinyblargon",
"contributions": [
"code"
]
},
{
"login": "amedranogil",
"name": "Alejandro Medrano",
"avatar_url": "https://avatars.githubusercontent.com/u/6515064?v=4",
"profile": "https://www.lst.tfo.upm.es/alejandro-medrano/",
"contributions": [
"code"
]
},
{
"login": "lukaskraic",
"name": "Lukas Kraic",
"avatar_url": "https://avatars.githubusercontent.com/u/58696401?v=4",
"profile": "https://github.com/lukaskraic",
"contributions": [
"code"
]
}
]
}
+2 -2
View File
@@ -10,10 +10,10 @@ name: Codacy Security Scan
on:
push:
branches: [ master ]
branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
branches: [ develop ]
schedule:
- cron: '36 23 * * 3'
+67 -600
View File
@@ -1,606 +1,73 @@
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:
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.snipe.net"><img src="https://avatars3.githubusercontent.com/u/197404?v=3?s=110" width="110px;" alt="snipe"/><br /><sub><b>snipe</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Code">💻</a> <a href="#infra-snipe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Documentation">📖</a> <a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Asnipe" title="Bug reports">🐛</a> <a href="#design-snipe" title="Design">🎨</a> <a href="https://github.com/snipe/snipe-it/pulls?q=is%3Apr+reviewed-by%3Asnipe" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.uberbrady.com"><img src="https://avatars0.githubusercontent.com/u/36335?v=3?s=110" width="110px;" alt="Brady Wetherington"/><br /><sub><b>Brady Wetherington</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=uberbrady" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=uberbrady" title="Documentation">📖</a> <a href="#infra-uberbrady" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/snipe/snipe-it/pulls?q=is%3Apr+reviewed-by%3Auberbrady" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dmeltzer"><img src="https://avatars0.githubusercontent.com/u/3803132?v=3?s=110" width="110px;" alt="Daniel Meltzer"/><br /><sub><b>Daniel Meltzer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.tuckertechonline.com"><img src="https://avatars0.githubusercontent.com/u/1609106?v=3?s=110" width="110px;" alt="Michael T"/><br /><sub><b>Michael T</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mtucker6784" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/madd15"><img src="https://avatars2.githubusercontent.com/u/3274937?v=3?s=110" width="110px;" alt="madd15"/><br /><sub><b>madd15</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=madd15" title="Documentation">📖</a> <a href="#question-madd15" title="Answering Questions">💬</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vsposato"><img src="https://avatars2.githubusercontent.com/u/894126?v=3?s=110" width="110px;" alt="Vincent Sposato"/><br /><sub><b>Vincent Sposato</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vsposato" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vjandrea"><img src="https://avatars0.githubusercontent.com/u/1639757?v=3?s=110" width="110px;" alt="Andrea Bergamasco"/><br /><sub><b>Andrea Bergamasco</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vjandrea" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kpawelski"><img src="https://avatars0.githubusercontent.com/u/10640152?v=3?s=110" width="110px;" alt="Karol"/><br /><sub><b>Karol</b></sub></a><br /><a href="#translation-kpawelski" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=kpawelski" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://blog.morph027.de/"><img src="https://avatars3.githubusercontent.com/u/600106?v=3?s=110" width="110px;" alt="morph027"/><br /><sub><b>morph027</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=morph027" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fvleminckx"><img src="https://avatars3.githubusercontent.com/u/22935755?v=3?s=110" width="110px;" alt="fvleminckx"/><br /><sub><b>fvleminckx</b></sub></a><br /><a href="#infra-fvleminckx" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/itsupportcmsukorg"><img src="https://avatars2.githubusercontent.com/u/15633547?v=3?s=110" width="110px;" alt="itsupportcmsukorg"/><br /><sub><b>itsupportcmsukorg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://override.io"><img src="https://avatars3.githubusercontent.com/u/12373799?v=3?s=110" width="110px;" alt="Frank"/><br /><sub><b>Frank</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=base-zero" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ghost"><img src="https://avatars0.githubusercontent.com/u/10137?v=3?s=110" width="110px;" alt="Deleted user"/><br /><sub><b>Deleted user</b></sub></a><br /><a href="#translation-ghost" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=ghost" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tiagom62"><img src="https://avatars1.githubusercontent.com/u/10802313?v=3?s=110" width="110px;" alt="tiagom62"/><br /><sub><b>tiagom62</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tiagom62" title="Code">💻</a> <a href="#infra-tiagom62" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rystaf"><img src="https://avatars3.githubusercontent.com/u/2389047?v=3?s=110" width="110px;" alt="Ryan Stafford"/><br /><sub><b>Ryan Stafford</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rystaf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ehanlon"><img src="https://avatars2.githubusercontent.com/u/10345935?v=3?s=110" width="110px;" alt="Eammon Hanlon"/><br /><sub><b>Eammon Hanlon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ehanlon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zjean"><img src="https://avatars0.githubusercontent.com/u/441924?v=3?s=110" width="110px;" alt="zjean"/><br /><sub><b>zjean</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zjean" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.frei.media"><img src="https://avatars0.githubusercontent.com/u/12660103?v=3?s=110" width="110px;" alt="Matthias Frei"/><br /><sub><b>Matthias Frei</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FREImedia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/opsydev"><img src="https://avatars0.githubusercontent.com/u/3767518?v=3?s=110" width="110px;" alt="opsydev"/><br /><sub><b>opsydev</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=opsydev" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.ddreier.com"><img src="https://avatars1.githubusercontent.com/u/82290?v=3?s=110" width="110px;" alt="Daniel Dreier"/><br /><sub><b>Daniel Dreier</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ddreier" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://rassie.org"><img src="https://avatars0.githubusercontent.com/u/23448?v=3?s=110" width="110px;" alt="Nikolai Prokoschenko"/><br /><sub><b>Nikolai Prokoschenko</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rassie" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YetAnotherCodeMonkey"><img src="https://avatars0.githubusercontent.com/u/13452757?v=3?s=110" width="110px;" alt="Drew"/><br /><sub><b>Drew</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/merid14"><img src="https://avatars0.githubusercontent.com/u/1342320?v=3?s=110" width="110px;" alt="Walter"/><br /><sub><b>Walter</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=merid14" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/balous"><img src="https://avatars3.githubusercontent.com/u/11254614?v=3?s=110" width="110px;" alt="Petr Baloun"/><br /><sub><b>Petr Baloun</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=balous" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/reidblomquist"><img src="https://avatars0.githubusercontent.com/u/6117660?v=3?s=110" width="110px;" alt="reidblomquist"/><br /><sub><b>reidblomquist</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=reidblomquist" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mathieuk"><img src="https://avatars0.githubusercontent.com/u/539914?v=3?s=110" width="110px;" alt="Mathieu Kooiman"/><br /><sub><b>Mathieu Kooiman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mathieuk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/csayre"><img src="https://avatars3.githubusercontent.com/u/6606421?v=3?s=110" width="110px;" alt="csayre"/><br /><sub><b>csayre</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=csayre" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/adamdunson"><img src="https://avatars1.githubusercontent.com/u/768488?v=3?s=110" width="110px;" alt="Adam Dunson"/><br /><sub><b>Adam Dunson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adamdunson" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/thehereward"><img src="https://avatars0.githubusercontent.com/u/5547470?v=3?s=110" width="110px;" alt="Hereward"/><br /><sub><b>Hereward</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thehereward" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/swoopdk"><img src="https://avatars0.githubusercontent.com/u/5802977?v=3?s=110" width="110px;" alt="swoopdk"/><br /><sub><b>swoopdk</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=swoopdk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://linkedin.com/in/ahimta"><img src="https://avatars1.githubusercontent.com/u/3470403?v=3?s=110" width="110px;" alt="Abdullah Alansari"/><br /><sub><b>Abdullah Alansari</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Ahimta" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MicaelRodrigues"><img src="https://avatars0.githubusercontent.com/u/796443?v=3?s=110" width="110px;" alt="Micael Rodrigues"/><br /><sub><b>Micael Rodrigues</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://macadmincorner.com"><img src="https://avatars0.githubusercontent.com/u/614564?v=3?s=110" width="110px;" alt="Patrick Gallagher"/><br /><sub><b>Patrick Gallagher</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=patgmac" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Miliamber"><img src="https://avatars3.githubusercontent.com/u/7165922?v=3?s=110" width="110px;" alt="Miliamber"/><br /><sub><b>Miliamber</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Miliamber" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hawk554"><img src="https://avatars3.githubusercontent.com/u/861766?v=3?s=110" width="110px;" alt="hawk554"/><br /><sub><b>hawk554</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=hawk554" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://jbirdkerr.net"><img src="https://avatars1.githubusercontent.com/u/1695622?v=3?s=110" width="110px;" alt="Justin Kerr"/><br /><sub><b>Justin Kerr</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jbirdkerr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.irasnyder.com/devel/"><img src="https://avatars3.githubusercontent.com/u/11426176?v=3?s=110" width="110px;" alt="Ira W. Snyder"/><br /><sub><b>Ira W. Snyder</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=irasnyd" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aalaily"><img src="https://avatars2.githubusercontent.com/u/2475759?v=3?s=110" width="110px;" alt="Aladin Alaily"/><br /><sub><b>Aladin Alaily</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=aalaily" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kobie-chasehansen"><img src="https://avatars0.githubusercontent.com/u/10247644?v=3?s=110" width="110px;" alt="Chase Hansen"/><br /><sub><b>Chase Hansen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen" title="Code">💻</a> <a href="#question-kobie-chasehansen" title="Answering Questions">💬</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/IDM-Helpdesk"><img src="https://avatars2.githubusercontent.com/u/13545400?v=3?s=110" width="110px;" alt="IDM Helpdesk"/><br /><sub><b>IDM Helpdesk</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://balticer.de"><img src="https://avatars2.githubusercontent.com/u/614439?v=3?s=110" width="110px;" alt="Kai"/><br /><sub><b>Kai</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=balticer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.michaeldaniels.me"><img src="https://avatars1.githubusercontent.com/u/8762511?v=3?s=110" width="110px;" alt="Michael Daniels"/><br /><sub><b>Michael Daniels</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mdaniels5757" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://tomcastleman.me"><img src="https://avatars3.githubusercontent.com/u/1532660?v=3?s=110" width="110px;" alt="Tom Castleman"/><br /><sub><b>Tom Castleman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tomcastleman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DanielNemanic"><img src="https://avatars3.githubusercontent.com/u/10723243?v=3?s=110" width="110px;" alt="Daniel Nemanic"/><br /><sub><b>Daniel Nemanic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DanielNemanic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/southwolf"><img src="https://avatars0.githubusercontent.com/u/150648?v=3?s=110" width="110px;" alt="SouthWolf"/><br /><sub><b>SouthWolf</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=southwolf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ivarne"><img src="https://avatars2.githubusercontent.com/u/131616?v=3?s=110" width="110px;" alt="Ivar Nesje"/><br /><sub><b>Ivar Nesje</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ivarne" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.j0k3r.net"><img src="https://avatars1.githubusercontent.com/u/62333?v=3?s=110" width="110px;" alt="Jérémy Benoist"/><br /><sub><b>Jérémy Benoist</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=j0k3r" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cleathley"><img src="https://avatars2.githubusercontent.com/u/724344?v=3?s=110" width="110px;" alt="Chris Leathley"/><br /><sub><b>Chris Leathley</b></sub></a><br /><a href="#infra-cleathley" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/splaer"><img src="https://avatars0.githubusercontent.com/u/972498?v=3?s=110" width="110px;" alt="splaer"/><br /><sub><b>splaer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/issues?q=author%3Asplaer" title="Bug reports">🐛</a> <a href="https://github.com/snipe/snipe-it/commits?author=splaer" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.joeferguson.me"><img src="https://avatars1.githubusercontent.com/u/967362?v=3?s=110" width="110px;" alt="Joe Ferguson"/><br /><sub><b>Joe Ferguson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=svpernova09" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/diwanicki"><img src="https://avatars3.githubusercontent.com/u/6108682?v=3?s=110" width="110px;" alt="diwanicki"/><br /><sub><b>diwanicki</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=diwanicki" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=diwanicki" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pakkua80"><img src="https://avatars3.githubusercontent.com/u/2527115?v=3?s=110" width="110px;" alt="Lee Thoong Ching"/><br /><sub><b>Lee Thoong Ching</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=pakkua80" title="Documentation">📖</a> <a href="https://github.com/snipe/snipe-it/commits?author=pakkua80" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://shu.io"><img src="https://avatars1.githubusercontent.com/u/461491?v=3?s=110" width="110px;" alt="Marek Šuppa"/><br /><sub><b>Marek Šuppa</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mrshu" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mizar1616"><img src="https://avatars1.githubusercontent.com/u/8693762?v=3?s=110" width="110px;" alt="Juan J. Martinez"/><br /><sub><b>Juan J. Martinez</b></sub></a><br /><a href="#translation-mizar1616" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rrdial"><img src="https://avatars1.githubusercontent.com/u/1458388?v=3?s=110" width="110px;" alt="R Ryan Dial"/><br /><sub><b>R Ryan Dial</b></sub></a><br /><a href="#translation-rrdial" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/burlito"><img src="https://avatars2.githubusercontent.com/u/2871745?v=3?s=110" width="110px;" alt="Andrej Manduch"/><br /><sub><b>Andrej Manduch</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=burlito" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.cordeos.com"><img src="https://avatars0.githubusercontent.com/u/8341172?v=3?s=110" width="110px;" alt="Jay Richards"/><br /><sub><b>Jay Richards</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=technogenus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://necurity.co.uk"><img src="https://avatars2.githubusercontent.com/u/7295127?v=3?s=110" width="110px;" alt="Alexander Innes"/><br /><sub><b>Alexander Innes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=leostat" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://buzzedword.codes"><img src="https://avatars2.githubusercontent.com/u/334485?v=3?s=110" width="110px;" alt="Danny Garcia"/><br /><sub><b>Danny Garcia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=buzzedword" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/archpoint"><img src="https://avatars2.githubusercontent.com/u/366855?v=3?s=110" width="110px;" alt="archpoint"/><br /><sub><b>archpoint</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=archpoint" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jakemcgraw.com"><img src="https://avatars1.githubusercontent.com/u/67991?v=3?s=110" width="110px;" alt="Jake McGraw"/><br /><sub><b>Jake McGraw</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jakemcgraw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FleischKarussel"><img src="https://avatars1.githubusercontent.com/u/1714374?v=3?s=110" width="110px;" alt="FleischKarussel"/><br /><sub><b>FleischKarussel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FleischKarussel" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/feeva"><img src="https://avatars3.githubusercontent.com/u/319644?v=3?s=110" width="110px;" alt="Dylan Yi"/><br /><sub><b>Dylan Yi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=feeva" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://FlashingCursor.com"><img src="https://avatars2.githubusercontent.com/u/857740?v=3?s=110" width="110px;" alt="Gil Rutkowski"/><br /><sub><b>Gil Rutkowski</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=flashingcursor" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.desmondmorris.com"><img src="https://avatars3.githubusercontent.com/u/129360?v=3?s=110" width="110px;" alt="Desmond Morris"/><br /><sub><b>Desmond Morris</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=desmondmorris" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://peelman.us"><img src="https://avatars2.githubusercontent.com/u/52936?v=3?s=110" width="110px;" alt="Nick Peelman"/><br /><sub><b>Nick Peelman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=peelman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://abrahamvegh.com"><img src="https://avatars0.githubusercontent.com/u/53161?v=3?s=110" width="110px;" alt="Abraham Vegh"/><br /><sub><b>Abraham Vegh</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=abrahamvegh" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rashivkp"><img src="https://avatars0.githubusercontent.com/u/2818680?v=3?s=110" width="110px;" alt="Mohamed Rashid"/><br /><sub><b>Mohamed Rashid</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rashivkp" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hinchk.github.io"><img src="https://avatars3.githubusercontent.com/u/1509456?v=3?s=110" width="110px;" alt="Kasey"/><br /><sub><b>Kasey</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=HinchK" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BrettFagerlund"><img src="https://avatars2.githubusercontent.com/u/10522541?v=3?s=110" width="110px;" alt="Brett"/><br /><sub><b>Brett</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BrettFagerlund" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://jasonspriggs.com"><img src="https://avatars2.githubusercontent.com/u/16108587?v=3?s=110" width="110px;" alt="Jason Spriggs"/><br /><sub><b>Jason Spriggs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jasonspriggs" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://n8felton.wordpress.com"><img src="https://avatars2.githubusercontent.com/u/1134568?v=3?s=110" width="110px;" alt="Nate Felton"/><br /><sub><b>Nate Felton</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=n8felton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://homepages.dcc.ufmg.br/~manassesferreira"><img src="https://avatars2.githubusercontent.com/u/14036694?v=3?s=110" width="110px;" alt="Manasses Ferreira"/><br /><sub><b>Manasses Ferreira</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=manassesferreira" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/steveelwood"><img src="https://avatars0.githubusercontent.com/u/15913949?v=3?s=110" width="110px;" alt="Steve"/><br /><sub><b>Steve</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=steveelwood" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/matc"><img src="https://avatars1.githubusercontent.com/u/3361683?v=3?s=110" width="110px;" alt="matc"/><br /><sub><b>matc</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=matc" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.davisracingteam.com"><img src="https://avatars3.githubusercontent.com/u/7405702?v=3?s=110" width="110px;" alt="Cole R. Davis"/><br /><sub><b>Cole R. Davis</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gibsonjoshua55"><img src="https://avatars2.githubusercontent.com/u/10167681?v=3?s=110" width="110px;" alt="gibsonjoshua55"/><br /><sub><b>gibsonjoshua55</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zwerch"><img src="https://avatars2.githubusercontent.com/u/2809241?v=4?s=110" width="110px;" alt="Robin Temme"/><br /><sub><b>Robin Temme</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zwerch" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imanghafoori1"><img src="https://avatars0.githubusercontent.com/u/6961695?v=4?s=110" width="110px;" alt="Iman"/><br /><sub><b>Iman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=imanghafoori1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/richardhofman6"><img src="https://avatars1.githubusercontent.com/u/6551003?v=4?s=110" width="110px;" alt="Richard Hofman"/><br /><sub><b>Richard Hofman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=richardhofman6" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gizzmojr"><img src="https://avatars0.githubusercontent.com/u/3697569?v=4?s=110" width="110px;" alt="gizzmojr"/><br /><sub><b>gizzmojr</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gizzmojr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imjennyli"><img src="https://avatars3.githubusercontent.com/u/404729?v=4?s=110" width="110px;" alt="Jenny Li"/><br /><sub><b>Jenny Li</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=imjennyli" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeoffYoung"><img src="https://avatars0.githubusercontent.com/u/869227?v=4?s=110" width="110px;" alt="Geoff Young"/><br /><sub><b>Geoff Young</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=GeoffYoung" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.elliotblackburn.com"><img src="https://avatars3.githubusercontent.com/u/1068477?v=4?s=110" width="110px;" alt="Elliot Blackburn"/><br /><sub><b>Elliot Blackburn</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BlueHatbRit" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://andmemasin.eu"><img src="https://avatars1.githubusercontent.com/u/6357451?v=4?s=110" width="110px;" alt="Tõnis Ormisson"/><br /><sub><b>Tõnis Ormisson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TonisOrmisson" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.nicolai-essig.de"><img src="https://avatars0.githubusercontent.com/u/449411?v=4?s=110" width="110px;" alt="Nicolai Essig"/><br /><sub><b>Nicolai Essig</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thakilla" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/techincolor"><img src="https://avatars1.githubusercontent.com/u/14809698?v=4?s=110" width="110px;" alt="Danielle"/><br /><sub><b>Danielle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=techincolor" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TheVakman"><img src="https://avatars1.githubusercontent.com/u/18545156?v=4?s=110" width="110px;" alt="Lawrence"/><br /><sub><b>Lawrence</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TheVakman" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/uknzaeinozpas"><img src="https://avatars1.githubusercontent.com/u/22473767?v=4?s=110" width="110px;" alt="uknzaeinozpas"/><br /><sub><b>uknzaeinozpas</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Gelob"><img src="https://avatars3.githubusercontent.com/u/422752?v=4?s=110" width="110px;" alt="Ryan"/><br /><sub><b>Ryan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Gelob" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vcordes79"><img src="https://avatars1.githubusercontent.com/u/10672546?v=4?s=110" width="110px;" alt="vcordes79"/><br /><sub><b>vcordes79</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vcordes79" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fordster78"><img src="https://avatars3.githubusercontent.com/u/27958330?v=4?s=110" width="110px;" alt="fordster78"/><br /><sub><b>fordster78</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fordster78" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CronKz"><img src="https://avatars0.githubusercontent.com/u/34064225?v=4?s=110" width="110px;" alt="CronKz"/><br /><sub><b>CronKz</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=CronKz" title="Code">💻</a> <a href="#translation-CronKz" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tdb"><img src="https://avatars1.githubusercontent.com/u/585486?v=4?s=110" width="110px;" alt="Tim Bishop"/><br /><sub><b>Tim Bishop</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tdb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.seanmcilvenna.com"><img src="https://avatars2.githubusercontent.com/u/5384694?v=4?s=110" width="110px;" alt="Sean McIlvenna"/><br /><sub><b>Sean McIlvenna</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=seanmcilvenna" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cepacs"><img src="https://avatars3.githubusercontent.com/u/36515590?v=4?s=110" width="110px;" alt="cepacs"/><br /><sub><b>cepacs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/issues?q=author%3Acepacs" title="Bug reports">🐛</a> <a href="https://github.com/snipe/snipe-it/commits?author=cepacs" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lea-mink"><img src="https://avatars2.githubusercontent.com/u/37537300?v=4?s=110" width="110px;" alt="lea-mink"/><br /><sub><b>lea-mink</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lea-mink" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hannahtinkler"><img src="https://avatars0.githubusercontent.com/u/7140719?v=4?s=110" width="110px;" alt="Hannah Tinkler"/><br /><sub><b>Hannah Tinkler</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=hannahtinkler" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doekman"><img src="https://avatars1.githubusercontent.com/u/1086388?v=4?s=110" width="110px;" alt="Doeke Zanstra"/><br /><sub><b>Doeke Zanstra</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=doekman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.sdhd.nl/"><img src="https://avatars1.githubusercontent.com/u/4325936?v=4?s=110" width="110px;" alt="Djamon Staal"/><br /><sub><b>Djamon Staal</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=SjamonDaal" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EarlRamirez"><img src="https://avatars3.githubusercontent.com/u/12306859?v=4?s=110" width="110px;" alt="Earl Ramirez"/><br /><sub><b>Earl Ramirez</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=EarlRamirez" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RichardRay"><img src="https://avatars2.githubusercontent.com/u/8671456?v=4?s=110" width="110px;" alt="Richard Ray Thomas"/><br /><sub><b>Richard Ray Thomas</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=RichardRay" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.taisun.io/"><img src="https://avatars3.githubusercontent.com/u/1852688?v=4?s=110" width="110px;" alt="Ryan Kuba"/><br /><sub><b>Ryan Kuba</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thelamer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ParadoxGuitarist"><img src="https://avatars1.githubusercontent.com/u/6751928?v=4?s=110" width="110px;" alt="Brian Monroe"/><br /><sub><b>Brian Monroe</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/plexorama"><img src="https://avatars1.githubusercontent.com/u/605167?v=4?s=110" width="110px;" alt="plexorama"/><br /><sub><b>plexorama</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=plexorama" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://tilldeeke.de"><img src="https://avatars2.githubusercontent.com/u/1795149?v=4?s=110" width="110px;" alt="Till Deeke"/><br /><sub><b>Till Deeke</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tilldeeke" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/5quirrel"><img src="https://avatars0.githubusercontent.com/u/12634129?v=4?s=110" width="110px;" alt="5quirrel"/><br /><sub><b>5quirrel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=5quirrel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jasonlshelton"><img src="https://avatars1.githubusercontent.com/u/13071957?v=4?s=110" width="110px;" alt="Jason"/><br /><sub><b>Jason</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jasonlshelton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chemfy"><img src="https://avatars3.githubusercontent.com/u/7128321?v=4?s=110" width="110px;" alt="Antti"/><br /><sub><b>Antti</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chemfy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DeusMaximus"><img src="https://avatars3.githubusercontent.com/u/10080364?v=4?s=110" width="110px;" alt="DeusMaximus"/><br /><sub><b>DeusMaximus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DeusMaximus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/A-ROYAL"><img src="https://avatars2.githubusercontent.com/u/16384611?v=4?s=110" width="110px;" alt="a-royal"/><br /><sub><b>a-royal</b></sub></a><br /><a href="#translation-A-ROYAL" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/albertoaldrigo"><img src="https://avatars0.githubusercontent.com/u/5358208?v=4?s=110" width="110px;" alt="Alberto Aldrigo"/><br /><sub><b>Alberto Aldrigo</b></sub></a><br /><a href="#translation-albertoaldrigo" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://alex.stanev.org/blog"><img src="https://avatars0.githubusercontent.com/u/1412342?v=4?s=110" width="110px;" alt="Alex Stanev"/><br /><sub><b>Alex Stanev</b></sub></a><br /><a href="#translation-RealEnder" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://devel.itsolution2.de"><img src="https://avatars0.githubusercontent.com/u/177295?v=4?s=110" width="110px;" alt="Andreas Rehm"/><br /><sub><b>Andreas Rehm</b></sub></a><br /><a href="#translation-sirrus" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xelan"><img src="https://avatars0.githubusercontent.com/u/5080535?v=4?s=110" width="110px;" alt="Andreas Erhard"/><br /><sub><b>Andreas Erhard</b></sub></a><br /><a href="#translation-xelan" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/angeldeejay"><img src="https://avatars2.githubusercontent.com/u/142350?v=4?s=110" width="110px;" alt="Andrés Vanegas Jiménez"/><br /><sub><b>Andrés Vanegas Jiménez</b></sub></a><br /><a href="#translation-angeldeejay" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aschiavon91"><img src="https://avatars0.githubusercontent.com/u/3910403?v=4?s=110" width="110px;" alt="Antonio Schiavon"/><br /><sub><b>Antonio Schiavon</b></sub></a><br /><a href="#translation-aschiavon91" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benunter"><img src="https://avatars0.githubusercontent.com/u/10464547?v=4?s=110" width="110px;" alt="benunter"/><br /><sub><b>benunter</b></sub></a><br /><a href="#translation-benunter" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://catweb24.pl"><img src="https://avatars1.githubusercontent.com/u/5038647?v=4?s=110" width="110px;" alt="Borys Żmuda"/><br /><sub><b>Borys Żmuda</b></sub></a><br /><a href="#translation-rudashi" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chibacityblues"><img src="https://avatars0.githubusercontent.com/u/5539359?v=4?s=110" width="110px;" alt="chibacityblues"/><br /><sub><b>chibacityblues</b></sub></a><br /><a href="#translation-chibacityblues" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cwlin0416"><img src="https://avatars1.githubusercontent.com/u/1954830?v=4?s=110" width="110px;" alt="Chien Wei Lin"/><br /><sub><b>Chien Wei Lin</b></sub></a><br /><a href="#translation-cwlin0416" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Againstreality"><img src="https://avatars3.githubusercontent.com/u/11700533?v=4?s=110" width="110px;" alt="Christian Schuster"/><br /><sub><b>Christian Schuster</b></sub></a><br /><a href="#translation-Againstreality" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://chriss.webhostid.com"><img src="https://avatars1.githubusercontent.com/u/4308704?v=4?s=110" width="110px;" alt="Christian Stefanus"/><br /><sub><b>Christian Stefanus</b></sub></a><br /><a href="#translation-kopi-item" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://wxcafe.net"><img src="https://avatars3.githubusercontent.com/u/3009327?v=4?s=110" width="110px;" alt="wxcafé"/><br /><sub><b>wxcafé</b></sub></a><br /><a href="#translation-wxcafe" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dpyroc"><img src="https://avatars3.githubusercontent.com/u/35761525?v=4?s=110" width="110px;" alt="dpyroc"/><br /><sub><b>dpyroc</b></sub></a><br /><a href="#translation-dpyroc" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.friedlmaier.net"><img src="https://avatars1.githubusercontent.com/u/2153639?v=4?s=110" width="110px;" alt="Daniel Friedlmaier"/><br /><sub><b>Daniel Friedlmaier</b></sub></a><br /><a href="#translation-da-friedl" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/danielheene"><img src="https://avatars1.githubusercontent.com/u/2947640?v=4?s=110" width="110px;" alt="Daniel Heene"/><br /><sub><b>Daniel Heene</b></sub></a><br /><a href="#translation-danielheene" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/danielcb"><img src="https://avatars3.githubusercontent.com/u/319022?v=4?s=110" width="110px;" alt="danielcb"/><br /><sub><b>danielcb</b></sub></a><br /><a href="#translation-danielcb" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dominiksenti"><img src="https://avatars3.githubusercontent.com/u/15846537?v=4?s=110" width="110px;" alt="Dominik Senti"/><br /><sub><b>Dominik Senti</b></sub></a><br /><a href="#translation-dominiksenti" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.konectik.com"><img src="https://avatars0.githubusercontent.com/u/25570954?v=4?s=110" width="110px;" alt="Eric Gautheron"/><br /><sub><b>Eric Gautheron</b></sub></a><br /><a href="#translation-EpixFr" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://erlpil.com"><img src="https://avatars1.githubusercontent.com/u/5732623?v=4?s=110" width="110px;" alt="Erlend Pilø"/><br /><sub><b>Erlend Pilø</b></sub></a><br /><a href="#translation-Erlpil" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://fabio.technology"><img src="https://avatars0.githubusercontent.com/u/541832?v=4?s=110" width="110px;" alt="Fabio Rapposelli"/><br /><sub><b>Fabio Rapposelli</b></sub></a><br /><a href="#translation-frapposelli" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fgbs"><img src="https://avatars2.githubusercontent.com/u/3605240?v=4?s=110" width="110px;" alt="Felipe Barros"/><br /><sub><b>Felipe Barros</b></sub></a><br /><a href="#translation-fgbs" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/possebon"><img src="https://avatars0.githubusercontent.com/u/257745?v=4?s=110" width="110px;" alt="Fernando Possebon"/><br /><sub><b>Fernando Possebon</b></sub></a><br /><a href="#translation-possebon" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gdraque"><img src="https://avatars3.githubusercontent.com/u/2540832?v=4?s=110" width="110px;" alt="gdraque"/><br /><sub><b>gdraque</b></sub></a><br /><a href="#translation-gdraque" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/georgwallisch"><img src="https://avatars0.githubusercontent.com/u/23440381?v=4?s=110" width="110px;" alt="Georg Wallisch"/><br /><sub><b>Georg Wallisch</b></sub></a><br /><a href="#translation-georgwallisch" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jgroblesr85"><img src="https://avatars1.githubusercontent.com/u/9852832?v=4?s=110" width="110px;" alt="Gerardo Robles"/><br /><sub><b>Gerardo Robles</b></sub></a><br /><a href="#translation-jgroblesr85" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://t.me/Gluek"><img src="https://avatars2.githubusercontent.com/u/11082640?v=4?s=110" width="110px;" alt="Gluek"/><br /><sub><b>Gluek</b></sub></a><br /><a href="#translation-mrgluek" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AdnanAbuShahad"><img src="https://avatars0.githubusercontent.com/u/6847946?v=4?s=110" width="110px;" alt="AdnanAbuShahad"/><br /><sub><b>AdnanAbuShahad</b></sub></a><br /><a href="#translation-AdnanAbuShahad" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://hafidzi.my"><img src="https://avatars1.githubusercontent.com/u/3580608?v=4?s=110" width="110px;" alt="Hafidzi My"/><br /><sub><b>Hafidzi My</b></sub></a><br /><a href="#translation-hafidzi" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fofwisdom"><img src="https://avatars2.githubusercontent.com/u/205521?v=4?s=110" width="110px;" alt="Harim Park"/><br /><sub><b>Harim Park</b></sub></a><br /><a href="#translation-fofwisdom" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.kentsson.se"><img src="https://avatars2.githubusercontent.com/u/3333841?v=4?s=110" width="110px;" alt="Henrik Kentsson"/><br /><sub><b>Henrik Kentsson</b></sub></a><br /><a href="#translation-Kentsson" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/husnulyaqien"><img src="https://avatars0.githubusercontent.com/u/36551034?v=4?s=110" width="110px;" alt="Husnul Yaqien"/><br /><sub><b>Husnul Yaqien</b></sub></a><br /><a href="#translation-husnulyaqien" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://abaalkhail.org"><img src="https://avatars1.githubusercontent.com/u/2372747?v=4?s=110" width="110px;" alt="Ibrahim"/><br /><sub><b>Ibrahim</b></sub></a><br /><a href="#translation-abaalkh" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/igolman"><img src="https://avatars0.githubusercontent.com/u/1389334?v=4?s=110" width="110px;" alt="igolman"/><br /><sub><b>igolman</b></sub></a><br /><a href="#translation-igolman" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/itangiang"><img src="https://avatars1.githubusercontent.com/u/3257070?v=4?s=110" width="110px;" alt="itangiang"/><br /><sub><b>itangiang</b></sub></a><br /><a href="#translation-itangiang" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jarby1211"><img src="https://avatars2.githubusercontent.com/u/14814254?v=4?s=110" width="110px;" alt="jarby1211"/><br /><sub><b>jarby1211</b></sub></a><br /><a href="#translation-jarby1211" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://jwillker.com"><img src="https://avatars3.githubusercontent.com/u/6719357?v=4?s=110" width="110px;" alt="Jhonn Willker"/><br /><sub><b>Jhonn Willker</b></sub></a><br /><a href="#translation-JohnWillker" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/joxelito94"><img src="https://avatars2.githubusercontent.com/u/10983635?v=4?s=110" width="110px;" alt="Jose"/><br /><sub><b>Jose</b></sub></a><br /><a href="#translation-joxelito94" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/laopangzi"><img src="https://avatars0.githubusercontent.com/u/5206122?v=4?s=110" width="110px;" alt="laopangzi"/><br /><sub><b>laopangzi</b></sub></a><br /><a href="#translation-laopangzi" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://usrportage.de"><img src="https://avatars2.githubusercontent.com/u/79707?v=4?s=110" width="110px;" alt="Lars Strojny"/><br /><sub><b>Lars Strojny</b></sub></a><br /><a href="#translation-lstrojny" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/marcosbl"><img src="https://avatars0.githubusercontent.com/u/389801?v=4?s=110" width="110px;" alt="MarcosBL"/><br /><sub><b>MarcosBL</b></sub></a><br /><a href="#translation-MarcosBL" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mariejoyacajes"><img src="https://avatars3.githubusercontent.com/u/35664606?v=4?s=110" width="110px;" alt="marie joy cajes"/><br /><sub><b>marie joy cajes</b></sub></a><br /><a href="#translation-mariejoyacajes" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.markjohansen.dk"><img src="https://avatars2.githubusercontent.com/u/3052816?v=4?s=110" width="110px;" alt="Mark S. Johansen"/><br /><sub><b>Mark S. Johansen</b></sub></a><br /><a href="#translation-msjohansen" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://martinstub.dk"><img src="https://avatars2.githubusercontent.com/u/982885?v=4?s=110" width="110px;" alt="Martin Stub"/><br /><sub><b>Martin Stub</b></sub></a><br /><a href="#translation-stubben" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/meyerf99"><img src="https://avatars2.githubusercontent.com/u/28959963?v=4?s=110" width="110px;" alt="Meyer Flavio"/><br /><sub><b>Meyer Flavio</b></sub></a><br /><a href="#translation-meyerf99" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MicaelRodrigues"><img src="https://avatars3.githubusercontent.com/u/796443?v=4?s=110" width="110px;" alt="Micael Rodrigues"/><br /><sub><b>Micael Rodrigues</b></sub></a><br /><a href="#translation-MicaelRodrigues" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://rubixy.com/"><img src="https://avatars0.githubusercontent.com/u/10481331?v=4?s=110" width="110px;" alt="Mikael Rasmussen"/><br /><sub><b>Mikael Rasmussen</b></sub></a><br /><a href="#translation-mikaelssen" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/IxFail"><img src="https://avatars1.githubusercontent.com/u/1544552?v=4?s=110" width="110px;" alt="IxFail"/><br /><sub><b>IxFail</b></sub></a><br /><a href="#translation-IxFail" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.mohammedfota.com"><img src="https://avatars3.githubusercontent.com/u/18483118?v=4?s=110" width="110px;" alt="Mohammed Fota"/><br /><sub><b>Mohammed Fota</b></sub></a><br /><a href="#translation-MohammedFota" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/omego"><img src="https://avatars0.githubusercontent.com/u/227080?v=4?s=110" width="110px;" alt="Moayad Alserihi"/><br /><sub><b>Moayad Alserihi</b></sub></a><br /><a href="#translation-omego" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/saymd"><img src="https://avatars0.githubusercontent.com/u/1680266?v=4?s=110" width="110px;" alt="saymd"/><br /><sub><b>saymd</b></sub></a><br /><a href="#translation-saymd" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nordsken.se"><img src="https://avatars0.githubusercontent.com/u/1826808?v=4?s=110" width="110px;" alt="Patrik Larsson"/><br /><sub><b>Patrik Larsson</b></sub></a><br /><a href="#translation-pooot" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/drcryo"><img src="https://avatars1.githubusercontent.com/u/20584746?v=4?s=110" width="110px;" alt="drcryo"/><br /><sub><b>drcryo</b></sub></a><br /><a href="#translation-drcryo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pawel1615"><img src="https://avatars1.githubusercontent.com/u/19408004?v=4?s=110" width="110px;" alt="pawel1615"/><br /><sub><b>pawel1615</b></sub></a><br /><a href="#translation-pawel1615" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bodrovics"><img src="https://avatars2.githubusercontent.com/u/23340468?v=4?s=110" width="110px;" alt="bodrovics"/><br /><sub><b>bodrovics</b></sub></a><br /><a href="#translation-bodrovics" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/priatna"><img src="https://avatars0.githubusercontent.com/u/3257654?v=4?s=110" width="110px;" alt="priatna"/><br /><sub><b>priatna</b></sub></a><br /><a href="#translation-priatna" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://amayume.net"><img src="https://avatars1.githubusercontent.com/u/5358374?v=4?s=110" width="110px;" alt="Fan Jiang"/><br /><sub><b>Fan Jiang</b></sub></a><br /><a href="#translation-ProfFan" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ragnarcx"><img src="https://avatars1.githubusercontent.com/u/22555451?v=4?s=110" width="110px;" alt="ragnarcx"/><br /><sub><b>ragnarcx</b></sub></a><br /><a href="#translation-ragnarcx" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.reinvanhaaren.nl/"><img src="https://avatars2.githubusercontent.com/u/18654582?v=4?s=110" width="110px;" alt="Rein van Haaren"/><br /><sub><b>Rein van Haaren</b></sub></a><br /><a href="#translation-reinvanhaaren" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://dheche.songolimo.net"><img src="https://avatars1.githubusercontent.com/u/386672?v=4?s=110" width="110px;" alt="Teguh Dwicaksana"/><br /><sub><b>Teguh Dwicaksana</b></sub></a><br /><a href="#translation-dheche" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FRaccie"><img src="https://avatars2.githubusercontent.com/u/2572552?v=4?s=110" width="110px;" alt="fraccie"/><br /><sub><b>fraccie</b></sub></a><br /><a href="#translation-FRaccie" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinzruzell"><img src="https://avatars0.githubusercontent.com/u/35182720?v=4?s=110" width="110px;" alt="vinzruzell"/><br /><sub><b>vinzruzell</b></sub></a><br /><a href="#translation-vinzruzell" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://kevinaustin.com"><img src="https://avatars1.githubusercontent.com/u/7883603?v=4?s=110" width="110px;" alt="Kevin Austin"/><br /><sub><b>Kevin Austin</b></sub></a><br /><a href="#translation-vipsystem" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://azuraweb.xyz"><img src="https://avatars3.githubusercontent.com/u/3861828?v=4?s=110" width="110px;" alt="Wira Sandy"/><br /><sub><b>Wira Sandy</b></sub></a><br /><a href="#translation-wira-sandy" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GrayHoax"><img src="https://avatars2.githubusercontent.com/u/8663789?v=4?s=110" width="110px;" alt="Илья"/><br /><sub><b>Илья</b></sub></a><br /><a href="#translation-GrayHoax" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/godusevpn"><img src="https://avatars3.githubusercontent.com/u/30119111?v=4?s=110" width="110px;" alt="GodUseVPN"/><br /><sub><b>GodUseVPN</b></sub></a><br /><a href="#translation-godusevpn" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EngrZhou"><img src="https://avatars1.githubusercontent.com/u/745576?v=4?s=110" width="110px;" alt="周周"/><br /><sub><b>周周</b></sub></a><br /><a href="#translation-EngrZhou" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/takuy"><img src="https://avatars3.githubusercontent.com/u/1631095?v=4?s=110" width="110px;" alt="Sam"/><br /><sub><b>Sam</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=takuy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.illisian.com.au"><img src="https://avatars1.githubusercontent.com/u/264022?v=4?s=110" width="110px;" alt="Azerothian"/><br /><sub><b>Azerothian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Azerothian" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://macfoo.wordpress.com/"><img src="https://avatars1.githubusercontent.com/u/4930051?v=4?s=110" width="110px;" alt="Wes Hulette"/><br /><sub><b>Wes Hulette</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jwhulette" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patrict"><img src="https://avatars0.githubusercontent.com/u/8134591?v=4?s=110" width="110px;" alt="patrict"/><br /><sub><b>patrict</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=patrict" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VELIKII-DIVAN"><img src="https://avatars3.githubusercontent.com/u/2611616?v=4?s=110" width="110px;" alt="Dmitriy Minaev"/><br /><sub><b>Dmitriy Minaev</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/liquidhorse"><img src="https://avatars0.githubusercontent.com/u/5132245?v=4?s=110" width="110px;" alt="liquidhorse"/><br /><sub><b>liquidhorse</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=liquidhorse" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://seld.be/"><img src="https://avatars1.githubusercontent.com/u/183678?v=4?s=110" width="110px;" alt="Jordi Boggiano"/><br /><sub><b>Jordi Boggiano</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Seldaek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/inietov"><img src="https://avatars0.githubusercontent.com/u/653557?v=4?s=110" width="110px;" alt="Ivan Nieto"/><br /><sub><b>Ivan Nieto</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=inietov" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benrubson"><img src="https://avatars2.githubusercontent.com/u/6764151?v=4?s=110" width="110px;" alt="Ben RUBSON"/><br /><sub><b>Ben RUBSON</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=benrubson" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NMathar"><img src="https://avatars2.githubusercontent.com/u/8554558?v=4?s=110" width="110px;" alt="NMathar"/><br /><sub><b>NMathar</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=NMathar" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/smb"><img src="https://avatars1.githubusercontent.com/u/139566?v=4?s=110" width="110px;" alt="Steffen"/><br /><sub><b>Steffen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=smb" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Sxderp"><img src="https://avatars0.githubusercontent.com/u/6609453?v=4?s=110" width="110px;" alt="Sxderp"/><br /><sub><b>Sxderp</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Sxderp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fanta8897"><img src="https://avatars1.githubusercontent.com/u/4807843?v=4?s=110" width="110px;" alt="fanta8897"/><br /><sub><b>fanta8897</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fanta8897" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://andreybolonin.com/phpconsulting/"><img src="https://avatars2.githubusercontent.com/u/2576509?v=4?s=110" width="110px;" alt="Andrey Bolonin"/><br /><sub><b>Andrey Bolonin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andreybolonin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.shinayoshi.net/"><img src="https://avatars3.githubusercontent.com/u/2173307?v=4?s=110" width="110px;" alt="shinayoshi"/><br /><sub><b>shinayoshi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=shinayoshi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/reuser"><img src="https://avatars3.githubusercontent.com/u/2130159?v=4?s=110" width="110px;" alt="Hubert"/><br /><sub><b>Hubert</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=reuser" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://brashear.me"><img src="https://avatars0.githubusercontent.com/u/6865789?v=4?s=110" width="110px;" alt="KeenRivals"/><br /><sub><b>KeenRivals</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=KeenRivals" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/omyno"><img src="https://avatars3.githubusercontent.com/u/2902513?v=4?s=110" width="110px;" alt="omyno"/><br /><sub><b>omyno</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=omyno" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jackka"><img src="https://avatars1.githubusercontent.com/u/6271335?v=4?s=110" width="110px;" alt="Evgeny"/><br /><sub><b>Evgeny</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jackka" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://digitalist.se"><img src="https://avatars2.githubusercontent.com/u/1169963?v=4?s=110" width="110px;" alt="Colin Campbell"/><br /><sub><b>Colin Campbell</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=colin-campbell" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lubo"><img src="https://avatars3.githubusercontent.com/u/2872098?v=4?s=110" width="110px;" alt="Ľubomír Kučera"/><br /><sub><b>Ľubomír Kučera</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lubo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.sourceguru.net"><img src="https://avatars3.githubusercontent.com/u/570639?v=4?s=110" width="110px;" alt="Martin Meredith"/><br /><sub><b>Martin Meredith</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Mezzle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/timothyfarmer"><img src="https://avatars1.githubusercontent.com/u/7632599?v=4?s=110" width="110px;" alt="Tim Farmer"/><br /><sub><b>Tim Farmer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=timothyfarmer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mskrip"><img src="https://avatars0.githubusercontent.com/u/17459600?v=4?s=110" width="110px;" alt="Marián Skrip"/><br /><sub><b>Marián Skrip</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mskrip" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Godmartinz"><img src="https://avatars2.githubusercontent.com/u/47435081?v=4?s=110" width="110px;" alt="Godfrey Martinez"/><br /><sub><b>Godfrey Martinez</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Godmartinz" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bigtreeEdo"><img src="https://avatars1.githubusercontent.com/u/2075128?v=4?s=110" width="110px;" alt="bigtreeEdo"/><br /><sub><b>bigtreeEdo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bigtreeEdo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://colinmcneil.me/"><img src="https://avatars0.githubusercontent.com/u/5000430?v=4?s=110" width="110px;" alt="Colin McNeil"/><br /><sub><b>Colin McNeil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ColinMcNeil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JoKneeMo"><img src="https://avatars0.githubusercontent.com/u/421625?v=4?s=110" width="110px;" alt="JoKneeMo"/><br /><sub><b>JoKneeMo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JoKneeMo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.redbridge.se"><img src="https://avatars0.githubusercontent.com/u/54849013?v=4?s=110" width="110px;" alt="Joshi"/><br /><sub><b>Joshi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=joshi-redbridge" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anthonypburns"><img src="https://avatars2.githubusercontent.com/u/15731458?v=4?s=110" width="110px;" alt="Anthony Burns"/><br /><sub><b>Anthony Burns</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=anthonypburns" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/johnson-yi"><img src="https://avatars1.githubusercontent.com/u/63399474?v=4?s=110" width="110px;" alt="johnson-yi"/><br /><sub><b>johnson-yi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=johnson-yi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://tangentmc.net"><img src="https://avatars1.githubusercontent.com/u/1862720?v=4?s=110" width="110px;" alt="Sanjay Govind"/><br /><sub><b>Sanjay Govind</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sanjay900" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://peter.upfold.org.uk/"><img src="https://avatars0.githubusercontent.com/u/1255375?v=4?s=110" width="110px;" alt="Peter Upfold"/><br /><sub><b>Peter Upfold</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PeterUpfold" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jbiel"><img src="https://avatars2.githubusercontent.com/u/961717?v=4?s=110" width="110px;" alt="Jared Biel"/><br /><sub><b>Jared Biel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jbiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dampfklon"><img src="https://avatars1.githubusercontent.com/u/1733625?v=4?s=110" width="110px;" alt="Dampfklon"/><br /><sub><b>Dampfklon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dampfklon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://communityclosing.com"><img src="https://avatars2.githubusercontent.com/u/52973156?v=4?s=110" width="110px;" alt="Charles Hamilton"/><br /><sub><b>Charles Hamilton</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chamilton-ccn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/giannello"><img src="https://avatars.githubusercontent.com/u/551789?v=4?s=110" width="110px;" alt="Giuseppe Iannello"/><br /><sub><b>Giuseppe Iannello</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=giannello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.peterdavehello.org/"><img src="https://avatars.githubusercontent.com/u/3691490?v=4?s=110" width="110px;" alt="Peter Dave Hello"/><br /><sub><b>Peter Dave Hello</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PeterDaveHello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sigmoidal"><img src="https://avatars.githubusercontent.com/u/6106332?v=4?s=110" width="110px;" alt="sigmoidal"/><br /><sub><b>sigmoidal</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sigmoidal" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/phenixdotnet"><img src="https://avatars.githubusercontent.com/u/2082554?v=4?s=110" width="110px;" alt="Vincent Lainé"/><br /><sub><b>Vincent Lainé</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=phenixdotnet" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.lucas-pless.com"><img src="https://avatars.githubusercontent.com/u/1943040?v=4?s=110" width="110px;" alt="Lucas Pleß"/><br /><sub><b>Lucas Pleß</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=derlucas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/iansltx"><img src="https://avatars.githubusercontent.com/u/472804?v=4?s=110" width="110px;" alt="Ian Littman"/><br /><sub><b>Ian Littman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=iansltx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PauloLuna"><img src="https://avatars.githubusercontent.com/u/3519029?v=4?s=110" width="110px;" alt="João Paulo"/><br /><sub><b>João Paulo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PauloLuna" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ThoBur"><img src="https://avatars.githubusercontent.com/u/70443365?v=4?s=110" width="110px;" alt="ThoBur"/><br /><sub><b>ThoBur</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ThoBur" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://phpprofi.ru/"><img src="https://avatars.githubusercontent.com/u/1972329?v=4?s=110" width="110px;" alt="Alexander Chibrikin"/><br /><sub><b>Alexander Chibrikin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=alek13" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/winstan"><img src="https://avatars.githubusercontent.com/u/438332?v=4?s=110" width="110px;" alt="Anthony Winstanley"/><br /><sub><b>Anthony Winstanley</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=winstan" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fashberg"><img src="https://avatars.githubusercontent.com/u/3075214?v=4?s=110" width="110px;" alt="Folke"/><br /><sub><b>Folke</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fashberg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benwa"><img src="https://avatars.githubusercontent.com/u/1351571?v=4?s=110" width="110px;" alt="Bennett Blodinger"/><br /><sub><b>Bennett Blodinger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=benwa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nmc.dev"><img src="https://avatars.githubusercontent.com/u/2974631?v=4?s=110" width="110px;" alt="NMC"/><br /><sub><b>NMC</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ncareau" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andres-baller"><img src="https://avatars.githubusercontent.com/u/52182449?v=4?s=110" width="110px;" alt="andres-baller"/><br /><sub><b>andres-baller</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andres-baller" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sean-borg"><img src="https://avatars.githubusercontent.com/u/67109348?v=4?s=110" width="110px;" alt="sean-borg"/><br /><sub><b>sean-borg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sean-borg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EDVLeer"><img src="https://avatars.githubusercontent.com/u/32170051?v=4?s=110" width="110px;" alt="EDVLeer"/><br /><sub><b>EDVLeer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=EDVLeer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Kurokat"><img src="https://avatars.githubusercontent.com/u/23075196?v=4?s=110" width="110px;" alt="Kurokat"/><br /><sub><b>Kurokat</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Kurokat" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://www.kevinkoellmann.de"><img src="https://avatars.githubusercontent.com/u/915514?v=4?s=110" width="110px;" alt="Kevin Köllmann"/><br /><sub><b>Kevin Köllmann</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koelle25" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sw-mreyes"><img src="https://avatars.githubusercontent.com/u/49025941?v=4?s=110" width="110px;" alt="sw-mreyes"/><br /><sub><b>sw-mreyes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sw-mreyes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://pittet.ca"><img src="https://avatars.githubusercontent.com/u/70129?v=4?s=110" width="110px;" alt="Joel Pittet"/><br /><sub><b>Joel Pittet</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=joelpittet" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://elyscape.com"><img src="https://avatars.githubusercontent.com/u/792695?v=4?s=110" width="110px;" alt="Eli Young"/><br /><sub><b>Eli Young</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=elyscape" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raelldottin"><img src="https://avatars.githubusercontent.com/u/317015?v=4?s=110" width="110px;" alt="Raell Dottin"/><br /><sub><b>Raell Dottin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=raelldottin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/misilot"><img src="https://avatars.githubusercontent.com/u/1446856?v=4?s=110" width="110px;" alt="Tom Misilo"/><br /><sub><b>Tom Misilo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=misilot" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://david.davenne.be"><img src="https://avatars.githubusercontent.com/u/4496300?v=4?s=110" width="110px;" alt="David Davenne"/><br /><sub><b>David Davenne</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JuustoMestari" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://markstenglein.com"><img src="https://avatars.githubusercontent.com/u/9255772?v=4?s=110" width="110px;" alt="Mark Stenglein"/><br /><sub><b>Mark Stenglein</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ocelotsloth" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ajsy"><img src="https://avatars.githubusercontent.com/u/35658596?v=4?s=110" width="110px;" alt="ajsy"/><br /><sub><b>ajsy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ajsy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/t3easy"><img src="https://avatars.githubusercontent.com/u/3628035?v=4?s=110" width="110px;" alt="Jan Kiesewetter"/><br /><sub><b>Jan Kiesewetter</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=t3easy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tetrachloromethane250"><img src="https://avatars.githubusercontent.com/u/79449630?v=4?s=110" width="110px;" alt="Tetrachloromethane250"/><br /><sub><b>Tetrachloromethane250</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.kajes.se/"><img src="https://avatars.githubusercontent.com/u/22004482?v=4?s=110" width="110px;" alt="Lars Kajes"/><br /><sub><b>Lars Kajes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kajes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Joly0"><img src="https://avatars.githubusercontent.com/u/13993216?v=4?s=110" width="110px;" alt="Joly0"/><br /><sub><b>Joly0</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Joly0" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/limeless"><img src="https://avatars.githubusercontent.com/u/1501022?v=4?s=110" width="110px;" alt="theburger"/><br /><sub><b>theburger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=limeless" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/deivishome"><img src="https://avatars.githubusercontent.com/u/36065681?v=4?s=110" width="110px;" alt="David Valin Alonso"/><br /><sub><b>David Valin Alonso</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=deivishome" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andreaci"><img src="https://avatars.githubusercontent.com/u/8290389?v=4?s=110" width="110px;" alt="andreaci"/><br /><sub><b>andreaci</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andreaci" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jellesebreghts.be"><img src="https://avatars.githubusercontent.com/u/1828542?v=4?s=110" width="110px;" alt="Jelle Sebreghts"/><br /><sub><b>Jelle Sebreghts</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Jelle-S" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Skywalker-11"><img src="https://avatars.githubusercontent.com/u/11180862?v=4?s=110" width="110px;" alt="Michael Pietsch"/><br /><sub><b>Michael Pietsch</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sh1hab"><img src="https://avatars.githubusercontent.com/u/22068886?v=4?s=110" width="110px;" alt="Masudul Haque Shihab"/><br /><sub><b>Masudul Haque Shihab</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sh1hab" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.freedomdive.com/"><img src="https://avatars.githubusercontent.com/u/16099942?v=4?s=110" width="110px;" alt="Supapong Areeprasertkul"/><br /><sub><b>Supapong Areeprasertkul</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zybersup" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/psarossy"><img src="https://avatars.githubusercontent.com/u/207358?v=4?s=110" width="110px;" alt="Peter Sarossy"/><br /><sub><b>Peter Sarossy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=psarossy" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nepella"><img src="https://avatars.githubusercontent.com/u/11823649?v=4?s=110" width="110px;" alt="Renee Margaret McConahy"/><br /><sub><b>Renee Margaret McConahy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nepella" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JohnnyPicnic"><img src="https://avatars.githubusercontent.com/u/5553884?v=4?s=110" width="110px;" alt="JohnnyPicnic"/><br /><sub><b>JohnnyPicnic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/markbrule"><img src="https://avatars.githubusercontent.com/u/8799594?v=4?s=110" width="110px;" alt="markbrule"/><br /><sub><b>markbrule</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=markbrule" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikecmpbll"><img src="https://avatars.githubusercontent.com/u/1962801?v=4?s=110" width="110px;" alt="Mike Campbell"/><br /><sub><b>Mike Campbell</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mikecmpbll" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tbrconnect"><img src="https://avatars.githubusercontent.com/u/11973217?v=4?s=110" width="110px;" alt="tbrconnect"/><br /><sub><b>tbrconnect</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tbrconnect" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kcoyo"><img src="https://avatars.githubusercontent.com/u/12447225?v=4?s=110" width="110px;" alt="kcoyo"/><br /><sub><b>kcoyo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kcoyo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://travismiller.com/"><img src="https://avatars.githubusercontent.com/u/494017?v=4?s=110" width="110px;" alt="Travis Miller"/><br /><sub><b>Travis Miller</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=travismiller" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Delta5"><img src="https://avatars.githubusercontent.com/u/1975640?v=4?s=110" width="110px;" alt="Evan Taylor"/><br /><sub><b>Evan Taylor</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Delta5" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PetriAsi"><img src="https://avatars.githubusercontent.com/u/8735148?v=4?s=110" width="110px;" alt="Petri Asikainen"/><br /><sub><b>Petri Asikainen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PetriAsi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/derdeagle"><img src="https://avatars.githubusercontent.com/u/11424540?v=4?s=110" width="110px;" alt="derdeagle"/><br /><sub><b>derdeagle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=derdeagle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://wh0rd.org/"><img src="https://avatars.githubusercontent.com/u/176950?v=4?s=110" width="110px;" alt="Mike Frysinger"/><br /><sub><b>Mike Frysinger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vapier" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AL4AL"><img src="https://avatars.githubusercontent.com/u/22044358?v=4?s=110" width="110px;" alt="ALPHA"/><br /><sub><b>ALPHA</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AL4AL" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.ifern.de"><img src="https://avatars.githubusercontent.com/u/1042587?v=4?s=110" width="110px;" alt="FliegenKLATSCH"/><br /><sub><b>FliegenKLATSCH</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jerm"><img src="https://avatars.githubusercontent.com/u/442138?v=4?s=110" width="110px;" alt="Jeremy Price"/><br /><sub><b>Jeremy Price</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jerm" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Toreg87"><img src="https://avatars.githubusercontent.com/u/84392209?v=4?s=110" width="110px;" alt="Toreg87"/><br /><sub><b>Toreg87</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Toreg87" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Computroniks"><img src="https://avatars.githubusercontent.com/u/67638596?v=4?s=110" width="110px;" alt="Matthew Nickson"/><br /><sub><b>Matthew Nickson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Computroniks" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jethron.id.au"><img src="https://avatars.githubusercontent.com/u/1646397?v=4?s=110" width="110px;" alt="Jethro Nederhof"/><br /><sub><b>Jethro Nederhof</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jethron" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/01ste02"><img src="https://avatars.githubusercontent.com/u/23289826?v=4?s=110" width="110px;" alt="Oskar Stenberg"/><br /><sub><b>Oskar Stenberg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=01ste02" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Robert-Azelis"><img src="https://avatars.githubusercontent.com/u/82208283?v=4?s=110" width="110px;" alt="Robert-Azelis"/><br /><sub><b>Robert-Azelis</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Robert-Azelis" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/alwism"><img src="https://avatars.githubusercontent.com/u/60648387?v=4?s=110" width="110px;" alt="Alexander William Smith"/><br /><sub><b>Alexander William Smith</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=alwism" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.leitwerk.de/"><img src="https://avatars.githubusercontent.com/u/24418301?v=4?s=110" width="110px;" alt="LEITWERK AG"/><br /><sub><b>LEITWERK AG</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=leitwerk-ag" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.aboutcher.co.uk"><img src="https://avatars.githubusercontent.com/u/1911435?v=4?s=110" width="110px;" alt="Adam"/><br /><sub><b>Adam</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adamboutcher" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://snksrv.com"><img src="https://avatars.githubusercontent.com/u/16104273?v=4?s=110" width="110px;" alt="Ian"/><br /><sub><b>Ian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sneak-it" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://blog.bestlong.idv.tw/"><img src="https://avatars.githubusercontent.com/u/4023909?v=4?s=110" width="110px;" alt="Shao Yu-Lung (Allen)"/><br /><sub><b>Shao Yu-Lung (Allen)</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bestlong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Haxatron"><img src="https://avatars.githubusercontent.com/u/76475453?v=4?s=110" width="110px;" alt="Haxatron"/><br /><sub><b>Haxatron</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Haxatron" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PlaneNuts"><img src="https://avatars.githubusercontent.com/u/88776392?v=4?s=110" width="110px;" alt="PlaneNuts"/><br /><sub><b>PlaneNuts</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PlaneNuts" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bjcpgd.cias.rit.edu"><img src="https://avatars.githubusercontent.com/u/3842948?v=4?s=110" width="110px;" alt="Bradley Coudriet"/><br /><sub><b>Bradley Coudriet</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=exula" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://daltondur.st"><img src="https://avatars.githubusercontent.com/u/21966173?v=4?s=110" width="110px;" alt="Dalton Durst"/><br /><sub><b>Dalton Durst</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://adagiohealth.org"><img src="https://avatars.githubusercontent.com/u/38761237?v=4?s=110" width="110px;" alt="Alex Janes"/><br /><sub><b>Alex Janes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adagioajanes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nuraeil"><img src="https://avatars.githubusercontent.com/u/32387849?v=4?s=110" width="110px;" alt="Nuraeil"/><br /><sub><b>Nuraeil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nuraeil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TenOfTens"><img src="https://avatars.githubusercontent.com/u/48162670?v=4?s=110" width="110px;" alt="TenOfTens"/><br /><sub><b>TenOfTens</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TenOfTens" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ditisjens.be/"><img src="https://avatars.githubusercontent.com/u/9415391?v=4?s=110" width="110px;" alt="waffle"/><br /><sub><b>waffle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=insert-waffle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qveensi"><img src="https://avatars.githubusercontent.com/u/19945501?v=4?s=110" width="110px;" alt="Yevhenii Huzii"/><br /><sub><b>Yevhenii Huzii</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=qveensi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/veenone"><img src="https://avatars.githubusercontent.com/u/3839381?v=4?s=110" width="110px;" alt="Achmad Fienan Rahardianto"/><br /><sub><b>Achmad Fienan Rahardianto</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=veenone" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chrisweirich"><img src="https://avatars.githubusercontent.com/u/97299851?v=4?s=110" width="110px;" alt="Christian Weirich"/><br /><sub><b>Christian Weirich</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chrisweirich" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/denzfarid"><img src="https://avatars.githubusercontent.com/u/1294403?v=4?s=110" width="110px;" alt="denzfarid"/><br /><sub><b>denzfarid</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ntbutler-nbcs"><img src="https://avatars.githubusercontent.com/u/94018771?v=4?s=110" width="110px;" alt="ntbutler-nbcs"/><br /><sub><b>ntbutler-nbcs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://naveensrinivasan.dev"><img src="https://avatars.githubusercontent.com/u/172697?v=4?s=110" width="110px;" alt="Naveen"/><br /><sub><b>Naveen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=naveensrinivasan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikeroq"><img src="https://avatars.githubusercontent.com/u/55674383?v=4?s=110" width="110px;" alt="Mike Roquemore"/><br /><sub><b>Mike Roquemore</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mikeroq" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/reederda"><img src="https://avatars.githubusercontent.com/u/7991086?v=4?s=110" width="110px;" alt="Daniel Reeder"/><br /><sub><b>Daniel Reeder</b></sub></a><br /><a href="#translation-reederda" title="Translation">🌍</a> <a href="#translation-reederda" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=reederda" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vickyjaura183"><img src="https://avatars.githubusercontent.com/u/109422491?v=4?s=110" width="110px;" alt="vickyjaura183"/><br /><sub><b>vickyjaura183</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vickyjaura183" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/julian-piehl"><img src="https://avatars.githubusercontent.com/u/32363424?v=4?s=110" width="110px;" alt="Peace"/><br /><sub><b>Peace</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=julian-piehl" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kylegordon"><img src="https://avatars.githubusercontent.com/u/231528?v=4?s=110" width="110px;" alt="Kyle Gordon"/><br /><sub><b>Kyle Gordon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kylegordon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.bfh.ch"><img src="https://avatars.githubusercontent.com/u/53009155?v=4?s=110" width="110px;" alt="Katharina Drexel"/><br /><sub><b>Katharina Drexel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sunflowerbofh" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://david.sferruzza.fr/"><img src="https://avatars.githubusercontent.com/u/1931963?v=4?s=110" width="110px;" alt="David Sferruzza"/><br /><sub><b>David Sferruzza</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dsferruzza" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rnelsonee"><img src="https://avatars.githubusercontent.com/u/19511639?v=4?s=110" width="110px;" alt="Rick Nelson"/><br /><sub><b>Rick Nelson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rnelsonee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BasO12"><img src="https://avatars.githubusercontent.com/u/94169344?v=4?s=110" width="110px;" alt="BasO12"/><br /><sub><b>BasO12</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BasO12" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vautia"><img src="https://avatars.githubusercontent.com/u/111710123?v=4?s=110" width="110px;" alt="Vautia"/><br /><sub><b>Vautia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Vautia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.littlehart.net/atthekeyboard"><img src="https://avatars.githubusercontent.com/u/28321?v=4?s=110" width="110px;" alt="Chris Hartjes"/><br /><sub><b>Chris Hartjes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chartjes" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geo-chen"><img src="https://avatars.githubusercontent.com/u/2404584?v=4?s=110" width="110px;" alt="geo-chen"/><br /><sub><b>geo-chen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=geo-chen" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nh314"><img src="https://avatars.githubusercontent.com/u/6006620?v=4?s=110" width="110px;" alt="Phan Nguyen"/><br /><sub><b>Phan Nguyen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nh314" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/StarlessNights"><img src="https://avatars.githubusercontent.com/u/115993812?v=4?s=110" width="110px;" alt="Iisakki Jaakkola"/><br /><sub><b>Iisakki Jaakkola</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=StarlessNights" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=110" width="110px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=eltociear" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukasfehling"><img src="https://avatars.githubusercontent.com/u/56871540?v=4?s=110" width="110px;" alt="Lukas Fehling"/><br /><sub><b>Lukas Fehling</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lukasfehling" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fernando-almeida"><img src="https://avatars.githubusercontent.com/u/1975990?v=4?s=110" width="110px;" alt="Fernando Almeida"/><br /><sub><b>Fernando Almeida</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fernando-almeida" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/akemidx"><img src="https://avatars.githubusercontent.com/u/116301219?v=4?s=110" width="110px;" alt="akemidx"/><br /><sub><b>akemidx</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=akemidx" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://oguz.site"><img src="https://avatars.githubusercontent.com/u/144778?v=4?s=110" width="110px;" alt="Oguz Bilgic"/><br /><sub><b>Oguz Bilgic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=oguzbilgic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/scoo73r"><img src="https://avatars.githubusercontent.com/u/9262438?v=4?s=110" width="110px;" alt="Scooter Crawford"/><br /><sub><b>Scooter Crawford</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=scoo73r" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/subdriven"><img src="https://avatars.githubusercontent.com/u/5957345?v=4?s=110" width="110px;" alt="subdriven"/><br /><sub><b>subdriven</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=subdriven" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AndrewSav"><img src="https://avatars.githubusercontent.com/u/658865?v=4?s=110" width="110px;" alt="Andrew Savinykh"/><br /><sub><b>Andrew Savinykh</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AndrewSav" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://kenchan0130.github.io"><img src="https://avatars.githubusercontent.com/u/1155067?v=4?s=110" width="110px;" alt="Tadayuki Onishi"/><br /><sub><b>Tadayuki Onishi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kenchan0130" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/floschoepfer"><img src="https://avatars.githubusercontent.com/u/112496896?v=4?s=110" width="110px;" alt="Florian"/><br /><sub><b>Florian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=floschoepfer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://spencerlong.com"><img src="https://avatars.githubusercontent.com/u/7305753?v=4?s=110" width="110px;" alt="Spencer Long"/><br /><sub><b>Spencer Long</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=spencerrlongg" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marcusmoore"><img src="https://avatars.githubusercontent.com/u/1141514?v=4?s=110" width="110px;" alt="Marcus Moore"/><br /><sub><b>Marcus Moore</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=marcusmoore" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mezzle"><img src="https://avatars.githubusercontent.com/u/570639?v=4?s=110" width="110px;" alt="Martin Meredith"/><br /><sub><b>Martin Meredith</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="http://dboth.de"><img src="https://avatars.githubusercontent.com/u/5731963?v=4?s=110" width="110px;" alt="dboth"/><br /><sub><b>dboth</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dboth" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zacharyfleck"><img src="https://avatars.githubusercontent.com/u/87536651?v=4?s=110" width="110px;" alt="Zachary Fleck"/><br /><sub><b>Zachary Fleck</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zacharyfleck" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vikaas-cyper"><img src="https://avatars.githubusercontent.com/u/74609912?v=4?s=110" width="110px;" alt="VIKAAS-A"/><br /><sub><b>VIKAAS-A</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vikaas-cyper" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ak-piracha"><img src="https://avatars.githubusercontent.com/u/88882041?v=4?s=110" width="110px;" alt="Abdul Kareem"/><br /><sub><b>Abdul Kareem</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ak-piracha" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NojoudAlshehri"><img src="https://avatars.githubusercontent.com/u/111287779?v=4?s=110" width="110px;" alt="NojoudAlshehri"/><br /><sub><b>NojoudAlshehri</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stefanstidlffg"><img src="https://avatars.githubusercontent.com/u/54367449?v=4?s=110" width="110px;" alt="Stefan Stidl"/><br /><sub><b>Stefan Stidl</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=stefanstidlffg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qay21"><img src="https://avatars.githubusercontent.com/u/87803479?v=4?s=110" width="110px;" alt="Quentin Aymard"/><br /><sub><b>Quentin Aymard</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=qay21" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cram42"><img src="https://avatars.githubusercontent.com/u/5396871?v=4?s=110" width="110px;" alt="Grant Le Roux"/><br /><sub><b>Grant Le Roux</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=cram42" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://@singrity"><img src="https://avatars.githubusercontent.com/u/58479551?v=4?s=110" width="110px;" alt="Bogdan"/><br /><sub><b>Bogdan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Singrity" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mmanjos"><img src="https://avatars.githubusercontent.com/u/3483684?v=4?s=110" width="110px;" alt="mmanjos"/><br /><sub><b>mmanjos</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mmanjos" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://azooz2014.github.io/"><img src="https://avatars.githubusercontent.com/u/7429229?v=4?s=110" width="110px;" alt="Abdelaziz Faki"/><br /><sub><b>Abdelaziz Faki</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Azooz2014" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bilias"><img src="https://avatars.githubusercontent.com/u/47315739?v=4?s=110" width="110px;" alt="bilias"/><br /><sub><b>bilias</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bilias" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coach1988"><img src="https://avatars.githubusercontent.com/u/2565989?v=4?s=110" width="110px;" alt="coach1988"/><br /><sub><b>coach1988</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=coach1988" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mauro-miatello"><img src="https://avatars.githubusercontent.com/u/11910225?v=4?s=110" width="110px;" alt="MrM"/><br /><sub><b>MrM</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mauro-miatello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/koiakoia"><img src="https://avatars.githubusercontent.com/u/60405354?v=4?s=110" width="110px;" alt="koiakoia"/><br /><sub><b>koiakoia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koiakoia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mustafa-online"><img src="https://avatars.githubusercontent.com/u/5323832?v=4?s=110" width="110px;" alt="Mustafa Online"/><br /><sub><b>Mustafa Online</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mustafa-online" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franceslui"><img src="https://avatars.githubusercontent.com/u/104601439?v=4?s=110" width="110px;" alt="franceslui"/><br /><sub><b>franceslui</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=franceslui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Q4kK"><img src="https://avatars.githubusercontent.com/u/125313163?v=4?s=110" width="110px;" alt="Q4kK"/><br /><sub><b>Q4kK</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Q4kK" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/squintfox"><img src="https://avatars.githubusercontent.com/u/55590532?v=4?s=110" width="110px;" alt="squintfox"/><br /><sub><b>squintfox</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=squintfox" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeffclay"><img src="https://avatars.githubusercontent.com/u/1380084?v=4?s=110" width="110px;" alt="Jeff Clay"/><br /><sub><b>Jeff Clay</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jeffclay" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PP-JN-RL"><img src="https://avatars.githubusercontent.com/u/52716446?v=4?s=110" width="110px;" alt="Phil J R"/><br /><sub><b>Phil J R</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PP-JN-RL" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.corelight.com/"><img src="https://avatars.githubusercontent.com/u/1496725?v=4?s=110" width="110px;" alt="i_virus"/><br /><sub><b>i_virus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chandanchowdhury" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gitgrimbo"><img src="https://avatars.githubusercontent.com/u/1020541?v=4?s=110" width="110px;" alt="Paul Grime"/><br /><sub><b>Paul Grime</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gitgrimbo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://leeporte.co.uk"><img src="https://avatars.githubusercontent.com/u/922815?v=4?s=110" width="110px;" alt="Lee Porte"/><br /><sub><b>Lee Porte</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=LeePorte" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bryanlopezinc"><img src="https://avatars.githubusercontent.com/u/23613427?v=4?s=110" width="110px;" alt="BRYAN "/><br /><sub><b>BRYAN </b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bryanlopezinc" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=bryanlopezinc" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/U-H-T"><img src="https://avatars.githubusercontent.com/u/64061710?v=4?s=110" width="110px;" alt="U-H-T"/><br /><sub><b>U-H-T</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=U-H-T" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tyree"><img src="https://avatars.githubusercontent.com/u/5395363?v=4?s=110" width="110px;" alt="Matt Tyree"/><br /><sub><b>Matt Tyree</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Tyree" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://spoontux.net"><img src="https://avatars.githubusercontent.com/u/292081?v=4?s=110" width="110px;" alt="Florent Bervas"/><br /><sub><b>Florent Bervas</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FlorentDotMe" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ditscheri.com"><img src="https://avatars.githubusercontent.com/u/4498077?v=4?s=110" width="110px;" alt="Daniel Albertsen"/><br /><sub><b>Daniel Albertsen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dbakan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/r-xyz"><img src="https://avatars.githubusercontent.com/u/100710244?v=4?s=110" width="110px;" alt="r-xyz"/><br /><sub><b>r-xyz</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=r-xyz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DrekiDegga"><img src="https://avatars.githubusercontent.com/u/47491036?v=4?s=110" width="110px;" alt="Steven Mainor"/><br /><sub><b>Steven Mainor</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DrekiDegga" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/arne-kroeger"><img src="https://avatars.githubusercontent.com/u/65785975?v=4?s=110" width="110px;" alt="arne-kroeger"/><br /><sub><b>arne-kroeger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=arne-kroeger" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Glukose1"><img src="https://avatars.githubusercontent.com/u/167117705?v=4?s=110" width="110px;" alt="Glukose1"/><br /><sub><b>Glukose1</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Glukose1" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Scarzy"><img src="https://avatars.githubusercontent.com/u/1197791?v=4?s=110" width="110px;" alt="Scarzy"/><br /><sub><b>Scarzy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Scarzy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/setpill"><img src="https://avatars.githubusercontent.com/u/37372069?v=4?s=110" width="110px;" alt="setpill"/><br /><sub><b>setpill</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=setpill" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/swift2512"><img src="https://avatars.githubusercontent.com/u/3755203?v=4?s=110" width="110px;" alt="swift2512"/><br /><sub><b>swift2512</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://darrenraineys.co.uk"><img src="https://avatars.githubusercontent.com/u/6136439?v=4?s=110" width="110px;" alt="Darren Rainey"/><br /><sub><b>Darren Rainey</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DarrenRainey" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/maciej-poleszczyk"><img src="https://avatars.githubusercontent.com/u/133033121?v=4?s=110" width="110px;" alt="maciej-poleszczyk"/><br /><sub><b>maciej-poleszczyk</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sgross-emlix"><img src="https://avatars.githubusercontent.com/u/143394709?v=4?s=110" width="110px;" alt="Sebastian Groß"/><br /><sub><b>Sebastian Groß</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sgross-emlix" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AnouarTouati"><img src="https://avatars.githubusercontent.com/u/41107778?v=4?s=110" width="110px;" alt="Anouar Touati"/><br /><sub><b>Anouar Touati</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AnouarTouati" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aHVzY2g"><img src="https://avatars.githubusercontent.com/u/25596663?v=4?s=110" width="110px;" alt="aHVzY2g"/><br /><sub><b>aHVzY2g</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=aHVzY2g" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://brlin.me"><img src="https://avatars.githubusercontent.com/u/13408130?v=4?s=110" width="110px;" alt="林博仁 Buo-ren Lin"/><br /><sub><b>林博仁 Buo-ren Lin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=brlin-tw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://orbalia.pythonanywhere.com/"><img src="https://avatars.githubusercontent.com/u/18550946?v=4?s=110" width="110px;" alt="Adugna Gizaw"/><br /><sub><b>Adugna Gizaw</b></sub></a><br /><a href="#translation-addex12" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jostrander"><img src="https://avatars.githubusercontent.com/u/760989?v=4?s=110" width="110px;" alt="Jesse Ostrander"/><br /><sub><b>Jesse Ostrander</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jostrander" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/azmcnutt"><img src="https://avatars.githubusercontent.com/u/31522486?v=4?s=110" width="110px;" alt="James M"/><br /><sub><b>James M</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=azmcnutt" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Fiala06"><img src="https://avatars.githubusercontent.com/u/5183146?v=4?s=110" width="110px;" alt="Fiala06"/><br /><sub><b>Fiala06</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Fiala06" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ntaylor-86"><img src="https://avatars.githubusercontent.com/u/28693782?v=4?s=110" width="110px;" alt="Nathan Taylor"/><br /><sub><b>Nathan Taylor</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ntaylor-86" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fvollmer"><img src="https://avatars.githubusercontent.com/u/16699443?v=4?s=110" width="110px;" alt="fvollmer"/><br /><sub><b>fvollmer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fvollmer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/36864"><img src="https://avatars.githubusercontent.com/u/109086466?v=4?s=110" width="110px;" alt="36864"/><br /><sub><b>36864</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=36864" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://clockwerx.blogspot.com/"><img src="https://avatars.githubusercontent.com/u/365751?v=4?s=110" width="110px;" alt="Daniel O'Connor"/><br /><sub><b>Daniel O'Connor</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=CloCkWeRX" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BeatSpark"><img src="https://avatars.githubusercontent.com/u/102852568?v=4?s=110" width="110px;" alt="BeatSpark"/><br /><sub><b>BeatSpark</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BeatSpark" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mrdahbi"><img src="https://avatars.githubusercontent.com/u/59203607?v=4?s=110" width="110px;" alt="mrdahbi"/><br /><sub><b>mrdahbi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mrdahbi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://sr.solutions"><img src="https://avatars.githubusercontent.com/u/6661332?v=4?s=110" width="110px;" alt="Fabian Schmid"/><br /><sub><b>Fabian Schmid</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chfsx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.chrisolin.com"><img src="https://avatars.githubusercontent.com/u/1288116?v=4?s=110" width="110px;" alt="Chris Olin"/><br /><sub><b>Chris Olin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=realchrisolin" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mnemonicly"><img src="https://avatars.githubusercontent.com/u/3803132?v=4?s=110" width="110px;" alt="Dan"/><br /><sub><b>Dan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mnemonicly" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NebelKreis"><img src="https://avatars.githubusercontent.com/u/43917728?v=4?s=110" width="110px;" alt="Nebel"/><br /><sub><b>Nebel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=NebelKreis" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/test1337ahp"><img src="https://avatars.githubusercontent.com/u/132433803?v=4?s=110" width="110px;" alt="test1337ahp"/><br /><sub><b>test1337ahp</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=test1337ahp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JonathonReinhart"><img src="https://avatars.githubusercontent.com/u/1916566?v=4?s=110" width="110px;" alt="Jonathon Reinhart"/><br /><sub><b>Jonathon Reinhart</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JonathonReinhart" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aranar-pro"><img src="https://avatars.githubusercontent.com/u/484742?v=4?s=110" width="110px;" alt="aranar-pro"/><br /><sub><b>aranar-pro</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=aranar-pro" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/phil-flip"><img src="https://avatars.githubusercontent.com/u/27019397?v=4?s=110" width="110px;" alt="Phil"/><br /><sub><b>Phil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=phil-flip" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://fe80.fr/"><img src="https://avatars.githubusercontent.com/u/6473460?v=4?s=110" width="110px;" alt="Steffy Fort"/><br /><sub><b>Steffy Fort</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fe80" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sorvani"><img src="https://avatars.githubusercontent.com/u/3302372?v=4?s=110" width="110px;" alt="Jared Busch"/><br /><sub><b>Jared Busch</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sorvani" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/seanborg-codethink"><img src="https://avatars.githubusercontent.com/u/111956991?v=4?s=110" width="110px;" alt="seanborg-codethink"/><br /><sub><b>seanborg-codethink</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=seanborg-codethink" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dkaatz"><img src="https://avatars.githubusercontent.com/u/160669961?v=4?s=110" width="110px;" alt="dkaatz"/><br /><sub><b>dkaatz</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dkaatz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://threema.id/74SF7MW6?text="><img src="https://avatars.githubusercontent.com/u/827205?v=4?s=110" width="110px;" alt="Daniel Ruf"/><br /><sub><b>Daniel Ruf</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DanielRuf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ahpaleus"><img src="https://avatars.githubusercontent.com/u/38883201?v=4?s=110" width="110px;" alt="ahpaleus"/><br /><sub><b>ahpaleus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ahpaleus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mink-adao-duy"><img src="https://avatars.githubusercontent.com/u/22906055?v=4?s=110" width="110px;" alt="Anh DAO-DUY"/><br /><sub><b>Anh DAO-DUY</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mink-adao-duy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Serdnad"><img src="https://avatars.githubusercontent.com/u/4723453?v=4?s=110" width="110px;" alt="Andres Gutierrez"/><br /><sub><b>Andres Gutierrez</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Serdnad" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wewhite"><img src="https://avatars.githubusercontent.com/u/111083379?v=4?s=110" width="110px;" alt="Warren White"/><br /><sub><b>Warren White</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=wewhite" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://robintemme.de/"><img src="https://avatars.githubusercontent.com/u/2809241?v=4?s=110" width="110px;" alt="Robin Temme"/><br /><sub><b>Robin Temme</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=robintemme" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/herroworrd"><img src="https://avatars.githubusercontent.com/u/47008367?v=4?s=110" width="110px;" alt="herroworrd"/><br /><sub><b>herroworrd</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=herroworrd" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://mubiu.com/"><img src="https://avatars.githubusercontent.com/u/28558609?v=4?s=110" width="110px;" alt="vicleos"/><br /><sub><b>vicleos</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vicleos" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://thinkl33t.co.uk/"><img src="https://avatars.githubusercontent.com/u/1016780?v=4?s=110" width="110px;" alt="Bob Clough"/><br /><sub><b>Bob Clough</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thinkl33t" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/brandon-bailey"><img src="https://avatars.githubusercontent.com/u/10648463?v=4?s=110" width="110px;" alt="Brandon Daniel Bailey"/><br /><sub><b>Brandon Daniel Bailey</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=brandon-bailey" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marcquark"><img src="https://avatars.githubusercontent.com/u/23556080?v=4?s=110" width="110px;" alt="Marc Bartelt"/><br /><sub><b>Marc Bartelt</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=marcquark" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/manu-crealytics"><img src="https://avatars.githubusercontent.com/u/18286893?v=4?s=110" width="110px;" alt="manu-crealytics"/><br /><sub><b>manu-crealytics</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=manu-crealytics" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.galaxy102.de/"><img src="https://avatars.githubusercontent.com/u/18245993?v=4?s=110" width="110px;" alt="Konstantin Köhring"/><br /><sub><b>Konstantin Köhring</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Galaxy102" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://deloz.net/"><img src="https://avatars.githubusercontent.com/u/685167?v=4?s=110" width="110px;" alt="Deloz"/><br /><sub><b>Deloz</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=deloz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mbrrg"><img src="https://avatars.githubusercontent.com/u/2682426?v=4?s=110" width="110px;" alt="Martin Berg"/><br /><sub><b>Martin Berg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mbrrg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nothing4You"><img src="https://avatars.githubusercontent.com/u/3694534?v=4?s=110" width="110px;" alt="Richard Schwab"/><br /><sub><b>Richard Schwab</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Nothing4You" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://rickheil.com/"><img src="https://avatars.githubusercontent.com/u/8959676?v=4?s=110" width="110px;" alt="Rick Heil"/><br /><sub><b>Rick Heil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rickheil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rosscdh"><img src="https://avatars.githubusercontent.com/u/397106?v=4?s=110" width="110px;" alt="Ross Crawford-d'Heureuse"/><br /><sub><b>Ross Crawford-d'Heureuse</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rosscdh" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/McG800"><img src="https://avatars.githubusercontent.com/u/1621107?v=4?s=110" width="110px;" alt="Ryan McGuire"/><br /><sub><b>Ryan McGuire</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=McG800" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SBrown2021"><img src="https://avatars.githubusercontent.com/u/77835667?v=4?s=110" width="110px;" alt="SBrown2021"/><br /><sub><b>SBrown2021</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=SBrown2021" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/serkanerip"><img src="https://avatars.githubusercontent.com/u/8780913?v=4?s=110" width="110px;" alt="Serkan"/><br /><sub><b>Serkan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=serkanerip" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.yudelei.com/"><img src="https://avatars.githubusercontent.com/u/63188620?v=4?s=110" width="110px;" alt="Shanks"/><br /><sub><b>Shanks</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Shankschn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cendai-mis"><img src="https://avatars.githubusercontent.com/u/198525698?v=4?s=110" width="110px;" alt="cendai-mis"/><br /><sub><b>cendai-mis</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=cendai-mis" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://smcpeck.github.io/"><img src="https://avatars.githubusercontent.com/u/8724583?v=4?s=110" width="110px;" alt="Shaun McPeck"/><br /><sub><b>Shaun McPeck</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=smcpeck" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/snazy2000"><img src="https://avatars.githubusercontent.com/u/1378836?v=4?s=110" width="110px;" alt="Stephen"/><br /><sub><b>Stephen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=snazy2000" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://nevets82.github.io/"><img src="https://avatars.githubusercontent.com/u/4462739?v=4?s=110" width="110px;" alt="Steven"/><br /><sub><b>Steven</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Nevets82" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://mateusvillar.com/"><img src="https://avatars.githubusercontent.com/u/29017267?v=4?s=110" width="110px;" alt="Mateus Villar"/><br /><sub><b>Mateus Villar</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Mateus-Romera" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mzack5020"><img src="https://avatars.githubusercontent.com/u/12749393?v=4?s=110" width="110px;" alt="Matthew Zackschewski"/><br /><sub><b>Matthew Zackschewski</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mzack5020" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.frei.media/"><img src="https://avatars.githubusercontent.com/u/12660103?v=4?s=110" width="110px;" alt="Matthias Frei"/><br /><sub><b>Matthias Frei</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=firefrei" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nticaric"><img src="https://avatars.githubusercontent.com/u/824840?v=4?s=110" width="110px;" alt="Nenad Ticaric"/><br /><sub><b>Nenad Ticaric</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nticaric" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Scorcher"><img src="https://avatars.githubusercontent.com/u/706439?v=4?s=110" width="110px;" alt="Nikolay Didenko"/><br /><sub><b>Nikolay Didenko</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Scorcher" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nunomaduro.com/sponsorships"><img src="https://avatars.githubusercontent.com/u/5457236?v=4?s=110" width="110px;" alt="Nuno Maduro"/><br /><sub><b>Nuno Maduro</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nunomaduro" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://tektikhq.com/"><img src="https://avatars.githubusercontent.com/u/8883074?v=4?s=110" width="110px;" alt="Oliver Walerys"/><br /><sub><b>Oliver Walerys</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=owalerys" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://keybase.io/rcmcdonald91"><img src="https://avatars.githubusercontent.com/u/3102039?v=4?s=110" width="110px;" alt="R. Christian McDonald"/><br /><sub><b>R. Christian McDonald</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rcmcdonald91" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nnix.net/"><img src="https://avatars.githubusercontent.com/u/1525581?v=4?s=110" width="110px;" alt="nix"/><br /><sub><b>nix</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nixn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/octobunny"><img src="https://avatars.githubusercontent.com/u/55462380?v=4?s=110" width="110px;" alt="octobunny"/><br /><sub><b>octobunny</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=octobunny" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sreyemnayr"><img src="https://avatars.githubusercontent.com/u/8558670?v=4?s=110" width="110px;" alt="Ryan"/><br /><sub><b>Ryan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sreyemnayr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://benji.ltd/"><img src="https://avatars.githubusercontent.com/u/1501022?v=4?s=110" width="110px;" alt="p3nj"/><br /><sub><b>p3nj</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=p3nj" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/timwsuqld"><img src="https://avatars.githubusercontent.com/u/6201617?v=4?s=110" width="110px;" alt="Tim White"/><br /><sub><b>Tim White</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=timwsuqld" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yannikp"><img src="https://avatars.githubusercontent.com/u/22473767?v=4?s=110" width="110px;" alt="yannikp"/><br /><sub><b>yannikp</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=yannikp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/viclou"><img src="https://avatars.githubusercontent.com/u/20525448?v=4?s=110" width="110px;" alt="victoria"/><br /><sub><b>victoria</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=viclou" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/valentyntu"><img src="https://avatars.githubusercontent.com/u/40685314?v=4?s=110" width="110px;" alt="Valentyn Tulub"/><br /><sub><b>Valentyn Tulub</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=valentyntu" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://wouter0100.nl/"><img src="https://avatars.githubusercontent.com/u/864520?v=4?s=110" width="110px;" alt="Wouter van Os"/><br /><sub><b>Wouter van Os</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Wouter0100" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/wyatt-teeter"><img src="https://avatars.githubusercontent.com/u/3946540?v=4?s=110" width="110px;" alt="Wyatt Teeter"/><br /><sub><b>Wyatt Teeter</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=xWyatt" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/terwey"><img src="https://avatars.githubusercontent.com/u/1596124?v=4?s=110" width="110px;" alt="Yorick Terweijden"/><br /><sub><b>Yorick Terweijden</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=terwey" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bmkalle"><img src="https://avatars.githubusercontent.com/u/69298836?v=4?s=110" width="110px;" alt="bmkalle"/><br /><sub><b>bmkalle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bmkalle" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bricelabelle"><img src="https://avatars.githubusercontent.com/u/28403467?v=4?s=110" width="110px;" alt="bricelabelle"/><br /><sub><b>bricelabelle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bricelabelle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/corydlamb"><img src="https://avatars.githubusercontent.com/u/97770090?v=4?s=110" width="110px;" alt="corydlamb"/><br /><sub><b>corydlamb</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=corydlamb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/splash"><img src="https://avatars.githubusercontent.com/u/1154133?v=4?s=110" width="110px;" alt="Diogenes S. Jesus"/><br /><sub><b>Diogenes S. Jesus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=splashx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dkmansion"><img src="https://avatars.githubusercontent.com/u/5826629?v=4?s=110" width="110px;" alt="D M"/><br /><sub><b>D M</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dkmansion" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jarli01"><img src="https://avatars.githubusercontent.com/u/14837699?v=4?s=110" width="110px;" alt="Dustin B"/><br /><sub><b>Dustin B</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Jarli01" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fabiang"><img src="https://avatars.githubusercontent.com/u/348344?v=4?s=110" width="110px;" alt="Fabian Grutschus"/><br /><sub><b>Fabian Grutschus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fabiang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MelonSmasher"><img src="https://avatars.githubusercontent.com/u/1491053?v=4?s=110" width="110px;" alt="MelonSmasher"/><br /><sub><b>MelonSmasher</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=MelonSmasher" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AlexanderWPapyrus"><img src="https://avatars.githubusercontent.com/u/80526133?v=4?s=110" width="110px;" alt="AlexanderWPapyrus"/><br /><sub><b>AlexanderWPapyrus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/disc"><img src="https://avatars.githubusercontent.com/u/306231?v=4?s=110" width="110px;" alt="Alexandr Hacicheant"/><br /><sub><b>Alexandr Hacicheant</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=disc" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://hex128.io/"><img src="https://avatars.githubusercontent.com/u/3032891?v=4?s=110" width="110px;" alt="Hex"/><br /><sub><b>Hex</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=hex128" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/arukompas"><img src="https://avatars.githubusercontent.com/u/8697942?v=4?s=110" width="110px;" alt="Arunas Skirius"/><br /><sub><b>Arunas Skirius</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=arukompas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benperiton"><img src="https://avatars.githubusercontent.com/u/104396?v=4?s=110" width="110px;" alt="Ben Periton"/><br /><sub><b>Ben Periton</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=benperiton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://wolfman.dev/"><img src="https://avatars.githubusercontent.com/u/11906832?v=4?s=110" width="110px;" alt="Byron Wolfman"/><br /><sub><b>Byron Wolfman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=byronwolfman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CalvinSchwartz"><img src="https://avatars.githubusercontent.com/u/56485508?v=4?s=110" width="110px;" alt="Calvin"/><br /><sub><b>Calvin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/juanfont"><img src="https://avatars.githubusercontent.com/u/181059?v=4?s=110" width="110px;" alt="Juan Font"/><br /><sub><b>Juan Font</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=juanfont" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/juhotaipale"><img src="https://avatars.githubusercontent.com/u/13137708?v=4?s=110" width="110px;" alt="Juho Taipale"/><br /><sub><b>Juho Taipale</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=juhotaipale" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KorvinSzanto"><img src="https://avatars.githubusercontent.com/u/1007419?v=4?s=110" width="110px;" alt="Korvin Szanto"/><br /><sub><b>Korvin Szanto</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=KorvinSzanto" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://lewisfoster.foo/"><img src="https://avatars.githubusercontent.com/u/8513053?v=4?s=110" width="110px;" alt="Lewis Foster"/><br /><sub><b>Lewis Foster</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sniff122" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loganswartz"><img src="https://avatars.githubusercontent.com/u/33877541?v=4?s=110" width="110px;" alt="Logan Swartzendruber"/><br /><sub><b>Logan Swartzendruber</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=loganswartz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lopezio"><img src="https://avatars.githubusercontent.com/u/1156208?v=4?s=110" width="110px;" alt="Lorenzo P."/><br /><sub><b>Lorenzo P.</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lopezio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/m4us1ne"><img src="https://avatars.githubusercontent.com/u/33946590?v=4?s=110" width="110px;" alt="Lukas Jung"/><br /><sub><b>Lukas Jung</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=m4us1ne" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://leafedfox.xyz/"><img src="https://avatars.githubusercontent.com/u/10965027?v=4?s=110" width="110px;" alt="Ellie"/><br /><sub><b>Ellie</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=LeafedFox" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gastamper"><img src="https://avatars.githubusercontent.com/u/20960555?v=4?s=110" width="110px;" alt="GA Stamper"/><br /><sub><b>GA Stamper</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gastamper" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gl-pup"><img src="https://avatars.githubusercontent.com/u/206553556?v=4?s=110" width="110px;" alt="Guillaume Lefranc"/><br /><sub><b>Guillaume Lefranc</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gl-pup" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dasjoe"><img src="https://avatars.githubusercontent.com/u/733892?v=4?s=110" width="110px;" alt="Hajo Möller"/><br /><sub><b>Hajo Möller</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dasjoe" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pottom"><img src="https://avatars.githubusercontent.com/u/3420063?v=4?s=110" width="110px;" alt="Istvan Basa"/><br /><sub><b>Istvan Basa</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=pottom" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jjasghar.github.io/"><img src="https://avatars.githubusercontent.com/u/810824?v=4?s=110" width="110px;" alt="JJ Asghar"/><br /><sub><b>JJ Asghar</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jjasghar" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JemCdo"><img src="https://avatars.githubusercontent.com/u/40404495?v=4?s=110" width="110px;" alt="James E. Msenga"/><br /><sub><b>James E. Msenga</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JemCdo" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jfwiebe"><img src="https://avatars.githubusercontent.com/u/6865786?v=4?s=110" width="110px;" alt="Jan Felix Wiebe"/><br /><sub><b>Jan Felix Wiebe</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jfwiebe" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.nfon.com/"><img src="https://avatars.githubusercontent.com/u/43412008?v=4?s=110" width="110px;" alt="Jo Drexl"/><br /><sub><b>Jo Drexl</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=drexljo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/austinsasko"><img src="https://avatars.githubusercontent.com/u/4807843?v=4?s=110" width="110px;" alt="Austin Sasko"/><br /><sub><b>Austin Sasko</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=austinsasko" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1695622?v=3" width="110px;"/><br /><sub>Justin Kerr</sub>](http://jbirdkerr.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [<img src="https://avatars3.githubusercontent.com/u/11426176?v=3" width="110px;"/><br /><sub>Ira W. Snyder</sub>](http://www.irasnyder.com/devel/)<br />[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2475759?v=3" width="110px;"/><br /><sub>Aladin Alaily</sub>](https://github.com/aalaily)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [<img src="https://avatars0.githubusercontent.com/u/10247644?v=3" width="110px;"/><br /><sub>Chase Hansen</sub>](https://github.com/kobie-chasehansen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [<img src="https://avatars2.githubusercontent.com/u/13545400?v=3" width="110px;"/><br /><sub>IDM Helpdesk</sub>](https://github.com/IDM-Helpdesk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [<img src="https://avatars2.githubusercontent.com/u/614439?v=3" width="110px;"/><br /><sub>Kai</sub>](http://balticer.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [<img src="https://avatars1.githubusercontent.com/u/8762511?v=3" width="110px;"/><br /><sub>Michael Daniels</sub>](http://www.michaeldaniels.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/1532660?v=3" width="110px;"/><br /><sub>Tom Castleman</sub>](http://tomcastleman.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [<img src="https://avatars3.githubusercontent.com/u/10723243?v=3" width="110px;"/><br /><sub>Daniel Nemanic</sub>](https://github.com/DanielNemanic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [<img src="https://avatars0.githubusercontent.com/u/150648?v=3" width="110px;"/><br /><sub>SouthWolf</sub>](https://github.com/southwolf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [<img src="https://avatars2.githubusercontent.com/u/131616?v=3" width="110px;"/><br /><sub>Ivar Nesje</sub>](https://github.com/ivarne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [<img src="https://avatars1.githubusercontent.com/u/62333?v=3" width="110px;"/><br /><sub>Jérémy Benoist</sub>](http://www.j0k3r.net)<br />[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/724344?v=3" width="110px;"/><br /><sub>Chris Leathley</sub>](https://github.com/cleathley)<br />[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/972498?v=3" width="110px;"/><br /><sub>splaer</sub>](https://github.com/splaer)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/967362?v=3" width="110px;"/><br /><sub>Joe Ferguson</sub>](http://www.joeferguson.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [<img src="https://avatars3.githubusercontent.com/u/6108682?v=3" width="110px;"/><br /><sub>diwanicki</sub>](https://github.com/diwanicki)<br />[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/2527115?v=3" width="110px;"/><br /><sub>Lee Thoong Ching</sub>](https://github.com/pakkua80)<br />[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [<img src="https://avatars1.githubusercontent.com/u/461491?v=3" width="110px;"/><br /><sub>Marek Šuppa</sub>](http://shu.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [<img src="https://avatars1.githubusercontent.com/u/8693762?v=3" width="110px;"/><br /><sub>Juan J. Martinez</sub>](https://github.com/mizar1616)<br />[🌍](#translation-mizar1616 "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1458388?v=3" width="110px;"/><br /><sub>R Ryan Dial</sub>](https://github.com/rrdial)<br />[🌍](#translation-rrdial "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2871745?v=3" width="110px;"/><br /><sub>Andrej Manduch</sub>](https://github.com/burlito)<br />[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") |
| [<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://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/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "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://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.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") | [<img src="https://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [<img src="https://avatars.githubusercontent.com/u/70129?v=4" width="110px;"/><br /><sub>Joel Pittet</sub>](https://pittet.ca)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [<img src="https://avatars.githubusercontent.com/u/792695?v=4" width="110px;"/><br /><sub>Eli Young</sub>](https://elyscape.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [<img src="https://avatars.githubusercontent.com/u/317015?v=4" width="110px;"/><br /><sub>Raell Dottin</sub>](https://github.com/raelldottin)<br />[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [<img src="https://avatars.githubusercontent.com/u/1446856?v=4" width="110px;"/><br /><sub>Tom Misilo</sub>](https://github.com/misilot)<br />[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [<img src="https://avatars.githubusercontent.com/u/4496300?v=4" width="110px;"/><br /><sub>David Davenne</sub>](http://david.davenne.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") |
| [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/qveensi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") |
| [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") |
| [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") |
| [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") |
| [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") |
| [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>Daniel O'Connor</sub>](http://clockwerx.blogspot.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [<img src="https://avatars.githubusercontent.com/u/59203607?v=4" width="110px;"/><br /><sub>mrdahbi</sub>](https://github.com/mrdahbi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [<img src="https://avatars.githubusercontent.com/u/6661332?v=4" width="110px;"/><br /><sub>Fabian Schmid</sub>](http://sr.solutions)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [<img src="https://avatars.githubusercontent.com/u/1288116?v=4" width="110px;"/><br /><sub>Chris Olin</sub>](https://www.chrisolin.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3803132?v=4" width="110px;"/><br /><sub>Dan</sub>](https://github.com/mnemonicly)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [<img src="https://avatars.githubusercontent.com/u/43917728?v=4" width="110px;"/><br /><sub>Nebel</sub>](https://github.com/NebelKreis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [<img src="https://avatars.githubusercontent.com/u/132433803?v=4" width="110px;"/><br /><sub>test1337ahp</sub>](https://github.com/test1337ahp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [<img src="https://avatars.githubusercontent.com/u/1916566?v=4" width="110px;"/><br /><sub>Jonathon Reinhart</sub>](https://github.com/JonathonReinhart)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [<img src="https://avatars.githubusercontent.com/u/484742?v=4" width="110px;"/><br /><sub>aranar-pro</sub>](https://github.com/aranar-pro)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [<img src="https://avatars.githubusercontent.com/u/27019397?v=4" width="110px;"/><br /><sub>Phil</sub>](https://github.com/phil-flip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [<img src="https://avatars.githubusercontent.com/u/6473460?v=4" width="110px;"/><br /><sub>Steffy Fort</sub>](https://fe80.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3302372?v=4" width="110px;"/><br /><sub>Jared Busch</sub>](https://github.com/sorvani)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [<img src="https://avatars.githubusercontent.com/u/111956991?v=4" width="110px;"/><br /><sub>seanborg-codethink</sub>](https://github.com/seanborg-codethink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [<img src="https://avatars.githubusercontent.com/u/160669961?v=4" width="110px;"/><br /><sub>dkaatz</sub>](https://github.com/dkaatz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [<img src="https://avatars.githubusercontent.com/u/827205?v=4" width="110px;"/><br /><sub>Daniel Ruf</sub>](https://threema.id/74SF7MW6?text=)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [<img src="https://avatars.githubusercontent.com/u/38883201?v=4" width="110px;"/><br /><sub>ahpaleus</sub>](https://github.com/ahpaleus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [<img src="https://avatars.githubusercontent.com/u/22906055?v=4" width="110px;"/><br /><sub>Anh DAO-DUY</sub>](https://github.com/mink-adao-duy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [<img src="https://avatars.githubusercontent.com/u/4723453?v=4" width="110px;"/><br /><sub>Andres Gutierrez</sub>](https://github.com/Serdnad)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111083379?v=4" width="110px;"/><br /><sub>Warren White</sub>](https://github.com/wewhite)<br />[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [<img src="https://avatars.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://robintemme.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [<img src="https://avatars.githubusercontent.com/u/47008367?v=4" width="110px;"/><br /><sub>herroworrd</sub>](https://github.com/herroworrd)<br />[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [<img src="https://avatars.githubusercontent.com/u/28558609?v=4" width="110px;"/><br /><sub>vicleos</sub>](https://mubiu.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [<img src="https://avatars.githubusercontent.com/u/1016780?v=4" width="110px;"/><br /><sub>Bob Clough</sub>](http://thinkl33t.co.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [<img src="https://avatars.githubusercontent.com/u/10648463?v=4" width="110px;"/><br /><sub>Brandon Daniel Bailey</sub>](https://github.com/brandon-bailey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [<img src="https://avatars.githubusercontent.com/u/23556080?v=4" width="110px;"/><br /><sub>Marc Bartelt</sub>](https://github.com/marcquark)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") |
| [<img src="https://avatars.githubusercontent.com/u/18286893?v=4" width="110px;"/><br /><sub>manu-crealytics</sub>](https://github.com/manu-crealytics)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [<img src="https://avatars.githubusercontent.com/u/18245993?v=4" width="110px;"/><br /><sub>Konstantin Köhring</sub>](https://www.galaxy102.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [<img src="https://avatars.githubusercontent.com/u/685167?v=4" width="110px;"/><br /><sub>Deloz</sub>](https://deloz.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [<img src="https://avatars.githubusercontent.com/u/2682426?v=4" width="110px;"/><br /><sub>Martin Berg</sub>](https://github.com/mbrrg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [<img src="https://avatars.githubusercontent.com/u/3694534?v=4" width="110px;"/><br /><sub>Richard Schwab</sub>](https://github.com/Nothing4You)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [<img src="https://avatars.githubusercontent.com/u/8959676?v=4" width="110px;"/><br /><sub>Rick Heil</sub>](https://rickheil.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [<img src="https://avatars.githubusercontent.com/u/397106?v=4" width="110px;"/><br /><sub>Ross Crawford-d'Heureuse</sub>](https://github.com/rosscdh)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1621107?v=4" width="110px;"/><br /><sub>Ryan McGuire</sub>](https://github.com/McG800)<br />[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [<img src="https://avatars.githubusercontent.com/u/77835667?v=4" width="110px;"/><br /><sub>SBrown2021</sub>](https://github.com/SBrown2021)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [<img src="https://avatars.githubusercontent.com/u/8780913?v=4" width="110px;"/><br /><sub>Serkan</sub>](https://github.com/serkanerip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [<img src="https://avatars.githubusercontent.com/u/63188620?v=4" width="110px;"/><br /><sub>Shanks</sub>](https://www.yudelei.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [<img src="https://avatars.githubusercontent.com/u/198525698?v=4" width="110px;"/><br /><sub>cendai-mis</sub>](https://github.com/cendai-mis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [<img src="https://avatars.githubusercontent.com/u/8724583?v=4" width="110px;"/><br /><sub>Shaun McPeck</sub>](https://smcpeck.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [<img src="https://avatars.githubusercontent.com/u/1378836?v=4" width="110px;"/><br /><sub>Stephen</sub>](https://github.com/snazy2000)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/4462739?v=4" width="110px;"/><br /><sub>Steven</sub>](http://nevets82.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [<img src="https://avatars.githubusercontent.com/u/29017267?v=4" width="110px;"/><br /><sub>Mateus Villar</sub>](https://mateusvillar.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [<img src="https://avatars.githubusercontent.com/u/12749393?v=4" width="110px;"/><br /><sub>Matthew Zackschewski</sub>](https://github.com/mzack5020)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [<img src="https://avatars.githubusercontent.com/u/12660103?v=4" width="110px;"/><br /><sub>Matthias Frei</sub>](https://www.frei.media/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [<img src="https://avatars.githubusercontent.com/u/824840?v=4" width="110px;"/><br /><sub>Nenad Ticaric</sub>](https://github.com/nticaric)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [<img src="https://avatars.githubusercontent.com/u/706439?v=4" width="110px;"/><br /><sub>Nikolay Didenko</sub>](https://github.com/Scorcher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [<img src="https://avatars.githubusercontent.com/u/5457236?v=4" width="110px;"/><br /><sub>Nuno Maduro</sub>](https://nunomaduro.com/sponsorships)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") |
| [<img src="https://avatars.githubusercontent.com/u/8883074?v=4" width="110px;"/><br /><sub>Oliver Walerys</sub>](https://tektikhq.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [<img src="https://avatars.githubusercontent.com/u/3102039?v=4" width="110px;"/><br /><sub>R. Christian McDonald</sub>](https://keybase.io/rcmcdonald91)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [<img src="https://avatars.githubusercontent.com/u/1525581?v=4" width="110px;"/><br /><sub>nix</sub>](https://nnix.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [<img src="https://avatars.githubusercontent.com/u/55462380?v=4" width="110px;"/><br /><sub>octobunny</sub>](https://github.com/octobunny)<br />[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [<img src="https://avatars.githubusercontent.com/u/8558670?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/sreyemnayr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>p3nj</sub>](https://benji.ltd/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [<img src="https://avatars.githubusercontent.com/u/6201617?v=4" width="110px;"/><br /><sub>Tim White</sub>](https://github.com/timwsuqld)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") |
| [<img src="https://avatars.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>yannikp</sub>](https://github.com/yannikp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [<img src="https://avatars.githubusercontent.com/u/20525448?v=4" width="110px;"/><br /><sub>victoria</sub>](https://github.com/viclou)<br />[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [<img src="https://avatars.githubusercontent.com/u/40685314?v=4" width="110px;"/><br /><sub>Valentyn Tulub</sub>](https://github.com/valentyntu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [<img src="https://avatars.githubusercontent.com/u/864520?v=4" width="110px;"/><br /><sub>Wouter van Os</sub>](http://wouter0100.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [<img src="https://avatars.githubusercontent.com/u/3946540?v=4" width="110px;"/><br /><sub>Wyatt Teeter</sub>](https://www.linkedin.com/in/wyatt-teeter)<br />[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [<img src="https://avatars.githubusercontent.com/u/1596124?v=4" width="110px;"/><br /><sub>Yorick Terweijden</sub>](https://github.com/terwey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [<img src="https://avatars.githubusercontent.com/u/69298836?v=4" width="110px;"/><br /><sub>bmkalle</sub>](https://github.com/bmkalle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28403467?v=4" width="110px;"/><br /><sub>bricelabelle</sub>](https://github.com/bricelabelle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [<img src="https://avatars.githubusercontent.com/u/97770090?v=4" width="110px;"/><br /><sub>corydlamb</sub>](https://github.com/corydlamb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [<img src="https://avatars.githubusercontent.com/u/1154133?v=4" width="110px;"/><br /><sub>Diogenes S. Jesus</sub>](http://twitter.com/splash)<br />[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [<img src="https://avatars.githubusercontent.com/u/5826629?v=4" width="110px;"/><br /><sub>D M</sub>](https://github.com/dkmansion)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [<img src="https://avatars.githubusercontent.com/u/14837699?v=4" width="110px;"/><br /><sub>Dustin B</sub>](https://github.com/Jarli01)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [<img src="https://avatars.githubusercontent.com/u/348344?v=4" width="110px;"/><br /><sub>Fabian Grutschus</sub>](https://github.com/fabiang)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [<img src="https://avatars.githubusercontent.com/u/1491053?v=4" width="110px;"/><br /><sub>MelonSmasher</sub>](https://github.com/MelonSmasher)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") |
| [<img src="https://avatars.githubusercontent.com/u/80526133?v=4" width="110px;"/><br /><sub>AlexanderWPapyrus</sub>](https://github.com/AlexanderWPapyrus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [<img src="https://avatars.githubusercontent.com/u/306231?v=4" width="110px;"/><br /><sub>Alexandr Hacicheant</sub>](https://github.com/disc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [<img src="https://avatars.githubusercontent.com/u/3032891?v=4" width="110px;"/><br /><sub>Hex</sub>](https://hex128.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [<img src="https://avatars.githubusercontent.com/u/8697942?v=4" width="110px;"/><br /><sub>Arunas Skirius</sub>](https://github.com/arukompas)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [<img src="https://avatars.githubusercontent.com/u/104396?v=4" width="110px;"/><br /><sub>Ben Periton</sub>](https://github.com/benperiton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [<img src="https://avatars.githubusercontent.com/u/11906832?v=4" width="110px;"/><br /><sub>Byron Wolfman</sub>](https://wolfman.dev/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [<img src="https://avatars.githubusercontent.com/u/56485508?v=4" width="110px;"/><br /><sub>Calvin</sub>](https://github.com/CalvinSchwartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") |
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
+1 -1
View File
@@ -1,6 +1,6 @@
![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System
@@ -0,0 +1,32 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class FixUpAssignedTypeWithoutAssignedTo extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:assigned-type-fixup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fixes up assets that have an assigned_type but no assigned_to';
/**
* Execute the console command.
*/
public function handle()
{
DB::table('assets')->whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]);
$this->info("Assets with an assigned_type but no assigned_to are fixed");
}
}
+5 -5
View File
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use Schema;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
@@ -66,8 +66,8 @@ class PaveIt extends Command
foreach ($custom_fields as $custom_field) {
$this->info('DROP the '.$custom_field->db_column.' column from assets as well.');
if (\Schema::hasColumn('assets', $custom_field->db_column)) {
\Schema::table('assets', function ($table) use ($custom_field) {
if (Schema::hasColumn('assets', $custom_field->db_column)) {
Schema::table('assets', function ($table) use ($custom_field) {
$table->dropColumn($custom_field->db_column);
});
}
@@ -84,8 +84,8 @@ class PaveIt extends Command
}
// Leave in the demo oauth keys so we don't have to reset them every day in the demos
\DB::statement('delete from oauth_clients WHERE id > 2');
\DB::statement('delete from oauth_access_tokens WHERE id > 2');
DB::statement('delete from oauth_clients WHERE id > 2');
DB::statement('delete from oauth_access_tokens WHERE user_id > 2');
}
}
@@ -99,8 +99,11 @@ class SendAcceptanceReminder extends Command
foreach ($no_email_list as $user) {
$rows[] = [$user['id'], $user['name']];
}
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
if (!empty($rows)) {
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
}
return 0;
}
+1 -1
View File
@@ -28,7 +28,7 @@ class CheckoutableCheckedIn
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
$this->action_date = $action_date ?? date('Y-m-d');
$this->action_date = $action_date ?? date('Y-m-d H:i:s');
$this->originalValues = $originalValues;
}
}
+16 -3
View File
@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Log;
use Throwable;
use JsonException;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
class Handler extends ExceptionHandler
{
@@ -107,18 +108,25 @@ class Handler extends ExceptionHandler
$statusCode = $e->getStatusCode();
// API throttle requests are handled in the RouteServiceProvider configureRateLimiting() method, so we don't need to handle them here
switch ($e->getStatusCode()) {
case '404':
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404);
case '429':
return response()->json(Helper::formatStandardApiResponse('error', null, 'Too many requests'), 429);
case '405':
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
default:
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode);
}
}
// This handles API validation exceptions that happen at the Form Request level, so they
// never even get to the controller where we normally nicely format JSON responses
if ($e instanceof ValidationException) {
$response = $this->invalidJson($request, $e);
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
}
}
@@ -143,6 +151,10 @@ class Handler extends ExceptionHandler
$route = 'maintenances.index';
} elseif ($route === 'licenseseats.index') {
$route = 'licenses.index';
} elseif ($route === 'customfields.index') {
$route = 'fields.index';
} elseif ($route === 'customfieldsets.index') {
$route = 'fields.index';
}
return redirect()
@@ -201,6 +213,7 @@ class Handler extends ExceptionHandler
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
+8 -9
View File
@@ -722,8 +722,8 @@ class Helper
// The check and message that the user is still using the deprecated version
$deprecations = [
'ms_teams_deprecated' => array(
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
];
// if item of concern is being used and its being used with the deprecated values return the notification array.
@@ -1313,25 +1313,24 @@ class Helper
switch ($item) {
case 'asset':
return 'fas fa-barcode';
break;
case 'accessory':
return 'fas fa-keyboard';
break;
case 'component':
return 'fas fa-hdd';
break;
case 'consumable':
return 'fas fa-tint';
break;
case 'license':
return 'far fa-save';
break;
case 'location':
return 'fas fa-map-marker-alt';
break;
case 'user':
return 'fas fa-user';
break;
case 'supplier':
return 'fa-solid fa-store';
case 'manufacturer':
return 'fa-solid fa-building';
case 'category':
return 'fa-solid fa-table-columns';
}
}
+26 -10
View File
@@ -16,14 +16,14 @@ class StorageHelper
$disk = config('filesystems.default');
}
switch (config("filesystems.disks.$disk.driver")) {
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
default:
return Storage::disk($disk)->download($filename);
default:
return Storage::disk($disk)->download($filename);
}
}
@@ -33,11 +33,12 @@ class StorageHelper
* to determine that they are safe to display inline.
*
* @author <A. Gianotto> [<snipe@snipe.net]>
* @since v7.0.14
* @param $file_with_path
* @since v7.0.14
* @param $file_with_path
* @return bool
*/
public static function allowSafeInline($file_with_path) {
public static function allowSafeInline($file_with_path)
{
$allowed_inline = [
'pdf',
@@ -48,6 +49,7 @@ class StorageHelper
'avif',
'webp',
'png',
'gif',
];
@@ -59,10 +61,24 @@ class StorageHelper
}
public static function getFiletype($file_with_path)
{
// The file exists and is allowed to be displayed inline
if (Storage::exists($file_with_path)) {
return pathinfo($file_with_path, PATHINFO_EXTENSION);
}
return null;
}
/**
* Decide whether to show the file inline or download it.
*/
public static function showOrDownloadFile($file, $filename) {
public static function showOrDownloadFile($file, $filename)
{
$headers = [];
@@ -220,7 +220,10 @@ class AccessoriesController extends Controller
*/
public function show(Accessory $accessory) : View | RedirectResponse
{
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id);
$accessory->loadCount('checkouts as checkouts_count');
$accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
$this->authorize('view', $accessory);
return view('accessories.view', compact('accessory'));
}
@@ -7,6 +7,7 @@ use App\Events\CheckoutDeclined;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Http\Controllers\Controller;
use App\Mail\CheckoutAcceptanceResponseMail;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
@@ -21,8 +22,10 @@ use App\Models\Component;
use App\Models\Consumable;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController;
@@ -337,6 +340,21 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.declined');
}
if ($acceptance->alert_on_response_id) {
try {
$recipient = User::find($acceptance->alert_on_response_id);
if ($recipient) {
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
$acceptance,
$recipient,
$request->input('asset_acceptance') === 'accepted',
));
}
} catch (Exception $e) {
Log::warning($e);
}
}
return redirect()->to('account/accept')->with('success', $return_msg);
@@ -1,200 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use App\Http\Transformers\UploadedFilesTransformer;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Illuminate\Http\Request;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function list(Asset $asset, Request $request) : JsonResponse | array
{
$this->authorize('view', $asset);
$allowed_columns =
[
'id',
'filename',
'eol',
'notes',
'created_at',
'updated_at',
];
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id);
if ($request->filled('search')) {
$files = $files->TextSearch($request->input('search'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $files->count()) ? $files->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$files = $files->orderBy($sort, $order);
$files = $files->skip($offset)->take($limit)->get();
return (new UploadedFilesTransformer())->transformFiles($files, $files->count());
}
/**
* Check for permissions and display the file.
*
* @param int $assetId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assets/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetId
* @param int $fileId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function destroy(Asset $asset, $fileId = null) : JsonResponse
{
$rel_path = 'private_uploads/assets';
// the asset is valid
if (isset($asset->id)) {
$this->authorize('update', $asset);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
}
@@ -75,6 +75,7 @@ class AssetMaintenancesController extends Controller
'serial',
'created_by',
'supplier',
'location',
'is_warranty',
'status_label',
];
@@ -98,6 +99,9 @@ class AssetMaintenancesController extends Controller
case 'serial':
$maintenances = $maintenances->OrderByAssetSerial($order);
break;
case 'location':
$maintenances = $maintenances->OrderLocationName($order);
break;
case 'status_label':
$maintenances = $maintenances->OrderStatusName($order);
break;
@@ -1,184 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\AssetModel;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\AssetModelsTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetModelFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $assetModel);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
$assetModel->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetmodel
* @since [v7.0.12]
* @author [r-xyz]
*/
public function list($assetmodel_id) : JsonResponse | array
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetmodel_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$assetmodel = AssetModel::with('uploads')->find($assetmodel_id);
$this->authorize('view', $assetmodel);
return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count());
}
/**
* Check for permissions and display the file.
*
* @param int $assetModelId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assetmodels/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetModelId
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('update', $assetModel);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
}
@@ -85,6 +85,12 @@ class AssetModelsController extends Controller
$assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number'));
}
if ($request->input('requestable') == 'true') {
$assetmodels = $assetmodels->where('models.requestable', '=', '1');
} elseif ($request->input('requestable') == 'false') {
$assetmodels = $assetmodels->where('models.requestable', '=', '0');
}
if ($request->filled('notes')) {
$assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes'));
}
+22 -39
View File
@@ -35,7 +35,6 @@ use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\AuditRequest;
/**
@@ -115,6 +114,7 @@ class AssetsController extends Controller
'byod',
'asset_eol_date',
'requestable',
'jobtitle',
];
$filter = [];
@@ -396,6 +396,9 @@ class AssetsController extends Controller
case 'assigned_to':
$assets->OrderAssigned($order);
break;
case 'jobtitle':
$assets->OrderByJobTitle($order);
break;
case 'created_by':
$assets->OrderByCreatedByName($order);
break;
@@ -1072,33 +1075,25 @@ class AssetsController extends Controller
* @param int $id
* @since [v4.0]
*/
public function audit(AuditRequest $request): JsonResponse
public function audit(Request $request, Asset $asset): JsonResponse
{
$this->authorize('audit', Asset::class);
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
// Check if it's an array or a string
if (is_array($request->input('asset_tag'))) {
$asset_tag = $request->input('asset_tag');
$multi_audit = true;
} else {
// If it's a string, make it into an array so we can use it in the whereIn query
$asset_tag = [$request->input('asset_tag')];
$multi_audit = false;
// Allow the asset tag to be passed in the payload (legacy method)
if ($request->filled('asset_tag')) {
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
}
$assets = Asset::whereIn('asset_tag', $asset_tag)->get();
foreach ($assets as $asset) {
if ($asset) {
$originalValues = $asset->getRawOriginal();
$asset->next_audit_date = $dt;
$asset->last_audit_date = date('Y-m-d H:i:s');
// Overwrite next_audit_date if it was specified in the request
if ($request->filled('next_audit_date')) {
$asset->next_audit_date = $request->input('next_audit_date');
}
@@ -1109,24 +1104,15 @@ class AssetsController extends Controller
$asset->location_id = $request->input('location_id');
}
$asset->last_audit_date = date('Y-m-d H:i:s');
// Set up the payload for re-display in the API response
if ($multi_audit === true) {
$payload[] = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
} else {
$payload[] = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
}
$payload = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
/**
@@ -1159,12 +1145,12 @@ class AssetsController extends Controller
}
}
// Validate custom fields
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()));
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors()));
}
@@ -1194,14 +1180,11 @@ class AssetsController extends Controller
*/
if ($asset->isValid() && $asset->save()) {
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
}
}
if (count($payload) > 0) {
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
}
// No matching asset for the asset tag that was passed.
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
@@ -56,7 +56,7 @@ class CategoriesController extends Controller
'notes',
])
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
/*
@@ -212,7 +212,7 @@ class CategoriesController extends Controller
public function destroy($id) : JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
if (! $category->isDeletable()) {
return response()->json(
@@ -234,6 +234,15 @@ class ImportController extends Controller
case 'location':
$redirectTo = 'locations.index';
break;
case 'supplier':
$redirectTo = 'suppliers.index';
break;
case 'manufacturer':
$redirectTo = 'manufacturers.index';
break;
case 'category':
$redirectTo = 'categories.index';
break;
}
if ($errors) { //Failure
@@ -87,7 +87,8 @@ class LocationsController extends Controller
->withCount('accessories as accessories_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count');
->withCount('users as users_count')
->with('adminuser');
// Only scope locations if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
@@ -218,6 +219,7 @@ class LocationsController extends Controller
'locations.updated_at',
'locations.image',
'locations.currency',
'locations.company_id',
'locations.notes',
])
->withCount('assignedAssets as assigned_assets_count')
@@ -4,15 +4,19 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ProfileTransformer;
use App\Models\CheckoutRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ProfileController extends Controller
{
@@ -167,6 +171,22 @@ class ProfileController extends Controller
}
/**
* Display the EULAs accepted by the user.
*
* @param \App\Http\Transformers\ActionlogsTransformer $transformer
* @return \Illuminate\Http\JsonResponse
*@since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas(ProfileTransformer $transformer)
{
// Only return this user's EULAs
$eulas = auth()->user()->eulas;
return response()->json(
$transformer->transformFiles($eulas, $eulas->count())
);
}
}
@@ -24,10 +24,15 @@ class SuppliersController extends Controller
public function index(Request $request): array
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['
id',
$allowed_columns = [
'id',
'name',
'address',
'address2',
'city',
'state',
'country',
'zip',
'phone',
'contact',
'fax',
@@ -39,21 +44,24 @@ class SuppliersController extends Controller
'components_count',
'consumables_count',
'url',
'notes',
];
$suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url'])
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip'])
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
->withCount('components as components_count')
->withCount('consumables as consumables_count');
->withCount('consumables as consumables_count')
->with('adminuser');
if ($request->filled('search')) {
$suppliers = $suppliers->TextSearch($request->input('search'));
$suppliers->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$suppliers->where('name', '=', $request->input('name'));
}
@@ -100,7 +108,15 @@ class SuppliersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$suppliers->orderBy($sort, $order);
switch ($request->input('sort')) {
case 'created_by':
$suppliers->OrderByCreatedByName($order);
break;
default:
$suppliers->orderBy($sort, $order);
break;
}
$total = $suppliers->count();
$suppliers = $suppliers->skip($offset)->take($limit)->get();
@@ -0,0 +1,255 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\UploadedFilesTransformer;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/assetmodels/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
/**
* List files for an object
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to list files for
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function index(Request $request, $object_type, $id) : JsonResponse | array
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Columns allowed for sorting
$allowed_columns =
[
'id',
'filename',
'action_type',
'note',
'created_at',
];
$uploads = $object->uploads();
$offset = ($request->input('offset') > $object->count()) ? $object->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'action_logs.created_at';
// Text search on action_logs fields
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searcghing across all relations
// And we generally won't need that here
if ($request->filled('search')) {
$uploads->where(
function ($query) use ($request) {
$query->where('filename', 'LIKE', '%' . $request->input('search') . '%')
->orWhere('note', 'LIKE', '%' . $request->input('search') . '%');
}
);
}
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
}
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to store so we can check permisisons
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $object_type, $id) : JsonResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// If the file storage directory doesn't exist, create it
if (! Storage::exists(self::$map_storage_path[$object_type])) {
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
}
if ($request->hasFile('file')) {
// Loop over the attached files and add them to the object
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files))));
}
// No files were submitted
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.nofiles')));
}
/**
* Check for permissions and display the file.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check that the file being requested exists for the object
if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id)
) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_id')), 200);
}
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.file_not_found'), 200));
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
}
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
}
/**
* Delete the associated file
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.1.17]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function destroy($object_type, $id, $file_id) : JsonResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check for the file
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)->first();
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
}
// Delete the record of the file
if ($log->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
}
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
}
}
+23 -4
View File
@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\SaveUserRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\ActionlogsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\LicensesTransformer;
@@ -80,7 +81,7 @@ class UsersController extends Controller
'users.autoassign_licenses',
'users.website',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations')
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
->withCount([
'assets as assets_count' => function(Builder $query) {
$query->withoutTrashed();
@@ -206,11 +207,11 @@ class UsersController extends Controller
}
if ($request->filled('manages_users_count')) {
$users->has('manages_users_count', '=', $request->input('manages_users_count'));
$users->has('managesUsers', '=', $request->input('manages_users_count'));
}
if ($request->filled('manages_locations_count')) {
$users->has('manages_locations_count', '=', $request->input('manages_locations_count'));
$users->has('managedLocations', '=', $request->input('manages_locations_count'));
}
if ($request->filled('autoassign_licenses')) {
@@ -676,7 +677,6 @@ class UsersController extends Controller
$this->authorize('view', License::class);
if ($user = User::where('id', $id)->withTrashed()->first()) {
$this->authorize('update', $user);
$licenses = $user->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
@@ -736,6 +736,25 @@ class UsersController extends Controller
return (new UsersTransformer)->transformUser($request->user());
}
/**
* Display the EULAs accepted by the user.
*
* @param \App\Models\User $user
* @param \App\Http\Transformers\ActionlogsTransformer $transformer
* @return \Illuminate\Http\JsonResponse
*@since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas(User $user, ActionlogsTransformer $transformer)
{
$this->authorize('view', User::class);
$eulas = $user->eulas;
return response()->json(
$transformer->transformActionlogs($eulas, $eulas->count())
);
}
/**
* Restore a soft-deleted user.
*
@@ -19,19 +19,6 @@ use \Illuminate\Http\RedirectResponse;
*/
class AssetMaintenancesController extends Controller
{
/**
* Checks for permissions for this action.
*
* @todo This should be replaced with middleware and/or policies
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
*/
private static function getInsufficientPermissionsRedirect(): RedirectResponse
{
return redirect()->route('maintenances.index')
->with('error', trans('general.insufficient_permissions'));
}
/**
* Returns a view that invokes the ajax tables which actually contains
@@ -67,16 +54,10 @@ class AssetMaintenancesController extends Controller
// 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
return view('asset_maintenances/edit')
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
->with('asset', $asset)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', new AssetMaintenance);
}
@@ -91,43 +72,45 @@ class AssetMaintenancesController extends Controller
public function store(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
// create a new model instance
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$asset = Asset::find($request->input('asset_id'));
if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) {
return static::getInsufficientPermissionsRedirect();
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
// Loop through the selected assets
foreach ($assets as $asset) {
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
// Save the asset maintenance data
$assetMaintenance->asset_id = $asset->id;
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id();
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
if (!$assetMaintenance->save()) {
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
}
}
// Save the asset maintenance data
$assetMaintenance->asset_id = $request->input('asset_id');
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id();
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.create.success'));
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
// Redirect to the new asset maintenance page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.create.success'));
}
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
}
/**
@@ -135,26 +118,19 @@ class AssetMaintenancesController extends Controller
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
*/
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
// Prepare Improvement Type List
$assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions();
$this->authorize('update', $maintenance->asset);
return view('asset_maintenances/edit')
->with('selectedAsset', null)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', $maintenance);
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
->with('asset_ids', request()->input('asset_ids', []))
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
->with('item', $maintenance);
}
/**
@@ -170,33 +146,21 @@ class AssetMaintenancesController extends Controller
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
$this->authorize('update', $maintenance->asset);
$maintenance->supplier_id = $request->input('supplier_id');
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->is_warranty = $request->input('is_warranty', 0);
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id'));
if (! Company::isCurrentUserHasAccess($asset)) {
return static::getInsufficientPermissionsRedirect();
}
// Save the asset maintenance data
$maintenance->asset_id = $request->input('asset_id');
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->title = $request->input('title');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
if (($maintenance->completion_date == null)
) {
// Todo - put this in a getter/setter?
if (($maintenance->completion_date == null))
{
if (($maintenance->asset_maintenance_time !== 0)
|| (! is_null($maintenance->asset_maintenance_time))
) {
@@ -213,10 +177,7 @@ class AssetMaintenancesController extends Controller
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
if ($maintenance->save()) {
// Redirect to the new asset maintenance page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.edit.success'));
}
@@ -232,21 +193,12 @@ class AssetMaintenancesController extends Controller
* @version v1.0
* @since [v1.8]
*/
public function destroy($assetMaintenanceId) : RedirectResponse
public function destroy(AssetMaintenance $maintenance) : RedirectResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
$this->authorize('update', $maintenance->asset);
// Delete the asset maintenance
$assetMaintenance->delete();
$maintenance->delete();
// Redirect to the asset_maintenance management page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.delete.success'));
@@ -262,11 +214,6 @@ class AssetMaintenancesController extends Controller
*/
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
}
}
@@ -96,7 +96,6 @@ class AssetCheckinController extends Controller
});
$asset->expected_checkin = null;
$asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = $request->get('name');
@@ -123,11 +122,14 @@ class AssetCheckinController extends Controller
$originalValues = $asset->getRawOriginal();
// Handle last checkin date
$checkin_at = date('Y-m-d H:i:s');
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
$checkin_at = $request->get('checkin_at');
}
$asset->last_checkin = $checkin_at;
$asset->licenseseats->each(function (LicenseSeat $seat) {
$seat->update(['assigned_to' => null]);
@@ -149,7 +149,7 @@ class AssetsController extends Controller
$asset->byod = request('byod', 0);
if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
$asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString();
}
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
@@ -188,14 +188,31 @@ class AssetsController extends Controller
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {
if (request('assigned_user')) {
$target = User::find(request('assigned_user'));
$target = null;
$location = null;
if ($userId = request('assigned_user')) {
$target = User::find($userId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
$location = $target->location_id;
} elseif (request('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
$location = $target->location_id;
} elseif (request('assigned_location')) {
$target = Location::find(request('assigned_location'));
} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
}
$location = $target->id;
}
@@ -446,7 +463,7 @@ class AssetsController extends Controller
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues));
DB::table('assets')
->where('id', $asset->id)
->update(['assigned_to' => null]);
->update(['assigned_to' => null, 'assigned_type' => null]);
}
@@ -458,6 +475,7 @@ class AssetsController extends Controller
}
}
$asset->delete();
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success'));
@@ -890,7 +908,7 @@ class AssetsController extends Controller
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
}
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
$dt = Carbon::now()->addMonths( (int) $settings->audit_interval)->toDateString();
return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
@@ -937,8 +955,8 @@ class AssetsController extends Controller
}
}
// Validate custom fields
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
@@ -52,11 +52,26 @@ class BulkAssetsController extends Controller
}
$asset_ids = $request->input('ids');
if ($request->input('bulk_actions') === 'checkout') {
$status_check =$this->hasUndeployableStatus($asset_ids);
if($status_check && $status_check['status'] === true){
$asset_tags = implode(', ', array_column($status_check['tags'], 'asset_tag'));
$asset_ids = $status_check['asset_ids'];
session()->flash('warning', trans('admin/hardware/message.undeployable', ['asset_tags' => $asset_tags]));
}
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('hardware.bulkcheckout.show');
}
if ($request->input('bulk_actions') === 'maintenance') {
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('maintenances.create');
}
// Figure out where we need to send the user after the update is complete, and store that in the session
$bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]);
@@ -214,6 +229,21 @@ class BulkAssetsController extends Controller
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
// find custom field input attributes that start with 'null_'
$null_custom_fields_inputs = array_filter($request->all(), function ($key) {
// filter out all keys that start with 'null_'
return (strpos($key, 'null_') === 0);
}, ARRAY_FILTER_USE_KEY);;
// remove 'null' from the keys
$custom_fields_to_null = [];
foreach ($null_custom_fields_inputs as $key => $value) {
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
}
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
@@ -251,7 +281,9 @@ class BulkAssetsController extends Controller
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
|| ($request->filled('null_asset_eol_date'))
|| ($request->filled('null_notes'))
|| ($request->anyFilled($custom_field_columns))
|| ($request->anyFilled(array_keys($null_custom_fields_inputs)))
) {
// Let's loop through those assets and build an update array
@@ -274,10 +306,14 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date')
->conditionallyAddItem('asset_eol_date');
->conditionallyAddItem('asset_eol_date')
->conditionallyAddItem('notes');
foreach ($custom_field_columns as $key => $custom_field_column) {
$this->conditionallyAddItem($custom_field_column);
}
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
$this->conditionallyAddItem($key);
}
if (!($asset->eol_explicit)) {
if ($request->filled('model_id')) {
@@ -328,6 +364,10 @@ class BulkAssetsController extends Controller
}
}
if ($request->input('null_notes')=='1') {
$this->update_array['notes'] = null;
}
if ($request->filled('purchase_cost')) {
@@ -368,10 +408,12 @@ class BulkAssetsController extends Controller
// This could probably be added to a form request.
// If the asset isn't assigned, we don't care what the status is.
// Otherwise we need to make sure the status type is still a deployable one.
if (
($asset->assigned_to == '')
|| ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1')
) {
$unassigned = $asset->assigned_to == '';
$deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1';
$pending = $updated_status->pending === 1;
if ($unassigned || $deployable || $pending) {
$this->update_array['status_id'] = $updated_status->id;
}
@@ -423,6 +465,7 @@ class BulkAssetsController extends Controller
}
/**
*
* Start all the custom fields shenanigans
*/
@@ -430,6 +473,15 @@ class BulkAssetsController extends Controller
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// null custom fields
if ($custom_fields_to_null) {
foreach ($custom_fields_to_null as $key => $custom_field_to_null) {
if ($field->db_column == $key) {
$this->update_array[$field->db_column] = null;
}
}
}
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
if (Gate::allows('admin')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
@@ -562,7 +614,10 @@ class BulkAssetsController extends Controller
public function showCheckout() : View
{
$this->authorize('checkout', Asset::class);
return view('hardware/bulk-checkout');
$do_not_change = ['' => trans('general.do_not_change')];
$status_label_list = $do_not_change + Helper::deployableStatusLabelList();
return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list);
}
/**
@@ -594,13 +649,13 @@ class BulkAssetsController extends Controller
}
$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'));
$checkout_at = $request->get('checkout_at');
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = e($request->get('expected_checkin'));
$expected_checkin = $request->get('expected_checkin');
}
$errors = [];
@@ -608,6 +663,11 @@ class BulkAssetsController extends Controller
foreach ($assets as $asset) {
$this->authorize('checkout', $asset);
// See if there is a status label passed
if ($request->filled('status_id')) {
$asset->status_id = $request->get('status_id');
}
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
//TODO - I think this logic is duplicated in the checkOut method?
@@ -651,4 +711,25 @@ class BulkAssetsController extends Controller
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
}
}
public function hasUndeployableStatus (array $asset_ids)
{
$undeployable = Asset::whereIn('id', $asset_ids)
->undeployable()
->get();
$undeployableTags = $undeployable->map(function ($asset) {
return [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
];
})->toArray();
$undeployableIds = array_column($undeployableTags, 'id');
$filtered_ids = array_diff($asset_ids, $undeployableIds);
if($undeployable->isNotEmpty()) {
return ['status' => true, 'tags' => $undeployableTags, 'asset_ids' => $filtered_ids];
}
return false;
}
}
@@ -68,6 +68,7 @@ class CategoriesController extends Controller
$category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->notes = $request->input('notes');
$category->created_by = auth()->id();
@@ -121,6 +122,7 @@ class CategoriesController extends Controller
$category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->notes = $request->input('notes');
@@ -145,7 +147,7 @@ class CategoriesController extends Controller
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::findOrFail($categoryId))) {
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
@@ -155,7 +157,6 @@ class CategoriesController extends Controller
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
// Redirect to the locations management page
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}
@@ -86,7 +86,7 @@ class LicenseCheckinController extends Controller
}
if($licenseSeat->assigned_to != null){
$return_to = User::find($licenseSeat->assigned_to);
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
session()->put('checkedInFrom', $return_to->id);
} else {
$return_to = Asset::find($licenseSeat->asset_id);
+31 -1
View File
@@ -3,15 +3,21 @@
namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\ProfileTransformer;
use App\Models\Actionlog;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This controller handles all actions related to User Profiles for
* the Snipe-IT Asset Management application.
@@ -220,7 +226,7 @@ class ProfileController extends Controller
if (!$user = User::find(auth()->id())) {
return redirect()->back()
->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
->with('error', trans('admin/users/message.user_not_found', ['id' => auth()->id()]));
}
if (empty($user->email)) {
return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email'));
@@ -234,4 +240,28 @@ class ProfileController extends Controller
return redirect()->back()->with('success', trans('admin/users/general.user_notified'));
}
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
{
$logentry = Actionlog::where('filename', $filename)->first();
// Make sure the user has permission to view this file
if (auth()->id() != $logentry->target_id) {
return redirect()->route('account')->with('error', trans('general.generic_model_not_found', ['model' => 'file']));
}
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
}
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
}
return redirect()->back()->with('error', trans('general.file_does_not_exist'));
}
}
+1 -1
View File
@@ -184,7 +184,7 @@ class ReportsController extends Controller
$currency = e(Setting::getSettings()->default_currency);
}
$row[] = $asset->purchase_date;
$row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false);
$row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost);
$row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue());
$row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
+3 -1
View File
@@ -352,6 +352,7 @@ class SettingsController extends Controller
$setting->dash_chart_type = $request->input('dash_chart_type');
$setting->profile_edit = $request->input('profile_edit', 0);
$setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0);
$setting->manager_view_enabled = $request->input('manager_view_enabled', 0);
if ($request->input('per_page') != '') {
@@ -650,6 +651,7 @@ class SettingsController extends Controller
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
$setting->admin_cc_always = $request->validated('admin_cc_always');
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
@@ -922,7 +924,7 @@ class SettingsController extends Controller
* @since v5.0.0
*/
public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse
{
{
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
@@ -178,7 +178,7 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param $permissions
* @return \Illuminate\Contracts\View\View
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
* @internal param int $id
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
@@ -190,6 +190,10 @@ class UsersController extends Controller
if ($user) {
if ($user->trashed()) {
return redirect()->route('users.show', $user->id);
}
$permissions = config('permissions');
$groups = Group::pluck('name', 'id');
+108 -32
View File
@@ -27,50 +27,126 @@ use Exception;
class ViewAssetsController extends Controller
{
/**
* Redirect to the profile page.
* Extract custom fields that should be displayed in user view.
*
* @param User $user
* @return array
*/
private function extractCustomFields(User $user): array
{
$fieldArray = [];
foreach ($user->assets as $asset) {
if ($asset->model && $asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_in_user_view == '1') {
$fieldArray[$field->db_column] = $field->name;
}
}
}
}
return array_unique($fieldArray);
}
/**
* Get list of users viewable by the current user.
*
* @param User $authUser
* @return \Illuminate\Support\Collection
*/
private function getViewableUsers(User $authUser): \Illuminate\Support\Collection
{
// SuperAdmin sees all users
if ($authUser->isSuperUser()) {
return User::select('id', 'first_name', 'last_name', 'username')
->where('activated', 1)
->orderBy('last_name')
->orderBy('first_name')
->get();
}
// Regular manager sees only their subordinates + self
$managedUsers = $authUser->getAllSubordinates();
// If user has subordinates, show them with self at beginning
if ($managedUsers->count() > 0) {
return collect([$authUser])->merge($managedUsers)
->sortBy('last_name')
->sortBy('first_name');
}
// User has no subordinates, only sees themselves
return collect([$authUser]);
}
/**
* Get the selected user ID from request or default to current user.
*
* @param Request $request
* @param \Illuminate\Support\Collection $subordinates
* @param int $defaultUserId
* @return int
*/
private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int
{
// If no subordinates or no user_id in request, return default
if ($subordinates->count() <= 1 || !$request->filled('user_id')) {
return $defaultUserId;
}
$requestedUserId = (int) $request->input('user_id');
// Validate if the requested user is allowed
if ($subordinates->contains('id', $requestedUserId)) {
return $requestedUserId;
}
// If invalid ID or not authorized, return default
return $defaultUserId;
}
/**
* Show user's assigned assets with optional manager view functionality.
*
*/
public function getIndex() : View | RedirectResponse
public function getIndex(Request $request) : View | RedirectResponse
{
$user = User::with(
$authUser = auth()->user();
$settings = Setting::getSettings();
$subordinates = collect();
$selectedUserId = $authUser->id;
// Process manager view if enabled
if ($settings->manager_view_enabled) {
$subordinates = $this->getViewableUsers($authUser);
$selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id);
}
// Load the data for the user to be viewed (either auth user or selected subordinate)
$userToView = User::with([
'assets',
'assets.model',
'assets.model.fieldset.fields',
'consumables',
'accessories',
'licenses',
)->find(auth()->id());
$field_array = array();
// Loop through all the custom fields that are applied to any model the user has assigned
foreach ($user->assets as $asset) {
// Make sure the model has a custom fieldset before trying to loop through the associated fields
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// check and make sure they're allowed to see the value of the custom field
if ($field->display_in_user_view == '1') {
$field_array[$field->db_column] = $field->name;
}
}
}
'licenses'
])->find($selectedUserId);
// If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error
if (!$userToView) {
return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found'));
}
// Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns
array_unique($field_array);
// Process custom fields for the user being viewed
$fieldArray = $this->extractCustomFields($userToView);
if (isset($user->id)) {
return view('account/view-assets', compact('user', 'field_array' ))
->with('settings', Setting::getSettings());
}
// Redirect to the user management page
return redirect()->route('users.index')
->with('error', trans('admin/users/message.user_not_found', $user->id));
// Pass the necessary data to the view
return view('account/view-assets', [
'user' => $userToView, // Use 'user' for compatibility with the existing view
'field_array' => $fieldArray,
'settings' => $settings,
'subordinates' => $subordinates,
'selectedUserId' => $selectedUserId
]);
}
/**
+1
View File
@@ -73,6 +73,7 @@ class Kernel extends HttpKernel
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'api-throttle' => \App\Http\Middleware\SetAPIResponseHeaders::class,
'health' => null,
];
}
@@ -0,0 +1,82 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Symfony\Component\HttpFoundation\Response;
class SetAPIResponseHeaders extends ThrottleRequests
{
/**
* Add the rate limit headers to the response.
*
* This extends the original ThrottleRequests middleware to add the 'X-RateLimit-Reset' and 'Retry-After' headers, even
* if the rate limit is not exceeded.
* @param $maxAttempts
* @param $remainingAttempts
* @param $retryAfter
* @param Response|null $response
* @return array|int[]
*/
protected function getHeaders($maxAttempts, $remainingAttempts, $retryAfter = null, ?Response $response = null)
{
if ($response &&
! is_null($response->headers->get('X-RateLimit-Remaining')) &&
(int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) {
$headers = [];
$headers['Retry-After'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed
return $headers;
}
$headers = [
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $remainingAttempts,
];
if (! is_null($retryAfter)) {
$headers['Retry-After'] = $retryAfter;
$headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed
$headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed
}
return $headers;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
protected function handleRequest($request, Closure $next, array $limits)
{
foreach ($limits as $limit) {
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
}
$this->limiter->hit($limit->key, $limit->decaySeconds);
}
$response = $next($request);
foreach ($limits as $limit) {
$response = $this->addHeaders(
$response,
$limit->maxAttempts,
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts),
$this->getTimeUntilNextRetry($limit->key) // this is the only line we changed
);
}
return $response;
}
}
-30
View File
@@ -1,30 +0,0 @@
<?php
namespace App\Http\Requests;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class AuditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('audit', new Asset);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'asset_tag' => 'required|exists:assets,asset_tag',
];
}
}
+22 -11
View File
@@ -41,6 +41,7 @@ class SettingsSamlRequest extends FormRequest
public function withValidator($validator)
{
$validator->after(function ($validator) {
$setting = Setting::getSettings();
if ($this->input('saml_enabled') == '1') {
$idpMetadata = $this->input('saml_idp_metadata');
if (! empty($idpMetadata)) {
@@ -56,7 +57,7 @@ class SettingsSamlRequest extends FormRequest
}
}
$was_custom_x509cert = strpos(Setting::getSettings()->saml_custom_settings, 'sp_x509cert') !== false;
$was_custom_x509cert = strpos($setting->saml_custom_settings, 'sp_x509cert') !== false;
$custom_x509cert = '';
$custom_privateKey = '';
@@ -126,10 +127,14 @@ class SettingsSamlRequest extends FormRequest
}
if (! (empty($x509cert) && empty($privateKey))) {
$this->merge([
'saml_sp_x509cert' => $x509cert,
'saml_sp_privatekey' => $privateKey,
]);
// $this->merge([
// 'saml_sp_x509cert' => $x509cert,
// 'saml_sp_privatekey' => $privateKey,
// ]);
$setting->saml_sp_x509cert = $x509cert;
$setting->saml_sp_privatekey = $privateKey;
$setting->save();
}
} else {
$validator->errors()->add('saml_integration', 'openssl.cnf is missing/invalid');
@@ -145,15 +150,21 @@ class SettingsSamlRequest extends FormRequest
}
if (! empty($x509certNew)) {
$this->merge([
'saml_sp_x509certNew' => $x509certNew,
]);
// $this->merge([
// 'saml_sp_x509certNew' => $x509certNew,
// ]);
$setting->saml_sp_x509certNew = $x509certNew;
$setting->save();
}
} else {
$this->merge([
'saml_sp_x509certNew' => '',
]);
// $this->merge([
// 'saml_sp_x509certNew' => '',
// ]);
$setting->saml_sp_x509certNew = '';
$setting->save();
}
});
}
}
+2 -2
View File
@@ -37,8 +37,8 @@ class StoreLabelSettings extends FormRequest
return [
'labels_per_page' => 'numeric',
'labels_width' => 'numeric',
'labels_height' => 'numeric',
'labels_width' => 'numeric|min:0.1',
'labels_height' => 'numeric|min:0.1',
'labels_pmargin_left' => 'numeric|nullable',
'labels_pmargin_right' => 'numeric|nullable',
'labels_pmargin_top' => 'numeric|nullable',
@@ -5,6 +5,7 @@ namespace App\Http\Requests;
use App\Models\Accessory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class StoreNotificationSettings extends FormRequest
{
@@ -26,6 +27,9 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
'admin_cc_always' => [
Rule::in('0', '1'),
],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
+43 -21
View File
@@ -6,6 +6,7 @@ use App\Http\Traits\ConvertsBase64ToFiles;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \App\Helpers\Helper;
class UploadFileRequest extends Request
{
@@ -27,44 +28,65 @@ class UploadFileRequest extends Request
*/
public function rules()
{
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
$max_file_size = Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
];
}
/**
* Sanitizes (if needed) and Saves a file to the appropriate location
* Returns the 'short' (storage-relative) filename
*
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
* a way to merge them or extend one into the other?
*/
public function handleFile(string $dirname, string $name_prefix, $file): string
{
$extension = $file->getClientOriginalExtension();
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
// Check for SVG and sanitize it
if ($file->getMimeType() === 'image/svg+xml') {
Log::debug('This is an SVG');
Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put($dirname.$file_name, $cleanSVG);
} catch (\Exception $e) {
Log::debug('Upload no workie :( ');
Log::debug($e);
}
$uploaded_file = $this->handleSVG($file);
} else {
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
$uploaded_file = file_get_contents($file);
}
try {
Storage::put($dirname.$file_name, $uploaded_file);
} catch (\Exception $e) {
Log::debug($e);
}
return $file_name;
}
}
public function handleSVG($file)
{
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
return $sanitizer->sanitize($dirtySVG);
}
/**
* Get the validation error messages that apply to the request, but
* replace the attribute name with the name of the file that was attempted and failed
* to make it clearer to the user which file is the bad one.
*
* @return array
*/
public function attributes(): array
{
$attributes = [];
if ($this->file) {
for ($i = 0; $i < count($this->file); $i++) {
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
}
}
return $attributes;
}
}
@@ -59,7 +59,10 @@ class AssetMaintenancesTransformer
'name'=> e($assetmaintenance->asset->defaultLoc->name),
] : null,
'notes' => ($assetmaintenance->notes) ? Helper::parseEscapedMarkedownInline($assetmaintenance->notes) : null,
'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null,
'supplier' => ($assetmaintenance->supplier) ? [
'id' => $assetmaintenance->supplier->id,
'name'=> e($assetmaintenance->supplier->name)
] : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'),
+2 -1
View File
@@ -80,6 +80,7 @@ class AssetsTransformer
'qr' => ($setting->qr_code=='1') ? config('app.url').'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png' : null,
'alt_barcode' => ($setting->alt_barcode_enabled=='1') ? config('app.url').'/uploads/barcodes/'.str_slug($setting->alt_barcode).'-'.str_slug($asset->asset_tag).'.png' : null,
'assigned_to' => $this->transformAssignedTo($asset),
'jobtitle' => $asset->assigned ? e($asset->assigned->jobtitle) : null,
'warranty_months' => ($asset->warranty_months > 0) ? e($asset->warranty_months.' '.trans('admin/hardware/form.months')) : null,
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
'created_by' => ($asset->adminuser) ? [
@@ -101,7 +102,7 @@ class AssetsTransformer
'checkout_counter' => (int) $asset->checkout_counter,
'requests_counter' => (int) $asset->requests_counter,
'user_can_checkout' => (bool) $asset->availableForCheckout(),
'book_value' => Helper::formatCurrencyOutput($asset->getLinearDepreciatedValue()),
'book_value' => Helper::formatCurrencyOutput($asset->getDepreciatedValue()),
];
@@ -4,6 +4,10 @@ namespace App\Http\Transformers;
class DatatablesTransformer
{
/**
* Transform data for bootstrap tables and API responses for lists of things
**/
public function transformDatatables($objects, $total = null)
{
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
@@ -11,4 +15,15 @@ class DatatablesTransformer
return $objects_array;
}
}
/**
* Transform data for returning the status of items within a bulk action
**/
public function transformBulkResponseWithStatusAndObjects($objects, $total)
{
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
$objects_array['rows'] = $objects;
return $objects_array;
}
}
@@ -57,6 +57,10 @@ class LocationsTransformer
'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null,
'notes' => Helper::parseEscapedMarkedownInline($location->notes),
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
'created_by' => $location->adminuser ? [
'id' => (int) $location->adminuser->id,
'name'=> e($location->adminuser->present()->fullName),
]: null,
'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'),
'parent' => ($location->parent) ? [
'id' => (int) $location->parent->id,
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Database\Eloquent\Collection;
class ProfileTransformer
{
public function transformFiles(Collection $files, $total)
{
$array = [];
foreach ($files as $file) {
$array[] = self::transformFile($file);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformFile(Actionlog $file)
{
$array = [
'id' => (int) $file->id,
'icon' => Helper::filetype_icon($file->filename),
'item' => ($file->item) ? [
'name' => ($file->itemType()=='user') ? e($file->item->getFullNameAttribute()) : e($file->item->getDisplayNameAttribute()),
'type' => e($file->itemType()),
] : null,
'filename' => e($file->filename),
'signature_file' => ($file->accept_signature) ? route('profile.signature.view', ['filename' => $file->accept_signature ]) : null,
'note' => e($file->note),
'url' => route('profile.storedeula.download', ['filename' => $file->filename]),
'file' => route('profile.storedeula.download', ['filename' => $file->filename]),
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
];
return $array;
}
}
@@ -45,6 +45,10 @@ class SuppliersTransformer
'components_count' => (int) $supplier->components_count,
'notes' => ($supplier->notes) ? Helper::parseEscapedMarkedownInline($supplier->notes) : null,
'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'),
'created_by' => $supplier->adminuser ? [
'id' => (int) $supplier->adminuser->id,
'name'=> e($supplier->adminuser->present()->fullName),
]: null,
'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'),
];
@@ -3,10 +3,10 @@
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
class UploadedFilesTransformer
@@ -26,23 +26,26 @@ class UploadedFilesTransformer
{
$snipeModel = $file->item_type;
// This will be used later as we extend out this transformer to handle more types of uploads
if ($file->item_type == Asset::class) {
$file_url = route('show/assetfile', [$file->item_id, $file->id]);
}
$array = [
'id' => (int) $file->id,
'icon' => Helper::filetype_icon($file->filename),
'name' => e($file->filename),
'item' => ($file->item_type) ? [
'id' => (int) $file->item_id,
'type' => strtolower(class_basename($file->item_type)),
] : null,
'filename' => e($file->filename),
'url' => $file_url,
'filetype' => StorageHelper::getFiletype($file->uploads_file_path()),
'url' => $file->uploads_file_url(),
'note' => ($file->note) ? e($file->note) : null,
'created_by' => ($file->adminuser) ? [
'id' => (int) $file->adminuser->id,
'name'=> e($file->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
'inline' => StorageHelper::allowSafeInline($file->uploads_file_path()),
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
];
$permissions_array['available_actions'] = [
@@ -53,4 +56,5 @@ class UploadedFilesTransformer
return $array;
}
}
}
@@ -50,6 +50,10 @@ class UsersTransformer
'id' => (int) $user->department->id,
'name'=> e($user->department->name),
] : null,
'department_manager' => ($user->department?->manager) ? [
'id' => (int) $user->department->manager->id,
'name'=> e($user->department->manager->full_name),
] : null,
'location' => ($user->userloc) ? [
'id' => (int) $user->userloc->id,
'name'=> e($user->userloc->name),
+10 -1
View File
@@ -80,7 +80,16 @@ class AssetImporter extends ItemImporter
$asset_tag = Asset::autoincrement_asset();
}
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($this->findCsvMatch($row, 'id')!='') {
// Override asset if an ID was given
\Log::debug('Finding asset by ID: '.$this->findCsvMatch($row, 'id'));
$asset = Asset::find($this->findCsvMatch($row, 'id'));
} else {
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
}
if ($asset) {
if (! $this->updating) {
$exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]);
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Importer;
use App\Models\Category;
use Illuminate\Support\Facades\Log;
/**
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
* Importer\Importer.php. [ALG]
*
* Class CategoryImporter
*/
class CategoryImporter extends ItemImporter
{
protected $categories;
public function __construct($filename)
{
parent::__construct($filename);
}
protected function handle($row)
{
parent::handle($row);
$this->createCategoryIfNotExists($row);
}
/**
* Create a category if a duplicate does not exist.
* @todo Investigate how this should interact with Importer::createCategoryIfNotExists
*
* @author A. Gianotto
* @since 6.1.0
* @param array $row
*/
public function createCategoryIfNotExists(array $row)
{
$editingCategory = false;
$category = Category::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
if ($this->findCsvMatch($row, 'id')!='') {
// Override category if an ID was given
\Log::debug('Finding category by ID: '.$this->findCsvMatch($row, 'id'));
$category = Category::find($this->findCsvMatch($row, 'id'));
}
if ($category) {
if (! $this->updating) {
$this->log('A matching Category '.$this->item['name'].' already exists');
return;
}
$this->log('Updating Category');
$editingCategory = true;
} else {
$this->log('No Matching Category, Create a new one');
$category = new Category;
$category->created_by = auth()->id();
}
// Pull the records from the CSV to determine their values
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
$this->item['eula_text'] = trim($this->findCsvMatch($row, 'eula_text'));
$this->item['category_type'] = trim(strtolower($this->findCsvMatch($row, 'category_type')));
$this->item['use_default_eula'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'use_default_eula'))) == 1) ? 1 : 0;
$this->item['require_acceptance'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_acceptance'))) == 1) ? 1 : 0;
$this->item['checkin_email'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'checkin_email'))) == 1) ? 1 : 0;
Log::debug('Item array is: ');
Log::debug(print_r($this->item, true));
if ($editingCategory) {
Log::debug('Updating existing category');
$category->update($this->sanitizeItemForUpdating($category));
} else {
Log::debug('Creating category');
$category->fill($this->sanitizeItemForStoring($category));
}
if ($category->save()) {
$this->log('Category '.$category->name.' created or updated from CSV import');
return $category;
} else {
Log::debug($category->getErrors());
$this->logError($category, 'Category "'.$this->item['name'].'"');
return $category->errors;
}
}
}
+1
View File
@@ -88,6 +88,7 @@ abstract class Importer
'department' => 'department',
'manager_name' => 'manager full name',
'manager_username' => 'manager username',
'manager_employee_num' => 'manager employee number',
'min_amt' => 'minimum quantity',
'remote' => 'remote',
'vip' => 'vip',
+16 -5
View File
@@ -110,7 +110,7 @@ class ItemImporter extends Importer
protected function determineCheckout($row)
{
// Locations don't get checked out to anyone/anything
if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class)) {
if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class) || (get_class($this) == SupplierImporter::class) || (get_class($this) == ManufacturerImporter::class) || (get_class($this) == CategoryImporter::class)) {
return;
}
@@ -353,16 +353,27 @@ class ItemImporter extends Importer
* @param $user_manager string
* @return int id of company created/found
*/
public function fetchManager($user_manager_first_name, $user_manager_last_name)
public function fetchManager($user_manager_username = null, $user_manager_employee_num = null, $user_manager_first_name = null, $user_manager_last_name = null)
{
$manager = User::where('first_name', '=', $user_manager_first_name)
->where('last_name', '=', $user_manager_last_name)->first();
if ($user_manager_username!='') {
$manager = User::where('username', '=', $user_manager_username)->first();
$this->log('Checking on username '.$user_manager_username);
} elseif ($user_manager_employee_num!='') {
$manager = User::where('employee_num', '=', $user_manager_employee_num)->first();
$this->log('Checking on employee_num '.$user_manager_employee_num);
} else {
$manager = User::where('first_name', '=', $user_manager_first_name)
->where('last_name', '=', $user_manager_last_name)->first();
$this->log('Checking on full name');
}
if ($manager) {
$this->log('A matching Manager '.$user_manager_first_name.' '.$user_manager_last_name.' already exists');
return $manager->id;
}
$this->log('No matching Manager '.$user_manager_first_name.' '.$user_manager_last_name.' found. If their user account is being created through this import, you should re-process this file again. ');
$this->log('No matching Manager found. If their user account is being created through this import, you should re-process this file again. ');
return null;
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Importer;
use App\Models\Manufacturer;
use Illuminate\Support\Facades\Log;
/**
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
* Importer\Importer.php. [ALG]
*
* Class ManufacturerImporter
*/
class ManufacturerImporter extends ItemImporter
{
protected $manufacturers;
public function __construct($filename)
{
parent::__construct($filename);
}
protected function handle($row)
{
parent::handle($row);
$this->createManufacturerIfNotExists($row);
}
/**
* Create a supplier if a duplicate does not exist.
* @todo Investigate how this should interact with Importer::createManufacturerIfNotExists
*
* @author A. Gianotto
* @since 6.1.0
* @param array $row
*/
public function createManufacturerIfNotExists(array $row)
{
$editingManufacturer = false;
$supplier = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
if ($this->findCsvMatch($row, 'id')!='') {
// Override supplier if an ID was given
\Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id'));
$supplier = Manufacturer::find($this->findCsvMatch($row, 'id'));
}
if ($supplier) {
if (! $this->updating) {
$this->log('A matching Manufacturer '.$this->item['name'].' already exists');
return;
}
$this->log('Updating Manufacturer');
$editingManufacturer = true;
} else {
$this->log('No Matching Manufacturer, Create a new one');
$supplier = new Manufacturer;
$supplier->created_by = auth()->id();
}
// Pull the records from the CSV to determine their values
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['support_phone'] = trim($this->findCsvMatch($row, 'support_phone'));
$this->item['fax'] = trim($this->findCsvMatch($row, 'fax'));
$this->item['support_email'] = trim($this->findCsvMatch($row, 'support_email'));
$this->item['contact'] = trim($this->findCsvMatch($row, 'contact'));
$this->item['url'] = trim($this->findCsvMatch($row, 'url'));
$this->item['support_url'] = trim($this->findCsvMatch($row, 'support_url'));
$this->item['warranty_lookup_url'] = trim($this->findCsvMatch($row, 'warranty_lookup_url'));
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
Log::debug('Item array is: ');
Log::debug(print_r($this->item, true));
if ($editingManufacturer) {
Log::debug('Updating existing supplier');
$supplier->update($this->sanitizeItemForUpdating($supplier));
} else {
Log::debug('Creating supplier');
$supplier->fill($this->sanitizeItemForStoring($supplier));
}
if ($supplier->save()) {
$this->log('Manufacturer '.$supplier->name.' created or updated from CSV import');
return $supplier;
} else {
Log::debug($supplier->getErrors());
$this->logError($supplier, 'Manufacturer "'.$this->item['name'].'"');
return $supplier->errors;
}
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace App\Importer;
use App\Models\Supplier;
use Illuminate\Support\Facades\Log;
/**
* When we are importing users via an Asset/etc import, we use createOrFetchUser() in
* Importer\Importer.php. [ALG]
*
* Class SupplierImporter
*/
class SupplierImporter extends ItemImporter
{
protected $suppliers;
public function __construct($filename)
{
parent::__construct($filename);
}
protected function handle($row)
{
parent::handle($row);
$this->createSupplierIfNotExists($row);
}
/**
* Create a supplier if a duplicate does not exist.
* @todo Investigate how this should interact with Importer::createSupplierIfNotExists
*
* @author A. Gianotto
* @since 6.1.0
* @param array $row
*/
public function createSupplierIfNotExists(array $row)
{
$editingSupplier = false;
$supplier = Supplier::where('name', '=', $this->findCsvMatch($row, 'name'))->first();
if ($this->findCsvMatch($row, 'id')!='') {
// Override supplier if an ID was given
\Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id'));
$supplier = Supplier::find($this->findCsvMatch($row, 'id'));
}
if ($supplier) {
if (! $this->updating) {
$this->log('A matching Supplier '.$this->item['name'].' already exists');
return;
}
$this->log('Updating Supplier');
$editingSupplier = true;
} else {
$this->log('No Matching Supplier, Create a new one');
$supplier = new Supplier;
$supplier->created_by = auth()->id();
}
// Pull the records from the CSV to determine their values
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
$this->item['address2'] = trim($this->findCsvMatch($row, 'address2'));
$this->item['city'] = trim($this->findCsvMatch($row, 'city'));
$this->item['state'] = trim($this->findCsvMatch($row, 'state'));
$this->item['country'] = trim($this->findCsvMatch($row, 'country'));
$this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
$this->item['phone'] = trim($this->findCsvMatch($row, 'phone'));
$this->item['fax'] = trim($this->findCsvMatch($row, 'fax'));
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
$this->item['contact'] = trim($this->findCsvMatch($row, 'contact'));
$this->item['url'] = trim($this->findCsvMatch($row, 'url'));
$this->item['notes'] = trim($this->findCsvMatch($row, 'notes'));
Log::debug('Item array is: ');
Log::debug(print_r($this->item, true));
if ($editingSupplier) {
Log::debug('Updating existing supplier');
$supplier->update($this->sanitizeItemForUpdating($supplier));
} else {
Log::debug('Creating supplier');
$supplier->fill($this->sanitizeItemForStoring($supplier));
}
if ($supplier->save()) {
$this->log('Supplier '.$supplier->name.' created or updated from CSV import');
return $supplier;
} else {
Log::debug($supplier->getErrors());
$this->logError($supplier, 'Supplier "'.$this->item['name'].'"');
return $supplier->errors;
}
}
}
+1 -1
View File
@@ -62,7 +62,7 @@ class UserImporter extends ItemImporter
$this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0;
$this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num'));
$this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department'))));
$this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name')));
$this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_username')), trim($this->findCsvMatch($row, 'manager_employee_num')), trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name')));
$this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0;
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
+220 -168
View File
@@ -12,6 +12,7 @@ use App\Mail\CheckoutConsumableMail;
use App\Mail\CheckoutLicenseMail;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\Category;
use App\Models\CheckoutAcceptance;
use App\Models\Component;
use App\Models\Consumable;
@@ -27,6 +28,7 @@ use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use Exception;
@@ -40,6 +42,24 @@ class CheckoutableListener
Component::class,
];
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
\App\Events\CheckoutableCheckedIn::class,
'App\Listeners\CheckoutableListener@onCheckedIn'
);
$events->listen(
\App\Events\CheckoutableCheckedOut::class,
'App\Listeners\CheckoutableListener@onCheckedOut'
);
}
/**
* Notify the user and post to webhook about the checked out checkoutable
* and add a record to the checkout_requests table.
@@ -50,93 +70,70 @@ class CheckoutableListener
return;
}
/**
* Make a checkout acceptance and attach it in the notification
*/
$settings = Setting::getSettings();
$acceptance = $this->getCheckoutAcceptance($event);
$adminCcEmailsArray = [];
if ($settings->admin_cc_email !== '') {
$adminCcEmail = $settings->admin_cc_email;
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance);
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUsers($event);
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUser($event);
// Send email notifications
try {
/**
* Send an email if any of the following conditions are met:
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
* 4. If the admin CC email is set, even if the item being checked out doesn't have an email address (location, etc)
*/
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() ||
$this->checkoutableShouldSendEmail($event)) {
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
// Send a checkout email to the admin CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
Log::info('Checkout Mail sent to CC addresses');
}
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
if (!empty($to)) {
try {
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
Log::info('Checkout Mail sent to checkout target');
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
// Send notification
try {
if ($this->shouldSendWebhookNotification()) {
if ($shouldSendWebhookNotification) {
try {
if ($this->newMicrosoftTeamsWebhookEnabled()) {
$message = $this->getCheckoutNotification($event)->toMicrosoftTeams();
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
$notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams
} else {
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event, $acceptance));
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found'));
} else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') );
}
else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail') );
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
}
/**
* Notify the user and post to webhook about the checked in checkoutable
*/
*/
public function onCheckedIn($event)
{
Log::debug('onCheckedIn in the Checkoutable listener fired');
@@ -145,61 +142,54 @@ class CheckoutableListener
return;
}
/**
* Send the appropriate notification
*/
if ($event->checkedOutTo && $event->checkoutable){
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
$shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable);
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
foreach($acceptances as $acceptance){
if($acceptance->isPending()){
$acceptance->delete();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
/**
* Send the appropriate notification
*/
if ($event->checkedOutTo && $event->checkoutable) {
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
foreach ($acceptances as $acceptance) {
if ($acceptance->isPending()) {
$acceptance->delete();
}
}
}
}
$settings = Setting::getSettings();
$adminCcEmailsArray = [];
if($settings->admin_cc_email !== '') {
$adminCcEmail = $settings->admin_cc_email;
$adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail));
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckinMailType($event);
$notifiable = $this->getNotifiableUsers($event);
$mailable = $this->getCheckinMailType($event);
$notifiable = $this->getNotifiableUser($event);
// Send email notifications
try {
/**
* Send an email if any of the following conditions are met:
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
* 4. If the admin CC email is set, even if the item being checked in doesn't have an email address (location, etc)
*/
$notifiableHasEmail = $notifiable instanceof User && $notifiable->email;
// Send a checkout email to the admin's CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
Log::info('Checkin Mail sent to CC addresses');
$shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail;
[$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable);
try {
if (!empty($to)) {
Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable);
Log::info('Checkin Mail sent to CC addresses');
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
}
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
Log::info('Checkin Mail sent to checkout target');
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
}
// Send Webhook notification
try {
if ($this->shouldSendWebhookNotification()) {
if ($shouldSendWebhookNotification) {
// Send Webhook notification
try {
if ($this->newMicrosoftTeamsWebhookEnabled()) {
$message = $this->getCheckinNotification($event)->toMicrosoftTeams();
$notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint);
@@ -208,25 +198,24 @@ class CheckoutableListener
Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckinNotification($event));
}
}
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected." notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_channel_not_found') );
}
else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
} catch (ClientException $e) {
if (strpos($e->getMessage(), 'channel_not_found') !== false) {
Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found'));
} else {
Log::error("ClientException caught during checkin notification: " . $e->getMessage());
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
]);
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail'));
}
}
}
/**
* Generates a checkout acceptance
@@ -239,6 +228,7 @@ class CheckoutableListener
if ($checkedOutToType != "App\Models\User") {
return null;
}
if (!$event->checkoutable->requireAcceptance()) {
return null;
}
@@ -246,15 +236,22 @@ class CheckoutableListener
$acceptance = new CheckoutAcceptance;
$acceptance->checkoutable()->associate($event->checkoutable);
$acceptance->assignedTo()->associate($event->checkedOutTo);
$category = $this->getCategoryFromCheckoutable($event->checkoutable);
if ($category?->alert_on_response) {
$acceptance->alert_on_response_id = auth()->id();
}
$acceptance->save();
return $acceptance;
return $acceptance;
}
/**
* Get the appropriate notification for the event
*
* @param CheckoutableCheckedIn $event
*
* @param CheckoutableCheckedIn $event
* @return Notification
*/
private function getCheckinNotification($event)
@@ -268,7 +265,7 @@ class CheckoutableListener
break;
case Asset::class:
$notificationClass = CheckinAssetNotification::class;
break;
break;
case LicenseSeat::class:
$notificationClass = CheckinLicenseSeatNotification::class;
break;
@@ -276,9 +273,8 @@ class CheckoutableListener
Log::debug('Notification class: '.$notificationClass);
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
}
/**
* Get the appropriate notification for the event
*
@@ -320,6 +316,7 @@ class CheckoutableListener
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
}
private function getCheckinMailType($event){
$lookup = [
Accessory::class => CheckinAccessoryMail::class,
@@ -341,7 +338,8 @@ class CheckoutableListener
* @param $event
* @return mixed
*/
private function getNotifiableUsers($event){
private function getNotifiableUser($event)
{
// If it's assigned to an asset, get that asset's assignedTo object
if ($event->checkedOutTo instanceof Asset){
@@ -357,6 +355,7 @@ class CheckoutableListener
return $event->checkedOutTo;
}
}
private function webhookSelected(){
if(Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general'){
return 'slack';
@@ -365,60 +364,113 @@ class CheckoutableListener
return Setting::getSettings()->webhook_selected;
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
\App\Events\CheckoutableCheckedIn::class,
'App\Listeners\CheckoutableListener@onCheckedIn'
);
$events->listen(
\App\Events\CheckoutableCheckedOut::class,
'App\Listeners\CheckoutableListener@onCheckedOut'
);
}
private function shouldNotSendAnyNotifications($checkoutable): bool
{
if(in_array(get_class($checkoutable), $this->skipNotificationsFor)) {
return true;
}
//runs a check if the category wants to send checkin/checkout emails to users
$category = match (true) {
$checkoutable instanceof Asset => $checkoutable->model->category,
$checkoutable instanceof Accessory,
$checkoutable instanceof Consumable => $checkoutable->category,
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
default => null,
};
if (!$category?->checkin_email) {
return true;
}
return false;
return in_array(get_class($checkoutable), $this->skipNotificationsFor);
}
private function shouldSendWebhookNotification(): bool
{
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
}
private function checkoutableShouldSendEmail($event): bool
private function checkoutableCategoryShouldSendEmail(Model $checkoutable): bool
{
if($event->checkoutable instanceof LicenseSeat){
return $event->checkoutable->license->checkin_email();
if ($checkoutable instanceof LicenseSeat) {
return $checkoutable->license->checkin_email();
}
return (method_exists($event->checkoutable, 'checkin_email') && $event->checkoutable->checkin_email());
return (method_exists($checkoutable, 'checkin_email') && $checkoutable->checkin_email());
}
private function newMicrosoftTeamsWebhookEnabled(): bool
{
return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows');
}
private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool
{
/**
* Send an email if any of the following conditions are met:
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
*/
if ($checkoutable->requireAcceptance()) {
return true;
}
if ($checkoutable->getEula()) {
return true;
}
if ($this->checkoutableCategoryShouldSendEmail($checkoutable)) {
return true;
}
return false;
}
private function shouldSendEmailToAlertAddress($acceptance = null): bool
{
$setting = Setting::getSettings();
if (!$setting) {
return false;
}
if (is_null($acceptance) && !$setting->admin_cc_always) {
return false;
}
return (bool) $setting->admin_cc_email;
}
private function getFormattedAlertAddresses(): array
{
$alertAddresses = Setting::getSettings()->admin_cc_email;
if ($alertAddresses !== '') {
return array_filter(array_map('trim', explode(',', $alertAddresses)));
}
return [];
}
private function generateEmailRecipients(
bool $shouldSendEmailToUser,
bool $shouldSendEmailToAlertAddress,
mixed $notifiable
): array {
$to = [];
$cc = [];
// if user && cc: to user, cc admin
if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
$to[] = $notifiable;
$cc[] = $this->getFormattedAlertAddresses();
}
// if user && no cc: to user
if ($shouldSendEmailToUser && !$shouldSendEmailToAlertAddress) {
$to[] = $notifiable;
}
// if no user && cc: to admin
if (!$shouldSendEmailToUser && $shouldSendEmailToAlertAddress) {
$to[] = $this->getFormattedAlertAddresses();
}
return array($to, $cc);
}
private function getCategoryFromCheckoutable(Model $checkoutable): ?Category
{
return match (true) {
$checkoutable instanceof Asset => $checkoutable->model->category,
$checkoutable instanceof Accessory,
$checkoutable instanceof Consumable => $checkoutable->category,
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
};
}
}
+2
View File
@@ -6,6 +6,8 @@ use Livewire\Component;
class CategoryEditForm extends Component
{
public bool $alertOnResponse;
public $defaultEulaText;
public $eulaText;
+63 -8
View File
@@ -35,10 +35,14 @@ class Importer extends Component
public $accessories_fields;
public $assets_fields;
public $users_fields;
public $assetmodels_fields;
public $suppliers_fields;
public $licenses_fields;
public $locations_fields;
public $consumables_fields;
public $components_fields;
public $manufacturers_fields;
public $categories_fields;
public $aliases_fields;
protected $rules = [
@@ -85,9 +89,6 @@ class Importer extends Component
case 'component':
$results = $this->components_fields;
break;
case 'consumable':
$results = $this->consumables_fields;
break;
case 'license':
$results = $this->licenses_fields;
break;
@@ -97,8 +98,14 @@ class Importer extends Component
case 'location':
$results = $this->locations_fields;
break;
case 'user':
$results = $this->users_fields;
case 'supplier':
$results = $this->suppliers_fields;
break;
case 'manufacturer':
$results = $this->manufacturers_fields;
break;
case 'category':
$results = $this->categories_fields;
break;
default:
$results = [];
@@ -128,7 +135,7 @@ class Importer extends Component
//yes, this key *is* valid. Continue on to the next field.
continue;
} else {
//no, this key is *INVALID* for this import type. Better set it to null
//no, this key is *INVALID* for this import type. Better set it to null,
// and we'll hope that the $aliases_fields or something else picks it up.
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
} // TODO - strictly speaking, this isn't necessary here I don't think.
@@ -149,7 +156,7 @@ class Importer extends Component
// in "Accessories"!)
if (array_key_exists($key, $this->columnOptions[$type])) {
$this->field_map[$i] = $key;
continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header
continue 3; // bust out of both of these loops and the surrounding one - e.g. move on to the next header
}
}
}
@@ -171,6 +178,9 @@ class Importer extends Component
'license' => trans('general.licenses'),
'location' => trans('general.locations'),
'user' => trans('general.users'),
'supplier' => trans('general.suppliers'),
'manufacturer' => trans('general.manufacturers'),
'category' => trans('general.categories'),
];
/**
@@ -193,6 +203,7 @@ class Importer extends Component
];
$this->assets_fields = [
'id' => trans('general.id'),
'asset_eol_date' => trans('admin/hardware/form.eol_date'),
'asset_model' => trans('general.model_name'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
@@ -319,6 +330,8 @@ class Importer extends Component
'location' => trans('general.location'),
'manager_first_name' => trans('general.importer.manager_first_name'),
'manager_last_name' => trans('general.importer.manager_last_name'),
'manager_employee_num' => trans('general.importer.manager_employee_num'),
'manager_username' => trans('general.importer.manager_username'),
'notes' => trans('general.notes'),
'phone_number' => trans('admin/users/table.phone'),
'remote' => trans('admin/users/general.remote'),
@@ -332,6 +345,7 @@ class Importer extends Component
$this->locations_fields = [
'id' => trans('general.id'),
'name' => trans('general.name'),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
'city' => trans('general.city'),
@@ -340,13 +354,52 @@ class Importer extends Component
'ldap_ou' => trans('admin/locations/table.ldap_ou'),
'manager' => trans('general.importer.manager_full_name'),
'manager_username' => trans('general.importer.manager_username'),
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'notes' => trans('general.notes'),
'parent_location' => trans('admin/locations/table.parent'),
'state' => trans('general.state'),
'zip' => trans('general.zip'),
];
$this->suppliers_fields = [
'id' => trans('general.id'),
'name' => trans('general.name'),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
'city' => trans('general.city'),
'notes' => trans('general.notes'),
'state' => trans('general.state'),
'zip' => trans('general.zip'),
'phone' => trans('general.phone'),
'fax' => trans('general.fax'),
'url' => trans('general.url'),
'contact' => trans('general.contact'),
'email' => trans('general.email'),
];
$this->manufacturers_fields = [
'id' => trans('general.id'),
'name' => trans('general.name'),
'notes' => trans('general.notes'),
'support_phone' => trans('admin/manufacturers/table.support_phone'),
'support_url' => trans('admin/manufacturers/table.support_url'),
'support_email' => trans('admin/manufacturers/table.support_email'),
'warranty_lookup_url' => trans('admin/manufacturers/table.warranty_lookup_url'),
'url' => trans('general.url'),
];
$this->categories_fields = [
'id' => trans('general.id'),
'name' => trans('general.name'),
'notes' => trans('general.notes'),
'category_type' => trans('admin/categories/general.import_category_type'),
'eula_text' => trans('admin/categories/general.import_eula_text'),
'use_default_eula' => trans('admin/categories/general.use_default_eula_column'),
'require_acceptance' => trans('admin/categories/general.import_require_acceptance'),
'checkin_email' => trans('admin/categories/general.import_checkin_email'),
];
$this->assetmodels_fields = [
'category' => trans('general.category'),
'eol' => trans('general.eol'),
@@ -371,6 +424,8 @@ class Importer extends Component
'consumable name',
'component name',
'name',
'supplier name',
'location name',
],
'item_no' => [
'item number',
+2 -1
View File
@@ -10,15 +10,16 @@ class LocationScopeCheck extends Component
{
public $mismatched = [];
public $setting;
public $is_tested = false;
public function check_locations()
{
$this->mismatched = Helper::test_locations_fmcs(false);
$this->is_tested = true;
}
public function mount() {
$this->setting = Setting::getSettings();
$this->mismatched = Helper::test_locations_fmcs(false);
}
public function render()
+7 -7
View File
@@ -71,12 +71,12 @@ class SlackSettingsForm extends Component
$this->setting = Setting::getSettings();
$this->save_button = trans('general.save');
$this->webhook_selected = $this->setting->webhook_selected;
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"];
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"];
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"];
$this->webhook_selected = ($this->setting->webhook_selected !== '') ? $this->setting->webhook_selected : 'slack';
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"];
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"];
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"];
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"] ?? $this->webhook_text['slack']["link"];
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"] ?? $this->webhook_text['slack']["test"];
$this->webhook_endpoint = $this->setting->webhook_endpoint;
$this->webhook_channel = $this->setting->webhook_channel;
$this->webhook_botname = $this->setting->webhook_botname;
@@ -90,7 +90,7 @@ class SlackSettingsForm extends Component
$this->isDisabled= '';
}
if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) {
session()->flash('warning', 'The selected Microsoft Teams webhook URL will be deprecated Jan 31st, 2025. Please use a workflow URL. Microsofts Documentation on creating a workflow can be found <a href="https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498" target="_blank"> here.</a>');
session()->flash('warning', trans('admin/settings/message.webhook.ms_teams_deprecation'));
}
}
public function updated($field) {
@@ -0,0 +1,79 @@
<?php
namespace App\Mail;
use App\Models\CheckoutAcceptance;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CheckoutAcceptanceResponseMail extends Mailable
{
use Queueable, SerializesModels;
public CheckoutAcceptance $acceptance;
public User $recipient;
public bool $wasAccepted;
/**
* Create a new message instance.
*/
public function __construct(CheckoutAcceptance $acceptance, User $recipient, bool $wasAccepted)
{
$this->acceptance = $acceptance;
$this->recipient = $recipient;
$this->wasAccepted = $wasAccepted;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$subject = $this->wasAccepted
? trans('mail.initiated_accepted')
: trans('mail.initiated_declined');
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.markdown.checkout-acceptance-response',
with: [
'assignedTo' => $this->acceptance->assignedTo,
'introduction' => $this->introduction(),
'item' => $this->acceptance->checkoutable,
'note' => $this->acceptance->note,
'recipient' => $this->recipient,
]
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
private function introduction(): string
{
return $this->wasAccepted
? trans('mail.following_accepted')
: trans('mail.following_declined');
}
}
+3 -19
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\Acceptable;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -22,6 +23,7 @@ class Accessory extends SnipeModel
protected $presenter = \App\Presenters\AccessoryPresenter::class;
use CompanyableTrait;
use HasUploads;
use Loggable, Presentable;
use SoftDeletes;
@@ -102,24 +104,6 @@ class Accessory extends SnipeModel
];
/**
* Establishes the accessories -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
* Establishes the accessory -> supplier relationship
*
@@ -308,7 +292,7 @@ class Accessory extends SnipeModel
*/
public function checkin_email()
{
return $this->category->checkin_email;
return $this->category?->checkin_email;
}
/**
+73 -12
View File
@@ -69,8 +69,8 @@ class Actionlog extends SnipeModel
*/
protected $searchableRelations = [
'company' => ['name'],
'adminuser' => ['first_name','last_name','username', 'email'],
'user' => ['first_name','last_name','username', 'email'],
'adminuser' => ['first_name','last_name','username', 'email', 'employee_num'],
'user' => ['first_name','last_name','username', 'email', 'employee_num'],
'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'],
'assets.model' => ['name', 'model_number', 'eol', 'notes'],
'assets.model.category' => ['name', 'notes'],
@@ -113,6 +113,11 @@ class Actionlog extends SnipeModel
} elseif (auth()->user() && auth()->user()->company) {
$actionlog->company_id = auth()->user()->company_id;
}
if ($actionlog->action_date == '') {
$actionlog->action_date = Carbon::now();
}
});
}
@@ -245,8 +250,8 @@ class Actionlog extends SnipeModel
public function uploads()
{
return $this->morphTo('item')
->where('action_type', '=', 'uploaded')
->withTrashed();
->where('action_type', '=', 'uploaded')
->withTrashed();
}
/**
@@ -271,7 +276,7 @@ class Actionlog extends SnipeModel
public function adminuser()
{
return $this->belongsTo(User::class, 'created_by')
->withTrashed();
->withTrashed();
}
/**
@@ -376,7 +381,7 @@ class Actionlog extends SnipeModel
if ($this->created_at > $override_default_next) {
$next_audit_days = '-'.$next_audit_days;
}
return $next_audit_days;
}
@@ -408,10 +413,10 @@ class Actionlog extends SnipeModel
public function getListingOfActionLogsChronologicalOrder()
{
return $this->all()
->where('action_type', '!=', 'uploaded')
->orderBy('item_id', 'asc')
->orderBy('created_at', 'asc')
->get();
->where('action_type', '!=', 'uploaded')
->orderBy('item_id', 'asc')
->orderBy('created_at', 'asc')
->get();
}
/**
@@ -434,7 +439,7 @@ class Actionlog extends SnipeModel
return 'api';
}
// This is probably NOT an API call
// This is probably NOT an API call
if (request()->filled('_token')) {
return 'gui';
}
@@ -444,6 +449,62 @@ class Actionlog extends SnipeModel
}
public function uploads_file_url()
{
switch ($this->item_type) {
case Accessory::class:
return route('show.accessoryfile', [$this->item_id, $this->id]);
case Asset::class:
return route('show/assetfile', [$this->item_id, $this->id]);
case AssetModel::class:
return route('show/modelfile', [$this->item_id, $this->id]);
case Consumable::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case Component::class:
return route('show.componentfile', [$this->item_id, $this->id]);
case License::class:
return route('show.licensefile', [$this->item_id, $this->id]);
case Location::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case User::class:
return route('show/userfile', [$this->item_id, $this->id]);
default:
return null;
}
}
public function uploads_file_path()
{
switch ($this->item_type) {
case Accessory::class:
return 'private_uploads/accessories/'.$this->filename;
case Asset::class:
return 'private_uploads/assets/'.$this->filename;
case AssetModel::class:
return 'private_uploads/assetmodels/'.$this->filename;
case Consumable::class:
return 'private_uploads/consumables/'.$this->filename;
case Component::class:
return 'private_uploads/components/'.$this->filename;
case License::class:
return 'private_uploads/licenses/'.$this->filename;
case Location::class:
return 'private_uploads/locations/'.$this->filename;
case User::class:
return 'private_uploads/users/'.$this->filename;
default:
return null;
}
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{
@@ -454,4 +515,4 @@ class Actionlog extends SnipeModel
{
return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
}
}
+29 -26
View File
@@ -7,19 +7,17 @@ use App\Exceptions\CheckoutNotAllowed;
use App\Helpers\Helper;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use App\Presenters\AssetPresenter;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
/**
* Model for Assets.
@@ -33,6 +31,7 @@ class Asset extends Depreciable
protected $with = ['model', 'adminuser'];
use CompanyableTrait;
use HasUploads;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
public const LOCATION = 'location';
@@ -122,9 +121,9 @@ class Asset extends Depreciable
'assigned_to' => ['nullable', 'integer', 'required_with:assigned_type'],
'assigned_type' => ['nullable', 'required_with:assigned_to', 'in:'.User::class.",".Location::class.",".Asset::class],
'requestable' => ['nullable', 'boolean'],
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'],
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
'assigned_user' => ['integer', 'nullable', 'exists:users,id,deleted_at,NULL'],
'assigned_location' => ['integer', 'nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'],
'assigned_asset' => ['integer', 'nullable', 'exists:assets,id,deleted_at,NULL']
];
@@ -472,26 +471,10 @@ class Asset extends Depreciable
}
/**
* Get uploads for this asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany('\App\Models\Actionlog', 'item_id')
->where('item_type', '=', Asset::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
* Determines whether the asset is checked out to a user
*
* Even though we allow allow for checkout to things beyond users
* Even though we allow for checkout to things beyond users
* this method is an easy way of seeing if we are checked out to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
@@ -656,6 +639,8 @@ class Asset extends Depreciable
return Storage::disk('public')->url(app('assets_upload_path').e($this->image));
} elseif ($this->model && ! empty($this->model->image)) {
return Storage::disk('public')->url(app('models_upload_path').e($this->model->image));
} elseif ($this->model?->category && ! empty($this->model->category->image)) {
return Storage::disk('public')->url(app('categories_upload_path').e($this->model->category->image));
}
return false;
@@ -804,6 +789,7 @@ class Asset extends Depreciable
->whereNotNull('warranty_months')
->whereNotNull('purchase_date')
->whereNull('deleted_at')
->NotArchived()
->whereRaw('DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL '
. $days
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()')
@@ -960,6 +946,7 @@ class Asset extends Depreciable
return $this->model->category->require_acceptance;
}
return false;
}
@@ -1126,6 +1113,7 @@ class Asset extends Depreciable
$query = $query
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.jobtitle', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%')
->orWhereMultipleColumns([
@@ -1300,7 +1288,7 @@ class Asset extends Depreciable
public function scopeDueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
$interval = (int) $settings->audit_warning_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
@@ -1368,7 +1356,7 @@ class Asset extends Depreciable
public function scopeDueForCheckin($query, $settings)
{
$interval = $settings->due_checkin_days ?? 0;
$interval = (int) $settings->due_checkin_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval)->format('Y-m-d');
@@ -1582,11 +1570,13 @@ class Asset extends Depreciable
})->orWhere(function ($query) use ($search) {
$query->where('assets_users.first_name', 'LIKE', '%'.$search.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$search.'%')
->orWhere('assets_users.jobtitle', 'LIKE', '%'.$search.'%')
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',
'assets_users.jobtitle',
], $search)
->orWhere('assets_users.username', 'LIKE', '%'.$search.'%')
->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%')
->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%');
})->orWhere('assets.name', 'LIKE', '%'.$search.'%')
@@ -1969,6 +1959,19 @@ class Asset extends Depreciable
return $query->leftJoin('suppliers as suppliers_assets', 'assets.supplier_id', '=', 'suppliers_assets.id')->orderBy('suppliers_assets.name', $order);
}
/**
* Query builder scope to order on supplier name
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderByJobTitle($query, $order)
{
return $query->leftJoin('users as users_sort', 'assets.assigned_to', '=', 'users_sort.id')->select('assets.*')->orderBy('users_sort.jobtitle', $order);
}
/**
* Query builder scope to search on location ID
*
+18 -2
View File
@@ -26,12 +26,12 @@ class AssetMaintenance extends Model implements ICompanyableChild
protected $table = 'asset_maintenances';
protected $rules = [
'asset_id' => 'required|integer',
'supplier_id' => 'required|integer',
'supplier_id' => 'nullable|integer',
'asset_maintenance_type' => 'required',
'title' => 'required|max:100',
'is_warranty' => 'boolean',
'start_date' => 'required|date_format:Y-m-d',
'completion_date' => 'date_format:Y-m-d|nullable',
'completion_date' => 'date_format:Y-m-d|nullable|after_or_equal:start_date',
'notes' => 'string|nullable',
'cost' => 'numeric|nullable',
];
@@ -166,6 +166,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
return $this->belongsTo(\App\Models\Asset::class, 'asset_id')
->withTrashed();
}
/**
* Get the admin who created the maintenance
@@ -265,6 +266,21 @@ class AssetMaintenance extends Model implements ICompanyableChild
->orderBy('maintained_asset_status.name', $order);
}
/**
* Query builder scope to order on status label name
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderLocationName($query, $order)
{
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
->leftjoin('locations as maintained_asset_location', 'maintained_asset_location.id', '=', 'maintained_asset.location_id')
->orderBy('maintained_asset_location.name', $order);
}
/**
* Query builder scope to order on the user that created it
*/
+2 -15
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -24,6 +25,7 @@ class AssetModel extends SnipeModel
use SoftDeletes;
use Loggable, Requestable, Presentable;
use TwoColumnUniqueUndeletedTrait;
use HasUploads;
/**
* Whether the model should inject its identifier to the unique
@@ -209,21 +211,6 @@ class AssetModel extends SnipeModel
&& ($this->deleted_at == '');
}
/**
* Get uploads for this model
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany('\App\Models\Actionlog', 'item_id')
->where('item_type', '=', AssetModel::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
* Get user who created the item
+10
View File
@@ -32,6 +32,7 @@ class Category extends SnipeModel
protected $hidden = ['created_by', 'deleted_at'];
protected $casts = [
'alert_on_response' => 'boolean',
'created_by' => 'integer',
];
@@ -69,6 +70,7 @@ class Category extends SnipeModel
'eula_text',
'name',
'require_acceptance',
'alert_on_response',
'use_default_eula',
'created_by',
'notes',
@@ -100,6 +102,14 @@ class Category extends SnipeModel
public function isDeletable()
{
// We have to check for models as well if the category type is asset
if ($this->category_type == 'asset') {
return Gate::allows('delete', $this)
&& ($this->itemCount() == 0)
&& ($this->models_count == 0)
&& ($this->deleted_at == '');
}
return Gate::allows('delete', $this)
&& ($this->itemCount() == 0)
&& ($this->deleted_at == '');
+1
View File
@@ -15,6 +15,7 @@ class CheckoutAcceptance extends Model
protected $casts = [
'accepted_at' => 'datetime',
'declined_at' => 'datetime',
'alert_on_response_id' => 'integer',
];
/**
+1 -1
View File
@@ -160,7 +160,7 @@ final class Company extends SnipeModel
if (auth()->user()) {
Log::warning('Companyable is '.$companyable);
// Log::warning('Companyable is '.$companyable);
$current_user_company_id = auth()->user()->company_id;
$companyable_company_id = $companyable->company_id;
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || auth()->user()->isSuperUser();
+2 -15
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -20,6 +21,7 @@ class Component extends SnipeModel
protected $presenter = \App\Presenters\ComponentPresenter::class;
use CompanyableTrait;
use HasUploads;
use Loggable, Presentable;
use SoftDeletes;
protected $casts = [
@@ -113,21 +115,6 @@ class Component extends SnipeModel
&& ($this->deleted_at == '');
}
/**
* Establishes the components -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
+3 -16
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\Acceptable;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -29,6 +30,7 @@ class Consumable extends SnipeModel
use Loggable, Presentable;
use SoftDeletes;
use Acceptable;
use HasUploads;
protected $table = 'consumables';
protected $casts = [
@@ -111,21 +113,6 @@ class Consumable extends SnipeModel
];
/**
* Establishes the components -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
@@ -284,7 +271,7 @@ class Consumable extends SnipeModel
*/
public function checkin_email()
{
return $this->category->checkin_email;
return $this->category?->checkin_email;
}
/**
+23 -4
View File
@@ -43,8 +43,8 @@ class DefaultLabel extends RectangleSheet
$this->textSize = Helper::convertUnit($settings->labels_fontsize, 'pt', 'in');
$this->labelWidth = $settings->labels_width;
$this->labelHeight = $settings->labels_height;
$this->labelWidth = $this->setLabelWidth($settings);
$this->labelHeight = $this->setLabelHeight($settings);
$this->labelSpacingH = $settings->labels_display_sgutter;
$this->labelSpacingV = $settings->labels_display_bgutter;
@@ -181,6 +181,25 @@ class DefaultLabel extends RectangleSheet
}
}
}
private function setLabelWidth(Setting $settings)
{
$labelWidth = $settings->labels_width;
?>
if ($labelWidth == 0) {
$labelWidth = 0.1;
}
return $labelWidth;
}
private function setLabelHeight(?Setting $settings)
{
$labelHeight = $settings->labels_height;
if ($labelHeight == 0) {
$labelHeight = 0.1;
}
return $labelHeight;
}
}
+2 -2
View File
@@ -55,7 +55,7 @@ class L7162_B extends L7162
$pdf, $record->get('logo'),
$pa->x1, $pa->y1,
self::LOGO_MAX_WIDTH, $usableHeight,
'L', 'T', 300, true, false, 0.1
'L', 'T', 300, true, false, 0
);
$currentX += $logoSize[0] + self::LOGO_MARGIN;
$usableWidth -= $logoSize[0] + self::LOGO_MARGIN;
@@ -100,4 +100,4 @@ class L7162_B extends L7162
}
?>
?>
@@ -0,0 +1,104 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
abstract class Continuous_53mm extends GenericTape
{
protected const TAPE_WIDTH = 53.0;
// Minimum height for the label
protected float $minHeight = 30.0;
private float $tapeHeight;
/**
* Constructor for 53mm tape
*
* Assumes tape is continuous, set to false and specify
* $spacing in concrete classfor die-cut labels
*
*
* @param float $height Height of the label in mm (default 60mm)
* @param bool $continuous Whether the tape is continuous or pre-cut
* @param float $spacing Spacing between labels for non-continuous tapes (in mm)
*/
public function __construct($height = 60.0, $continuous = true, $spacing = 0.0) {
parent::__construct(self::TAPE_WIDTH, $height, $continuous, $spacing);
$this->tapeHeight = $height;
}
public function getBarcodeRatio() {
return 0.9; // Barcode should use 90% of available width
}
/**
* Calculate the required height for the content
*
* @param $record The record to calculate height for
* @return float The calculated height in mm
*/
protected function calculateRequiredHeight($record) {
$height = $this->marginTop + $this->marginBottom;
// Add title height if present
if ($record->has('title') && $this->getSupportTitle()) {
$height += $this->titleSize + $this->titleMargin;
}
// Add barcode height if present
if (($record->has('barcode2d') && $this->getSupport2DBarcode()) ||
($record->has('barcode') && $this->getSupport1DBarcode())) {
$pa = $this->getPrintableArea();
$usableWidth = $pa->w;
$barcodeSize = $usableWidth * $this->getBarcodeRatio();
$height += $barcodeSize + $this->barcodeMargin;
}
// Add fields height if present
if ($record->has('fields') && $this->getSupportFields() > 0) {
foreach ($record->get('fields') as $field) {
$height += $this->labelSize + $this->labelMargin;
$height += $this->fieldSize + $this->fieldMargin;
}
}
// Add a small buffer to ensure everything fits
$height += 2.0;
// Ensure minimum height
return max($this->minHeight, $height);
}
/**
* Override the writeAll method to support dynamic page sizes for continuous tapes
*/
public function writeAll($pdf, $data) {
// Use auto-sizing for continuous tapes, fixed height for die-cut tapes
if ($this->continuous) {
$data->each(function ($record, $index) use ($pdf) {
// Calculate the required height for this record
$requiredHeight = $this->calculateRequiredHeight($record);
// Temporarily update the height property
$originalHeight = $this->height;
$this->height = $requiredHeight;
// Add a new page with the calculated dimensions
$pdf->AddPage(
$this->getOrientation(),
[$this->getWidth(), $requiredHeight],
false, // Don't reset page number
false // Don't reset object ID
);
// Write the content
$this->write($pdf, $record);
// Restore the original height
$this->height = $originalHeight;
});
} else {
// Use the default implementation for non-continuous (die-cut) tapes
parent::writeAll($pdf, $data);
}
}
}
@@ -0,0 +1,73 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
class Continuous_53mm_A extends Continuous_53mm
{
public function getUnit() { return 'mm'; }
public function getSupportAssetTag() { return false; }
public function getSupport1DBarcode() { return true; }
public function getSupport2DBarcode() { return true; }
public function getSupportFields() { return 5; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return true; }
public function preparePDF($pdf) {
$pdf->SetAutoPageBreak(false);
}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', $this->titleSize, 'C',
$pa->w, $this->titleSize, true, 0
);
$currentY += $this->titleSize + $this->titleMargin;
$usableHeight -= $this->titleSize + $this->titleMargin;
}
// Make the barcode as large as possible while still leaving room for fields
$barcodeSize = min($usableHeight * 0.8, $usableWidth * $this->getBarcodeRatio());
if ($record->has('barcode2d')) {
$barcodeX = $pa->x1 + ($usableWidth - $barcodeSize) / 2;
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$barcodeX, $currentY,
$barcodeSize, $barcodeSize
);
$currentY += $barcodeSize + $this->barcodeMargin;
}
if ($record->has('fields')) {
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', $this->labelSize, 'L',
$usableWidth, $this->labelSize, true, 0
);
$currentY += $this->labelSize + $this->labelMargin;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', $this->fieldSize, 'L',
$usableWidth, $this->fieldSize, true, 0, 0.01
);
$currentY += $this->fieldSize + $this->fieldMargin;
}
}
}
}
@@ -0,0 +1,170 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
use App\Helpers\Helper;
abstract class Continuous_Landscape_0_59in extends GenericTape
{
// abstract class for printers using 0.59in width paper in landscape orientation
// Using a larger TAPE_WIDTH value to increase font sizes
protected const TAPE_WIDTH = 0.59;
private float $tapeHeight;
protected float $minHeight = 1;
/**
* @param float $length Length of the label in inches (default 2.36in which is 60mm)
* @param bool $continuous Whether the tape is continuous or pre-cut
* @param float $spacing Spacing between labels for non-continuous tapes (in inches)
*/
public function __construct($length = 0.6, $continuous = true, $spacing = 0.0) {
// Swap width and height for landscape orientation
// The height becomes the width, and the length becomes the height
parent::__construct($length, self::TAPE_WIDTH, $continuous, $spacing);
$this->tapeHeight = $length;
$this->marginTop = 0.1;
$this->marginBottom = 0.1;
// Keep small horizontal margins
$this->marginLeft = self::TAPE_WIDTH * 0.2;
// $this->marginRight = self::TAPE_WIDTH * 0.1;
// Override font sizes to make them larger
// Calculate a larger base font size (3x the default)
$baseFontSize = self::TAPE_WIDTH * 0.16; // 3x the default 0.07
// Recalculate all element sizing based on the larger base font size
$this->titleSize = $baseFontSize; // Same as base font size
$this->titleMargin = $baseFontSize * 0.3; // 30% of base font size
$this->fieldSize = $baseFontSize * 1.1; // 110% of base font size
$this->fieldMargin = $baseFontSize * 0.1; // 10% of base font size
$this->labelSize = $baseFontSize * 0.7; // 70% of base font size
$this->labelMargin = $baseFontSize * -0.1; // -10% of base font size
$this->barcodeMargin = $baseFontSize * 0.9; // 20% of base font size
$this->tagSize = $baseFontSize * 0.8; // 80% of base font size
}
public function getBarcodeRatio() {
return 1.0; // Barcode should use 100% of available height
}
/**
* Calculate the required length for the content
*
* @param $record The record to calculate length for
* @return float The calculated length in inches
*/
protected function calculateRequiredLength($record) {
// Calculate length needed for barcode and fields side by side
$requiredLength = 0;
// Add barcode length if present
if (($record->has('barcode2d') && $this->getSupport2DBarcode()) ||
($record->has('barcode') && $this->getSupport1DBarcode())) {
// Use full tape width for barcode size
$barcodeSize = self::TAPE_WIDTH;
$requiredLength += $barcodeSize + $this->barcodeMargin * 0.3; // Minimal margin
}
// Add fields length if present - calculate based on actual content
if ($record->has('fields') && $this->getSupportFields() > 0) {
$fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields());
// Base width for field area
$fieldsWidth = self::TAPE_WIDTH;
// Calculate additional width based on text length
foreach ($fields as $field) {
// Get label and value text
$labelText = $field['label'] ?? '';
$valueText = $field['value'] ?? '';
// Calculate approximate width needed based on text length
// Increase character width to ensure enough space (0.15 inches per character)
$labelWidth = strlen($labelText) * 0.09;
$valueWidth = strlen($valueText) * 0.09;
// Use the longer of the two
$textWidth = max($labelWidth, $valueWidth);
// Ensure minimum width and add to total
$fieldsWidth = max($fieldsWidth, $textWidth);
}
// Add the calculated width for fields
$requiredLength += $fieldsWidth;
// Add minimal extra space for field padding
// Reduce padding to eliminate extraneous space on right edge
// $requiredLength += self::TAPE_WIDTH * 0.1;
}
// Ensure minimum length
return max($this->minHeight, $requiredLength);
}
/**
* Calculate text width accurately using the PDF object
*
* @param $pdf The PDF object
* @param string $text The text to measure
* @param string $font The font to use
* @param string $style The font style
* @param float $size The font size
* @return float The calculated width
*/
protected function calculateTextWidth($pdf, $text, $font, $style, $size) {
$originalFont = $pdf->getFontFamily();
$originalStyle = $pdf->getFontStyle();
$originalSize = $pdf->getFontSizePt();
$pdf->SetFont($font, $style, Helper::convertUnit($size, $this->getUnit(), 'pt', true));
$width = $pdf->GetStringWidth($text);
// Restore original font settings
$pdf->SetFont($originalFont, $originalStyle, $originalSize);
return $width;
}
/**
* Override the writeAll method to support dynamic page sizes for continuous tapes
*/
public function writeAll($pdf, $data) {
// Use auto-sizing for continuous tapes, fixed height for die-cut tapes
if ($this->continuous) {
$data->each(function ($record, $index) use ($pdf) {
// Calculate the required length by calling write with calculateOnly=true
$requiredLength = $this->write($pdf, $record);
// If write didn't return a length (old implementation), fall back to calculateRequiredLength
if ($requiredLength === null) {
$requiredLength = $this->calculateRequiredLength($record);
}
// Temporarily update the height property
$originalHeight = $this->height;
$this->height = self::TAPE_WIDTH; // Keep height fixed at tape width
// Add a new page with the calculated dimensions
// Keep height fixed at TAPE_WIDTH, use calculated length for width
$pdf->AddPage(
$this->getOrientation(),
[$requiredLength, self::TAPE_WIDTH],
false, // Don't reset page number
false // Don't reset object ID
);
// Write the content
$this->write($pdf, $record);
// Restore the original height
$this->height = $originalHeight;
});
} else {
// Use the default implementation for non-continuous (die-cut) tapes
parent::writeAll($pdf, $data);
}
}
}
@@ -0,0 +1,157 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
class Continuous_Landscape_0_59in_A extends Continuous_Landscape_0_59in
{
public function getUnit() { return 'in'; }
public function getSupportAssetTag() { return false; }
public function getSupport1DBarcode() { return true; }
public function getSupport2DBarcode() { return true; }
public function getSupportFields() { return 2; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return false; }
public function preparePDF($pdf) {
$pdf->SetAutoPageBreak(false);
}
public function write($pdf, $record, $calculateOnly = false) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
// Calculate required length based on content
$requiredLength = 0;
// Use full usable height for barcode
$barcodeSize = $usableHeight;
// Add barcode width to required length
if ($record->has('barcode2d') && $this->getSupport2DBarcode()) {
$requiredLength += $barcodeSize;
// Add gap between barcode and fields
$requiredLength += $this->barcodeMargin;
}
// Calculate fields width using accurate text measurement
if ($record->has('fields') && $this->getSupportFields() > 0) {
$fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields());
$fieldsWidth = 0;
foreach ($fields as $field) {
$labelText = $field['label'] ?? '';
$valueText = $field['value'] ?? '';
// Calculate accurate width using the PDF object
$labelWidth = $this->calculateTextWidth($pdf, $labelText, 'freesans', 'B', $this->labelSize * 1.2);
$valueWidth = $this->calculateTextWidth($pdf, $valueText, 'freemono', 'B', $this->fieldSize * 1.3);
// Use the longer of the two
$textWidth = max($labelWidth, $valueWidth);
$fieldsWidth = max($fieldsWidth, $textWidth);
}
$requiredLength += $fieldsWidth;
}
// Add more padding to prevent text from being cut off
// $requiredLength += self::TAPE_WIDTH * 0.8;
// Ensure minimum length
$requiredLength = max($this->minHeight, $requiredLength);
// If we're just calculating, return the length
if ($calculateOnly) {
return $requiredLength;
}
// Otherwise, render the content
// Position barcode on the left side
if ($record->has('barcode2d') && $this->getSupport2DBarcode()) {
// Position at top of usable area
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + $this->barcodeMargin;
$usableWidth -= $barcodeSize + $this->barcodeMargin;
}
// Position fields to the right of the barcode
if ($record->has('fields') && $this->getSupportFields() > 0) {
// Limit to the number of supported fields
$fields = array_slice($record->get('fields')->toArray(), 0, $this->getSupportFields());
// Calculate total height needed for fields
$totalFieldsHeight = 0;
foreach ($fields as $field) {
$totalFieldsHeight += $this->labelSize * 1.2 + $this->labelMargin; // Increased label size by 20%
$totalFieldsHeight += $this->fieldSize * 1.3 + $this->fieldMargin * 2; // Increased field size by 30% and margin
}
// Start position - respect top margin
$fieldY = $currentY; // $currentY already includes the top margin
$fieldWidth = $usableWidth;
// Calculate available height for fields (respecting margins)
$availableHeight = $usableHeight;
// If fields don't fill available height, adjust spacing proportionally
// but don't exceed the available height
$scaleFactor = 1.0; // Default scale factor
if ($totalFieldsHeight < $availableHeight && count($fields) > 0) {
// Scale up to fill available height, but not too much
$scaleFactor = min(1.5, $availableHeight / $totalFieldsHeight);
} else if ($totalFieldsHeight > $availableHeight && count($fields) > 0) {
// Scale down to fit within available height
$scaleFactor = $availableHeight / $totalFieldsHeight;
}
foreach ($fields as $field) {
// Calculate scaled spacing
$labelHeight = $this->labelSize * 1.2 * $scaleFactor;
$labelSpacing = $this->labelMargin * $scaleFactor;
$fieldHeight = $this->fieldSize * 1.3 * $scaleFactor;
$fieldSpacing = $this->fieldMargin * 2 * $scaleFactor;
// Check if label is empty or null
$labelText = $field['label'] ?? '';
$valueText = $field['value'] ?? '';
if (empty(trim($labelText))) {
// If label is empty, just render the value at the current Y position
static::writeText(
$pdf, $valueText,
$currentX, $fieldY,
'freemono', 'B', $this->fieldSize * 1.3, 'L', // Increased field size by 30%
$fieldWidth, $fieldHeight, false, 0, 0.00
);
$fieldY += ($fieldHeight + $fieldSpacing) + 0.02; // Increased spacing after value
} else {
// If label has content, render both label and value
static::writeText(
$pdf, $labelText,
$currentX, $fieldY,
'freesans', 'B', $this->labelSize * 1.2, 'L', // Increased label size by 20% and made bold
$labelWidth, $labelHeight, false, 0,
);
$fieldY += ($labelHeight + $labelSpacing) + 0.01;
// Value
static::writeText(
$pdf, $valueText,
$currentX, $fieldY, // Position value directly below label
'freemono', 'B', $this->fieldSize * 1.3, 'L', // Increased field size by 30%
$fieldWidth, $fieldHeight, false, 0, 0.00
);
$fieldY += ($fieldHeight + $fieldSpacing) + 0.02; // Increased spacing after value
}
}
}
}
}
@@ -0,0 +1,103 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
use App\Helpers\Helper;
use App\Models\Labels\Label;
abstract class GenericTape extends Label
{
// Default tape width in mm
protected const TAPE_WIDTH = 42.0;
// Tape properties
protected float $width;
protected float $height;
protected bool $continuous;
protected float $spacing = 0.0; // Space between labels for non-continuous tapes
// Margins in mm
protected float $marginTop;
protected float $marginBottom;
protected float $marginLeft;
protected float $marginRight;
// Element sizing in mm
protected float $titleSize;
protected float $titleMargin;
protected float $fieldSize;
protected float $fieldMargin;
protected float $labelSize;
protected float $labelMargin;
protected float $barcodeMargin;
protected float $tagSize;
/**
* Constructor for generic tape
*
* @param float $width Width of the tape in mm
* @param float $height Height of the label in mm (for continuous tapes, this is the default height)
* @param bool $continuous Whether the tape is continuous or pre-cut
* @param float $spacing Spacing between labels for non-continuous tapes (in mm)
*/
public function __construct(float $width, float $height, bool $continuous = true, float $spacing = 0.0) {
$this->width = $width;
$this->height = $height;
$this->continuous = $continuous;
$this->spacing = $spacing;
// Calculate base font size (7% of tape width)
$baseFontSize = static::TAPE_WIDTH * 0.07;
// Calculate margin (4% of tape width)
$margin = static::TAPE_WIDTH * 0.04;
// Set margins
$this->marginTop = $margin;
$this->marginBottom = $margin;
$this->marginLeft = $margin;
$this->marginRight = $margin;
// Calculate and set element sizing based on base font size
$this->titleSize = $baseFontSize; // Same as base font size
$this->titleMargin = $baseFontSize * 0.3; // 30% of base font size
$this->fieldSize = $baseFontSize * 1.1; // 110% of base font size
$this->fieldMargin = $baseFontSize * 0.1; // 10% of base font size
$this->labelSize = $baseFontSize * 0.7; // 70% of base font size
$this->labelMargin = $baseFontSize * -0.1; // -10% of base font size
$this->barcodeMargin = $baseFontSize * 0.5; // 50% of base font size
$this->tagSize = $baseFontSize * 0.8; // 80% of base font size
}
// Unit of measurement
public function getUnit() { return 'mm'; }
// Label dimensions
public function getWidth() { return $this->width; }
public function getHeight() { return $this->height; }
// Margins
public function getMarginTop() { return $this->marginTop; }
public function getMarginBottom() { return $this->marginBottom; }
public function getMarginLeft() { return $this->marginLeft; }
public function getMarginRight() { return $this->marginRight; }
/**
* Check if this is a continuous tape
*
* @return bool
*/
public function isContinuous() {
return $this->continuous;
}
/**
* Get spacing between labels (for die-cut tapes)
*
* @return float
*/
public function getSpacing() {
return $this->spacing;
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
abstract class Tape_53mm extends GenericTape
{
// Override tape width to 53mm
protected const TAPE_WIDTH = 53.0;
private float $tapeHeight;
/**
* Constructor for 53mm tape
*
* @param float $height Height of the label in mm (default 30mm)
* @param bool $continuous Whether the tape is continuous or pre-cut
* @param float $spacing Spacing between labels for non-continuous tapes (in mm)
*/
public function __construct(float $height = 30.0, bool $continuous = true, float $spacing = 0.0) {
parent::__construct(self::TAPE_WIDTH, $height, $continuous, $spacing);
$this->tapeHeight = $height;
}
/**
* Get the barcode size ratio for calculations
*
* @return float
*/
public function getBarcodeRatio() {
return 0.9; // Barcode should use 90% of available width
}
}
@@ -0,0 +1,77 @@
<?php
namespace App\Models\Labels\Tapes\Generic;
class Tape_53mm_A extends Tape_53mm
{
public function __construct() {
parent::__construct(40.0, true, 0.0);
}
public function getUnit() { return 'mm'; }
public function getSupportAssetTag() { return false; }
public function getSupport1DBarcode() { return false; }
public function getSupport2DBarcode() { return true; }
public function getSupportFields() { return 5; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return true; }
public function preparePDF($pdf) {
$pdf->SetAutoPageBreak(false);
}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', $this->titleSize, 'C',
$pa->w, $this->titleSize, true, 0
);
$currentY += $this->titleSize + $this->titleMargin;
$usableHeight -= $this->titleSize + $this->titleMargin;
}
// Make the barcode as large as possible while still leaving room for fields
$barcodeSize = min($usableHeight * 0.8, $usableWidth * $this->getBarcodeRatio());
if ($record->has('barcode2d')) {
$barcodeX = $pa->x1 + ($usableWidth - $barcodeSize) / 2;
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$barcodeX, $currentY,
$barcodeSize, $barcodeSize
);
$currentY += $barcodeSize + $this->barcodeMargin;
}
if ($record->has('fields')) {
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', $this->labelSize, 'L',
$usableWidth, $this->labelSize, true, 0
);
$currentY += $this->labelSize + $this->labelMargin;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', $this->fieldSize, 'L',
$usableWidth, $this->fieldSize, true, 0, 0.01
);
$currentY += $this->fieldSize + $this->fieldMargin;
}
}
}
}
+15 -23
View File
@@ -3,6 +3,7 @@
namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Carbon\Carbon;
@@ -21,6 +22,7 @@ class License extends Depreciable
use SoftDeletes;
use CompanyableTrait;
use HasUploads;
use Loggable, Presentable;
protected $injectUniqueIdentifier = true;
use ValidatingTrait;
@@ -43,7 +45,7 @@ class License extends Depreciable
protected $rules = [
'name' => 'required|string|min:3|max:255',
'seats' => 'required|min:1|integer',
'seats' => 'required|min:1|integer|limit_change:10000', // limit_change is a "pseudo-rule" that translates into 'between', see prepareLimitChangeRule() below
'license_email' => 'email|nullable|max:120',
'license_name' => 'string|nullable|max:100',
'notes' => 'string|nullable',
@@ -113,6 +115,7 @@ class License extends Depreciable
'company' => ['name'],
'category' => ['name'],
'depreciation' => ['name'],
'supplier' => ['name'],
];
protected $appends = ['free_seat_count'];
@@ -148,6 +151,14 @@ class License extends Depreciable
});
}
public function prepareLimitChangeRule($parameters, $field)
{
$actual_seat_count = $this->licenseseats()->count(); //we use the *actual* seat count here, in case your license has gone wonky
$lower_bound = $actual_seat_count - $parameters[0];
$upper_bound = $actual_seat_count + $parameters[0];
return ["between", ($lower_bound <= 0 ? 1 : $lower_bound), $upper_bound];
}
/**
* Balance seat counts
*
@@ -164,21 +175,17 @@ class License extends Depreciable
// On Create, we just make one for each of the seats.
$change = abs($oldSeats - $newSeats);
if ($oldSeats > $newSeats) {
$license->load('licenseseats.user');
// Need to delete seats... lets see if if we have enough.
$seatsAvailableForDelete = $license->licenseseats->reject(function ($seat) {
return ((bool) $seat->assigned_to) || ((bool) $seat->asset_id);
});
$seatsAvailableForDelete = $license->licenseseats()->whereNull('assigned_to')->whereNull('asset_id')->limit($change);
if ($change > $seatsAvailableForDelete->count()) {
Session::flash('error', trans('admin/licenses/message.assoc_users'));
return false;
}
for ($i = 1; $i <= $change; $i++) {
$seatsAvailableForDelete->pop()->delete();
}
$seatsAvailableForDelete->delete();
// Log Deletion of seats.
$logAction = new Actionlog;
$logAction->item_type = self::class;
@@ -407,21 +414,6 @@ class License extends Depreciable
->orderBy('created_at', 'desc');
}
/**
* Establishes the license -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
+22 -16
View File
@@ -6,6 +6,7 @@ use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\SnipeModel;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Searchable;
use App\Models\User;
use App\Presenters\Presentable;
@@ -25,6 +26,7 @@ class Location extends SnipeModel
protected $presenter = \App\Presenters\LocationPresenter::class;
use Presentable;
use SoftDeletes;
use HasUploads;
protected $table = 'locations';
protected $rules = [
@@ -135,6 +137,17 @@ class Location extends SnipeModel
return $this->hasMany(\App\Models\User::class, 'location_id');
}
/**
* Establishes the location -> admin user relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function adminuser()
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
}
/**
* Find assets with this location as their location_id
*
@@ -289,22 +302,6 @@ class Location extends SnipeModel
return $this->attributes['ldap_ou'] = empty($ldap_ou) ? null : $ldap_ou;
}
/**
* Get uploads for this location
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany('\App\Models\Actionlog', 'item_id')
->where('item_type', '=', Location::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
* Query builder scope to order on parent
@@ -374,4 +371,13 @@ class Location extends SnipeModel
{
return $query->leftJoin('companies as company_sort', 'locations.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order);
}
/**
* Query builder scope to order on the user that created it
*/
public function scopeOrderByCreatedByName($query, $order)
{
return $query->leftJoin('users as admin_sort', 'locations.created_by', '=', 'admin_sort.id')->select('locations.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
}
+3 -6
View File
@@ -89,13 +89,10 @@ trait Loggable
$log->note = $note;
$log->action_date = $action_date;
if (! $log->action_date) {
$log->action_date = date('Y-m-d H:i:s');
}
$changed = [];
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
@@ -182,7 +179,7 @@ trait Loggable
$log->note = $note;
$log->action_date = $action_date;
if (! $log->action_date) {
if (!$action_date) {
$log->action_date = date('Y-m-d H:i:s');
}
@@ -193,7 +190,7 @@ trait Loggable
$changed = [];
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
+2
View File
@@ -67,11 +67,13 @@ class Setting extends Model
'google_login',
'google_client_id',
'google_client_secret',
'manager_view_enabled',
];
protected $casts = [
'label2_asset_logo' => 'boolean',
'require_checkinout_notes' => 'boolean',
'manager_view_enabled' => 'boolean',
];
/**
+22 -1
View File
@@ -48,7 +48,7 @@ class Supplier extends SnipeModel
*
* @var array
*/
protected $searchableAttributes = ['name'];
protected $searchableAttributes = ['name', 'notes', 'phone', 'fax', 'url', 'email', 'contact', 'address', 'address2', 'city', 'state', 'country', 'zip'];
/**
* The relations and their attributes that should be included when searching the model.
@@ -128,6 +128,18 @@ class Supplier extends SnipeModel
return $this->hasMany(\App\Models\Consumable::class, 'supplier_id');
}
/**
* Establishes the supplier -> admin user relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function adminuser()
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
}
/**
* Establishes the supplier -> asset maintenances relationship
*
@@ -197,4 +209,13 @@ class Supplier extends SnipeModel
return $url;
}
/**
* Query builder scope to order on the user that created it
*/
public function scopeOrderByCreatedByName($query, $order)
{
return $query->leftJoin('users as admin_sort', 'suppliers.created_by', '=', 'admin_sort.id')->select('suppliers.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Models\Traits;
use App\Models\Actionlog;
trait HasUploads
{
public function uploads() {
return $this->hasMany(Actionlog::class, 'item_id')
->where('item_type', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename');
}
}
+1 -1
View File
@@ -164,7 +164,7 @@ trait Searchable
}
}
// I put this here because I only want to add the concat one time in the end of the user relation search
if($relation == 'user') {
if(($relation == 'adminuser') || ($relation == 'user')) {
$query->orWhereRaw(
$this->buildMultipleColumnSearch([
'users.first_name',
+104 -17
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Searchable;
use App\Models\Traits\HasUploads;
use App\Presenters\Presentable;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
@@ -18,6 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use Watson\Validating\ValidatingTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -26,9 +28,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
use HasFactory;
use CompanyableTrait;
use HasUploads;
protected $presenter = \App\Presenters\UserPresenter::class;
use SoftDeletes, ValidatingTrait;
use SoftDeletes, ValidatingTrait, Loggable;
use Authenticatable, Authorizable, CanResetPassword, HasApiTokens;
use UniqueUndeletedTrait;
use Notifiable;
@@ -168,6 +171,15 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
}
public function isAvatarExternal() {
// Check if it's a google avatar or some external avatar
if (Str::startsWith($this->avatar, ['http://', 'https://'])) {
return true;
}
return false;
}
/**
* Internally check the user permission for the given section
*
@@ -320,7 +332,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
$setting = Setting::getSettings();
if ($setting->name_display_format=='last_first') {
if ($setting?->name_display_format == 'last_first') {
return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name;
}
return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name;
@@ -520,21 +532,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->hasMany(\App\Models\Asset::class, 'id')->withTrashed();
}
/**
* Establishes the user -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/**
* Establishes the user -> acceptances relationship
@@ -551,6 +549,25 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
->orderBy('created_at', 'desc');
}
/**
* Establishes the user -> eula relationship
*
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas()
{
return $this->hasMany(Actionlog::class, 'target_id')
->with('item')
->select(['id', 'target_id', 'target_type', 'action_type', 'filename', 'accept_signature', 'created_at', 'note', 'item_id', 'item_type'])
->where('target_type', self::class)
->where('action_type', 'accepted')
->whereNotNull('filename')
->whereNotNull('accept_signature')
->orderBy('created_at', 'desc');
}
/**
* Establishes the user -> requested assets relationship
*
@@ -956,5 +973,75 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
}
/**
* Get all direct and indirect subordinates for this user.
*
* @return \Illuminate\Support\Collection
*/
public function getAllSubordinates()
{
$subordinates = collect();
$this->fetchSubordinatesRecursive($this, $subordinates);
return $subordinates->unique('id');
}
/**
* Get all direct and indirect subordinates for this user, including self.
*
* @return \Illuminate\Support\Collection
*/
public function getAllSubordinatesIncludingSelf()
{
$subordinates = collect([$this]);
$this->fetchSubordinatesRecursive($this, $subordinates);
return $subordinates->unique('id');
}
/**
* Recursive helper function to fetch subordinates.
*
* @param User $manager
* @param \Illuminate\Support\Collection $subs
*/
protected function fetchSubordinatesRecursive(User $manager, \Illuminate\Support\Collection &$subs)
{
// Eager load 'managesUsers' to prevent N+1 queries in recursion
$directSubordinates = $manager->managesUsers()->with('managesUsers')->get();
foreach ($directSubordinates as $directSubordinate) {
// Add subordinate if not already in the collection
if (!$subs->contains('id', $directSubordinate->id)) {
$subs->push($directSubordinate);
// Recursive call for this subordinate's subordinates
$this->fetchSubordinatesRecursive($directSubordinate, $subs);
}
}
}
/**
* Check if the current user is a direct or indirect manager of the given user.
*
* @param User $userToCheck
* @return bool
*/
public function isManagerOf(User $userToCheck): bool
{
// Optimization: If it's the same user, they are not their own manager
if ($this->id === $userToCheck->id) {
return false;
}
// Eager load manager relationship to potentially reduce queries in the loop
$manager = $userToCheck->load('manager')->manager;
while ($manager) {
if ($manager->id === $this->id) {
return true;
}
// Move up the hierarchy (load relationship if not already loaded)
$manager = $manager->load('manager')->manager;
}
return false;
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Notifications;
use AllowDynamicProperties;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Channels\SlackWebhookChannel;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
class AuditNotification extends Notification
#[AllowDynamicProperties] class AuditNotification extends Notification
{
use Queueable;
/**
+4
View File
@@ -62,6 +62,7 @@ class AssetObserver
$logAction = new Actionlog();
$logAction->item_type = Asset::class;
$logAction->item_id = $asset->id;
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
$logAction->log_meta = json_encode($changed);
@@ -108,6 +109,7 @@ class AssetObserver
$logAction = new Actionlog();
$logAction->item_type = Asset::class; // can we instead say $logAction->item = $asset ?
$logAction->item_id = $asset->id;
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
if($asset->imported) {
@@ -128,6 +130,7 @@ class AssetObserver
$logAction->item_type = Asset::class;
$logAction->item_id = $asset->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
$logAction->logaction('delete');
}
@@ -143,6 +146,7 @@ class AssetObserver
$logAction = new Actionlog();
$logAction->item_type = Asset::class;
$logAction->item_id = $asset->id;
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
$logAction->logaction('restore');
+3
View File
@@ -21,6 +21,7 @@ class AccessoryPresenter extends Presenter
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
'printIgnore' => true,
], [
'field' => 'image',
'searchable' => false,
@@ -172,6 +173,7 @@ class AccessoryPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'accessoriesActionsFormatter',
'printIgnore' => true,
],
];
@@ -240,6 +242,7 @@ class AccessoryPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'accessoriesInOutFormatter',
'printIgnore' => true,
],
];
+2
View File
@@ -21,6 +21,7 @@ class AssetAuditPresenter extends Presenter
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
'printIgnore' => true,
],
[
'field' => 'id',
@@ -266,6 +267,7 @@ class AssetAuditPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'hardwareAuditFormatter',
'printIgnore' => true,
];
return json_encode($layout);
@@ -150,6 +150,7 @@ class AssetMaintenancesPresenter extends Presenter
'title' => trans('table.actions'),
'visible' => true,
'formatter' => 'maintenancesActionsFormatter',
'printIgnore' => true,
],
];
+2
View File
@@ -16,6 +16,7 @@ class AssetModelPresenter extends Presenter
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
'printIgnore' => true,
],
[
'field' => 'id',
@@ -185,6 +186,7 @@ class AssetModelPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'modelsActionsFormatter',
'printIgnore' => true,
];
return json_encode($layout);
+11
View File
@@ -22,6 +22,7 @@ class AssetPresenter extends Presenter
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
'printIgnore' => true,
], [
'field' => 'id',
'searchable' => false,
@@ -108,6 +109,12 @@ class AssetPresenter extends Presenter
'title' => trans('general.employee_number'),
'visible' => false,
'formatter' => 'employeeNumFormatter',
],[
'field' => 'jobtitle',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/users/table.title'),
'visible' => false,
], [
'field' => 'location',
'searchable' => true,
@@ -298,6 +305,7 @@ class AssetPresenter extends Presenter
'sortable' => true,
'visible' => false,
'title' => trans('general.byod'),
'class' => 'byod',
'formatter' => 'trueFalseFormatter',
],
@@ -339,6 +347,7 @@ class AssetPresenter extends Presenter
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'hardwareInOutFormatter',
'printIgnore' => true,
];
$layout[] = [
@@ -348,6 +357,7 @@ class AssetPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'hardwareActionsFormatter',
'printIgnore' => true,
];
return json_encode($layout);
@@ -415,6 +425,7 @@ class AssetPresenter extends Presenter
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'accessoriesInOutFormatter',
'printIgnore' => true,
],
];
+2 -1
View File
@@ -112,7 +112,8 @@ class CategoryPresenter extends Presenter
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'categoriesActionsFormatter',
'formatter' => 'categoriesActionsFormatter',
'printIgnore' => true,
],
];
+1
View File
@@ -142,6 +142,7 @@ class CompanyPresenter extends Presenter
'title' => trans('table.actions'),
'visible' => true,
'formatter' => 'companiesActionsFormatter',
'printIgnore' => true,
],
];

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