Compare commits

...

649 Commits

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

# Conflicts:
#	config/version.php
2024-03-29 13:20:22 +00:00
snipe c2d863da99 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-03-29 13:19:22 +00:00
snipe 6f9ba6ede4 Merge remote-tracking branch 'origin/develop' 2024-03-29 12:49:22 +00:00
snipe df4e6a0023 Fixed typo in Polish
I will fix this in CrowdIn shortly

Signed-off-by: snipe <snipe@snipe.net>
2024-03-29 12:47:43 +00:00
snipe d60fa65f85 Merge remote-tracking branch 'origin/develop' 2024-03-28 21:59:36 +00:00
snipe 8081a82fe4 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2024-03-28 21:58:58 +00:00
snipe f42e5d5292 Reverting the store/update asset API responses for now
This currently breaks the Jamf integration - need a better longer term plan.

Signed-off-by: snipe <snipe@snipe.net>
2024-03-28 21:58:49 +00:00
snipe 5a3f5b03d0 Merge pull request #14456 from akemidx/feature/sc-25079/legacy-locations
Default Locale value changed to en-US
2024-03-28 21:47:40 +00:00
snipe 2b4886f37f Commented out Heroic deploy for now 2024-03-28 17:43:18 +00:00
snipe b45de3e17f Update README.md
Added alert notations
2024-03-28 17:41:46 +00:00
snipe b2eea3e5a5 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/all-defer.js
#	public/mix-manifest.json
2024-03-28 13:02:51 +00:00
snipe f411c0fdd8 Merge pull request #14518 from snipe/fixes/downgrade_alpine
Downgrade alpine to 3.13.5
2024-03-28 12:59:52 +00:00
snipe 74d8431d01 Downgrade alpine to 3.13.5
Signed-off-by: snipe <snipe@snipe.net>
2024-03-28 12:58:43 +00:00
snipe 3ea0cd75a5 Merge remote-tracking branch 'origin/develop' 2024-03-28 12:27:24 +00:00
snipe 5f6c746d25 Merge pull request #14517 from snipe/bug/sc-25186/label_index
Check that the array key exists in the label engine
2024-03-28 12:26:20 +00:00
snipe 1f2d30ebf4 Check that the array key exists
Signed-off-by: snipe <snipe@snipe.net>
2024-03-28 12:25:29 +00:00
snipe 6dd6b45829 Merge remote-tracking branch 'origin/develop' 2024-03-27 20:43:34 +00:00
snipe e5800a2dac Merge pull request #14516 from snipe/fixes/fixed_sorting_on_last_checkin_assets_api
Fixes/fixed sorting on last checkin assets api
2024-03-27 20:39:12 +00:00
snipe 0d3d172108 Make last_checkin searchable and fillable
Signed-off-by: snipe <snipe@snipe.net>
2024-03-27 20:37:40 +00:00
snipe 86677b5f13 Make last_checkin sortable
Signed-off-by: snipe <snipe@snipe.net>
2024-03-27 20:37:25 +00:00
snipe 3f5b94f8ad Merge remote-tracking branch 'origin/develop' 2024-03-27 19:52:23 +00:00
snipe cf8cb8521b Merge pull request #14515 from squintfox/fixes-results-not-limited-by-api-params
Fixes #14289: /reports/activity API endpoint returns too many results due to orwhere
2024-03-27 19:51:58 +00:00
Robert Spadaro ccd00caa70 Wrap where logic in additional where statement to protect appended params 2024-03-27 15:36:00 -04:00
snipe 57010b4c23 Merge pull request #14500 from Godmartinz/add-accessory-signature-to-print-all
Added signature to user print report for Accessories and Consumables
2024-03-27 19:02:59 +00:00
snipe 545a185614 Merge pull request #14262 from akemidx/bug/sc-24812
Last Checkin Date added to Hardware View and Index
2024-03-27 19:02:25 +00:00
snipe 1572e339f9 Merge pull request #14514 from snipe/localizations/new_translations_march_224
Updated translations
2024-03-27 19:01:23 +00:00
snipe 7782e5cc93 Updated translations
Signed-off-by: snipe <snipe@snipe.net>
2024-03-27 18:59:28 +00:00
snipe 3bb81d1e4d Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/all-defer.js
#	public/mix-manifest.json
2024-03-27 16:18:28 +00:00
snipe 756c44fcba Merge pull request #14510 from snipe/snyk/Upgrade-alpinejs-from-3.13.5-to-3.13.6
[Snyk] Upgrade alpinejs from 3.13.5 to 3.13.6
2024-03-27 16:14:53 +00:00
snipe befa4428d0 Merge pull request #14509 from snipe/feature/sc-25173/filter_assigned_user_assets_by_category_id
Added ability to filter in user's assigned assets by category ID and model ID
2024-03-27 16:13:51 +00:00
snipe 60c678680a [Snyk] Upgrade alpinejs from 3.13.5 to 3.13.6
Signed-off-by: snipe <snipe@snipe.net>
2024-03-27 16:12:57 +00:00
snipe 8bc9688d71 Added ability to filter on category ID and model ID from user’s asset API
Signed-off-by: snipe <snipe@snipe.net>
2024-03-27 16:04:00 +00:00
akemidx b124b9af4d adding in change to settings table 2024-03-26 16:50:39 -04:00
snipe ae403da8c1 Merge pull request #14502 from marcusmoore/bug/sc-25004/pwd_secure_complexity-validation
Added validation around `pwd_secure_complexity`
2024-03-26 20:05:04 +00:00
Marcus Moore b5b8777c94 Extract translation string 2024-03-26 12:23:57 -07:00
snipe 850f85ff59 Merge pull request #14369 from spencerrlongg/bug/sc-24343
Add new validator for custom field checkboxes and fix asset model default updates
2024-03-26 19:22:29 +00:00
Godfrey M 84cc88831d adds signature for components 2024-03-26 12:18:10 -07:00
Godfrey M f4f9b165a7 moves signature check inside of td 2024-03-26 11:24:34 -07:00
Godfrey M 3222d4c8df adds signature check 2024-03-26 09:18:34 -07:00
Godfrey M 3173ead2e7 adds accessory signature to user print report 2024-03-26 09:11:54 -07:00
snipe 89d733d442 Merge remote-tracking branch 'origin/develop' 2024-03-26 14:53:24 +00:00
snipe df49e8350f Merge pull request #14498 from snipe/features/limit_report_by_admin
Fixed #14495 - Allow user_id to be passed to limit to only specific admins
2024-03-26 14:52:03 +00:00
snipe 3ced85080a Fixed #14495 - Allow user_id to be passed to limit to only specific admins
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 14:45:20 +00:00
snipe 0611ab9b4c Merge remote-tracking branch 'origin/develop' 2024-03-26 12:29:39 +00:00
snipe f450cafe3e Fixed missing div
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 12:29:25 +00:00
snipe 1f29eb1875 Merge pull request #14494 from snipe/features/add_supplier_address_to_licenses
Added supplier details to license view
2024-03-26 12:20:57 +00:00
snipe a4e5ae0938 Removed duplicates
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 12:20:27 +00:00
snipe 77dacfcc30 Show if deleted supplier
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 12:19:29 +00:00
snipe 0d9b6eaf71 Added supplier details to license view
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 12:18:04 +00:00
snipe 7060ffaf34 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2024-03-26 11:17:07 +00:00
snipe 02a37e2f89 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 10:16:46 +00:00
snipe 9c1b1bc2b5 Removed fre-order columns - it’s not currently used
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 10:15:46 +00:00
snipe b34156ca25 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2024-03-26 09:58:35 +00:00
snipe 61bdb57b5d Merge pull request #14492 from snipe/feature/sc-25142/deeplink_search_urls
Fixed #14483 - adds deeplinking to search/sort/pagination
2024-03-26 09:55:50 +00:00
snipe 9df84e235c Check for config variable
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 09:54:40 +00:00
snipe 566ba4783e Fixed #14483 - adds deeplinking to search/sort/pagination
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 09:50:01 +00:00
snipe b41b4b1732 Merge remote-tracking branch 'origin/develop' 2024-03-26 08:48:44 +00:00
snipe ccf9457c45 Merge pull request #14491 from PP-JN-RL/patch-1
Added Dymo Labelwriter 1933081
2024-03-26 08:46:03 +00:00
snipe 53aabdab66 Merge remote-tracking branch 'origin/develop' 2024-03-26 08:33:40 +00:00
snipe 8ff85c952e Fixed labelwriter 2112283 namespace
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 08:33:30 +00:00
Phil J R afe1cb8234 Create LabelWriter_1933081.php
Added a longer version of the existing label (25 x 54mm vs. 25 x 89mm) due to better availability and to support longer asset names.
2024-03-26 08:29:47 +00:00
snipe bd42505799 Merge remote-tracking branch 'origin/develop' 2024-03-26 08:23:40 +00:00
snipe 6c735c97c6 Merge pull request #14490 from snipe/bug/sc-25141/bad_method_call_model_restore_from_view
Fixed #14482 - bad method call model restore from view
2024-03-26 08:21:27 +00:00
snipe 31a57cdf14 Fixed #14482 - bad method call on restore from view
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 08:18:59 +00:00
snipe 7ebbef25e7 Standardize button styling
Signed-off-by: snipe <snipe@snipe.net>
2024-03-26 08:18:36 +00:00
Phil J R f836342194 Rename Label_Writer_2112283.php to LabelWriter_2112283.php
Fix the name of LabelWWriter_2112283 to be consistent with the existing one.
2024-03-26 08:16:00 +00:00
spencerrlongg 5cf1a6c300 new validator for radio buttons 2024-03-25 21:03:13 -05:00
Marcus Moore bd506820b7 Display error message 2024-03-25 17:59:39 -07:00
Marcus Moore 5815607924 Add validation for pwd_secure_complexity 2024-03-25 17:45:41 -07:00
spencerrlongg 9b40c9788f rm commented tests 2024-03-25 18:58:49 -05:00
snipe 67b5e9093e Merge pull request #14488 from marcusmoore/bug/pass-last-audit-date-through-for-validation
Handle badly formatted `last_audit_date` in `StoreAssetRequest`
2024-03-25 20:57:20 +00:00
Marcus Moore 57d1c036ec Improve method name 2024-03-25 13:53:30 -07:00
Marcus Moore 71722b753d Little bit of clean up 2024-03-25 13:49:03 -07:00
Marcus Moore a2625c889a Improve comment 2024-03-25 13:48:32 -07:00
Marcus Moore c98b9da612 Pass last_audit_date through for model level validation if not a date 2024-03-25 13:47:24 -07:00
Marcus Moore 675717ff82 Add failing test 2024-03-25 13:46:22 -07:00
snipe 83ef7c6fe8 Merge pull request #14486 from marcusmoore/bug/sc-25139
Fixes `last_audit_date` not being stored via API correctly
2024-03-25 19:49:54 +00:00
Marcus Moore 5f4c964309 Account for last_audit_date not being provided 2024-03-25 12:45:11 -07:00
Marcus Moore 66ba96d531 Set last_audit_date to valid format in StoreAssetRequest 2024-03-25 12:38:12 -07:00
snipe d67b2da064 Merge pull request #14485 from PP-JN-RL/patch-1
Created Dymo LabelWriter Label 2112283
2024-03-25 16:50:10 +00:00
Phil J R e45fd4088f Created Dymo LabelWriter Label 2112283
I have added the layout for the Dymo LabelWriter 2112283 Labels.
They are: DYMO 2112283 DURABLE SMALL MULTI PURPOSE LABELS (160 LABELS) - 25 X 54MM
2024-03-25 16:31:20 +00:00
spencerrlongg 7e4a0eedf0 rm dumb note 2024-03-25 10:46:26 -05:00
snipe 3f812f696d Merge pull request #14472 from snipe/fixes/last_audit_date_via_api
Added validation for last_audit_date and next_audit_date
2024-03-25 14:35:42 +00:00
snipe e9e6f925bf Updated validation
Signed-off-by: snipe <snipe@snipe.net>
2024-03-21 18:34:39 +00:00
snipe 828b84084d Added validation for last_audit_date and next_audit_date
Signed-off-by: snipe <snipe@snipe.net>
2024-03-21 18:29:38 +00:00
snipe a6a0bfacc2 Merge remote-tracking branch 'origin/develop' 2024-03-21 18:03:19 +00:00
snipe c4b7e77498 Merge pull request #14089 from spencerrlongg/bug/escaped_quotes_in_listbox
Use `htmlentities()` instead of `htmlspecialcharacters()` on Custom Field Listbox Values
2024-03-21 12:55:25 +00:00
snipe 91c7180bfd Merge pull request #14469 from snipe/feature/sc-19515/2fa_reset_log
Added 2FA reset log entry
2024-03-21 12:52:22 +00:00
snipe 7c39f516b9 Merge pull request #14421 from marcusmoore/fixes/add-minimal-validation-around-asset-tags
Adds a minimal amount of validation around asset_tags in AssetsController
2024-03-21 00:04:44 +00:00
snipe ce1ddcfcee Merge pull request #14438 from marcusmoore/chore/sc-25071
Removed the need to add `InteractsWithSettings` to each test case
2024-03-21 00:03:34 +00:00
snipe 945e8b402f Only offer the 2FA reset if the user already has 2FA set up
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:52:51 +00:00
snipe 5ed2bd0fb7 Skip the normal edit observer
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:52:22 +00:00
snipe bc908b854d Added icon
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:44:47 +00:00
snipe 2067b1138a Added the log item
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:43:58 +00:00
snipe 1ffbdee156 Updated 2FA text to not be google authenticator specific
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:43:30 +00:00
snipe bd2812cac1 Added new string
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:43:12 +00:00
snipe f2a5eac256 Tightened up 2FA text
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 23:43:05 +00:00
snipe b4e647dbd1 Merge remote-tracking branch 'origin/develop' 2024-03-20 21:31:04 +00:00
snipe de18e449a6 Merge pull request #14464 from snipe/features/toggle_all_columns
Added "toggle all" to column selector
2024-03-20 21:26:23 +00:00
snipe dce19e0bea Set names and actions to switchable = false
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 21:25:50 +00:00
snipe 1f586d3102 Removed data-show-columns-toggle-all data attribute
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 21:25:16 +00:00
snipe e5d01170d2 Fixed locale, added showColumnsToggleAll and minimumCountColumns
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 21:24:59 +00:00
snipe 0f11963127 Merge remote-tracking branch 'origin/develop' 2024-03-20 19:01:03 +00:00
snipe d8378f2a10 Merge pull request #14468 from snipe/features/added_default_location_to_print_all_assigned
Added default location to print all assigned
2024-03-20 18:57:01 +00:00
snipe a0a5480c97 Added default location to print all assigned
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 18:55:47 +00:00
snipe 3391108551 Merge remote-tracking branch 'origin/develop' 2024-03-20 12:57:30 +00:00
snipe 52551dad0f Merge pull request #14465 from snipe/features/14460_add_avif_format
Added #14460 add avif format
2024-03-20 12:39:14 +00:00
snipe 321414f6e3 Updated comment
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:38:48 +00:00
snipe 7787fe42c8 Added avif to helpers
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:30:28 +00:00
snipe 3b66912742 Fixed #14460 - added support for avif
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:25:02 +00:00
snipe 278a25c63b Added toggle all to column selector
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:14:41 +00:00
snipe 0d124bb5a1 Fixed indent
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:08:35 +00:00
snipe 417caae589 Added translation
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 12:07:18 +00:00
snipe 3d306aacc5 Fixed mismatched HTML tag
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 11:52:51 +00:00
snipe ea4ecaea03 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
2024-03-20 11:04:34 +00:00
snipe 6c90f9e395 Merge pull request #14462 from snipe/fixes/small_ui_tweaks_for_country_dropdown
Wider country dropdown
2024-03-20 11:01:39 +00:00
snipe ddf81ba135 Wider country dropdown
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 11:00:06 +00:00
snipe 8ebb41caa6 Merge pull request #14461 from snipe/security/update_bs_tables
[Snyk] Upgrade bootstrap-table from 1.22.2 to 1.22.3 #14455
2024-03-20 10:39:05 +00:00
snipe faf48b1684 [Snyk] Upgrade bootstrap-table from 1.22.2 to 1.22.3 #14455
Signed-off-by: snipe <snipe@snipe.net>
2024-03-20 10:37:53 +00:00
snipe b8232d205b Merge pull request #14457 from Godmartinz/bulk_delete_locations_bug
Fixed Bulk delete locations bug
2024-03-20 10:35:45 +00:00
Godfrey M 62745923cf fixes API location delete 2024-03-19 15:25:28 -07:00
Godfrey M 090466123f add withCount to query instead 2024-03-19 15:18:18 -07:00
Godfrey M 38a3e36cd6 fixes translation usage 2024-03-19 14:32:21 -07:00
Godfrey M e8dc634a40 fixes translation usage 2024-03-19 14:30:53 -07:00
Godfrey M 0d0984a400 Bulk Delete Locations does not work [sc-25100] 2024-03-19 14:26:40 -07:00
akemidx 8640cad033 was using lcoations and not locale. fixed 2024-03-19 16:11:32 -04:00
akemidx dc29717623 fixing default 2024-03-19 15:51:05 -04:00
snipe 814914924f Merge remote-tracking branch 'origin/develop' 2024-03-19 15:51:07 +00:00
snipe c7083488b2 Merge pull request #14447 from snipe/feature/sc-25020_better_print_view
Add additional options to print all assigned view
2024-03-19 15:01:50 +00:00
snipe 4b4d383509 Removed - it’s for another PR
Signed-off-by: snipe <snipe@snipe.net>
2024-03-19 15:00:26 +00:00
akemidx 3680e04817 migration to set legacy locations to an standardized format 2024-03-18 18:29:59 -04:00
snipe 21e23baa37 Fixed ternary
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 21:28:46 +00:00
snipe fcd130ae15 Layout improvements
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 21:17:46 +00:00
Marcus Moore d1dffb84dc Remove InteractsWithSettings trait for remaining tests 2024-03-18 12:33:45 -07:00
Marcus Moore 541350916d Merge branch 'develop' into chore/sc-25071
# Conflicts:
#	tests/Feature/Api/Users/UpdateUserApiTest.php
#	tests/Feature/Notifications/AccessoryWebhookTest.php
#	tests/Feature/Notifications/AssetWebhookTest.php
#	tests/Feature/Notifications/ComponentWebhookTest.php
#	tests/Feature/Notifications/ConsumableWebhookTest.php
#	tests/Feature/Notifications/LicenseWebhookTest.php
2024-03-18 12:33:31 -07:00
snipe 002fd4ce30 Nicer formatting for signoff
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 13:44:51 +00:00
snipe 7b4ecb275f Use trans choice for language
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 13:29:01 +00:00
snipe 2df5d3a8ff Fixed column selector
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 13:06:08 +00:00
snipe 7070bad53b 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
2024-03-18 12:32:22 +00:00
snipe da62d6af26 Merge pull request #14445 from snipe/fixes/check_for_valid_license_category_on_view_assets
Make sure the category is still valid before displaying on view assets
2024-03-18 12:30:15 +00:00
snipe d0d4d14787 Make sure the category is still valid before displaying on view assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-18 12:29:00 +00:00
snipe 09d69b214b Merge pull request #14436 from marcusmoore/chore/sc-24901
Organized notification test cases
2024-03-18 12:20:23 +00:00
snipe 0e2aaebda4 Merge pull request #14406 from mauro-miatello/develop
Hide/Show ecnrypted values when click on the lock icon
2024-03-18 12:19:54 +00:00
snipe ec2c58163f Merge pull request #14437 from marcusmoore/chore/sc-25070
Removed dead test code
2024-03-18 12:19:35 +00:00
Marcus Moore a28bee86ba Extract method 2024-03-14 16:33:49 -07:00
Marcus Moore fb64892971 Re-order 2024-03-14 16:26:27 -07:00
Marcus Moore 95ff692b14 Improve InteractsWithSettings name 2024-03-14 15:06:52 -07:00
Marcus Moore 8003615b1f Move InteractsWithSettings to TestCase 2024-03-14 14:15:11 -07:00
Marcus Moore 948dc3c974 Remove duplicate trait 2024-03-14 13:11:53 -07:00
Marcus Moore 1afb724606 Remove commented (and old) test cases 2024-03-14 13:11:43 -07:00
Marcus Moore 451281d833 Delete test cases with only commented code 2024-03-14 13:11:27 -07:00
Marcus Moore 485f11c945 Move trait usage to parent TestCase 2024-03-14 12:56:49 -07:00
Marcus Moore dbc79655b0 Cleanup 2024-03-14 12:02:56 -07:00
Marcus Moore 02f6aa6161 Add group tag for new test case 2024-03-14 11:19:36 -07:00
Marcus Moore 07c5264b41 Organize email check in notifications test 2024-03-14 11:17:47 -07:00
Marcus Moore 7c178a6a78 Merge branch 'develop' into chore/sc-24901
# Conflicts:
#	tests/Feature/Notifications/AssetWebhookTest.php
2024-03-14 11:13:14 -07:00
snipe f56d53d7c1 Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-14 17:14:33 +00:00
snipe 4cd4a936d8 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-14 15:43:47 +00:00
snipe 7bae4c39c6 Merge pull request #14401 from uberbrady/fix_locale_warning
Fix [sc-25008] - correct and improve legacy language warnings
2024-03-14 15:42:28 +00:00
snipe d55d95bbea Merge pull request #14415 from Godmartinz/date_picker_z_order
Fixed z-index of date-picker
2024-03-14 15:39:11 +00:00
snipe 5e48d56561 Merge pull request #14427 from marcusmoore/bug/sc-25014
Removes the unused `mediconesystems/livewire-datatables` package
2024-03-14 15:35:53 +00:00
snipe 6a5098fb0c Merge remote-tracking branch 'origin/develop' 2024-03-14 13:26:38 +00:00
snipe 4e835e1772 Merge pull request #14430 from snipe/bug/sc-25066/fixed_requestable_search
Move requestable scope below sorting, etc
2024-03-14 13:25:44 +00:00
snipe cce8cb4f5e Move requestable scope below sorting, etc
Signed-off-by: snipe <snipe@snipe.net>
2024-03-14 13:24:22 +00:00
Marcus Moore 8aed26aab1 Remove unneeded test cases 2024-03-13 16:56:54 -07:00
Marcus Moore 01d5d4c2c8 Improve data provider name 2024-03-13 16:37:50 -07:00
Marcus Moore 592385cb07 Remove mediconesystems/livewire-datatables package 2024-03-13 11:58:03 -07:00
snipe 85123b3e66 Rework print page
Still seeing a weird error tho, not done yet

Signed-off-by: snipe <snipe@snipe.net>
2024-03-13 12:36:17 +00:00
Marcus Moore 0fcf223960 Add minimal validation for asset_tags 2024-03-12 12:00:10 -07:00
Godfrey M bf0bd06f20 put important back 2024-03-11 15:56:53 -07:00
Godfrey M f07b0b6bd7 removed important 2024-03-11 15:54:59 -07:00
Godfrey M 717b26c834 fixes z-index of date-picker 2024-03-11 15:53:59 -07:00
snipe 8bb8eab69b Merge remote-tracking branch 'origin/develop' 2024-03-11 15:00:38 +00:00
snipe 693d1c9452 Merge pull request #14413 from snipe/fixes/deprecation_on_strtoupper
Fixed deprecation warning on `strtoupper()`
2024-03-11 14:59:55 +00:00
snipe b049bb1d5c Check for null on select
Signed-off-by: snipe <snipe@snipe.net>
2024-03-11 14:55:49 +00:00
snipe c5c6b3bbc6 Starting the revised print page
Signed-off-by: snipe <snipe@snipe.net>
2024-03-11 14:23:25 +00:00
snipe 1df56a7cab Merge pull request #14407 from snipe/bug/sc-25019_added_created_at_pivot
Added created_at to license pivot
2024-03-10 16:40:56 +00:00
snipe 3f5cc2507d Used updated_at instead
Signed-off-by: snipe <snipe@snipe.net>
2024-03-10 16:39:49 +00:00
snipe 5a85424295 Added created_at to license pivot
Signed-off-by: snipe <snipe@snipe.net>
2024-03-10 16:33:04 +00:00
snipe a53a8cca74 Merge remote-tracking branch 'origin/develop' 2024-03-10 14:40:41 +00:00
snipe 3fdee881f9 Merge pull request #14404 from snipe/fixes/Uninitialized-string-offset-0-in-labels
Fixed uninitialized offset in labels in new label engine
2024-03-10 14:40:12 +00:00
snipe a289dfaf88 Cleanup
Signed-off-by: snipe <snipe@snipe.net>
2024-03-10 14:33:53 +00:00
MrM 8abd359b5b Script to hide / show encrypted values in web ui
added a function to show/hide encrypted values with a simple trick because ClipboardJS can't copy display:none element's value
2024-03-10 15:18:54 +01:00
MrM 9359809b4f Hide / Show encrypted values in web ui
added a span element to hide encrypted values
2024-03-10 15:09:46 +01:00
snipe cde5502f94 Handle blank labels on asset label fields
Signed-off-by: snipe <snipe@snipe.net>
2024-03-10 14:03:21 +00:00
spencerrlongg 04e0a9d4a5 commented tests for now 2024-03-08 13:14:59 -06:00
snipe 28ec0b8ebb Removed debugging console lines
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 17:19:31 +00:00
snipe 37dfecf098 Fixed uninitialized offset in labels
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 16:46:56 +00:00
snipe 9d8ce872a4 Merge remote-tracking branch 'origin/develop' 2024-03-08 14:56:21 +00:00
snipe 07880dfe50 Merge pull request #14400 from snipe/localizations/updated_strings
Updated language strings, added Somali
2024-03-08 14:07:32 +00:00
snipe 7eed9f8542 Added Somali
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 14:06:21 +00:00
Brady Wetherington a2e70dd6b2 Fix [sc-25008] - correct and improve legacy language warnings
The legacy language warning was misfiring when a user's language
didn't match the APP_LOCALE from .env.

Additionally, we weren't properly warning when the legacy-language
came from Settings or from the user themselves. Both of which should
be impossible but still probably not a bad idea to warn on it, anyways
2024-03-08 14:04:21 +00:00
snipe b9aeded957 Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 14:01:34 +00:00
snipe 027361f079 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-03-08 10:01:21 +00:00
snipe ff10d1540f Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 10:00:00 +00:00
snipe 0f63fa23e0 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	package-lock.json
2024-03-08 09:08:29 +00:00
snipe 22ef569e5e Merge branch 'security/snyc_Upgrade-webpack-from-5.90.1-to-5.90.2_14395' into develop 2024-03-08 08:50:26 +00:00
snipe f3663f0983 [Snyk] Upgrade webpack from 5.90.1 to 5.90.2 #14395
Signed-off-by: snipe <snipe@snipe.net>
2024-03-08 08:45:49 +00:00
snipe 112ddaf55b Merge pull request #14394 from marcusmoore/fixes/check-endpoint-is-set-for-checkout-in-notifications
Ensure Chat and Teams endpoints are not blank before attempting to send webhook on checkout and check in
2024-03-08 08:44:52 +00:00
snipe bda6fdf09c Merge pull request #14395 from snipe/snyk-upgrade-9e465161f7c9fd096a214ca3ad2fae7b
[Snyk] Upgrade webpack from 5.90.1 to 5.90.2
2024-03-08 08:44:07 +00:00
snyk-bot a62198f33f fix: upgrade webpack from 5.90.1 to 5.90.2
Snyk has created this PR to upgrade webpack from 5.90.1 to 5.90.2.

See this package in npm:
https://www.npmjs.com/package/webpack

See this project in Snyk:
https://app.snyk.io/org/snipe/project/3d53e1dd-b8bf-46b5-ba61-18ce26933166?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-08 06:20:14 +00:00
Marcus Moore 4b0bfc52b4 Ensure webhook endpoint is set before attempting to send to google or microsoft 2024-03-07 18:10:10 -08:00
snipe 78868813b1 Merge remote-tracking branch 'origin/develop' 2024-03-08 01:05:07 +00:00
snipe e8be178ac7 Merge pull request #14393 from marcusmoore/bug/sc-24862
Guard against checking require acceptance on non-existent relationship in accessory model
2024-03-08 01:04:26 +00:00
Marcus Moore 695428cd44 Guard against unrelated category 2024-03-07 16:59:01 -08:00
snipe 9ae779e442 Merge remote-tracking branch 'origin/develop' 2024-03-07 23:44:30 +00:00
snipe db92febdc7 Merge pull request #14392 from snipe/fixes/removed_unneeded_starts_with_validation_string
Removed unneeded validation message
2024-03-07 23:42:58 +00:00
snipe 5cf10cec34 Remloved unneeded validation message
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 23:40:30 +00:00
snipe ea2d54b0f7 Merge remote-tracking branch 'origin/develop' 2024-03-07 21:22:01 +00:00
snipe c66267a0f9 Merge pull request #14389 from Godmartinz/remove-pdf-for-old-labels
old engine works as intended
2024-03-07 21:06:57 +00:00
snipe f1d2f24534 Merge pull request #14388 from marcusmoore/bug/sc-25001
Fixed attempting to run bulk actions on an empty asset collection
2024-03-07 21:03:45 +00:00
Godfrey M d83974e07f old engine works as intended 2024-03-07 13:03:10 -08:00
Marcus Moore f270f30728 Redirect if no assets are returned from query 2024-03-07 12:30:08 -08:00
snipe 55c237913c Merge remote-tracking branch 'origin/develop' 2024-03-07 15:11:33 +00:00
snipe 806671df7c Merge pull request #14385 from snipe/feature/sc-24971_convert_unencrypted_custom_fields
Added console command to encrypt previously unencrypted fields
2024-03-07 15:08:19 +00:00
snipe d64ee42ec3 Wrapped in a transaction
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 15:04:08 +00:00
snipe fe08f39900 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 14:43:06 +00:00
snipe 5123ab57c9 Added console command to encrypt previously unencrypted fields
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 14:40:52 +00:00
snipe 313b327cd7 Bumped hash for master
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 11:34:38 +00:00
snipe 855d922a3e Merge remote-tracking branch 'origin/develop' 2024-03-07 11:31:38 +00:00
snipe cbe07ad23b Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2024-03-07 11:30:54 +00:00
snipe b3aba4ad99 Merge pull request #14379 from chandanchowdhury/master
Fix #13515: Cannot restore backup
2024-03-07 11:30:06 +00:00
snipe 21ad7f549d Merge remote-tracking branch 'origin/develop' 2024-03-07 10:34:30 +00:00
snipe 4b13fa45c5 Merge pull request #14320 from Godmartinz/default_label_bug
Default label setup with custom fields
2024-03-07 10:33:43 +00:00
snipe 485d40c752 Merge remote-tracking branch 'origin/develop' 2024-03-07 10:30:31 +00:00
snipe 8346ae8235 Merge pull request #14370 from ubc-cpsc/bugfix/CVE-2024-27354
Fixes CVE-2024-27354 and CVE-2024-27355 in phpseclib/phpseclib
2024-03-07 10:28:31 +00:00
snipe 109c0f202a Merge remote-tracking branch 'origin/develop' 2024-03-07 10:19:06 +00:00
snipe 4231058aa1 Merge pull request #14371 from Godmartinz/RB17900_notification_bug
Adds User email check when sending Asset acceptance reminder
2024-03-07 10:18:19 +00:00
snipe 6156d67e4a Merge remote-tracking branch 'origin/develop' 2024-03-07 10:17:05 +00:00
snipe 58bf036f52 Merge pull request #14380 from Godmartinz/1dbarcode_unselected_bug
Fixed 1dbarcodes to populate based on settings
2024-03-07 10:16:37 +00:00
Godfrey M ff7d25c0f2 simplified 1dbarcode logic 2024-03-06 15:30:53 -08:00
Godfrey M 5f0b7f328c if statement order change 2024-03-06 15:24:03 -08:00
Godfrey M 6559581bad conditions 1dbarcode to populate based on settings 2024-03-06 15:10:42 -08:00
Godfrey M d78262f52b resolved merge conflicts 2024-03-06 14:48:47 -08:00
snipe bd566324ed Updated assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-06 20:20:23 +00:00
chandanchowdhury b65bf5082d Fix #13515: create directories expected by RestoreFromBackup.php 2024-03-06 15:11:41 -05:00
snipe 939e4cba3e Updated prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-06 20:05:16 +00:00
snipe 015a8763a0 Merge remote-tracking branch 'origin/develop' 2024-03-06 20:04:48 +00:00
snipe 02862d80eb Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-03-06 20:04:31 +00:00
snipe 7b0a3fc6d3 Merge remote-tracking branch 'origin/develop' 2024-03-06 20:01:23 +00:00
snipe b40b31b7b1 Merge pull request #14378 from snipe/security/snyk_Upgrade-jspdf-autotable-from-3.8.1-to-3.8.2-14365
Upgrade jspdf-autotable from 3.8.1 to 3.8.2 #14365
2024-03-06 19:55:59 +00:00
snipe 149d3d13d1 Upgrade jspdf-autotable from 3.8.1 to 3.8.2 #14365
Signed-off-by: snipe <snipe@snipe.net>
2024-03-06 19:55:19 +00:00
snipe 0f64d66cd8 Merge remote-tracking branch 'origin/develop' 2024-03-06 19:48:55 +00:00
snipe a85e2f7aad Merge pull request #14372 from snipe/bug/sc-24158
Added route parameter
2024-03-06 19:45:33 +00:00
snipe 4f883b1264 Merge pull request #14375 from Godmartinz/Label-info_fix
Fixed Labels: bulk actions are handled before sorting
2024-03-06 19:44:59 +00:00
Godfrey M feb78d00cf conditionally shows label fields if on old label engine only 2024-03-06 11:25:49 -08:00
Godfrey M c9d54baa10 bulk actions are handled before sorting 2024-03-05 15:47:52 -08:00
Godfrey M 940f54dab1 revert some changes 2024-03-05 15:45:35 -08:00
Godfrey M d83827a44e bulk actions are handled before sorting 2024-03-05 15:43:41 -08:00
spencerrlongg 3a0a13d06d tests written but something not working... 2024-03-05 16:35:06 -06:00
snipe 23f8e35716 Added route parameter
Signed-off-by: snipe <snipe@snipe.net>
2024-03-05 20:39:56 +00:00
spencerrlongg a251e61d73 rm commented code and add comment 2024-03-05 13:40:44 -06:00
spencerrlongg af06b1cd06 hide encryption option for checkbox and radio 2024-03-05 13:37:30 -06:00
snipe 7769a93a10 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-03-05 19:07:40 +00:00
snipe 95a6c7058f Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-03-05 19:06:55 +00:00
Godfrey M 6c3a668400 checks to see if a user has an email before sending them a reminder email. 2024-03-05 10:30:24 -08:00
spencerrlongg ad0f873ece rm validation stuff 2024-03-05 11:58:00 -06:00
Joël Pittet 3008a4ed7a Fixes CVE-2024-27354 and CVE-2024-27355 2024-03-05 09:55:56 -08:00
Godfrey M 95ef3a336b Merge branch 'develop' into default_label_bug 2024-03-04 12:03:54 -08:00
snipe 9a5c1b8126 Merge pull request #14362 from Godmartinz/labels-not-generating-properly
Fixes inconsistencies with generating labels
2024-03-04 18:49:44 +00:00
Godfrey M e926db76a0 fixes inconsistencies with generating labels 2024-03-04 10:26:38 -08:00
snipe 9419c7fdeb Fixed contributing link
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 12:42:26 +00:00
snipe 9dbb4abe7e Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 12:30:05 +00:00
snipe 19e0fb7955 Reverting CSP change
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 12:25:14 +00:00
snipe 5b9b21a7d1 Merge pull request #14357 from snipe/fixes/stricter_csp_rules
Removed unsafe-inline and unsafe-eval
2024-03-01 11:48:51 +00:00
snipe 93e69ab0c6 Removed unsafe-inline and unsafe-eval
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 11:44:49 +00:00
snipe bc5c559413 Merge pull request #14356 from snipe/fixes/set_csp_rule_to_true_by_default
Set CSP headers to true by default
2024-03-01 11:37:53 +00:00
snipe 9b2fcbff08 Set CSP headers to true by default
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 11:36:37 +00:00
snipe ad2674d20b Merge pull request #14355 from snipe/localization/new_strings
Updated language strings
2024-03-01 11:18:12 +00:00
snipe 10ac2c830f Updated language strings
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 11:17:02 +00:00
snipe df3053eafb Merge pull request #14354 from snipe/bug/sc-24911
Use SVG cleaner on favicons
2024-03-01 10:19:59 +00:00
snipe aaae952acb Use SVG cleaner on favicons
Signed-off-by: snipe <snipe@snipe.net>
2024-03-01 10:17:57 +00:00
akemidx eb0657c953 Merge branch 'develop' into bug/sc-24812 2024-02-28 17:22:16 -05:00
snipe b1cd44341b Merge pull request #14347 from snipe/feature/sc-24856_requestable_page
Dynamically show models/assets on requestable page
2024-02-28 15:04:48 +00:00
snipe 3da47cdacd Removed header for consistency
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 15:03:34 +00:00
snipe 658dda916c Changed operator
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 15:01:00 +00:00
snipe 7c3d8b896b Updated asset transformers/presenters to display requestable status
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 14:59:55 +00:00
snipe 1ce9df7998 New translation
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 14:59:27 +00:00
snipe a0cbf66c81 Only show tabs if there are requestable assets/models
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 14:59:20 +00:00
snipe a4941031cb Merge pull request #14260 from marcusmoore/chore/sc-24808
Added tests around asset check in and added missing actions to the api controller action
2024-02-28 13:34:12 +00:00
snipe 9fd6aea325 Merge pull request #14281 from Godmartinz/preview-label-fields
Added better label previews with the new label engine
2024-02-28 11:48:05 +00:00
snipe a52181c995 Merge pull request #14283 from akemidx/bug/sc-24788
FIXED: Checked in Assets Did Not Show in Custom Report when Selecting a Valid Checked Out Date
2024-02-28 11:47:46 +00:00
snipe ee75df0f0a Merge pull request #14346 from snipe/feature/sc-18728
Added `min_amt` functionality to licenses
2024-02-28 11:26:25 +00:00
snipe 92eff653f1 Removed logging
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:26:03 +00:00
snipe 1e66985a78 Merge pull request #14344 from snipe/features/added_created_by_to_groups
Added created_by to groups table
2024-02-28 11:19:52 +00:00
snipe a059a42799 Added use Auth to API controller
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:14:48 +00:00
snipe b5603fbfe9 Added min amt to license blade
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:09:07 +00:00
snipe a0423a9cc3 Added translations
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:08:56 +00:00
snipe 4ab7112988 Added validation and fillable attributes for min_amt
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:08:47 +00:00
snipe 767cf96010 Added min_amt to form
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:08:33 +00:00
snipe 4ffb7790df Added min_amt to API transformer/presenter
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:07:18 +00:00
snipe 3ccc55a78f Added license min amt formatter for BS table
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:06:50 +00:00
snipe d0d29e03db Migration for min_amt on licenses
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:06:01 +00:00
snipe 92b7c4b5ec Allow sorting on min_amt
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:05:13 +00:00
snipe dff7c43aed Added minimum account check for licenses
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:05:02 +00:00
snipe 9efbcbbd5e Added min_amt to licenses
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:04:42 +00:00
snipe 31fa0a7044 Removed logging
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 11:04:32 +00:00
snipe fcb2bf7fea Merge pull request #14335 from mauro-miatello/develop
Copy encrypted custom fields
2024-02-28 09:04:11 +00:00
snipe 0bd27d61b0 Added created_by to groups table
Signed-off-by: snipe <snipe@snipe.net>
2024-02-28 08:15:59 +00:00
snipe 369ecfa490 Merge pull request #14341 from marcusmoore/chore/sc-24900
Generate unique department names in factory
2024-02-28 07:07:45 +00:00
Marcus Moore 551354b1bb Add group annotation for tests 2024-02-27 18:05:18 -08:00
Marcus Moore 03b7891edc Remove unneeded factory states 2024-02-27 18:03:53 -08:00
Marcus Moore 8978dff054 Consolidate helpers into trait 2024-02-27 18:01:08 -08:00
Marcus Moore d20844fefa Improve readability by extracting additional helpers 2024-02-27 17:57:12 -08:00
Marcus Moore cd579a04dd Improve readability by extracting fireCheckOutEvent method 2024-02-27 17:52:18 -08:00
Marcus Moore 15b8140bff Fix test helper 2024-02-27 17:48:17 -08:00
Marcus Moore 9a93ad2e06 Remove unneeded factory state 2024-02-27 17:46:27 -08:00
Marcus Moore bd4d3aa52b Improve readability by extracting additional helpers 2024-02-27 17:44:19 -08:00
Marcus Moore bf32ab177f Improve readability by extracting fireCheckInEvent method 2024-02-27 17:37:07 -08:00
Marcus Moore 2ea883aa15 Move Notification::fake() to setUp method 2024-02-27 17:23:30 -08:00
Marcus Moore 43cc296582 Consolidate additional tests 2024-02-27 17:14:35 -08:00
Marcus Moore 4c1aadd74e Improve naming and inline helper 2024-02-27 17:09:22 -08:00
Marcus Moore 7d3719bf70 Consolidate some slack notification tests 2024-02-27 17:07:40 -08:00
Marcus Moore c08164d864 Update test names 2024-02-27 16:48:17 -08:00
Marcus Moore b156aa74a5 Update helper name 2024-02-27 16:45:49 -08:00
Marcus Moore 87ba2cb407 Generate unique names for departments 2024-02-27 12:47:56 -08:00
Marcus Moore 5084e5d3ef Improve variable type 2024-02-27 12:23:26 -08:00
Marcus Moore a5516e3511 Improve trait name 2024-02-27 12:06:29 -08:00
Marcus Moore 0e460baf82 Improve readability 2024-02-27 12:03:36 -08:00
Godfrey M ef52777ffb removed conditional 2024-02-27 11:33:00 -08:00
Godfrey M b67ceab849 fields appear in preview, but not bulk generate labels 2024-02-27 11:29:19 -08:00
Marcus Moore 69022bb8b6 Implement test 2024-02-27 11:12:35 -08:00
Marcus Moore 29d729171c Align test with actual values passed from the web 2024-02-26 11:13:39 -08:00
MrM 9475871edb Fix multiple tooltip issue
If there are multiple tooltips, they all report the message "copied" because there is no element selection. This fix resolves the issue.
2024-02-25 14:48:05 +01:00
MrM b69364d5ff Add copy of encrypted custom fields
Encrypted custom fields may contain sensitive or important information, and it may be useful to have the ability to copy them.
2024-02-25 14:47:06 +01:00
snipe 10dad8e6e6 Merge pull request #14327 from snipe/fixes/better_ui_for_history_importer
Nicer UI for history importer
2024-02-23 11:43:15 +00:00
snipe a184b4e67c Nicer UI for history importer
Signed-off-by: snipe <snipe@snipe.net>
2024-02-23 11:40:10 +00:00
snipe 8cd0a90ecd Merge pull request #14325 from snipe/fixes/RB-17890
Check for valid supplier in license view
2024-02-23 10:49:56 +00:00
snipe 4a37632ef3 Check for valid supplier in license view
Signed-off-by: snipe <snipe@snipe.net>
2024-02-23 10:48:53 +00:00
snipe 3271d020e9 Merge pull request #14319 from snipe/fixes/RB-17889
Check for activeFile before trying to get header on import
2024-02-23 08:19:21 +00:00
snipe e494a2670f Merge pull request #14321 from akemidx/accessory_pdf_dl_link
Adding in PDF download to Accesories/Asset/License Histories
2024-02-23 08:18:55 +00:00
snipe d3a0a337b9 Merge pull request #14323 from ubc-cpsc/bugfix/CVE-2024-25117
Fixes CVE-2024-25117 php-svg-lib lacks path validation on font through SVG inline styles
2024-02-23 08:05:03 +00:00
Joël Pittet 3951ee746d Fixes CVE-2024-25117, no other dependencies 2024-02-22 16:49:02 -08:00
Joël Pittet 84e4257e75 Fixes CVE-2024-25117 2024-02-22 16:40:08 -08:00
Marcus Moore c401c88702 Scope event fake 2024-02-22 16:19:33 -08:00
Godfrey M 550f9e2afa removes visibility checkmarks for certain fields when in new label engine 2024-02-22 16:19:28 -08:00
Marcus Moore b55a19cebb Add assertion event is dispatched with correct timestamp 2024-02-22 13:50:46 -08:00
Marcus Moore 4caadcfa19 Clear pending checkout acceptances when checking in asset via api 2024-02-22 13:33:16 -08:00
Marcus Moore dba837b1d2 Move location migration logic to trait 2024-02-22 13:21:52 -08:00
Marcus Moore 714fc63050 Have legacy locations updated upon api asset checkin 2024-02-22 13:14:30 -08:00
spencerrlongg b6fa6cba22 note before switching tasks 2024-02-22 15:01:14 -06:00
Marcus Moore 2df026bcb5 Allow updating asset default location when checking in asset via api 2024-02-22 12:40:14 -08:00
Godfrey M 6ee24a7527 remove unnecessary code reformat 2024-02-22 12:36:27 -08:00
Godfrey M e2dcee1959 cleans up dead code 2024-02-22 12:26:57 -08:00
Godfrey M 4fbea9512f puts textY inside of the forloop 2024-02-22 12:08:47 -08:00
Godfrey M b7850ab839 puts textY outside of the forloop 2024-02-22 12:08:04 -08:00
Godfrey M c0215baca5 minor changes 2024-02-22 11:41:04 -08:00
Godfrey M c62758c5b5 adds custom field select to default label view 2024-02-22 11:36:39 -08:00
spencerrlongg 14358651e4 pushing to test other branches 2024-02-22 13:28:23 -06:00
snipe f04aeb9f2b Fixed spacing
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 18:31:02 +00:00
snipe dc902e7a5a Fixed return type
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 18:30:23 +00:00
snipe 4fc66e19bb Check for activeFile before trying to get header
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 18:28:01 +00:00
snipe e12d2b2a42 Merge pull request #14317 from snipe/fixes/make_website_searchable_on_users_list
Made website searchable on users table
2024-02-22 17:01:58 +00:00
snipe cb78451d6c Made website searchable on users table
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 16:13:54 +00:00
snipe 7979bc63ae Merge pull request #14316 from snipe/feature/sc-24871
Added email, phone icons
2024-02-22 14:50:46 +00:00
snipe e8ad8a7448 Added email, phone icons
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 14:48:01 +00:00
snipe eb61f5aa9e Merge pull request #14315 from snipe/fixes/rb-3565
Only send notification if the item is assigned to a person
2024-02-22 12:05:59 +00:00
snipe 3351998efd Only send notification if the item is assigned to a person
Signed-off-by: snipe <snipe@snipe.net>
2024-02-22 12:03:07 +00:00
spencerrlongg 20dbacd22f store good, update needs work 2024-02-21 21:33:34 -06:00
akemidx 0f3be4fdf8 adding in eula pdf dl link. mimics activity log 2024-02-21 17:45:10 -05:00
Marcus Moore 3ae8adfbf9 Remove incomplete flag on test case 2024-02-21 12:33:32 -08:00
Marcus Moore aa2632fe46 Merge branch 'develop' into chore/sc-24808 2024-02-21 12:33:06 -08:00
Godfrey M 14c86d447b added filter to collectifunction 2024-02-21 11:38:03 -08:00
Godfrey M bee016e0be keeps example values in the example preview, fixes indent 2024-02-21 09:31:01 -08:00
snipe 54552fc95c Merge pull request #14192 from inietov/fixes/api_issue_checkin_licenses
Fixed #14183  API /hardware/:id/checkin doesn't return Licenses
2024-02-21 11:35:06 +00:00
snipe ffa7d25fc0 Merge pull request #14308 from snipe/fixes/eager_load_relations_on_locations
Eager load relations to determine deletability on locations
2024-02-20 22:28:39 +00:00
snipe a37d3b00d0 Merge pull request #14307 from marcusmoore/bug/sc-24867
Fixed title not appearing in new label engine
2024-02-20 22:25:45 +00:00
snipe 04891c7c61 Eager load relations to determine
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 22:25:00 +00:00
spencerrlongg d67ff54f4b temporary decrypt, almost there 2024-02-20 16:20:03 -06:00
Marcus Moore ccec190985 Set label title properly 2024-02-20 13:59:50 -08:00
spencerrlongg 26728a85ad this seems to work for patches 2024-02-20 13:18:40 -06:00
spencerrlongg c6d85a1b0b allows arrays on checkbox values 2024-02-20 12:23:24 -06:00
snipe 71610fb20f Merge pull request #14304 from snipe/feature/sc-24018
Added ability to bulk delete locations
2024-02-20 18:14:51 +00:00
snipe cb0f9024b1 Fixed gate
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 17:56:40 +00:00
snipe 1797480128 Few more translations
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 17:27:56 +00:00
snipe 972b198248 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 17:27:16 +00:00
snipe 9010b7acd0 Use isDeletable instead of asset count
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 17:27:10 +00:00
snipe b57b68571e Added blade views
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:44:45 +00:00
snipe 1ca9420baa Fixed incorrect gate
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:44:29 +00:00
snipe 0383938536 API-ify the managed locations tab in user view
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:44:20 +00:00
snipe 50b841d54d Added table data-dash attributes to make the checkbox stuff work
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:43:57 +00:00
snipe 8f71460fa1 Formatting changes only
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:43:27 +00:00
snipe d5324bce6a Added more generic bulk translations for trans_choice
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:43:07 +00:00
snipe 660f3ccba1 Removed duplicate key
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:42:28 +00:00
snipe bc8db3deab Added bulk delete routes
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:42:14 +00:00
snipe b2c8fbf349 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:42:04 +00:00
snipe c3f21d9292 Removed new checkbox selectability on users and assets - more complicated work to be done
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:41:58 +00:00
snipe 9b146ae1d2 Formatting for API response
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:40:51 +00:00
snipe a32c679519 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:40:31 +00:00
snipe 1e602793b2 Added manager ID to filter
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:40:23 +00:00
snipe a6a65b7523 Fixed locations isDeletable check
Signed-off-by: snipe <snipe@snipe.net>
2024-02-20 16:40:09 +00:00
snipe 04c1d9cbff Merge pull request #14302 from uberbrady/fix_uploaded_file_extensions
Clean up the file extension on image file uploads
2024-02-19 16:53:47 +00:00
Brady Wetherington a6dfd67cd7 Clean up the file extension on image file uploads 2024-02-19 16:51:23 +00:00
snipe 7d178da61c Merge pull request #14301 from snipe/bug/sc-24375
Disallow branding uploads in demo mode
2024-02-19 16:27:20 +00:00
snipe 30f9acfcf3 Disallow uploads in demo mode
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 16:11:17 +00:00
snipe 1e351b4d63 Merge pull request #14299 from snipe/fixes/fix_crash_on_purged_models_in_activity_report
Check that the model exists before trying to access properties
2024-02-19 15:36:44 +00:00
snipe cb5b691ec1 Check that the model exists before trying to access properties
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 15:34:57 +00:00
snipe b47b401245 Merge pull request #14298 from uberbrady/docker_fix_alpine_exif
Add missing EXIF PHP library for Alpine Docker image
2024-02-19 15:09:02 +00:00
Brady Wetherington 119e79e248 Add missing EXIF PHP library for Alpine Docker image 2024-02-19 15:00:13 +00:00
snipe d7254053b6 Merge pull request #14297 from snipe/feature/sc-24855
Added `/backups/latest` to API endpoint
2024-02-19 14:40:13 +00:00
snipe 8f8edd4126 Added /backups/latest endpoint
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 14:35:09 +00:00
snipe 10bb844087 Added comment
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 14:34:51 +00:00
snipe ba3baabb50 Fixed @return
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 14:34:36 +00:00
snipe c76fbe4edb Return 404 if download file isn’t found
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 13:05:44 +00:00
snipe cae2de4fc9 Merge remote-tracking branch 'origin/develop' 2024-02-19 12:25:26 +00:00
snipe 7c346d977a Merge pull request #14296 from snipe/fixes/return_download_for_backup_api
Better handle large downloads on backup API
2024-02-19 12:22:49 +00:00
snipe 9847934de9 Better handle large downloads on backup API
Signed-off-by: snipe <snipe@snipe.net>
2024-02-19 12:20:37 +00:00
snipe db73f80058 Starting off
Signed-off-by: snipe <snipe@snipe.net>
2024-02-16 18:57:50 +00:00
akemidx 4dd479dad7 formatting 2024-02-15 19:59:26 -05:00
akemidx d01e1e8eeb query works, but still only shows the asset once even if it's been checked out multiple times 2024-02-15 19:56:08 -05:00
akemidx e3ef737ac4 query first go 2024-02-15 19:15:27 -05:00
Godfrey M 69317fb403 Marcus is a beast and condensed everything into 4 lines 2024-02-15 13:26:47 -08:00
Godfrey M 1b80c8938a removed unnecessary if statement 2024-02-15 13:04:00 -08:00
Godfrey M dc77c01fd3 removes spacing 2024-02-15 13:00:38 -08:00
Godfrey M 83474d6e59 moves the array manipulation up the page where it should be 2024-02-15 12:59:47 -08:00
Godfrey M 529310c93a fixed typo in comment 2024-02-15 12:41:31 -08:00
Godfrey M 70f26f33a5 does a collect and map instead of a for loop for the array solution 2024-02-15 12:34:09 -08:00
Godfrey M 414bc10c40 removes the double for loop with a better solution 2024-02-15 12:02:08 -08:00
Godfrey M 250b0a7afb gives a better example with custom fields in label preview 2024-02-15 11:50:41 -08:00
snipe e5355db672 Merge remote-tracking branch 'origin/develop' 2024-02-15 18:39:57 +00:00
snipe aef45a90b2 Merge pull request #14278 from uberbrady/allowlist_and_db_prefix_for_restore
Allowlist and db prefix for restore
2024-02-15 14:50:44 +00:00
snipe 9221641bba Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-02-15 14:50:37 +00:00
snipe 6bca1e3b22 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:48:07 +00:00
snipe 00cea3eb3c Merge remote-tracking branch 'origin/develop' 2024-02-15 14:45:30 +00:00
snipe 19b52a2f24 Merge pull request #14279 from snipe/fixes/clearer_ui_for_fieldsets
Improved UI for fieldsets
2024-02-15 14:19:34 +00:00
snipe 6237f6192c Added fieldset button
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:14:18 +00:00
snipe cc58d4c3ad Nicer form UI
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:08:06 +00:00
Brady Wetherington 6a78706a3e New 'sanitize' version of backup-restore tool. Optional. 2024-02-15 13:27:18 +00:00
snipe 060c59bf9d Merge remote-tracking branch 'origin/develop' 2024-02-15 12:30:22 +00:00
snipe 6fd3c494be Merge pull request #14277 from snipe/fixes/change_log_level_to_info
Lower log level to warning on webhook failure
2024-02-15 12:28:57 +00:00
snipe 8adfa8dd83 Change exceptions to warnings
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 12:28:12 +00:00
snipe 6c9001df09 Changed log level in checkoutable listener to info
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 12:20:14 +00:00
snipe d6159d8cb5 Merge remote-tracking branch 'origin/develop' 2024-02-15 09:59:23 +00:00
snipe 81b9753b79 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/all-defer.js
#	public/mix-manifest.json
2024-02-15 09:58:09 +00:00
snipe d8b1eec91b Merge pull request #14197 from inietov/fixes/component_licenses_checkouts_not_displayed_in_asset_history
Fixes #14010 Adds Components and Licenses logs to Assets history view
2024-02-15 09:57:46 +00:00
snipe b871813cfd Merge pull request #14172 from spencerrlongg/bug/sc-23514
Resolve Duplicate Activity Logs for Imports
2024-02-15 09:54:43 +00:00
snipe 6f7dce53cc Merge pull request #14276 from snipe/bug/sc-24828
Fixed ambiguous query on supplier_id in maintenances
2024-02-15 09:52:23 +00:00
snipe 13d2af2155 Fixed ambiguous query on supplier_id in maintenances
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 09:51:14 +00:00
snipe bf3794822c Updated assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 09:47:45 +00:00
snipe 1fa8fba5fe Merge pull request #14268 from Godmartinz/checked-out-notif-fix
Fixed the notification listener
2024-02-15 08:29:15 +00:00
snipe 3d7697da6f Merge pull request #14275 from snipe/snyk/14270
Upgrade webpack from 5.89.0 to 5.90.0
2024-02-15 08:28:26 +00:00
snipe 42cf17d3bf Upgrade webpack from 5.89.0 to 5.90.0
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 08:27:52 +00:00
snipe 783e37f06d Merge pull request #14274 from snipe/snyk/14269
Upgrade alpinejs from 3.13.3 to 3.13.5
2024-02-15 08:21:58 +00:00
snipe 9a4cda0bf6 Upgrade alpinejs from 3.13.3 to 3.13.5
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 08:21:19 +00:00
snipe b6279af1d8 Merge pull request #14272 from marcusmoore/testing/update-state-helper-name
Updated testing helper name
2024-02-15 08:19:40 +00:00
Marcus Moore bc0a7542ac Change enableWebhook to enableSlackWebhook 2024-02-14 18:17:34 -08:00
akemidx 232fad0145 query and progress notes 2024-02-14 20:00:23 -05:00
Godfrey M 5cbcac28b1 adds custom fields as an example in label settings 2024-02-14 16:53:38 -08:00
Marcus Moore aec59f2da6 Update assertion to be more correct 2024-02-14 12:27:42 -08:00
Marcus Moore 905df5ec25 Consolidate test cases 2024-02-14 12:14:27 -08:00
Marcus Moore bacfdc5049 Scaffold additional tests 2024-02-14 11:33:03 -08:00
Marcus Moore 3cc72021b6 Move notification test to notifications test suite 2024-02-14 11:16:31 -08:00
spencerrlongg 115e0fc119 implode submitted arrays to save 2024-02-14 13:15:23 -06:00
Marcus Moore 4354e126b1 Scaffold tests 2024-02-14 11:11:08 -08:00
Marcus Moore 02f39472f9 Remove duplicate test 2024-02-14 11:10:59 -08:00
Marcus Moore d7aed2edc9 Remove unneeded code 2024-02-14 11:10:45 -08:00
Marcus Moore af513946a2 Implement test 2024-02-14 10:48:49 -08:00
Marcus Moore 7bfd02054b Remove duplicate authorization check 2024-02-14 10:48:41 -08:00
spencerrlongg 1ceb703129 rm var 2024-02-14 12:44:09 -06:00
spencerrlongg fb28882f65 trim potential spaces 2024-02-14 11:59:14 -06:00
spencerrlongg d9c61fdb02 validation msg 2024-02-14 11:52:25 -06:00
spencerrlongg 72c118a70f cleanup 2024-02-14 11:41:46 -06:00
Godfrey M b136e9e29d fix the listener to fire when checking in 2024-02-14 09:16:20 -08:00
snipe 50c910461a Merge remote-tracking branch 'origin/develop' 2024-02-14 11:36:28 +00:00
snipe 88a84e9350 Merge pull request #14264 from snipe/feature/sc-24816
Added serial and status label to asset maintenances page and API
2024-02-14 10:37:01 +00:00
snipe 43bb8ae0a8 Added string
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:44 +00:00
snipe 638071dadd Check the asset is not deleted when creating the permissions array
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:38 +00:00
snipe 8a4c90ade8 Check that the asset is not deleted
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:25 +00:00
snipe 1dfa1da0ee Disable the edit button if permission is not allowed
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:10 +00:00
snipe 71ebade641 Added serial and status to searchable relations
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:59:10 +00:00
snipe b86b05c4fc Added serial and status fields to presenter
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:55 +00:00
snipe 35a70988cb Added sorting by status label
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:39 +00:00
snipe 56ba26eb45 Disallow editing of deleted assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:07 +00:00
snipe adb8be9345 Added bootstrap formatting to show deleted assets more clearly
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:57:57 +00:00
snipe abc9ee22c6 Added serial and status to transformer
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:48:02 +00:00
snipe e5ee9760c0 I don’t think we use these anyore because of the modifiers on date
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:47:33 +00:00
snipe 7868a8c174 Added serial for API controller
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:47:13 +00:00
snipe 03df4cec45 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:46:43 +00:00
spencerrlongg 25241542d2 progress, going to sleep 2024-02-14 02:12:31 -06:00
spencerrlongg 57a75e68b9 maybe i do the inverse here? 2024-02-14 00:52:50 -06:00
Marcus Moore ad1846fed6 Implement tests 2024-02-13 17:50:26 -08:00
spencerrlongg dcf2168454 initial stuff, need to switch branches 2024-02-13 19:35:37 -06:00
Marcus Moore 9ab56fe9ca Implement tests 2024-02-13 17:04:53 -08:00
Marcus Moore f708b8b299 Implement test 2024-02-13 14:30:26 -08:00
akemidx c5e8d1c276 custom report tinker 2024-02-13 17:09:51 -05:00
akemidx 4093327b7f adding in Last Checkin Date to Hardware view and index 2024-02-13 16:52:12 -05:00
Marcus Moore 391b832613 Implement test 2024-02-13 13:34:55 -08:00
Marcus Moore b653d19579 Implement test 2024-02-13 13:29:54 -08:00
akemidx 4a57cfaf3e commenting out the nulling of last checkout date on checkin page and API 2024-02-13 16:21:18 -05:00
Marcus Moore 31a75bd252 Add some assertions 2024-02-13 13:17:02 -08:00
snipe e2c3386c33 Merge remote-tracking branch 'origin/develop' 2024-02-13 20:37:17 +00:00
Marcus Moore 0506f3bef9 Scaffold additional tests 2024-02-13 12:35:31 -08:00
snipe a866d6b16e Merge pull request #14261 from snipe/fixed/missing_location_string
Fixed incorrect string for location not existing
2024-02-13 20:29:32 +00:00
snipe a76a69d085 Fixed incorrect string for location not existing
Signed-off-by: snipe <snipe@snipe.net>
2024-02-13 20:23:32 +00:00
Marcus Moore 852b0b3f11 Scaffold additional tests 2024-02-13 12:15:59 -08:00
Marcus Moore 307b39bd38 Implement tests around asset check in 2024-02-13 12:03:27 -08:00
spencerrlongg d55358652b cleanup for pr 2024-02-13 13:45:56 -06:00
Brady Wetherington 955f75f733 Fixes for the prefix-guessing and sanitizing. 2024-02-13 16:45:24 +00:00
snipe 280e778749 Merge remote-tracking branch 'origin/develop' 2024-02-13 13:46:52 +00:00
snipe 60ba898167 Merge pull request #14255 from marcusmoore/chore/sc-24805
Added LDAP group tag to LDAP tests
2024-02-13 13:42:51 +00:00
snipe 2c9d5b9ea3 Merge pull request #14247 from ubc-cpsc/bugfix/CVE-2023-37260
Fixes CVE-2023-37260 upgrading league/oauth2-server
2024-02-13 13:40:53 +00:00
snipe eb6e2636b5 Merge pull request #14246 from ubc-cpsc/bugfix/CVE-2022-24894
Fixes CVE-2022-24894 by upgrading symfony/http-kernel
2024-02-13 13:40:33 +00:00
snipe 4c1964d509 Merge pull request #14245 from ubc-cpsc/bugfix/CVE-2024-24821
Fixes CVE-2024-24821 by upgrading composer/composer
2024-02-13 13:39:16 +00:00
snipe 7547277352 Merge pull request #14236 from snipe/jerm/upgrade-script-enhancements
Change how we check forward-looking upgrade requirements
2024-02-13 13:38:35 +00:00
snipe 3e00bc49fd Merge pull request #14250 from mauro-miatello/develop
Cleaned up navbar-custom-menu
2024-02-13 13:33:03 +00:00
snipe 99e0b65de7 Merge pull request #14256 from marcusmoore/bug/sc-24790
Fixed accessory check in emails being sent when setting disabled
2024-02-13 13:32:42 +00:00
Brady Wetherington 70ef904951 Actually got this pretty close to being able to do a restore 2024-02-13 12:16:15 +00:00
Brady Wetherington fcf023e3d2 WIP: trying to get prefixing and sanitization working 2024-02-13 12:16:15 +00:00
Brady Wetherington 8c882ddead Starting to abstract out the SQL Streaming logic into its own class 2024-02-13 12:16:15 +00:00
Brady Wetherington 7d136f9970 Initial rough stabs at prefix removal and SQL sanitization 2024-02-13 12:16:15 +00:00
Jeremy Price f4c1460c2b remove help text options until i put together the help text 2024-02-12 19:18:26 -08:00
Jeremy Price bb2e1de0a8 Change how we check forward-looking upgrade requirements
In https://github.com/snipe/snipe-it/pull/14128 we added the capability
for the upgrade.php script to check version requirements _before_
downloading the new source, to help keep from breaking installations.

Turns out, `file_get_contents()` isn't a reliable way to grab a url, because
some systems have `allow_url_fopen` turned off in their PHP
configurations.

In this iteration, we swap that out for a curl function, while also
adding more error handling, the ability to entirely skip the
PHP version checks if for some reason you Just Can't query the upgrade
json correctly, as well as adding a lot of helpful text around the whole
issue.

Additionally, I've added some error checking around DB backups and
initial artisan down-ing, since shell_exec would happily march right
past any errors.
2024-02-12 19:18:26 -08:00
Marcus Moore c81bc1d2ee Scaffold tests around asset check in 2024-02-12 17:54:22 -08:00
Marcus Moore 7154d23759 Pass the correct variable to the route helper 2024-02-12 16:45:18 -08:00
Marcus Moore df23fd0dee Remove usused import 2024-02-12 16:35:54 -08:00
Marcus Moore adfb8895df Improve factory state name 2024-02-12 16:31:32 -08:00
Marcus Moore c8e12ddb5c Remove bug in factory state 2024-02-12 16:30:09 -08:00
Marcus Moore 5b181ecea7 Remove old comment 2024-02-12 16:29:34 -08:00
Marcus Moore 728aaaab20 Ensure accessory check in emails are not sent when the setting is disabled 2024-02-12 16:22:59 -08:00
Marcus Moore 095a7d9b34 Scaffold tests around accessory check in 2024-02-12 12:54:48 -08:00
Marcus Moore cf53f2778f Add LDAP test cases to group 2024-02-12 12:28:27 -08:00
snipe 65e20282b6 Merge pull request #14251 from snipe/dependabot/github_actions/develop/codacy/codacy-analysis-cli-action-4.4.0
Bump codacy/codacy-analysis-cli-action from 4.3.0 to 4.4.0
2024-02-12 08:35:30 +00:00
dependabot[bot] 405c5b5ad0 Bump codacy/codacy-analysis-cli-action from 4.3.0 to 4.4.0
Bumps [codacy/codacy-analysis-cli-action](https://github.com/codacy/codacy-analysis-cli-action) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/codacy/codacy-analysis-cli-action/releases)
- [Commits](https://github.com/codacy/codacy-analysis-cli-action/compare/v4.3.0...v4.4.0)

---
updated-dependencies:
- dependency-name: codacy/codacy-analysis-cli-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 08:33:17 +00:00
MrM 6f0fe16b87 Update default.blade.php
removed some repeated attributes
2024-02-11 18:45:37 +01:00
snipe 111daffc17 Merge pull request #14188 from spencerrlongg/bug/14146
Fixes Default Location Being Set During Asset Creation and Checkout
2024-02-10 11:47:46 +00:00
Joël Pittet b8a478f558 Fixes by CVE-2023-37260 upgrading league/oauth2-server 2024-02-09 17:24:07 -08:00
Joël Pittet 9f7084d077 Revert "Fixes by CVE-2022-24894 upgrading league/oauth2-server"
This reverts commit 0840cd3df3.
2024-02-09 17:22:36 -08:00
Joël Pittet 0840cd3df3 Fixes by CVE-2022-24894 upgrading league/oauth2-server 2024-02-09 17:21:24 -08:00
Joël Pittet cefdaf9a9b Fixes CVE-2022-24894 2024-02-09 17:17:44 -08:00
Joël Pittet 13335b19e9 Fixes CVE-2024-24821 2024-02-09 17:04:34 -08:00
snipe 6e471a27e7 Merge remote-tracking branch 'origin/develop' 2024-02-09 21:10:27 +00:00
snipe 513ea67e7d Merge pull request #14244 from snipe/fixes/null_barcode_if_hard_deleted
Return null if asset was hard-deleted/purged
2024-02-09 21:09:17 +00:00
snipe 3868e711f4 Return null if asset was hard-deleted/purged
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 21:08:07 +00:00
snipe f33f712de7 Merge remote-tracking branch 'origin/develop' 2024-02-09 21:00:16 +00:00
snipe c12e1f6d6c Merge pull request #14243 from snipe/fixes/reports_controller_when_item_is_deleted
Fixed ReportsController to not try to return a serial if the item doesn’t exist
2024-02-09 20:54:37 +00:00
snipe 479abd5231 Do not try to return a serial if the item doesn’t exist
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:53:33 +00:00
snipe a60a24a4a8 Merge remote-tracking branch 'origin/develop' 2024-02-09 20:42:48 +00:00
snipe 55b3050ca8 Re-applied previous withTrashed PR
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:37:18 +00:00
snipe 2c996a8508 Merge pull request #14241 from snipe/revert-14240-feature/sc-24786
Revert "Fixed barcodes crashing if asset was deleted"
2024-02-09 20:36:02 +00:00
snipe 84f8eee869 Revert "Fixed barcodes crashing if asset was deleted" 2024-02-09 20:35:45 +00:00
snipe 590c19dbd7 Merge pull request #14240 from snipe/feature/sc-24786
Feature/sc 24786
2024-02-09 20:28:31 +00:00
snipe fa47707974 Use withTrashed() to get the barcode on deleted assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:26:49 +00:00
snipe ca62481083 Added button and route
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:23:13 +00:00
snipe 1c3306046c Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/mix-manifest.json
2024-02-08 23:14:54 +00:00
snipe f4fc845375 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-08 23:06:47 +00:00
snipe a7af987322 Merge pull request #14229 from Godmartinz/select2-target-fix
Fixed select inputs un-select2-ifying on mobile
2024-02-08 23:04:55 +00:00
Godfrey M c4eaae923a removed vue comments since they do not apply anymore 2024-02-08 15:02:47 -08:00
snipe 849ba02516 Merge pull request #14187 from Godmartinz/general-hook_fix
Fixed the general webhook not notifying anymore
2024-02-08 23:01:36 +00:00
Godfrey Martinez 9dcd14a712 Merge branch 'develop' into general-hook_fix 2024-02-08 14:59:25 -08:00
snipe 3412b4dc5a Merge remote-tracking branch 'origin/develop' 2024-02-08 14:58:24 +00:00
snipe a3b96aff1f Merge pull request #14233 from uberbrady/prevent_svg_injection_with_fake_extensions_rebased
Fixes file upload XSS vulnerability [sc-24156]
2024-02-08 14:56:59 +00:00
Brady Wetherington 9bb191f29f Fixes file upload XSS vulnerability [sc-24156] 2024-02-08 14:30:40 +00:00
snipe 4a43ccfa92 Merge pull request #14228 from akemidx/bug/sc-23516
Fixed: 404 Error on Importer When Uploading a .csv Under Certain Circumstance
2024-02-08 13:43:07 +00:00
spencerrlongg bcfa913450 condition makes this work, needs more testing 2024-02-07 20:03:37 -06:00
spencerrlongg 43d8474caa a note to remember this tomorrow 2024-02-06 17:45:46 -06:00
Godfrey M b73e8642d3 removed unnecessary changes 2024-02-06 13:06:21 -08:00
Godfrey M cfe2277a64 forgot to remove comment line 2024-02-06 13:05:04 -08:00
akemidx e776c2cffa formatting the button tag 2024-02-06 15:51:04 -05:00
Godfrey M ca59bc3c9c removes if statement that prevents select2-ifying inputs 2024-02-06 12:46:17 -08:00
akemidx 575362f4dc adding in line to null out the active file on click of button 2024-02-06 15:38:07 -05:00
snipe f0139c5e41 Merge remote-tracking branch 'origin/develop' 2024-02-06 18:25:19 +00:00
snipe 5f8ac66036 Merge pull request #14223 from Godmartinz/user-bulk-edit
Fixed Select2 functionality in User bulk check-in Delete User
2024-02-06 18:22:46 +00:00
snipe a49e66c689 Merge remote-tracking branch 'origin/develop' 2024-02-06 16:42:16 +00:00
snipe 1630e4bc2f Merge pull request #14227 from snipe/fixes/bulk_update_by_audit_interval
Switch to bulk updating to handle audit interval updates
2024-02-06 16:06:08 +00:00
snipe bf674a0f4d Removed backticks
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 15:58:36 +00:00
snipe b170755c3d Switch to bulk updating to handle audit interval updates
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 15:52:46 +00:00
snipe b25612bbac Merge remote-tracking branch 'origin/develop' 2024-02-06 09:09:39 +00:00
snipe a43183ff96 Increased chunk
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 09:09:27 +00:00
Godfrey M 5aa34695a1 z 2024-02-05 16:41:29 -08:00
snipe 3b36372a66 Merge pull request #14222 from marcusmoore/tests/company-get-id-for-current-user
Added tests around getIdForCurrentUser method
2024-02-05 22:04:08 +00:00
snipe e763693829 Merge remote-tracking branch 'origin/develop' 2024-02-05 20:51:37 +00:00
snipe e08d60ed18 Merge pull request #14221 from snipe/bug/sc-24749
Chunk data to reduce memory on large datasets when updating `next_audit_date`
2024-02-05 20:50:41 +00:00
Marcus Moore 9e6e2de71e Add docblock 2024-02-05 12:49:29 -08:00
snipe b49935701b Chunk data to reduce memory on large datasets when updating next_audit_date
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 20:48:24 +00:00
Marcus Moore 76cc5995d9 Write tests around getIdForCurrentUser method [sc-24748] 2024-02-05 12:38:33 -08:00
snipe c0fbf106ed Merge remote-tracking branch 'origin/develop' 2024-02-05 19:13:17 +00:00
snipe 7b4020c5e9 Updated string paths for URL
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 19:09:16 +00:00
snipe 383e3829e5 Merge remote-tracking branch 'origin/develop' 2024-02-05 19:04:40 +00:00
snipe 91356af838 Merge pull request #14220 from snipe/bug/sc-24740
Removed loading of assets for label count
2024-02-05 19:02:10 +00:00
snipe 4c967a43a7 Removed loading of assets for label count
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:56:51 +00:00
snipe deca842eb0 Merge remote-tracking branch 'origin/develop' 2024-02-05 18:35:56 +00:00
snipe 44366746dd Merge pull request #14219 from snipe/fixes/resurface_load_remote
Fixed #14185 - Resurfaced `load_remote` in admin
2024-02-05 18:33:11 +00:00
snipe 3806cec10e Updated language
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:28:05 +00:00
snipe a800fa07f9 Removed duplicate string
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:27:58 +00:00
snipe 32c360f032 Re-added UI for load_remote
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:23:52 +00:00
snipe 25f1445afc Merge branch 'master' of https://github.com/snipe/snipe-it 2024-02-05 17:26:54 +00:00
snipe 0042359e5f Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:23:30 +00:00
snipe cf2e2bfbc1 Updated prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:23:21 +00:00
snipe 60f9a8be9a 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
2024-02-05 17:22:58 +00:00
snipe 8316db9702 Merge pull request #13805 from gitgrimbo/more_line_based_markdown
Use parseEscapedMarkedownInline for more views
2024-02-05 17:19:41 +00:00
snipe 1a480435de Merge pull request #14190 from marcusmoore/bug/sc-24685
Fixed consumable checkout via API not sending notification
2024-02-05 17:16:57 +00:00
snipe ebc373744a Merge pull request #14218 from snipe/security/alpine_3_18_6
Security upgrade alpine from 3.18.5 to 3.18.6 #14178
2024-02-05 17:16:18 +00:00
snipe 650aa25659 Merge pull request #14191 from Godmartinz/googlechat_webhook
Added support for Google Chat notifications
2024-02-05 17:10:54 +00:00
snipe 212dd06948 Security upgrade alpine from 3.18.5 to 3.18.6 #14178
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:07:31 +00:00
snipe b6eea2e170 Merge pull request #14217 from snipe/security/snyk_bs_table_14199
Upgrade bootstrap-table from 1.22.1 to 1.22.2 #14199
2024-02-05 16:59:51 +00:00
snipe 688017bd8a Upgrade bootstrap-table from 1.22.1 to 1.22.2
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 16:59:01 +00:00
snipe 58a9c27342 Merge remote-tracking branch 'origin/develop' 2024-02-05 16:31:32 +00:00
snipe f52b00256d Merge pull request #14213 from snipe/fixes/orientate_on_exif
Fixed FD-40296 - mobile uploads sometimes uploading with incorrect orientation
2024-02-05 16:22:03 +00:00
snipe 2d48d96b3c Merge remote-tracking branch 'origin/develop' 2024-02-05 16:10:54 +00:00
snipe abd13a1140 Merge pull request #14216 from snipe/bug/sc-24727
Fixed company asset counts for dashboard widget
2024-02-05 16:07:40 +00:00
snipe 5882d71f9b Fix company asset counts for dashboard widget
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 15:55:53 +00:00
snipe dc92bbf61e Merge pull request #14215 from snipe/bug/sc-24726
Removed initial check for assets, licenses, etc
2024-02-05 15:54:45 +00:00
snipe e6fdeb0e8a Removed initial check for assets, licenses, etc
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 15:30:18 +00:00
snipe 3b948c7b7e Add orientate to image upload
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 14:29:43 +00:00
snipe d965fe759a Fixed #14211 - duplicate array key ldap_emp_num
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 12:28:24 +00:00
Ivan Nieto Vivanco 5e4a3379a9 Adds Components and Licenses logs to Assets history view 2024-02-01 01:35:25 -06:00
Godfrey M 9dc428b720 ran composer update on the package since the version was explicitized 2024-01-31 17:30:56 -08:00
Godfrey M 8a5b469ff8 adds # to webhook channel trigger 2024-01-31 12:54:30 -08:00
Godfrey M ed06f32a7a adds # to webhook channel trigger 2024-01-31 12:53:56 -08:00
Godfrey M a876703f8f spacing 2024-01-31 10:48:48 -08:00
Godfrey M 01bb8d8c9a spacing 2024-01-31 10:48:23 -08:00
Godfrey M f452204d62 applies a conditional to have the the integration test button function properly 2024-01-31 10:47:25 -08:00
Ivan Nieto Vivanco 51ae485f20 When asset is checked-in the assigned licenses also are taken from the user 2024-01-30 19:44:38 -06:00
Godfrey M 0eabb147b2 adds google notif to consumable check out 2024-01-30 13:19:28 -08:00
Marcus Moore 2e0e39ccc8 Ensure notification is sent when consumable is checked out via api 2024-01-30 13:19:20 -08:00
spencerrlongg 63e733f0d6 changes from a note to a source 2024-01-30 15:14:59 -06:00
Godfrey M 2406d2cfdb adds google notifs to license seats check in and out 2024-01-30 13:14:24 -08:00
Godfrey M e074ca0bf9 adds google notifs to accessories check in and out 2024-01-30 12:57:45 -08:00
Marcus Moore 13c37e708f Implement tests around consumable checkout 2024-01-30 12:43:20 -08:00
Marcus Moore 3f76d65b95 Improve test method name 2024-01-30 12:43:07 -08:00
Godfrey M 87bce0c097 adds google notifs for asset check in and out 2024-01-30 12:38:17 -08:00
Marcus Moore 6d41c8cf67 Merge branch 'develop' into bug/sc-24685 2024-01-30 11:44:36 -08:00
Godfrey M 22385a8e35 fix for general webhook not notifiying anymore 2024-01-30 10:47:18 -08:00
snipe 77fa213ccd Merge pull request #14181 from marcusmoore/bug/sc-24671
Fixed accessory checkout via API not sending notification and not adhering to qty limit
2024-01-30 14:24:42 +00:00
spencerrlongg d0a82adc3f changed condition 2024-01-29 20:44:26 -06:00
Marcus Moore de2aa903c5 Scaffold tests 2024-01-29 17:56:55 -08:00
Marcus Moore 42ec2548c9 Fire event when accessory checked out via API
Brings behavior in line with GUI controller
2024-01-29 17:03:19 -08:00
Marcus Moore a2cba67f4e Improve assertion 2024-01-29 16:59:57 -08:00
Marcus Moore 7d45cfff2c Ensure accessory available when checking out via api 2024-01-29 16:49:09 -08:00
Marcus Moore f1ab8253f0 Implement tests included two currently failing tests 2024-01-29 16:48:40 -08:00
Marcus Moore e5d3df7d24 Scaffold accessory checkout tests for api 2024-01-29 15:59:23 -08:00
Marcus Moore 987676df08 Implement additional tests 2024-01-29 15:56:18 -08:00
Marcus Moore f16f62f76c Scaffold and implement some tests around accessory checkout 2024-01-29 14:21:30 -08:00
Godfrey M dfa33f651a webhook test works 2024-01-29 12:58:09 -08:00
Godfrey M 30c2927987 merged develope 2024-01-29 11:21:00 -08:00
Godfrey M e8159d97fa changes 2024-01-29 11:12:25 -08:00
spencerrlongg 1248260df3 i don't think the output needs to separately escaped, the entire statement is already wrapped in {{}} 2024-01-26 12:33:35 -06:00
spencerrlongg 4cb804cf03 get rid of e() on store and update 2024-01-26 11:56:02 -06:00
spencerrlongg 2b0dd8851c probably needs more testing... but should work 2024-01-26 11:47:09 -06:00
spencerrlongg 2deba17d91 that's all of 'em 2024-01-25 20:04:02 -06:00
spencerrlongg 3574ef5bb9 a few more imports, component sample 2024-01-25 19:54:53 -06:00
spencerrlongg abf13f1619 revert spacing 2024-01-25 19:37:59 -06:00
spencerrlongg 4a7df470f0 this works 2024-01-25 19:34:41 -06:00
spencerrlongg c9101f4d97 initial work, not working yet 2024-01-25 18:18:24 -06:00
Godfrey M 6e9a46e582 working on Chat integration test 2024-01-24 15:50:36 -08:00
Godfrey M 1d3124f89f adding a test variable for test methods 2024-01-24 14:38:45 -08:00
Godfrey M ada1a593a4 add google placeholder 2024-01-24 11:29:32 -08:00
spencerrlongg 41e0275c95 htmlentities() 2024-01-03 12:42:36 -06:00
gitgrimbo d556f0d275 Use parseEscapedMarkedownInline for more views 2023-10-27 15:37:56 +01:00
990 changed files with 16727 additions and 9718 deletions
+1
View File
@@ -86,6 +86,7 @@ COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
BS_TABLE_DEEPLINK=true
# --------------------------------------------
# OPTIONAL: SECURITY HEADER SETTINGS
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.3.0
uses: codacy/codacy-analysis-cli-action@v4.4.0
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations
+2 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:3.18.5
FROM alpine:3.18.6
# Apache + PHP
RUN apk add --no-cache \
apache2 \
@@ -29,6 +29,7 @@ RUN apk add --no-cache \
php81-sodium \
php81-redis \
php81-pecl-memcached \
php81-exif \
curl \
wget \
vim \
+12 -8
View File
@@ -1,7 +1,7 @@
![snipe-it-by-grok](https://github.com/snipe/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/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk)
[![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
@@ -11,7 +11,8 @@ It is built on [Laravel 8](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
__This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
> [!TIP]
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
-----
@@ -21,7 +22,7 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
<!-- [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) -->
-----
### User's Manual
@@ -32,8 +33,9 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
**PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
> [!IMPORTANT]
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
>
-----
### Upgrading
@@ -57,6 +59,9 @@ Please see the [translations documentation](https://snipe-it.readme.io/docs/tran
Since the release of the JSON REST API, several third-party developers have been developing modules and libraries to work with Snipe-IT.
> [!NOTE]
> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
@@ -73,8 +78,6 @@ Since the release of the JSON REST API, several third-party developers have been
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
-----
@@ -92,4 +95,5 @@ The ERD is available [online here](https://drawsql.app/templates/snipe-it).
### Security
To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.
> [!IMPORTANT]
> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.**
+15 -2
View File
@@ -45,8 +45,21 @@ DB_PASSWORD={}
Now you are ready to run the entire test suite from your terminal:
`php artisan test`
```shell
php artisan test
````
To run individual test files, you can pass the path to the test that you want to run:
`php artisan test tests/Unit/AccessoryTest.php`
```shell
php artisan test tests/Unit/AccessoryTest.php
```
Some tests, like ones concerning LDAP, are marked with the `@group` annotation. Those groups can be run, or excluded, using the `--group` or `--exclude-group` flags:
```shell
php artisan test --group=ldap
php artisan test --exclude-group=ldap
```
This can be helpful if a set of tests are failing because you don't have an extension, like LDAP, installed.
+184 -21
View File
@@ -5,6 +5,151 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use ZipArchive;
class SQLStreamer {
private $input;
private $output;
// embed the prefix here?
public ?string $prefix;
private bool $reading_beginning_of_line = true;
public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
public array $tablenames = [];
private bool $should_guess = false;
private bool $statement_is_permitted = false;
public function __construct($input, $output, string $prefix = null)
{
$this->input = $input;
$this->output = $output;
$this->prefix = $prefix;
}
public function parse_sql(string $line): string {
// take into account the 'start of line or not' setting as an instance variable?
// 'continuation' lines for a permitted statement are PERMITTED.
if($this->statement_is_permitted && $line[0] === ' ') {
return $line;
}
$table_regex = '`?([a-zA-Z0-9_]+)`?';
$allowed_statements = [
"/^(DROP TABLE (?:IF EXISTS )?)`$table_regex(.*)$/" => false,
"/^(CREATE TABLE )$table_regex(.*)$/" => true, //sets up 'continuation'
"/^(LOCK TABLES )$table_regex(.*)$/" => false,
"/^(INSERT INTO )$table_regex(.*)$/" => false,
"/^UNLOCK TABLES/" => false,
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
"/^\\)[a-zA-Z0-9_= ]*;$/" => false
// ^^^^^^ that bit should *exit* the 'perimitted' black
];
foreach($allowed_statements as $statement => $statechange) {
// $this->info("Checking regex: $statement...\n");
$matches = [];
if (preg_match($statement,$line,$matches)) {
$this->statement_is_permitted = $statechange;
// matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement
// (with of course 0 being "the whole match")
if (@$matches[2]) {
// print "Found a tablename! It's: ".$matches[2]."\n";
if ($this->should_guess) {
@$this->tablenames[$matches[2]] += 1;
continue; //oh? FIXME
} else {
$cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/','',$matches[2]);
$line = preg_replace($statement,'$1`'.$cleaned_tablename.'`$3' , $line);
}
} else {
// no explicit tablename in this one, leave the line alone
}
//how do we *replace* the tablename?
// print "RETURNING LINE: $line";
return $line;
}
}
// all that is not allowed is denied.
return "";
}
//this is used in exactly *TWO* places, and in both cases should return a prefix I think?
// first - if you do the --sanitize-only one (which is mostly for testing/development)
// next - when you run *without* a guessed prefix, this is run first to figure out the prefix
// I think we have to *duplicate* the call to be able to run it again?
public static function guess_prefix($input):string
{
$parser = new self($input, null);
$parser->should_guess = true;
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
//can't use 'users' because the 'accessories_users' table?
// can't use 'assets' because 'ver1_components_assets'
foreach($check_tables as $check_table => $_ignore) {
foreach ($parser->tablenames as $tablename => $_count) {
// print "Comparing $tablename to $check_table\n";
if (str_ends_with($tablename,$check_table)) {
// print "Found one!\n";
$check_tables[$check_table] = substr($tablename,0,-strlen($check_table));
}
}
}
$guessed_prefix = null;
foreach ($check_tables as $clean_table => $prefix_guess) {
if(is_null($prefix_guess)) {
print("Couldn't find table $clean_table\n");
die();
}
if(is_null($guessed_prefix)) {
$guessed_prefix = $prefix_guess;
} else {
if ($guessed_prefix != $prefix_guess) {
print("Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n");
die();
}
}
}
return $guessed_prefix;
}
public function line_aware_piping(): int
{
$bytes_read = 0;
if (! $this->input) {
throw new \Exception("No Input available for line_aware_piping");
}
while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) {
$bytes_read += strlen($buffer);
if ($this->reading_beginning_of_line) {
// \Log::debug("Buffer is: '$buffer'");
$cleaned_buffer = $this->parse_sql($buffer);
if ($this->output) {
$bytes_written = fwrite($this->output, $cleaned_buffer);
if ($bytes_written === false) {
throw new \Exception("Unable to write to pipe");
}
}
}
// if we got a newline at the end of this, then the _next_ read is the beginning of a line
if($buffer[strlen($buffer)-1] === "\n") {
$this->reading_beginning_of_line = true;
} else {
$this->reading_beginning_of_line = false;
}
}
return $bytes_read;
}
}
class RestoreFromBackup extends Command
{
/**
@@ -12,10 +157,13 @@ class RestoreFromBackup extends Command
*
* @var string
*/
// FIXME - , stripping prefixes and nonstandard SQL statements. Without --prefix, guess and return the correct prefix to strip
protected $signature = 'snipeit:restore
{--force : Skip the danger prompt; assuming you enter "y"}
{filename : The zip file to be migrated}
{--no-progress : Don\'t show a progress bar}';
{--no-progress : Don\'t show a progress bar}
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
/**
* The console command description.
@@ -34,8 +182,6 @@ class RestoreFromBackup extends Command
parent::__construct();
}
public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
/**
* Execute the console command.
*
@@ -55,7 +201,7 @@ class RestoreFromBackup extends Command
return $this->error('Missing required filename');
}
if (! $this->option('force') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
if (! $this->option('force') && ! $this->option('sanitize-guess-prefix') && ! $this->confirm('Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!')) {
return $this->error('Data loss not confirmed');
}
@@ -158,11 +304,11 @@ class RestoreFromBackup extends Command
}
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
$last_pos = strrpos($raw_path, $dir.'/');
$last_pos = strrpos($raw_path, $dir . '/');
if ($last_pos !== false) {
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
$interesting_files[$raw_path] = ['dest' =>$dir, 'index' => $i];
$interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
continue 2;
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
// we don't care about that; we just want files with the appropriate prefix
@@ -171,7 +317,7 @@ class RestoreFromBackup extends Command
}
}
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', ];
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
foreach (array_merge($private_files, $public_files) as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
@@ -180,8 +326,8 @@ class RestoreFromBackup extends Command
$last_pos = strrpos($raw_path, $file); // no trailing slash!
if ($last_pos !== false) {
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
if (! in_array($extension, $good_extensions)) {
$this->warn('Potentially unsafe file '.$raw_path.' is being skipped');
if (!in_array($extension, $good_extensions)) {
$this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
$boring_files[] = $raw_path;
continue 2;
}
@@ -196,7 +342,6 @@ class RestoreFromBackup extends Command
}
$boring_files[] = $raw_path; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
} // end of pre-processing the ZIP file for-loop
// print_r($interesting_files);exit(-1);
if (count($sqlfiles) != 1) {
@@ -208,6 +353,17 @@ class RestoreFromBackup extends Command
//older Snipe-IT installs don't have the db-dumps subdirectory component
}
$sql_stat = $za->statIndex($sqlfile_indices[0]);
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
$sql_contents = $za->getStream($sql_stat['name']); // maybe copy *THIS* thing?
// OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think?
if ($this->option('sanitize-guess-prefix')) {
$prefix = SQLStreamer::guess_prefix($sql_contents);
$this->line($prefix);
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
}
//how to invoke the restore?
$pipes = [];
@@ -228,6 +384,7 @@ class RestoreFromBackup extends Command
return $this->error('Unable to invoke mysql via CLI');
}
// I'm not sure about these?
stream_set_blocking($pipes[1], false); // use non-blocking reads for stdout
stream_set_blocking($pipes[2], false); // use non-blocking reads for stderr
@@ -238,9 +395,9 @@ class RestoreFromBackup extends Command
//$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
$sql_stat = $za->statIndex($sqlfile_indices[0]);
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
$sql_contents = $za->getStream($sql_stat['name']);
// FIXME - this feels like it wants to go somewhere else?
// and it doesn't seem 'right' - if you can't get a stream to the .sql file,
// why do we care what's happening with pipes and stdout and stderr?!
if ($sql_contents === false) {
$stdout = fgets($pipes[1]);
$this->info($stdout);
@@ -249,20 +406,27 @@ class RestoreFromBackup extends Command
return false;
}
$bytes_read = 0;
try {
while (($buffer = fgets($sql_contents, self::$buffer_size)) !== false) {
$bytes_read += strlen($buffer);
// \Log::debug("Buffer is: '$buffer'");
if ( $this->option('sanitize-with-prefix') === null) {
// "Legacy" direct-piping
$bytes_read = 0;
while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) {
$bytes_read += strlen($buffer);
// \Log::debug("Buffer is: '$buffer'");
$bytes_written = fwrite($pipes[0], $buffer);
if ($bytes_written === false) {
throw new Exception("Unable to write to pipe");
if ($bytes_written === false) {
throw new Exception("Unable to write to pipe");
}
}
} else {
$sql_importer = new SQLStreamer($sql_contents, $pipes[0], $this->option('sanitize-with-prefix'));
$bytes_read = $sql_importer->line_aware_piping();
}
} catch (\Exception $e) {
\Log::error("Error during restore!!!! ".$e->getMessage());
// FIXME - put these back and/or put them in the right places?!
$err_out = fgets($pipes[1]);
$err_err = fgets($pipes[2]);
\Log::error("Error OUTPUT: ".$err_out);
@@ -271,7 +435,6 @@ class RestoreFromBackup extends Command
$this->error($err_err);
throw $e;
}
if (!feof($sql_contents) || $bytes_read == 0) {
return $this->error("Not at end of file for sql file, or zero bytes read. aborting!");
}
@@ -303,7 +466,7 @@ class RestoreFromBackup extends Command
$fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
while (($buffer = fgets($fp, self::$buffer_size)) !== false) {
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
fwrite($migrated_file, $buffer);
}
fclose($migrated_file);
@@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class ToggleCustomfieldEncryption extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:customfield-encryption
{fieldname : the db_column_name of the field}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command should be used to convert an unencrypted custom field into a custom field and encrypt the associated data in the assets table for that column.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$fieldname = $this->argument('fieldname');
if ($field = CustomField::where('db_column', $fieldname)->first()) {
// If the field is not encrypted, make it encrypted and encrypt the data in the assets table for the
// corresponding field.
DB::transaction(function () use ($field) {
if ($field->field_encrypted == 0) {
$assets = Asset::whereNotNull($field->db_column)->get();
foreach ($assets as $asset) {
$asset->{$field->db_column} = encrypt($asset->{$field->db_column});
$asset->save();
}
$field->field_encrypted = 1;
$field->save();
// This field is already encrypted. Do nothing.
} else {
$this->error('The custom field ' . $field->db_column.' is already encrypted. No action was taken.');
}
});
// No matching column name found
} else {
$this->error('No matching results for unencrypted custom fields with db_column name: ' . $fieldname.'. Please check the fieldname.');
}
}
}
+32 -6
View File
@@ -11,6 +11,7 @@ use App\Models\CustomFieldset;
use App\Models\Depreciation;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\License;
use Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
use Image;
@@ -715,18 +716,19 @@ class Helper
*/
public static function checkLowInventory()
{
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
$licenses = License::where('min_amt', '>', 0)->get();
$avail_consumables = 0;
$items_array = [];
$all_count = 0;
foreach ($consumables as $consumable) {
$avail = $consumable->numRemaining();
if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($avail < ($consumable->min_amt) + $alert_threshold) {
if ($consumable->qty > 0) {
$percent = number_format((($avail / $consumable->qty) * 100), 0);
} else {
@@ -745,7 +747,7 @@ class Helper
foreach ($accessories as $accessory) {
$avail = $accessory->qty - $accessory->users_count;
if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0);
} else {
@@ -764,7 +766,7 @@ class Helper
foreach ($components as $component) {
$avail = $component->numRemaining();
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
if ($avail < ($component->min_amt) + $alert_threshold) {
if ($component->qty > 0) {
$percent = number_format((($avail / $component->qty) * 100), 0);
} else {
@@ -787,7 +789,7 @@ class Helper
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
if ($avail < ($asset_model->min_amt) + $alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $total_owned) * 100), 0);
} else {
@@ -803,6 +805,26 @@ class Helper
}
}
foreach ($licenses as $license){
$avail = $license->remaincount();
if ($avail < ($license->min_amt) + $alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $license->min_amt) * 100), 0);
} else {
$percent = 100;
}
$items_array[$all_count]['id'] = $license->id;
$items_array[$all_count]['name'] = $license->name;
$items_array[$all_count]['type'] = 'licenses';
$items_array[$all_count]['percent'] = $percent;
$items_array[$all_count]['remaining'] = $avail;
$items_array[$all_count]['min_amt'] = $license->min_amt;
$all_count++;
}
}
return $items_array;
}
@@ -820,7 +842,7 @@ class Helper
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif')) {
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
return $filetype;
}
@@ -1084,6 +1106,8 @@ class Helper
'jpeg' => 'far fa-image',
'gif' => 'far fa-image',
'png' => 'far fa-image',
'webp' => 'far fa-image',
'avif' => 'far fa-image',
// word
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
@@ -1119,6 +1143,8 @@ class Helper
case 'jpeg':
case 'gif':
case 'png':
case 'webp':
case 'avif':
return true;
break;
default:
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Accessory\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $accessoryId = null)
public function store(UploadFileRequest $request, $accessoryId = null)
{
if (config('app.lock_passwords')) {
@@ -45,30 +44,7 @@ class AccessoriesFilesController extends Controller
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\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('private_uploads/accessories/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AccessoriesTransformer;
@@ -278,7 +279,7 @@ class AccessoriesController extends Controller
public function checkout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
@@ -302,7 +303,7 @@ class AccessoriesController extends Controller
'note' => $request->get('note'),
]);
$accessory->logCheckout($request->input('note'), $user);
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
@@ -36,7 +36,8 @@ class AssetMaintenancesController extends Controller
{
$this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin');
$maintenances = AssetMaintenance::select('asset_maintenances.*')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@@ -47,7 +48,7 @@ class AssetMaintenancesController extends Controller
}
if ($request->filled('supplier_id')) {
$maintenances->where('supplier_id', '=', $request->input('supplier_id'));
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('asset_maintenance_type')) {
@@ -70,10 +71,13 @@ class AssetMaintenancesController extends Controller
'notes',
'asset_tag',
'asset_name',
'serial',
'user_id',
'supplier',
'is_warranty',
'status_label',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@@ -90,6 +94,12 @@ class AssetMaintenancesController extends Controller
case 'asset_name':
$maintenances = $maintenances->OrderByAssetName($order);
break;
case 'serial':
$maintenances = $maintenances->OrderByAssetSerial($order);
break;
case 'status_label':
$maintenances = $maintenances->OrderStatusName($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
+55 -10
View File
@@ -4,6 +4,10 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
@@ -45,6 +49,8 @@ use Route;
*/
class AssetsController extends Controller
{
use MigratesLegacyAssetLocations;
/**
* Returns JSON listing of all assets
*
@@ -88,6 +94,7 @@ class AssetsController extends Controller
'serial',
'model_number',
'last_checkout',
'last_checkin',
'notes',
'expected_checkin',
'order_number',
@@ -105,6 +112,7 @@ class AssetsController extends Controller
'requests_counter',
'byod',
'asset_eol_date',
'requestable',
];
$filter = [];
@@ -584,6 +592,11 @@ class AssetsController extends Controller
}
}
}
if ($field->element == 'checkbox') {
if(is_array($field_val)) {
$field_val = implode(',', $field_val);
}
}
$asset->{$field->db_column} = $field_val;
@@ -607,6 +620,8 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
@@ -652,13 +667,22 @@ class AssetsController extends Controller
// Update custom fields
if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
$field_val = $request->input($field->db_column, null);
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
$asset->{$field->db_column} = Crypt::encrypt($field_val);
}
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
if ($field->element == 'checkbox') {
if(is_array($field_val)) {
$field_val = implode(',', $field_val);
$asset->{$field->db_column} = $field_val;
}
}
else {
$asset->{$field->db_column} = $field_val;
}
}
}
@@ -686,6 +710,7 @@ class AssetsController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
@@ -864,11 +889,9 @@ class AssetsController extends Controller
*/
public function checkin(Request $request, $asset_id)
{
$this->authorize('checkin', Asset::class);
$asset = Asset::with('model')->findOrFail($asset_id);
$this->authorize('checkin', $asset);
$target = $asset->assignedTo;
if (is_null($target)) {
return response()->json(Helper::formatStandardApiResponse('error', [
@@ -879,9 +902,8 @@ class AssetsController extends Controller
}
$asset->expected_checkin = null;
$asset->last_checkout = null;
//$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -889,10 +911,16 @@ class AssetsController extends Controller
$asset->name = $request->input('name');
}
$this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
$asset->location_id = $request->input('location_id');
if ($request->input('update_default_location')){
$asset->rtd_location_id = $request->input('location_id');
}
}
if ($request->has('status_id')) {
@@ -906,6 +934,23 @@ class AssetsController extends Controller
$originalValues['action_date'] = $checkin_at;
}
$asset->licenseseats->each(function (LicenseSeat $seat) {
$seat->update(['assigned_to' => null]);
});
// Get all pending Acceptances for this asset and delete them
CheckoutAcceptance::pending()
->whereHasMorph(
'checkoutable',
[Asset::class],
function (Builder $query) use ($asset) {
$query->where('id', $asset->id);
})
->get()
->map(function ($acceptance) {
$acceptance->delete();
});
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
@@ -1035,8 +1080,7 @@ class AssetsController extends Controller
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
->requestableAssets();
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests');
@@ -1044,7 +1088,7 @@ class AssetsController extends Controller
if ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name())) {
@@ -1074,6 +1118,7 @@ class AssetsController extends Controller
break;
}
$assets->requestableAssets();
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
@@ -40,7 +40,9 @@ class CompaniesController extends Controller
'components_count',
];
$companies = Company::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
$companies = Company::withCount(['assets as assets_count' => function ($query) {
$query->AssetsForShow();
}])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ConsumablesTransformer;
@@ -11,6 +12,7 @@ use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Auth;
class ConsumablesController extends Controller
{
@@ -290,17 +292,9 @@ class ConsumablesController extends Controller
]
);
// Log checkout event
$logaction = $consumable->logCheckout($request->input('note'), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $consumable->name;
$data['checkout_date'] = $logaction->created_at;
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
}
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\GroupsTransformer;
use App\Models\Group;
use Illuminate\Http\Request;
use Auth;
class GroupsController extends Controller
@@ -25,7 +26,7 @@ class GroupsController extends Controller
$this->authorize('view', Group::class);
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at')->withCount('users as users_count');
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count');
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
@@ -63,6 +64,7 @@ class GroupsController extends Controller
$group = new Group;
$group->name = $request->input('name');
$group->created_by = Auth::user()->id;
$group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here
if ($group->save()) {
@@ -136,6 +136,7 @@ class LicensesController extends Controller
'seats',
'termination_date',
'depreciation_id',
'min_amt',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$licenses = $licenses->orderBy($sort, $order);
@@ -25,9 +25,27 @@ class LocationsController extends Controller
{
$this->authorize('view', Location::class);
$allowed_columns = [
'id', 'name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'created_at',
'updated_at', 'manager_id', 'image',
'assigned_assets_count', 'users_count', 'assets_count','assigned_assets_count', 'assets_count', 'rtd_assets_count', 'currency', 'ldap_ou', ];
'id',
'name',
'address',
'address2',
'city',
'state',
'country',
'zip',
'created_at',
'updated_at',
'manager_id',
'image',
'assigned_assets_count',
'users_count',
'assets_count',
'assigned_assets_count',
'assets_count',
'rtd_assets_count',
'currency',
'ldap_ou',
];
$locations = Location::with('parent', 'manager', 'children')->select([
'locations.id',
@@ -50,6 +68,7 @@ class LocationsController extends Controller
])->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count');
if ($request->filled('search')) {
@@ -80,6 +99,10 @@ class LocationsController extends Controller
$locations->where('locations.country', '=', $request->input('country'));
}
if ($request->filled('manager_id')) {
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -212,7 +235,13 @@ class LocationsController extends Controller
public function destroy($id)
{
$this->authorize('delete', Location::class);
$location = Location::findOrFail($id);
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->findOrFail($id);
if (! $location->isDeletable()) {
return response()
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
+14 -2
View File
@@ -32,14 +32,26 @@ class ReportsController extends Controller
}
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
$actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id'))
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
$actionlogs = $actionlogs->where(function($query) use ($request)
{
$query->where('item_id', '=', $request->input('item_id'))
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')))
->orWhere(function($query) use ($request)
{
$query->where('target_id', '=', $request->input('item_id'))
->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
});
});
}
if ($request->filled('action_type')) {
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
}
if ($request->filled('user_id')) {
$actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id'));
}
if ($request->filled('action_source')) {
$actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc');
}
@@ -148,7 +148,7 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return Redirect
* @return JsonResponse
*/
public function ajaxTestEmail()
{
@@ -170,7 +170,7 @@ class SettingsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.0]
* @return Response
* @return JsonResponse
*/
public function purgeBarcodes()
{
@@ -211,7 +211,7 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.0]
* @param \Illuminate\Http\Request $request
* @return array
* @return array | JsonResponse
*/
public function showLoginAttempts(Request $request)
{
@@ -229,6 +229,12 @@ class SettingsController extends Controller
}
/**
* Lists backup files
*
* @author [A. Gianotto]
* @return array | JsonResponse
*/
public function listBackups() {
$settings = Setting::getSettings();
$path = 'app/backups';
@@ -249,12 +255,12 @@ class SettingsController extends Controller
'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])),
'modified_value' => $file_timestamp,
'modified_display' => date($settings->date_display_format.' '.$settings->time_display_format, $file_timestamp),
'backup_url' => config('app.url').'/settings/backups/download/'.basename($backup_files[$f]),
];
$count++;
}
}
}
@@ -264,15 +270,56 @@ class SettingsController extends Controller
}
/**
* Downloads a backup file.
* We use response()->download() here instead of Storage::download() because Storage::download()
* exhausts memory on larger files.
*
* @author [A. Gianotto]
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function downloadBackup($file) {
$path = 'app/backups';
if (Storage::exists($path.'/'.$file)) {
$path = storage_path('app/backups');
if (Storage::exists('app/backups/'.$file)) {
$headers = ['ContentType' => 'application/zip'];
return Storage::download($path.'/'.$file, $file, $headers);
return response()->download($path.'/'.$file, $file, $headers);
} else {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
}
}
/**
* Determines and downloads the latest backup
*
* @author [A. Gianotto]
* @since [v6.3.1]
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function downloadLatestBackup() {
$fileData = collect();
foreach (Storage::files('app/backups') as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) == 'zip') {
$fileData->push([
'file' => $file,
'date' => Storage::lastModified($file)
]);
}
}
$newest = $fileData->sortByDesc('date')->first();
if (Storage::exists($newest['file'])) {
$headers = ['ContentType' => 'application/zip'];
return response()->download(storage_path($newest['file']), basename($newest['file']), $headers);
} else {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 404);
}
}
}
+37 -7
View File
@@ -73,6 +73,7 @@ class UsersController extends Controller
'users.end_date',
'users.vip',
'users.autoassign_licenses',
'users.website',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
@@ -122,6 +123,10 @@ class UsersController extends Controller
$users = $users->where('users.country', '=', $request->input('country'));
}
if ($request->filled('website')) {
$users = $users->where('users.website', '=', $request->input('website'));
}
if ($request->filled('zip')) {
$users = $users->where('users.zip', '=', $request->input('zip'));
}
@@ -254,6 +259,7 @@ class UsersController extends Controller
'start_date',
'end_date',
'autoassign_licenses',
'website',
];
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
@@ -274,11 +280,6 @@ class UsersController extends Controller
$offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
$limit = app('api_limit_value');
\Log::debug('Requested offset: '. $request->input('offset'));
\Log::debug('App offset: '. app('api_offset_value'));
\Log::debug('Actual offset: '. $offset);
\Log::debug('Limit: '. $limit);
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@@ -559,7 +560,26 @@ class UsersController extends Controller
{
$this->authorize('view', User::class);
$this->authorize('view', Asset::class);
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model');
// Filter on category ID
if ($request->filled('category_id')) {
$assets = $assets->InCategory($request->input('category_id'));
}
// Filter on model ID
if ($request->filled('model_id')) {
$model_ids = $request->input('model_id');
if (!is_array($model_ids)) {
$model_ids = array($model_ids);
}
$assets = $assets->InModelList($model_ids);
}
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
}
@@ -660,7 +680,17 @@ class UsersController extends Controller
$user = User::find($request->get('id'));
$user->two_factor_secret = null;
$user->two_factor_enrolled = 0;
$user->save();
$user->saveQuietly();
// Log the reset
$logaction = new Actionlog();
$logaction->target_type = User::class;
$logaction->target_id = $user->id;
$logaction->item_type = User::class;
$logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = Auth::user()->id;
$logaction->logaction('2FA reset');
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
} catch (\Exception $e) {
@@ -148,30 +148,20 @@ class AssetMaintenancesController extends Controller
*/
public function edit($assetMaintenanceId = null)
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the improvement management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif (! $assetMaintenance->asset) {
return redirect()->route('maintenances.index')
->with('error', 'The asset associated with this maintenance does not exist.');
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
if ($assetMaintenance->completion_date == '0000-00-00') {
$assetMaintenance->completion_date = null;
}
if ($assetMaintenance->start_date == '0000-00-00') {
$assetMaintenance->start_date = null;
}
if ($assetMaintenance->cost == '0.00') {
$assetMaintenance->cost = null;
}
// Prepare Improvement Type List
$assetMaintenanceType = [
@@ -203,8 +193,10 @@ class AssetMaintenancesController extends Controller
// 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'));
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -7,6 +7,7 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -442,7 +443,6 @@ class AssetModelsController extends Controller
$del_count = 0;
foreach ($models as $model) {
\Log::debug($model->id);
if ($model->assets_count > 0) {
$del_error_count++;
@@ -452,8 +452,6 @@ class AssetModelsController extends Controller
}
}
\Log::debug($del_count);
\Log::debug($del_error_count);
if ($del_error_count == 0) {
return redirect()->route('models.index')
@@ -489,11 +487,11 @@ class AssetModelsController extends Controller
* @param array $defaultValues
* @return void
*/
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool
{
$data = array();
foreach ($defaultValues as $customFieldId => $defaultValue) {
$customField = \App\Models\CustomField::find($customFieldId);
$customField = CustomField::find($customFieldId);
$data[$customField->db_column] = $defaultValue;
}
@@ -3,26 +3,25 @@
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetModelsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $modelId
* @return Redirect
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(AssetFileRequest $request, $modelId = null)
public function store(UploadFileRequest $request, $modelId = null)
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,27 +36,7 @@ class AssetModelsFilesController extends Controller
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'model-'.$model->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension=='svg') {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/assetmodels/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/assetmodels/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
$model->logUpload($file_name, e($request->get('notes')));
}
@@ -6,8 +6,10 @@ use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
@@ -15,6 +17,8 @@ use Illuminate\Support\Facades\View;
class AssetCheckinController extends Controller
{
use MigratesLegacyAssetLocations;
/**
* Returns a view that presents a form to check an asset back into inventory.
*
@@ -67,11 +71,9 @@ class AssetCheckinController extends Controller
}
$asset->expected_checkin = null;
$asset->last_checkout = null;
//$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
$asset->accepted = null;
$asset->name = $request->get('name');
@@ -79,24 +81,7 @@ class AssetCheckinController extends Controller
$asset->status_id = e($request->get('status_id'));
}
// This is just meant to correct legacy issues where some user data would have 0
// as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
// rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
// people (and their data) in the long run
if ($asset->rtd_location_id == '0') {
\Log::debug('Manually override the RTD location IDs');
\Log::debug('Original RTD Location ID: '.$asset->rtd_location_id);
$asset->rtd_location_id = '';
\Log::debug('New RTD Location ID: '.$asset->rtd_location_id);
}
if ($asset->location_id == '0') {
\Log::debug('Manually override the location IDs');
\Log::debug('Original Location ID: '.$asset->location_id);
$asset->location_id = '';
\Log::debug('New Location ID: '.$asset->location_id);
}
$this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
@@ -117,12 +102,9 @@ class AssetCheckinController extends Controller
$checkin_at = $request->get('checkin_at');
}
if(!empty($asset->licenseseats->all())){
foreach ($asset->licenseseats as $seat){
$seat->assigned_to = null;
$seat->save();
}
}
$asset->licenseseats->each(function (LicenseSeat $seat) {
$seat->update(['assigned_to' => null]);
});
// Get all pending Acceptances for this asset and delete them
$acceptances = CheckoutAcceptance::pending()->whereHasMorph('checkoutable',
@@ -4,26 +4,25 @@ namespace App\Http\Controllers\Assets;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $assetId
* @return Redirect
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(AssetFileRequest $request, $assetId = null)
public function store(UploadFileRequest $request, $assetId = null)
{
if (! $asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,28 +36,7 @@ class AssetFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension=='svg') {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/assets/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
@@ -102,6 +102,10 @@ class AssetsController extends Controller
{
$this->authorize(Asset::class);
// There are a lot more rules to add here but prevents
// errors around `asset_tags` not being present below.
$this->validate($request, ['asset_tags' => ['required', 'array']]);
// Handle asset tags - there could be one, or potentially many.
// This is only necessary on create, not update, since bulk editing is handled
// differently
@@ -146,7 +150,8 @@ class AssetsController extends Controller
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
}
if ($asset->assigned_to == '') {
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}
@@ -521,31 +526,33 @@ class AssetsController extends Controller
public function getBarCode($assetId = null)
{
$settings = Setting::getSettings();
$asset = Asset::find($assetId);
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
if ($asset = Asset::withTrashed()->find($assetId)) {
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
if (isset($asset->id, $asset->asset_tag)) {
if (file_exists($barcode_file)) {
$header = ['Content-type' => 'image/png'];
if (isset($asset->id, $asset->asset_tag)) {
if (file_exists($barcode_file)) {
$header = ['Content-type' => 'image/png'];
return response()->file($barcode_file, $header);
} else {
// Calculate barcode width in pixel based on label width (inch)
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
return response()->file($barcode_file, $header);
} else {
// Calculate barcode width in pixel based on label width (inch)
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
$barcode = new \Com\Tecnick\Barcode\Barcode();
try {
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
file_put_contents($barcode_file, $barcode_obj->getPngData());
$barcode = new \Com\Tecnick\Barcode\Barcode();
try {
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
file_put_contents($barcode_file, $barcode_obj->getPngData());
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
Log::debug('The barcode format is invalid.');
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
}
}
}
}
return null;
}
/**
@@ -734,11 +741,11 @@ class AssetsController extends Controller
if ($isCheckinHeaderExplicit) {
//if checkin date header exists, assume that empty or future date is still checked out
//if checkin is before todays date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items is checked out
// if checkin date header exists, assume that empty or future date is still checked out
// if checkin is before today's date, assume it's checked in and do not assign user ID, if checkin date is in the future or blank, this is the expected checkin date, items are checked out
if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date))
) {
if ((strtotime($checkin_date) > strtotime(Carbon::now())) || (empty($checkin_date)))
{
//only do this if item is checked out
$asset->assigned_to = $user->id;
$asset->assigned_type = User::class;
@@ -14,6 +14,7 @@ use App\View\Label;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField;
@@ -93,6 +94,59 @@ class BulkAssetsController extends Controller
$assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
$assets = $assets->get();
if ($assets->isEmpty()) {
Log::debug('No assets were found for the provided IDs', ['ids' => $asset_ids]);
return redirect()->back()->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
}
$models = $assets->unique('model_id');
$modelNames = [];
foreach($models as $model) {
$modelNames[] = $model->model->name;
}
if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
return (new Label)
->with('assets', $assets)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
case 'delete':
$this->authorize('delete', Asset::class);
$assets->each(function ($assets) {
$this->authorize('delete', $assets);
});
return view('hardware/bulk-delete')->with('assets', $assets);
case 'restore':
$this->authorize('update', Asset::class);
$assets = Asset::withTrashed()->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit':
$this->authorize('update', Asset::class);
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList())
->with('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
@@ -128,54 +182,6 @@ class BulkAssetsController extends Controller
break;
}
$assets = $assets->get();
$models = $assets->unique('model_id');
$modelNames = [];
foreach($models as $model) {
$modelNames[] = $model->model->name;
}
if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
return (new Label)
->with('assets', $assets)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
case 'delete':
$this->authorize('delete', Asset::class);
$assets->each(function ($assets) {
$this->authorize('delete', $assets);
});
return view('hardware/bulk-delete')->with('assets', $assets);
case 'restore':
$this->authorize('update', Asset::class);
$assets = Asset::withTrashed()->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
});
return view('hardware/bulk-restore')->with('assets', $assets);
case 'edit':
$this->authorize('update', Asset::class);
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList())
->with('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
return redirect()->back()->with('error', 'No action selected');
}
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $componentId = null)
public function store(UploadFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
@@ -43,30 +42,7 @@ class ComponentsFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\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('private_uploads/components/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
@@ -76,7 +76,6 @@ class ConsumableCheckoutController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
$admin_user = Auth::user();
$assigned_to = e($request->input('assigned_to'));
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $consumableId = null)
public function store(UploadFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
@@ -42,30 +41,7 @@ class ConsumablesFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\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('private_uploads/consumables/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
@@ -260,7 +260,7 @@ class CustomFieldsController extends Controller
$field->name = trim(e($request->get("name")));
$field->element = e($request->get("element"));
$field->field_values = e($request->get("field_values"));
$field->field_values = $request->get("field_values");
$field->user_id = Auth::id();
$field->help_text = $request->get("help_text");
$field->show_in_email = $show_in_email;
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Group;
use Illuminate\Http\Request;
use Auth;
/**
* This controller handles all actions related to User Groups for
@@ -63,6 +64,7 @@ class GroupsController extends Controller
$group = new Group();
$group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
$group->created_by = Auth::user()->id;
if ($group->save()) {
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
+15
View File
@@ -6,6 +6,7 @@ use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\Labels\Label;
use App\Models\Location;
use App\Models\Manufacturer;
@@ -65,6 +66,20 @@ class LabelsController extends Controller
$exampleAsset->model->category->id = 999999;
$exampleAsset->model->category->name = trans('admin/labels/table.example_category');
$customFieldColumns = CustomField::all()->pluck('db_column');
collect(explode(';', Setting::getSettings()->label2_fields))
->filter()
->each(function ($item) use ($customFieldColumns, $exampleAsset) {
$pair = explode('=', $item);
if (array_key_exists(1, $pair)) {
if ($customFieldColumns->contains($pair[1])) {
$exampleAsset->{$pair[1]} = "{{$pair[0]}}";
}
}
});
$settings = Setting::getSettings();
if (request()->has('settings')) {
$overrides = request()->get('settings');
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Licenses;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $licenseId = null)
public function store(UploadFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
@@ -38,30 +37,7 @@ class LicenseFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\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('private_uploads/licenses/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/licenses/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
@@ -99,6 +99,7 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
$license->min_amt = $request->input('min_amt');
if ($license->save()) {
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
@@ -176,6 +177,7 @@ class LicensesController extends Controller
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->min_amt = $request->input('min_amt');
if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
@@ -245,12 +247,6 @@ class LicensesController extends Controller
$available_seats_count = $license->availCount()->count();
$checkedout_seats_count = ($total_seats_count - $available_seats_count);
\Log::debug('Total: '.$total_seats_count);
\Log::debug('Users: '.$users_count);
\Log::debug('Available: '.$available_seats_count);
\Log::debug('Checkedout: '.$checkedout_seats_count);
$this->authorize('view', $license);
return view('licenses.view', compact('license'))
->with('users_count', $users_count)
+97 -2
View File
@@ -8,6 +8,7 @@ use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Locations for
@@ -168,7 +169,7 @@ class LocationsController extends Controller
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($location->users()->count() > 0) {
@@ -238,7 +239,7 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
* @since [v6.0.14]
* @return View
* @return \Illuminate\Contracts\View\View
*/
public function getClone($locationId = null)
{
@@ -272,8 +273,102 @@ class LocationsController extends Controller
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
/**
* Returns a view that allows the user to bulk delete locations
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.1]
* @return \Illuminate\Contracts\View\View
*/
public function postBulkDelete(Request $request)
{
$locations_raw_array = $request->input('ids');
// Make sure some IDs have been selected
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
$locations = Location::whereIn('id', $locations_raw_array)
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')->get();
$valid_count = 0;
foreach ($locations as $location) {
if ($location->isDeletable()) {
$valid_count++;
}
}
return view('locations/bulk-delete', compact('locations'))->with('valid_count', $valid_count);
}
return redirect()->route('models.index')
->with('error', 'You must select at least one model to edit.');
}
/**
* Checks that locations can be deleted and deletes them if they can
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.1]
* @return \Illuminate\Http\RedirectResponse
*/
public function postBulkDeleteStore(Request $request) {
$locations_raw_array = $request->input('ids');
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
$locations = Location::whereIn('id', $locations_raw_array)
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')->get();
$success_count = 0;
$error_count = 0;
foreach ($locations as $location) {
// Can we delete this location?
if ($location->isDeletable()) {
$location->delete();
$success_count++;
} else {
$error_count++;
}
}
\Log::debug('Success count: '.$success_count);
\Log::debug('Error count: '.$error_count);
// Complete success
if ($success_count == count($locations_raw_array)) {
return redirect()
->route('locations.index')
->with('success', trans_choice('general.bulk.delete.success', $success_count,
['object_type' => trans_choice('general.location_plural', $success_count), 'count' => $success_count]
));
}
// Partial success
if ($error_count > 0) {
return redirect()
->route('locations.index')
->with('warning', trans('general.bulk.delete.partial',
['success' => $success_count, 'error' => $error_count, 'object_type' => trans('general.locations')]
));
}
}
// Nothing was selected - return to the index
return redirect()
->route('locations.index')
->with('error', trans('general.bulk.nothing_selected',
['object_type' => trans('general.locations')]
));
}
}
+35 -20
View File
@@ -295,9 +295,9 @@ class ReportsController extends Controller
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
($actionlog->item->serial) ? $actionlog->item->serial : null,
($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
($actionlog->item->model) ? $actionlog->item->model->model_number : null,
($actionlog->item) ? $actionlog->item->serial : null,
(($actionlog->item) && ($actionlog->item->model)) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
(($actionlog->item) && ($actionlog->item->model)) ? $actionlog->item->model->model_number : null,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
@@ -616,7 +616,7 @@ class ReportsController extends Controller
}
if ($request->filled('url')) {
$header[] = trans('admin/manufacturers/table.url');
$header[] = trans('general.url');
}
@@ -686,20 +686,27 @@ class ReportsController extends Controller
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
}
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
$checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
$checkout_end = \Carbon::parse($request->input('checkout_date_end'))->endOfDay();
$checkout_end = \Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
$actionlogassets = Actionlog::where('action_type','=', 'checkout')
->where('item_type', 'LIKE', '%Asset%',)
->whereBetween('action_date',[$checkout_start, $checkout_end])
->pluck('item_id');
$assets->whereIn('id',$actionlogassets);
}
if (($request->filled('checkin_date_start'))) {
$assets->whereBetween('last_checkin', [
Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
$checkin_start = \Carbon::parse($request->input('checkin_date_start'))->startOfDay();
// use today's date is `checkin_date_end` is not provided
Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
]);
$checkin_end = \Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
$assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]);
}
//last checkin is exporting, but currently is a date and not a datetime in the custom report ONLY.
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
@@ -1156,16 +1163,24 @@ class ReportsController extends Controller
$logItem = $logItem_res[0];
}
if (!$assetItem->assignedTo->locale){
Notification::locale(Setting::getSettings()->locale)->send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
} else {
Notification::send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
// Only send notification if assigned
if ($assetItem->assignedTo) {
if (!$assetItem->assignedTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
} else {
Notification::send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
}
}
if ($assetItem->assignedTo->email == ''){
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
+59 -65
View File
@@ -20,6 +20,7 @@ use DB;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Image;
use Input;
use Redirect;
@@ -28,6 +29,7 @@ use App\Http\Requests\SlackSettingsRequest;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
/**
* This controller handles all actions related to Settings for
@@ -356,6 +358,7 @@ class SettingsController extends Controller
}
$setting->default_eula_text = $request->input('default_eula_text');
$setting->load_remote = $request->input('load_remote', 0);
$setting->thumbnail_max_h = $request->input('thumbnail_max_h');
$setting->privacy_policy_link = $request->input('privacy_policy_link');
@@ -420,68 +423,46 @@ class SettingsController extends Controller
// Only allow the site name and CSS to be changed if lock_passwords is false
// Because public demos make people act like dicks
if (! config('app.lock_passwords')) {
$setting->site_name = $request->input('site_name');
$setting->custom_css = $request->input('custom_css');
}
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
if ('1' == $request->input('clear_logo')) {
if ('1' == $request->input('clear_logo')) {
Storage::disk('public')->delete($setting->logo);
$setting->logo = null;
$setting->logo = null;
$setting->brand = 1;
}
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
if ('1' == $request->input('clear_email_logo')) {
Storage::disk('public')->delete($setting->email_logo);
$setting->email_logo = null;
// If they are uploading an image, validate it and upload it
}
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
if ('1' == $request->input('clear_label_logo')) {
Storage::disk('public')->delete($setting->label_logo);
$setting->label_logo = null;
}
// If the user wants to clear the favicon...
if ($request->hasFile('favicon')) {
$favicon_image = $favicon_upload = $request->file('favicon');
$favicon_ext = $favicon_image->getClientOriginalExtension();
$setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext;
if (($favicon_image->getClientOriginalExtension() != 'ico') && ($favicon_image->getClientOriginalExtension() != 'svg')) {
$favicon_upload = Image::make($favicon_image->getRealPath())->resize(null, 36, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($favicon_file_name, (string) $favicon_upload->encode());
} else {
Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon')));
}
// Remove Current image if exists
if (($setting->favicon) && (file_exists($favicon_file_name))) {
Storage::disk('public')->delete($favicon_file_name);
}
} elseif ('1' == $request->input('clear_favicon')) {
Storage::disk('public')->delete($setting->clear_favicon);
$setting->favicon = null;
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
// If they are uploading an image, validate it and upload it
}
if ('1' == $request->input('clear_email_logo')) {
Storage::disk('public')->delete($setting->email_logo);
$setting->email_logo = null;
// If they are uploading an image, validate it and upload it
}
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
if ('1' == $request->input('clear_label_logo')) {
Storage::disk('public')->delete($setting->label_logo);
$setting->label_logo = null;
}
$setting = $request->handleImages($setting, 600, 'favicon', '', 'favicon');
// If the user wants to clear the favicon...
if ('1' == $request->input('clear_favicon')) {
Storage::disk('public')->delete($setting->favicon);
$setting->favicon = null;
}
}
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -519,6 +500,19 @@ class SettingsController extends Controller
*/
public function postSecurity(Request $request)
{
$this->validate($request, [
'pwd_secure_complexity' => 'array',
'pwd_secure_complexity.*' => [
Rule::in([
'disallow_same_pwd_as_user_fields',
'letters',
'numbers',
'symbols',
'case_diff',
])
]
]);
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
@@ -635,21 +629,21 @@ class SettingsController extends Controller
// Check if the audit interval has changed - if it has, we want to update ALL of the assets audit dates
if ($request->input('audit_interval') != $setting->audit_interval) {
// Be careful - this could be a negative number
// This could be a negative number if the user is trying to set the audit interval to a lower number than it was before
$audit_diff_months = ((int)$request->input('audit_interval') - (int)($setting->audit_interval));
// Batch update the dates. We have to use this method to avoid time limit exceeded errors on very large datasets,
// but it DOES mean this change doesn't get logged in the action logs, since it skips the observer.
// @see https://stackoverflow.com/questions/54879160/laravel-observer-not-working-on-bulk-insert
$affected = Asset::whereNotNull('next_audit_date')
->whereNull('deleted_at')
->update(
['next_audit_date' => DB::raw('DATE_ADD(next_audit_date, INTERVAL '.$audit_diff_months.' MONTH)')]
);
\Log::debug($affected .' assets affected by audit interval update');
// Grab all of the assets that have an existing next_audit_date
$assets = Asset::whereNotNull('next_audit_date')->get();
// Update all of the assets' next_audit_date values
foreach ($assets as $asset) {
if ($asset->next_audit_date != '') {
$old_next_audit = new \DateTime($asset->next_audit_date);
$asset->next_audit_date = $old_next_audit->modify($audit_diff_months.' month')->format('Y-m-d');
$asset->forceSave();
}
}
}
$alert_email = rtrim($request->input('alert_email'), ',');
@@ -4,14 +4,13 @@ namespace App\Http\Controllers\Users;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller
@@ -19,14 +18,14 @@ class UserFilesController extends Controller
/**
* Return JSON response with a list of user details for the getIndex() view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $userId
* @return string JSON
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
*/
public function store(AssetFileRequest $request, $userId = null)
public function store(UploadFileRequest $request, $userId = null)
{
$user = User::find($userId);
$destinationPath = config('app.private_uploads').'/users';
@@ -41,31 +40,7 @@ class UserFilesController extends Controller
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'user-'.$user->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\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('private_uploads/users/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/users/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
+13 -4
View File
@@ -59,12 +59,21 @@ class Importer extends Component
'field_map' => 'array'
];
/**
* This is used in resources/views/livewire/importer.blade.php, and we kinda shouldn't need to check for
* activeFile here, but there's some UI goofiness that allows this to crash out on some imports.
*
* @return string
*/
public function generate_field_map()
{
\Log::debug("header row is: ".print_r($this->activeFile->header_row,true));
\Log::debug("Field map is: ".print_r($this->field_map,true));
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
return json_encode(array_filter($tmp));
$tmp = array();
if ($this->activeFile) {
$tmp = array_combine($this->activeFile->header_row, $this->field_map);
$tmp = array_filter($tmp);
}
return json_encode($tmp);
}
+53 -8
View File
@@ -31,29 +31,37 @@ class SlackSettingsForm extends Component
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
'webhook_botname' => 'string|nullable',
];
public $messages = [
'webhook_endpoint.starts_with' => 'your webhook endpoint should begin with http://, https:// or other protocol.',
];
public function mount() {
$this->webhook_text= [
"slack" => array(
"slack" => array(
"name" => trans('admin/settings/general.slack') ,
"icon" => 'fab fa-slack',
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
"link" => 'https://api.slack.com/messaging/webhooks',
"icon" => 'fab fa-slack',
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
"link" => 'https://api.slack.com/messaging/webhooks',
"test" => "testWebhook"
),
"general"=> array(
"name" => trans('admin/settings/general.general_webhook'),
"icon" => "fab fa-hashtag",
"placeholder" => trans('general.url'),
"link" => "",
"test" => "testWebhook"
),
"google" => array(
"name" => trans('admin/settings/general.google_workspaces'),
"icon" => "fa-brands fa-google",
"placeholder" => "https://chat.googleapis.com/v1/spaces/xxxxxxxx/messages?key=xxxxxx",
"link" => "https://developers.google.com/chat/how-tos/webhooks#register_the_incoming_webhook",
"test" => "googleWebhookTest"
),
"microsoft" => array(
"name" => trans('admin/settings/general.ms_teams'),
"icon" => "fa-brands fa-microsoft",
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
"link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1",
"test" => "msTeamTestWebhook"
),
];
@@ -64,10 +72,14 @@ class SlackSettingsForm extends Component
$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_endpoint = $this->setting->webhook_endpoint;
$this->webhook_channel = $this->setting->webhook_channel;
$this->webhook_botname = $this->setting->webhook_botname;
$this->webhook_options = $this->setting->webhook_selected;
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
$this->webhook_channel = '#NA';
}
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
@@ -87,10 +99,14 @@ class SlackSettingsForm extends Component
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
$this->webhook_endpoint = null;
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
$this->webhook_test = $this->webhook_text[$this->webhook_selected]["test"];
if($this->webhook_selected != 'slack'){
$this->isDisabled= '';
$this->save_button = trans('general.save');
}
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
$this->webhook_channel = '#NA';
}
}
@@ -151,6 +167,7 @@ class SlackSettingsForm extends Component
}
public function clearSettings(){
if (Helper::isDemoMode()) {
@@ -187,7 +204,35 @@ class SlackSettingsForm extends Component
}
}
public function msTeamTestWebhook(){
public function googleWebhookTest(){
$payload = [
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
];
try {
$response = Http::withHeaders([
'content-type' => 'applications/json',
])->post($this->webhook_endpoint,
$payload)->throw();
if (($response->getStatusCode() == 302) || ($response->getStatusCode() == 301)) {
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
}
$this->isDisabled='';
$this->save_button = trans('general.save');
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
} catch (\Exception $e) {
$this->isDisabled='disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
}
}
public function msTeamTestWebhook(){
$payload =
[
+10 -5
View File
@@ -8,6 +8,12 @@ use \App\Helpers\Helper;
class CheckLocale
{
private function warn_legacy_locale($language, $source)
{
if ($language != Helper::mapLegacyLocale($language)) {
\Log::warning("$source $language and should be updated to be ".Helper::mapLegacyLocale($language));
}
}
/**
* Handle the locale for the user, default to settings otherwise.
*
@@ -22,24 +28,23 @@ class CheckLocale
// Default app settings from config
$language = config('app.locale');
$this->warn_legacy_locale($language, "APP_LOCALE in .env is set to");
if ($settings = Setting::getSettings()) {
// User's preference
if (($request->user()) && ($request->user()->locale)) {
$language = $request->user()->locale;
$this->warn_legacy_locale($language, "username ".$request->user()->username." (".$request->user()->id.") has a language");
// App setting preference
} elseif ($settings->locale != '') {
$language = $settings->locale;
$this->warn_legacy_locale($language, "App Settings is set to");
}
}
if (config('app.locale') != Helper::mapLegacyLocale($language)) {
\Log::warning('Your current APP_LOCALE in your .env is set to "'.config('app.locale').'" and should be updated to be "'.Helper::mapLegacyLocale($language).'" in '.base_path().'/.env. Translations may display unexpectedly until this is updated.');
}
\App::setLocale(Helper::mapLegacyLocale($language));
return $next($request);
}
-30
View File
@@ -1,30 +0,0 @@
<?php
namespace App\Http\Requests;
class AssetFileRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$max_file_size = \App\Helpers\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|max:'.$max_file_size,
];
}
}
+22 -31
View File
@@ -34,8 +34,8 @@ class ImageUploadRequest extends Request
{
return [
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp',
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp',
'image' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
'avatar' => 'mimes:png,gif,jpg,jpeg,svg,bmp,svg+xml,webp,avif',
];
}
@@ -97,22 +97,36 @@ class ImageUploadRequest extends Request
if (!config('app.lock_passwords')) {
$ext = $image->getClientOriginalExtension();
$ext = $image->guessExtension();
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
\Log::info('File name will be: '.$file_name);
\Log::debug('File extension is: '.$ext);
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
if (($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
// If the file is a webp or avif, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
\Log::debug('Not an SVG or webp - resize');
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
} elseif($image->getMimeType() == 'image/svg+xml') {
// If the file is an SVG, we need to clean it and NOT encode it
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($image->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
Storage::disk('public')->put($path . '/' . $file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug($e);
}
} else {
try {
$upload = Image::make($image->getRealPath())->setFileInfoFromPath($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
})->orientate();
} catch(NotReadableException $e) {
\Log::debug($e);
$validator = \Validator::make([], []);
@@ -124,35 +138,12 @@ class ImageUploadRequest extends Request
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
} else {
// If the file is a webp, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available
if ($image->getClientOriginalExtension() == 'webp') {
\Log::debug('This is a webp, just move it');
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
// If the file is an SVG, we need to clean it and NOT encode it
} else {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($image->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
Storage::disk('public')->put($path.'/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
}
}
// Remove Current image if exists
if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
try {
Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
} catch (\Exception $e) {
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
}
+21
View File
@@ -4,6 +4,8 @@ namespace App\Http\Requests;
use App\Models\Asset;
use App\Models\Company;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Support\Facades\Gate;
class StoreAssetRequest extends ImageUploadRequest
@@ -27,6 +29,8 @@ class StoreAssetRequest extends ImageUploadRequest
? Company::getIdForCurrentUser($this->company_id)
: $this->company_id;
$this->parseLastAuditDate();
$this->merge([
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
'company_id' => $idForCurrentUser,
@@ -48,4 +52,21 @@ class StoreAssetRequest extends ImageUploadRequest
return $rules;
}
private function parseLastAuditDate(): void
{
if ($this->input('last_audit_date')) {
try {
$lastAuditDate = Carbon::parse($this->input('last_audit_date'));
$this->merge([
'last_audit_date' => $lastAuditDate->startOfDay()->format('Y-m-d H:i:s'),
]);
} catch (InvalidFormatException $e) {
// we don't need to do anything here...
// we'll keep the provided date in an
// invalid format so validation picks it up later
}
}
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Requests;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UploadFileRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$max_file_size = \App\Helpers\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,
];
}
/**
* 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();
\Log::debug("Your filetype IS: ".$file->getMimeType());
// 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);
}
} else {
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
\Log::debug("Here are the '$put_results' (should be 0 or 1 or true or false or something?)");
}
return $file_name;
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Http\Traits;
use App\Models\Asset;
trait MigratesLegacyAssetLocations
{
/**
* This is just meant to correct legacy issues where some user data would have 0
* as a location ID, which isn't valid. Later versions of Snipe-IT have stricter validation
* rules, so it's necessary to fix this for long-time users. It's kinda gross, but will help
* people (and their data) in the long run
* @param Asset $asset
* @return void
*/
private function migrateLegacyLocations(Asset $asset): void
{
if ($asset->rtd_location_id == '0') {
\Log::debug('Manually override the RTD location IDs');
\Log::debug('Original RTD Location ID: ' . $asset->rtd_location_id);
$asset->rtd_location_id = '';
\Log::debug('New RTD Location ID: ' . $asset->rtd_location_id);
}
if ($asset->location_id == '0') {
\Log::debug('Manually override the location IDs');
\Log::debug('Original Location ID: ' . $asset->location_id);
$asset->location_id = '';
\Log::debug('New Location ID: ' . $asset->location_id);
}
}
}
@@ -28,12 +28,20 @@ class AssetMaintenancesTransformer
'id' => (int) $assetmaintenance->asset->id,
'name'=> ($assetmaintenance->asset->name) ? e($assetmaintenance->asset->name) : null,
'asset_tag'=> e($assetmaintenance->asset->asset_tag),
'serial'=> e($assetmaintenance->asset->serial),
'deleted_at'=> e($assetmaintenance->asset->deleted_at),
'created_at'=> e($assetmaintenance->asset->created_at),
] : null,
'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [
'id' => (int) $assetmaintenance->asset->model->id,
'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null,
] : null,
'status_label' => ($assetmaintenance->asset->assetstatus) ? [
'id' => (int) $assetmaintenance->asset->assetstatus->id,
'name'=> e($assetmaintenance->asset->assetstatus->name),
'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()),
'status_meta' => e($assetmaintenance->asset->present()->statusMeta),
] : null,
'company' => (($assetmaintenance->asset) && ($assetmaintenance->asset->company)) ? [
'id' => (int) $assetmaintenance->asset->company->id,
'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null,
@@ -64,7 +72,7 @@ class AssetMaintenancesTransformer
];
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Asset::class),
'update' => (Gate::allows('update', Asset::class) && ($assetmaintenance->asset->deleted_at=='')) ? true : false,
'delete' => Gate::allows('delete', Asset::class),
];
@@ -37,6 +37,7 @@ class AssetsTransformer
'name'=> e($asset->model->name),
] : null,
'byod' => ($asset->byod ? true : false),
'requestable' => ($asset->requestable ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
@@ -87,6 +88,7 @@ class AssetsTransformer
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '',
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
'last_checkin' => Helper::getFormattedDateObject($asset->last_checkin, 'datetime'),
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
'checkin_counter' => (int) $asset->checkin_counter,
@@ -26,6 +26,7 @@ class GroupsTransformer
'name' => e($group->name),
'permissions' => json_decode($group->permissions),
'users_count' => (int) $group->users_count,
'created_by' => ($group->admin) ? e($group->admin->present()->fullName) : null,
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),
];
@@ -27,8 +27,8 @@ class LicensesTransformer
'company' => ($license->company) ? ['id' => (int) $license->company->id, 'name'=> e($license->company->name)] : null,
'manufacturer' => ($license->manufacturer) ? ['id' => (int) $license->manufacturer->id, 'name'=> e($license->manufacturer->name)] : null,
'product_key' => (Gate::allows('viewKeys', License::class)) ? e($license->serial) : '------------',
'order_number' => e($license->order_number),
'purchase_order' => e($license->purchase_order),
'order_number' => ($license->order_number) ? e($license->order_number) : null,
'purchase_order' => ($license->purchase_order) ? e($license->purchase_order) : null,
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
@@ -38,8 +38,9 @@ class LicensesTransformer
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'seats' => (int) $license->seats,
'free_seats_count' => (int) $license->free_seats_count,
'license_name' => e($license->license_name),
'license_email' => e($license->license_email),
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
'license_name' => ($license->license_name) ? e($license->license_name) : null,
'license_email' => ($license->license_email) ? e($license->license_email) : null,
'reassignable' => ($license->reassignable == 1) ? true : false,
'maintained' => ($license->maintained == 1) ? true : false,
'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null,
@@ -65,6 +65,9 @@ class LocationsTransformer
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Location::class) ? true : false,
'delete' => $location->isDeletable(),
'bulk_selectable' => [
'delete' => $location->isDeletable()
],
'clone' => (Gate::allows('create', Location::class) && ($location->deleted_at == '')),
];
+2 -3
View File
@@ -46,10 +46,9 @@ class AccessoryImporter extends ItemImporter
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
$accessory->fill($this->sanitizeItemForStoring($accessory));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
// $accessory->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$accessory->setImported(true);
if ($accessory->save()) {
$accessory->logCreate('Imported using CSV Importer');
$this->log('Accessory '.$this->item['name'].' was created');
return;
+2 -2
View File
@@ -135,10 +135,10 @@ class AssetImporter extends ItemImporter
$asset->{$custom_field} = $val;
}
}
// This sets an attribute on the Loggable trait for the action log
$asset->setImported(true);
if ($asset->save()) {
$asset->logCreate(trans('general.importer.import_note'));
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// If we have a target to checkout to, lets do so.
+3 -3
View File
@@ -48,10 +48,10 @@ class ComponentImporter extends ItemImporter
$this->log('No matching component, creating one');
$component = new Component;
$component->fill($this->sanitizeItemForStoring($component));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$component->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$component->setImported(true);
if ($component->save()) {
$component->logCreate('Imported using CSV Importer');
$this->log('Component '.$this->item['name'].' was created');
// If we have an asset tag, checkout to that asset.
+3 -3
View File
@@ -45,10 +45,10 @@ class ConsumableImporter extends ItemImporter
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
$consumable->fill($this->sanitizeItemForStoring($consumable));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$consumable->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$consumable->setImported(true);
if ($consumable->save()) {
$consumable->logCreate('Imported using CSV Importer');
$this->log('Consumable '.$this->item['name'].' was created');
return;
+3 -3
View File
@@ -85,10 +85,10 @@ class LicenseImporter extends ItemImporter
} else {
$license->fill($this->sanitizeItemForStoring($license));
}
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
// $license->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$license->setImported(true);
if ($license->save()) {
$license->logCreate('Imported using csv importer');
$this->log('License '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// Lets try to checkout seats if the fields exist and we have seats.
+8 -6
View File
@@ -60,16 +60,18 @@ class CheckoutableListener
if ($this->shouldSendWebhookNotification()) {
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
if(Setting::getSettings()->webhook_selected =='slack') {
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event));
}
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
}
}
@@ -113,7 +115,7 @@ class CheckoutableListener
);
}
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
if(Setting::getSettings()->webhook_selected =='slack') {
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
@@ -122,9 +124,9 @@ class CheckoutableListener
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkin notification: " . $e->getMessage());
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
}
}
+1 -1
View File
@@ -305,7 +305,7 @@ class Accessory extends SnipeModel
*/
public function requireAcceptance()
{
return $this->category->require_acceptance;
return $this->category->require_acceptance ?? false;
}
/**
+15 -1
View File
@@ -19,6 +19,9 @@ class Actionlog extends SnipeModel
{
use HasFactory;
// This is to manually set the source (via setActionSource()) for determineActionSource()
protected ?string $source = null;
protected $presenter = \App\Presenters\ActionlogPresenter::class;
use SoftDeletes;
use Presentable;
@@ -341,7 +344,12 @@ class Actionlog extends SnipeModel
* @since v6.3.0
* @return string
*/
public function determineActionSource() {
public function determineActionSource(): string
{
// This is a manually set source
if($this->source) {
return $this->source;
}
// This is an API call
if (((request()->header('content-type') && (request()->header('accept'))=='application/json'))
@@ -358,4 +366,10 @@ class Actionlog extends SnipeModel
return 'cli/unknown';
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{
$this->source = $source;
}
}
+6 -1
View File
@@ -96,7 +96,10 @@ class Asset extends Depreciable
'company_id' => 'nullable|integer|exists:companies,id',
'warranty_months' => 'nullable|numeric|digits_between:0,240',
'last_checkout' => 'nullable|date_format:Y-m-d H:i:s',
'last_checkin' => 'nullable|date_format:Y-m-d H:i:s',
'expected_checkin' => 'nullable|date',
'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s',
'next_audit_date' => 'nullable|date|after:last_audit_date',
'location_id' => 'nullable|exists:locations,id',
'rtd_location_id' => 'nullable|exists:locations,id',
'purchase_date' => 'nullable|date|date_format:Y-m-d',
@@ -167,6 +170,8 @@ class Asset extends Depreciable
'expected_checkin',
'next_audit_date',
'last_audit_date',
'last_checkin',
'last_checkout',
'asset_eol_date',
];
@@ -1560,7 +1565,7 @@ class Asset extends Depreciable
*
* In short, this set of statements tells the query builder to ONLY query against an
* actual field that's being passed if it doesn't meet known relational fields. This
* allows us to query custom fields directly in the assetsv table
* allows us to query custom fields directly in the assets table
* (regardless of their name) and *skip* any fields that we already know can only be
* searched through relational searches that we do earlier in this method.
*
+41 -2
View File
@@ -62,7 +62,15 @@ class AssetMaintenance extends Model implements ICompanyableChild
*
* @var array
*/
protected $searchableAttributes = ['title', 'notes', 'asset_maintenance_type', 'cost', 'start_date', 'completion_date'];
protected $searchableAttributes =
[
'title',
'notes',
'asset_maintenance_type',
'cost',
'start_date',
'completion_date'
];
/**
* The relations and their attributes that should be included when searching the model.
@@ -70,9 +78,10 @@ class AssetMaintenance extends Model implements ICompanyableChild
* @var array
*/
protected $searchableRelations = [
'asset' => ['name', 'asset_tag'],
'asset' => ['name', 'asset_tag', 'serial'],
'asset.model' => ['name', 'model_number'],
'asset.supplier' => ['name'],
'asset.assetstatus' => ['name'],
'supplier' => ['name'],
];
@@ -197,6 +206,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
->orderBy('suppliers_maintenances.name', $order);
}
/**
* Query builder scope to order on admin user
*
@@ -239,4 +249,33 @@ class AssetMaintenance extends Model implements ICompanyableChild
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.name', $order);
}
/**
* Query builder scope to order on serial
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param string $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderByAssetSerial($query, $order)
{
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.serial', $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 scopeOrderStatusName($query, $order)
{
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id')
->orderBy('maintained_asset_status.name', $order);
}
}
+8
View File
@@ -113,6 +113,14 @@ final class Company extends SnipeModel
}
}
/**
* Get the company id for the current user taking into
* account the full multiple company support setting
* and if the current user is a super user.
*
* @param $unescaped_input
* @return int|mixed|string|null
*/
public static function getIdForCurrentUser($unescaped_input)
{
if (! static::isFullMultipleCompanySupportEnabled()) {
+15 -2
View File
@@ -5,6 +5,8 @@ namespace App\Models;
use Gate;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Watson\Validating\ValidatingTrait;
class CustomFieldset extends Model
@@ -92,8 +94,19 @@ class CustomFieldset extends Model
array_push($rule, $field->attributes['format']);
$rules[$field->db_column_name()] = $rule;
//add not_array to rules for all fields
$rules[$field->db_column_name()][] = 'not_array';
// add not_array to rules for all fields but checkboxes
if ($field->element != 'checkbox') {
$rules[$field->db_column_name()][] = 'not_array';
}
if ($field->element == 'checkbox') {
$rules[$field->db_column_name()][] = 'checkboxes';
}
if ($field->element == 'radio') {
$rules[$field->db_column_name()][] = 'radio_buttons';
}
}
return $rules;
+12
View File
@@ -58,6 +58,18 @@ class Group extends SnipeModel
return $this->belongsToMany(\App\Models\User::class, 'users_groups');
}
/**
* Get the user that created the group
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function admin()
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
}
/**
* Decode JSON permissions into array
*
+18 -66
View File
@@ -160,75 +160,27 @@ class DefaultLabel extends RectangleSheet
$textY += $this->textSize + self::TEXT_MARGIN;
}
// Fields
// Render the selected fields with their labels
$fieldsDone = 0;
if ($settings->labels_display_name && $fieldsDone < $this->getSupportFields()) {
if ($asset->name) {
static::writeText(
$pdf, 'N: '.$asset->name,
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
if ($settings->labels_display_company_name && $fieldsDone < $this->getSupportFields()) {
if ($asset->company) {
static::writeText(
$pdf, 'C: '.$asset->company->name,
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
if ($settings->labels_display_tag && $fieldsDone < $this->getSupportFields()) {
if ($asset->asset_tag) {
static::writeText(
$pdf, 'T: '.$asset->asset_tag,
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
if ($settings->labels_display_serial && $fieldsDone < $this->getSupportFields()) {
if ($asset->serial) {
static::writeText(
$pdf, 'S: '.$asset->serial,
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
if ($settings->labels_display_model && $fieldsDone < $this->getSupportFields()) {
if ($asset->model) {
static::writeText(
$pdf, 'M: '.$asset->model->name,
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
if ($fieldsDone < $this->getSupportFields()) {
foreach ($record->get('fields') as $field) {
// Actually write the selected fields and their matching values
static::writeText(
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
$textX1, $textY,
'freesans', '', $this->textSize, 'L',
$textW, $this->textSize,
true, 0
);
$textY += $this->textSize + self::TEXT_MARGIN;
$fieldsDone++;
}
}
}
}
?>
@@ -0,0 +1,89 @@
<?php
namespace App\Models\Labels\Tapes\Dymo;
class LabelWriter_1933081 extends LabelWriter
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 2.80;
private const TITLE_SIZE = 2.80;
private const TITLE_MARGIN = 0.50;
private const LABEL_SIZE = 2.80;
private const LABEL_MARGIN = - 0.35;
private const FIELD_SIZE = 2.80;
private const FIELD_MARGIN = 0.15;
public function getUnit() { return 'mm'; }
public function getWidth() { return 89; }
public function getHeight() { return 25; }
public function getSupportAssetTag() { return true; }
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) {}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$barcodeSize = $pa->h - self::TAG_SIZE;
if ($record->has('barcode2d')) {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freesans', 'b', self::TAG_SIZE, 'C',
$barcodeSize, self::TAG_SIZE, true, 0
);
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
} else {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freesans', 'b', self::TAG_SIZE, 'R',
$usableWidth, self::TAG_SIZE, true, 0
);
}
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$currentX, $currentY,
'freesans', 'b', self::TITLE_SIZE, 'L',
$usableWidth, self::TITLE_SIZE, true, 0
);
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
$currentX, $currentY,
'freesans', '', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
if ($record->has('barcode1d')) {
static::write1DBarcode(
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
);
}
}
}
@@ -0,0 +1,89 @@
<?php
namespace App\Models\Labels\Tapes\Dymo;
class LabelWriter_2112283 extends LabelWriter
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 2.80;
private const TITLE_SIZE = 2.80;
private const TITLE_MARGIN = 0.50;
private const LABEL_SIZE = 2.80;
private const LABEL_MARGIN = - 0.35;
private const FIELD_SIZE = 2.80;
private const FIELD_MARGIN = 0.15;
public function getUnit() { return 'mm'; }
public function getWidth() { return 54; }
public function getHeight() { return 25; }
public function getSupportAssetTag() { return true; }
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) {}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$barcodeSize = $pa->h - self::TAG_SIZE;
if ($record->has('barcode2d')) {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freesans', 'b', self::TAG_SIZE, 'C',
$barcodeSize, self::TAG_SIZE, true, 0
);
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
} else {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freesans', 'b', self::TAG_SIZE, 'R',
$usableWidth, self::TAG_SIZE, true, 0
);
}
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$currentX, $currentY,
'freesans', 'b', self::TITLE_SIZE, 'L',
$usableWidth, self::TITLE_SIZE, true, 0
);
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
$currentX, $currentY,
'freesans', '', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
if ($record->has('barcode1d')) {
static::write1DBarcode(
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
);
}
}
}
+2
View File
@@ -53,6 +53,7 @@ class License extends Depreciable
'purchase_date' => 'date_format:Y-m-d|nullable|max:10',
'expiration_date' => 'date_format:Y-m-d|nullable|max:10',
'termination_date' => 'date_format:Y-m-d|nullable|max:10',
'min_amt' => 'numeric|nullable|gte:0',
];
/**
@@ -81,6 +82,7 @@ class License extends Depreciable
'supplier_id',
'termination_date',
'user_id',
'min_amt',
];
use Searchable;
+9 -4
View File
@@ -95,7 +95,10 @@ class Location extends SnipeModel
/**
* Determine whether or not this location can be deleted
* Determine whether or not this location can be deleted.
*
* This method requires the eager loading of the relationships in order to determine whether
* it can be deleted. It's tempting to load those here, but that increases the query load considerably.
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
@@ -103,10 +106,12 @@ class Location extends SnipeModel
*/
public function isDeletable()
{
return Gate::allows('delete', $this)
&& ($this->assignedAssets()->count() === 0)
&& ($this->assets()->count() === 0)
&& ($this->users()->count() === 0);
&& ($this->assets_count === 0)
&& ($this->assigned_assets_count === 0)
&& ($this->children_count === 0)
&& ($this->users_count === 0);
}
/**
+8
View File
@@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
trait Loggable
{
// an attribute for setting whether or not the item was imported
public ?bool $imported = false;
/**
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
@@ -18,6 +21,11 @@ trait Loggable
return $this->morphMany(Actionlog::class, 'item');
}
public function setImported(bool $bool): void
{
$this->imported = $bool;
}
/**
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
-1
View File
@@ -352,7 +352,6 @@ class Setting extends Model
'ldap_client_tls_cert',
'ldap_default_group',
'ldap_dept',
'ldap_emp_num',
'ldap_phone_field',
'ldap_jobtitle',
'ldap_manager',
+3 -1
View File
@@ -67,6 +67,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'gravatar',
'vip',
'autoassign_licenses',
'website',
];
protected $casts = [
@@ -120,6 +121,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'phone',
'jobtitle',
'employee_num',
'website',
];
/**
@@ -335,7 +337,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function licenses()
{
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id');
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at');
}
/**
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -38,13 +43,17 @@ class CheckinAccessoryNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -54,34 +63,8 @@ class CheckinAccessoryNotification extends Notification
if ($this->target instanceof User && $this->target->email != '') {
\Log::debug('The target is a user');
/**
* Send an email if the asset requires acceptance,
* so the user can accept or decline the asset
*/
if (($this->item->requireAcceptance()) || ($this->item->getEula()) || ($this->item->checkin_email())) {
$notifyBy[] = 'mail';
}
/**
* Send an email if the asset requires acceptance,
* so the user can accept or decline the asset
*/
if ($this->item->requireAcceptance()) {
\Log::debug('This accessory requires acceptance');
}
/**
* Send an email if the item has a EULA, since the user should always receive it
*/
if ($this->item->getEula()) {
\Log::debug('This accessory has a EULA');
}
/**
* Send an email if an email should be sent at checkin/checkout
*/
if ($this->item->checkin_email()) {
\Log::debug('This accessory has a checkin_email()');
$notifyBy[] = 'mail';
}
}
@@ -132,6 +115,32 @@ class CheckinAccessoryNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Accessory_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checked_into').': '.$item->location->name ? $item->location->name : '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
trans('admin/hardware/form.notes').": ".$note ?: '',
)
->onClick(route('accessories.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
+38 -2
View File
@@ -10,6 +10,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -46,12 +51,16 @@ class CheckinAssetNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
\Log::debug('use webhook');
$notifyBy[] = 'slack';
}
@@ -108,6 +117,33 @@ class CheckinAssetNotification extends Notification
->fact(trans('admin/hardware/form.status'), $item->assetstatus->name)
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Asset_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checked_into') ?: '',
$item->location->name ? $item->location->name : '',
trans('admin/hardware/form.status').": ".$item->assetstatus->name,
)
->onClick(route('hardware.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -43,12 +48,16 @@ class CheckinLicenseSeatNotification extends Notification
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'microsoft'){
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -113,6 +122,34 @@ class CheckinLicenseSeatNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.License_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checkedin_from') ?: '',
$target->present()->fullName() ?: '',
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
)
->onClick(route('licenses.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -37,13 +42,17 @@ class CheckoutAccessoryNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -123,6 +132,34 @@ class CheckoutAccessoryNotification extends Notification
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Accessory_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
trans('admin/consumables/general.remaining').": ". $item->numRemaining(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -11,6 +11,13 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\Enums\Icon;
use NotificationChannels\GoogleChat\Enums\ImageStyle;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -54,13 +61,20 @@ class CheckoutAssetNotification extends Notification
*/
public function via()
{
if (Setting::getSettings()->webhook_selected == 'microsoft'){
return [MicrosoftTeamsChannel::class];
}
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
if ((Setting::getSettings()) && (Setting::getSettings()->webhook_selected == 'slack')) {
\Log::debug('use webhook');
$notifyBy[] = 'slack';
}
@@ -143,6 +157,33 @@ class CheckoutAssetNotification extends Notification
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Asset_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
$note ?: '',
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -44,13 +49,17 @@ class CheckoutConsumableNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -128,6 +137,33 @@ class CheckoutConsumableNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Consumable_checkout_notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->fullName() ?: '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -43,15 +48,18 @@ class CheckoutLicenseSeatNotification extends Notification
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -129,6 +137,33 @@ class CheckoutLicenseSeatNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.License_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
+3
View File
@@ -38,6 +38,9 @@ class AccessoryObserver
$logAction->item_id = $accessory->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($accessory->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -109,6 +109,9 @@ class AssetObserver
$logAction->item_id = $asset->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($asset->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class ComponentObserver
$logAction->item_id = $component->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($component->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class ConsumableObserver
$logAction->item_id = $consumable->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($consumable->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class LicenseObserver
$logAction->item_id = $license->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($license->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+1
View File
@@ -41,6 +41,7 @@ class AccessoryPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'formatter' => 'accessoriesLinkFormatter',
], [
+6 -1
View File
@@ -38,10 +38,14 @@ class ActionlogPresenter extends Presenter
public function icon()
{
// User related icons
if ($this->itemType() == 'user') {
if ($this->actionType()=='2fa reset') {
return 'fa-solid fa-mobile-screen';
}
if ($this->actionType()=='create new') {
return 'fa-solid fa-user-plus';
}
@@ -61,6 +65,7 @@ class ActionlogPresenter extends Presenter
if ($this->actionType()=='update') {
return 'fa-solid fa-user-pen';
}
return 'fa-solid fa-user';
}
@@ -41,6 +41,19 @@ class AssetMaintenancesPresenter extends Presenter
'sortable' => true,
'title' => trans('admin/hardware/table.asset_tag'),
'formatter' => 'assetTagLinkFormatter',
], [
'field' => 'serial',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/hardware/table.serial'),
'formatter' => 'assetSerialLinkFormatter',
], [
'field' => 'status_label',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/hardware/table.status'),
'visible' => true,
'formatter' => 'statuslabelsLinkObjFormatter',
], [
'field' => 'model',
'searchable' => true,
@@ -72,6 +85,7 @@ class AssetMaintenancesPresenter extends Presenter
'field' => 'title',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/asset_maintenances/form.title'),
], [
'field' => 'start_date',
+1
View File
@@ -35,6 +35,7 @@ class AssetModelPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'visible' => true,
'title' => trans('general.name'),
'formatter' => 'modelsLinkFormatter',
+17 -1
View File
@@ -55,6 +55,7 @@ class AssetPresenter extends Presenter
'field' => 'asset_tag',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/hardware/table.asset_tag'),
'visible' => true,
'formatter' => 'hardwareLinkFormatter',
@@ -195,6 +196,14 @@ class AssetPresenter extends Presenter
'visible' => false,
'title' => trans('admin/hardware/form.warranty_expires'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'requestable',
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('admin/hardware/general.requestable'),
'formatter' => 'trueFalseFormatter',
], [
'field' => 'notes',
'searchable' => true,
@@ -244,6 +253,13 @@ class AssetPresenter extends Presenter
'visible' => false,
'title' => trans('admin/hardware/table.checkout_date'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'last_checkin',
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('admin/hardware/table.last_checkin_date'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'expected_checkin',
'searchable' => false,
@@ -308,7 +324,7 @@ class AssetPresenter extends Presenter
'field' => 'checkincheckout',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'switchable' => false,
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'hardwareInOutFormatter',
+1
View File
@@ -25,6 +25,7 @@ class CategoryPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'visible' => true,
'formatter' => 'categoriesLinkFormatter',
+1 -1
View File
@@ -25,7 +25,7 @@ class CompanyPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'switchable' => false,
'title' => trans('admin/companies/table.name'),
'visible' => true,
'formatter' => 'companiesLinkFormatter',
+1 -1
View File
@@ -126,7 +126,7 @@ class ComponentPresenter extends Presenter
'field' => 'checkincheckout',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'switchable' => false,
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'componentsInOutFormatter',
+1
View File
@@ -35,6 +35,7 @@ class ConsumablePresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'visible' => true,
'formatter' => 'consumablesLinkFormatter',
+1
View File
@@ -25,6 +25,7 @@ class DepreciationPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'visible' => true,
'formatter' => 'depreciationsLinkFormatter',
@@ -34,6 +34,7 @@ class DepreciationReportPresenter extends Presenter
"field" => "name",
"searchable" => true,
"sortable" => true,
'switchable' => false,
"title" => trans('admin/hardware/form.name'),
"visible" => false,
], [
+11 -3
View File
@@ -33,6 +33,7 @@ class LicensePresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('general.name'),
'formatter' => 'licensesLinkFormatter',
], [
@@ -89,7 +90,14 @@ class LicensePresenter extends Presenter
'searchable' => false,
'sortable' => true,
'title' => trans('admin/accessories/general.remaining'),
], [
],
[
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'title' => trans('mail.min_QTY'),
'formatter' => 'minAmtFormatter',
],[
'field' => 'purchase_date',
'searchable' => true,
'sortable' => true,
@@ -179,7 +187,7 @@ class LicensePresenter extends Presenter
'field' => 'checkincheckout',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'switchable' => false,
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'licensesInOutFormatter',
@@ -273,7 +281,7 @@ class LicensePresenter extends Presenter
'field' => 'checkincheckout',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'switchable' => false,
'title' => trans('general.checkin').'/'.trans('general.checkout'),
'visible' => true,
'formatter' => 'licenseSeatInOutFormatter',
+6 -1
View File
@@ -14,7 +14,11 @@ class LocationPresenter extends Presenter
public static function dataTableLayout()
{
$layout = [
[
'field' => 'bulk_selectable',
'checkbox' => true,
'formatter' => 'checkboxEnabledFormatter',
],
[
'field' => 'id',
'searchable' => false,
@@ -27,6 +31,7 @@ class LocationPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/locations/table.name'),
'visible' => true,
'formatter' => 'locationsLinkFormatter',
+2 -1
View File
@@ -27,6 +27,7 @@ class ManufacturerPresenter extends Presenter
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/manufacturers/table.name'),
'visible' => true,
'formatter' => 'manufacturersLinkFormatter',
@@ -45,7 +46,7 @@ class ManufacturerPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/manufacturers/table.url'),
'title' => trans('general.url'),
'visible' => true,
'formatter' => 'externalLinkFormatter',
],
+11 -2
View File
@@ -38,7 +38,7 @@ class UserPresenter extends Presenter
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => 'Avatar',
'title' => trans('general.importer.avatar'),
'visible' => false,
'formatter' => 'imageFormatter',
],
@@ -122,6 +122,15 @@ class UserPresenter extends Presenter
'visible' => true,
'formatter' => 'phoneFormatter',
],
[
'field' => 'website',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.website'),
'visible' => false,
'formatter' => 'externalLinkFormatter',
],
[
'field' => 'address',
'searchable' => true,
@@ -166,7 +175,7 @@ class UserPresenter extends Presenter
'field' => 'username',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'switchable' => false,
'title' => trans('admin/users/table.username'),
'visible' => true,
'formatter' => 'usersLinkFormatter',

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