Compare commits

..

388 Commits

Author SHA1 Message Date
snipe
480091f47e Added second driver for DO
Signed-off-by: snipe <snipe@snipe.net>
2025-09-08 09:51:00 +01:00
snipe
1e150e2683 Naive support for Digital Ocean spaces
Signed-off-by: snipe <snipe@snipe.net>
2023-12-01 14:17:47 +00:00
snipe
5730518fc6 Merge pull request #13978 from uberbrady/test_ldap
Test ldap
2023-11-30 19:23:29 +00:00
snipe
fb1965331c Merge pull request #13981 from Godmartinz/license-table-header-fix
Fixed the Column header Serial to Product Key for Licenses
2023-11-30 18:51:42 +00:00
Godfrey M
fdaa44654d changes the table header Serial to Product Key 2023-11-30 10:46:18 -08:00
snipe
1455331a90 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2023-11-30 18:25:38 +00:00
snipe
42109dc1eb Merge pull request #13976 from Godmartinz/sc-24144_final
Fixed black dark theme, contrast issues etc
2023-11-30 16:23:13 +00:00
snipe
47c8ae000a Merge pull request #13975 from ubc-cpsc/bugfix/CVE-2023-49316
Fixes CVE-2023-49316 by upgrading phpseclib/phpseclib
2023-11-30 16:22:08 +00:00
Brady Wetherington
5a60df55d1 Add more LDAP tests - including pagination(!) 2023-11-30 16:09:37 +00:00
Brady Wetherington
16da994e28 Add LDAP as 'recommended' requirement; flesh out LDAP tests 2023-11-30 14:00:20 +00:00
Brady Wetherington
2a00dc2d28 Merge branch 'develop' into test_ldap 2023-11-30 12:04:43 +00:00
Godfrey M
4a19f23e7c fixes for black dark theme 2023-11-29 11:30:02 -08:00
Joël Pittet
5b4d6b346b Update for CVE-2023-49316 by upgrading phpseclib/phpseclib (3.0.14 => 3.0.34) 2023-11-29 11:03:12 -08:00
snipe
96210e9498 Merge pull request #13970 from snipe/bug/sc-24149
Created mutator for requestable attribute
2023-11-29 10:08:41 +00:00
snipe
3152df2c48 Created mutator for requestable attribute
Signed-off-by: snipe <snipe@snipe.net>
2023-11-29 10:07:47 +00:00
snipe
189c90e9e5 Merge pull request #13954 from Godmartinz/feature/sc-23756
Fixed user total asset cost to appear conditionally
2023-11-29 09:40:39 +00:00
snipe
c35d234cde Merge pull request #13959 from snipe/fixes/rebased_bulk_edit_fix
Fixed status and model in bulk edit
2023-11-29 09:40:03 +00:00
snipe
80fafeaebe Merge pull request #13965 from akemidx/no_group_notice_trans
Translation of No Group notice on User edit page
2023-11-29 09:39:28 +00:00
akemidx
63065bab5d adding in icons for clarity, since there are two places called settings 2023-11-28 17:51:43 -05:00
akemidx
108658520d translating no group message on user edit page 2023-11-28 17:15:51 -05:00
Godfrey M
9e59550b79 removes the row instead of just the optional values 2023-11-28 10:08:32 -08:00
snipe
899890b224 Use update array nomenclature
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 16:25:35 +00:00
snipe
7ac3341b4f Merge pull request #13955 from marcusmoore/bug/sc-24137
Fixed sqlite exception in migration
2023-11-28 16:13:10 +00:00
snipe
9d5ceb685d Merge pull request #13958 from uberbrady/encryption_key_rotator
Modified re-crypter to also work when given a CLI old-key
2023-11-28 16:10:45 +00:00
snipe
601ebcc1e6 Removed table header contraint
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 16:00:42 +00:00
snipe
2247be77d8 Fixed translation
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:56:20 +00:00
snipe
2d4a14d4bb Fixed from rebase :(
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:55:41 +00:00
snipe
3b9f069627 Switched back to old version
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:54:42 +00:00
snipe
2c6b957fbe Removed unused query
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:28:37 +00:00
snipe
6ae03a204b Added the model info back in
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:21:14 +00:00
snipe
187d3abeb4 Use translation string
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:15:07 +00:00
snipe
c91713e20a Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:14:55 +00:00
snipe
fa1176ce14 Added translation
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 15:14:12 +00:00
Brady Wetherington
c71726c83e Whoops, left in some debug lines 2023-11-28 13:52:07 +00:00
Brady Wetherington
ca430ec9b3 Modified re-crypter to also work when given a CLI old-key 2023-11-28 13:49:58 +00:00
snipe
9d786d9386 Removed unused method
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 13:43:33 +00:00
snipe
4723cfd4ba More refactoring
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 13:33:32 +00:00
snipe
f9d5c451bc Committing this for now - not done
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 13:33:32 +00:00
snipe
5574218966 More FAFO - cleanup needed, but model_id works now
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 13:33:32 +00:00
snipe
c997ae44c2 FAFO troubleshooting
Signed-off-by: snipe <snipe@snipe.net>
2023-11-28 13:33:32 +00:00
Marcus Moore
77ceac5747 Update docblock 2023-11-27 16:36:51 -08:00
Marcus Moore
990358750b Add migration for postgres 2023-11-27 16:33:57 -08:00
Marcus Moore
72dbe95168 Add table prefix 2023-11-27 15:55:33 -08:00
Marcus Moore
37bd297094 Fix typo 2023-11-27 14:05:38 -08:00
Marcus Moore
5d32956070 Allow for migrating data when using sqlite 2023-11-27 12:35:13 -08:00
Godfrey M
45e9d0597a makes user total asset cost appear conditionally 2023-11-27 11:36:47 -08:00
Brady Wetherington
8e8b1068ff Beginnings of LDAP test suite 2023-11-27 14:50:43 +00:00
snipe
8c7edcb357 Merge pull request #13953 from snipe/localizations/new_strings
Updated language strings
2023-11-27 14:44:01 +00:00
snipe
dbb6002f9f Updated language strings
Signed-off-by: snipe <snipe@snipe.net>
2023-11-27 14:43:16 +00:00
snipe
776c91934b Updated dir name for Khmer
Signed-off-by: snipe <snipe@snipe.net>
2023-11-27 14:39:13 +00:00
snipe
77ab5eb875 Merge pull request #13909 from akemidx/bug/sc-23999
Hiding Order Column in Custom Fieldsets
2023-11-24 19:10:42 +00:00
snipe
eca13d0593 Merge pull request #13950 from snipe/fixes/array_key_in_import
Check for the array key in $activeFile->first_row
2023-11-24 10:10:08 +00:00
snipe
9607567b75 Check for the array key in $activeFile->first_row
Signed-off-by: snipe <snipe@snipe.net>
2023-11-24 10:08:35 +00:00
snipe
1217bff4b6 Merge pull request #13947 from snipe/fixes/refactor_checkout_with_bad_category
Refactored checkout for items with bad or missing category
2023-11-23 16:59:51 +00:00
snipe
ea55daf913 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 16:51:39 +00:00
snipe
0ff99f0479 Merge pull request #13948 from uberbrady/scim_active_flag
SCIM active flag must be a boolean
2023-11-23 16:44:22 +00:00
Brady Wetherington
8916c976e1 Merge branch 'develop' into scim_active_flag 2023-11-23 16:36:17 +00:00
Brady Wetherington
7b12d511f8 Force active flag to be returned as true or false 2023-11-23 16:34:03 +00:00
snipe
81b2273c37 Refactored checkout screen to redirect if invalid category
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 16:18:28 +00:00
snipe
978bbeccc5 Added strings
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 16:18:09 +00:00
snipe
accfbc1b99 Show number of remaining seats and category in form
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 16:18:03 +00:00
snipe
cf3608ae10 Add @bilias as a contributor 2023-11-23 15:33:08 +00:00
snipe
aaa6a31eb3 Merge pull request #13945 from snipe/fixes/green_skin_fix_13915_to_develop
Higher contrast on green dark skin (#13915 for develop)
2023-11-23 15:30:00 +00:00
snipe
88cc234cbd Higher contrast for green dark skin
Applies #13915 to develop

Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:28:43 +00:00
snipe
94eeca7429 Merge pull request #13944 from snipe/security/snyk_13937
Upgrade jspdf-autotable from 3.5.31 to 3.7.1 #13937
2023-11-23 15:24:33 +00:00
snipe
e0e11845bb Upgrade jspdf-autotable from 3.5.31 to 3.7.1 #13937
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:23:13 +00:00
snipe
a7f98ea2a5 Merge pull request #13943 from snipe/security/snyk_13936
Upgrade alpinejs from 3.12.3 to 3.13.2 #13936
2023-11-23 15:20:47 +00:00
snipe
8e3657d62e Upgrade alpinejs from 3.12.3 to 3.13.2 #13936
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:19:59 +00:00
snipe
ddd7f206d3 Merge pull request #13942 from snipe/security/snyk_13935
Updated acorn to 8.11.2
2023-11-23 15:17:34 +00:00
snipe
81cf08adf5 Updated acorn to 8.11.2
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:16:40 +00:00
snipe
1b77632ecb Merge pull request #13941 from snipe/security/snyk_13938
Upgraded webpack to 5.89.0
2023-11-23 15:15:08 +00:00
snipe
d4bdaf16e7 Upgraded webpack to 5.89.0
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:13:13 +00:00
snipe
e3a8952f4b Merge pull request #13940 from snipe/features/add_location_to_user_view_assets
Added location to user view assets
2023-11-23 15:02:23 +00:00
snipe
ad6b286e10 Added default location to view-assets view
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:01:39 +00:00
snipe
7f94b7df32 Removed refresh from asset view table
Signed-off-by: snipe <snipe@snipe.net>
2023-11-23 15:01:24 +00:00
snipe
6409018b08 Bumped hash and moved to pre version
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 23:41:03 +00:00
snipe
31f429e1c4 Merge pull request #13934 from snipe/fixes/escaped_asset_tag_in_return_msg
Escape the asset tag before passing it to the view
2023-11-22 23:21:54 +00:00
snipe
3a0b994443 Merge pull request #13933 from snipe/fixes/removed_extra_return
Removed extra return statement in consumables API checkout method
2023-11-22 23:21:06 +00:00
snipe
1307ef19cf Escape the asset tag before passing it to the view
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 23:20:47 +00:00
snipe
aed6b531cb Derp. Extra return statement
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 23:05:38 +00:00
snipe
bb0ba0bebe Merge pull request #13932 from snipe/features/use_allowlist_for_user_logging
Use allowlist for user observer logging
2023-11-22 23:01:52 +00:00
snipe
ba127be344 Use saveQuietly to prevent double entries
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 23:00:30 +00:00
snipe
c6178bd619 Added translation for success password save
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 22:59:59 +00:00
snipe
3929c8f260 Nicer alert layout
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 22:57:54 +00:00
snipe
f922d0518e Added allow list - quiet the observer down for magical laravel things
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 22:35:34 +00:00
snipe
45d9119733 Removed debugging/comments
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 22:32:34 +00:00
snipe
e269415fec Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:55:44 +00:00
snipe
8481768c3d Save quietly for login methods
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:42:21 +00:00
snipe
d3175b2ca4 Removed remember token from changed
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:39:06 +00:00
snipe
a8123db4fb Fixed comments
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:35:12 +00:00
snipe
b1e782d2f0 Small nits picked to make Brady happy
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:34:39 +00:00
snipe
60517f811e Merge pull request #13931 from snipe/features/nicer_icons_for_activity_report
Use updated icons for activity report
2023-11-22 21:10:22 +00:00
snipe
58679a13eb Use updated icons for activity report
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 21:07:07 +00:00
snipe
3d9a9c7fe8 Merge pull request #13930 from snipe/features/added_user_observer
Added user observer
2023-11-22 20:51:27 +00:00
snipe
22246dd099 Unset the specific arrays we don’t want to keep
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 20:50:46 +00:00
snipe
a7f76f9860 Hide certain fields from changelog
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 20:28:58 +00:00
snipe
8b79a8afdf Added user observer
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 20:22:05 +00:00
snipe
299db76009 Merge pull request #13928 from snipe/fixes/double_logging_on_asset_restore
Fixes double logging on asset restore
2023-11-22 20:10:45 +00:00
snipe
20c0f687e9 Added restored to asset observer, removed manual logging
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 20:09:10 +00:00
snipe
d06cfe6502 Changed verb to “restore” from “restored”
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 20:08:41 +00:00
snipe
534ac5ac53 Merge pull request #13926 from snipe/bug/sc-24106
Fixed issue where delete then restore could result in duplicate asset tags
2023-11-22 18:33:17 +00:00
snipe
e2d0136336 Fixed goofy translation
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 18:14:44 +00:00
snipe
6a3ab526de Refactorer API controller restore methods
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 18:04:24 +00:00
snipe
19877244cd Added/refactorerd isDeletable()
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 18:03:26 +00:00
snipe
f7ccef16e7 Refactorer controller restore methods
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 18:02:47 +00:00
snipe
0e51a0935d Added language strings
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 17:41:12 +00:00
snipe
ffc1266d0e UI fix - don’t show the trash can icon on things that can be restored
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 17:41:06 +00:00
snipe
2972c31a11 Added restore endpoints for manufacturers and asset models API
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 17:40:47 +00:00
snipe
0d29ac9856 Fixded gate return
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 13:48:20 +00:00
snipe
eae98d3f35 Merge pull request #13923 from snipe/bug/sc-24098
Quick revert for #13830
2023-11-22 12:28:25 +00:00
snipe
a08dcbdfdb Quick revert for #13830
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 12:25:42 +00:00
snipe
9c4fbbe915 Merge pull request #13922 from snipe/bug/sc-24097
Created mutator on category checkin_email
2023-11-22 12:16:07 +00:00
snipe
d6a337a584 English hard
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 11:23:17 +00:00
snipe
83ee07cca1 Created mutator on category checkin_email
Signed-off-by: snipe <snipe@snipe.net>
2023-11-22 11:14:04 +00:00
snipe
9502525a41 Merge pull request #13914 from snipe/bug/sc-24073
Fixed missing translation string for `validation.two_column_unique_undeleted`
2023-11-21 15:44:56 +00:00
snipe
68ac1aaae0 Additional comments
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 15:36:11 +00:00
snipe
6a2ab2cfb2 Removed line break
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 15:10:19 +00:00
snipe
fb125af0df Tweaked language
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 15:09:50 +00:00
snipe
590cd0c71f Added Validator::replace() for friendlier interpretation of validation string
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 15:03:59 +00:00
snipe
0ae91d305d Add the two_column_unique_undeleted translation
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 15:03:03 +00:00
snipe
9347af0970 Merge pull request #13913 from snipe/bug/sc-24071
Fixed #13905 - Use `unique_undeleted` instead of `unique_serial`
2023-11-21 14:12:15 +00:00
snipe
766eee78b2 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 13:47:49 +00:00
snipe
2b9e4110bc Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 13:37:56 +00:00
snipe
4eb435e148 Use unique_undeleted instead of unique_serial
Signed-off-by: snipe <snipe@snipe.net>
2023-11-21 12:36:11 +00:00
akemidx
e5a4214b8b hiding all order stuff 2023-11-20 20:32:14 -05:00
snipe
a203189724 Add @Azooz2014 as a contributor 2023-11-20 15:41:36 +00:00
snipe
b02c60c2ce Merge pull request #13898 from Azooz2014/develop
Fixed #13850: Too few arguments to function in App\Importer\UserImporter::fetchManager()
2023-11-20 15:41:04 +00:00
snipe
c14b3d9780 Merge pull request #13904 from snipe/fixes/redirect_to_consumable_on_invalid_category
Redirect back to the specific consumable for correction
2023-11-20 15:40:40 +00:00
snipe
01fec1d6bb Redirect back to the specific consumable for correction
Signed-off-by: snipe <snipe@snipe.net>
2023-11-20 15:39:39 +00:00
snipe
01740b7969 Merge pull request #13903 from snipe/fixes/redirect_if_category_invalid
Redirect if category is missing or invalid
2023-11-20 15:31:58 +00:00
snipe
7c88601066 Grab the API
Signed-off-by: snipe <snipe@snipe.net>
2023-11-20 15:31:39 +00:00
snipe
fe2cfa0d38 Redirect if category is missing or invalid
Signed-off-by: snipe <snipe@snipe.net>
2023-11-20 15:28:44 +00:00
Abdelaziz Faki
2c62a7e78d Fixing #13850 UserImporter bug 2023-11-19 19:59:01 +03:00
snipe
ad6822875d Merge pull request #13892 from Godmartinz/ldap_sync_bug
added a `empty()` around location_id to fix  ldap sync bug
2023-11-19 06:58:38 +00:00
Godfrey Martinez
5569c2ba4a Merge branch 'develop' into ldap_sync_bug 2023-11-16 10:02:56 -08:00
Godfrey M
2f12a9cfe2 add a conditional around location_id to fix bug 2023-11-16 09:52:44 -08:00
snipe
81583207a9 Merge pull request #13887 from spencerrlongg/bug/sc-24024
Fix for Unique Serial
2023-11-16 15:27:55 +00:00
spencerrlongg
b1f75a3bb3 remove unused import 2023-11-15 13:20:17 -06:00
spencerrlongg
c7f90ad86d tested, working 2023-11-15 12:50:40 -06:00
spencerrlongg
0924a53789 quick fix 2023-11-15 12:28:30 -06:00
snipe
eb87720661 Merge pull request #13878 from snipe/fixes/clearer_error_text_on_import
Clarified upload error text
2023-11-14 16:16:16 +00:00
snipe
9316ecb8d6 CLarified upload error text
Signed-off-by: snipe <snipe@snipe.net>
2023-11-14 16:14:39 +00:00
snipe
a81e2be6da Merge pull request #13876 from snipe/fixes/added_fieldmap_back_for_cli_import
Added default map back to the importer
2023-11-14 14:40:08 +00:00
snipe
73ebd9017f Added default map back to the importer
Signed-off-by: snipe <snipe@snipe.net>
2023-11-14 14:37:48 +00:00
snipe
4546e87eb5 Merge pull request #13680 from marcusmoore/feature/sc-23769
Changed data source input to select in new label engine
2023-11-13 20:38:42 +00:00
snipe
9dc9834bcb Merge pull request #13859 from inietov/fixes/attempt_to_read_id_on_boolean
Fixed ErrorExemption: Attempt to read property "id" on bool [sc-23945]
2023-11-13 20:22:52 +00:00
snipe
4ecce51b57 Merge pull request #13831 from Godmartinz/multi-sync-ldap-locations
Added multi location sync for ldap
2023-11-13 20:20:45 +00:00
snipe
000a28d648 Merge pull request #13830 from spencerrlongg/bug/sc-23921
Resolves Exceptions When An Array is Submitted and Exception Occurs Before Validation
2023-11-13 20:19:44 +00:00
snipe
495df737fc Unfixed indenting :(
Signed-off-by: snipe <snipe@snipe.net>
2023-11-13 16:39:35 +00:00
snipe
0bae1a1f5e Merge pull request #13853 from snipe/features/link_back_to_asset_on_create
Link back to asset on create
2023-11-13 16:37:35 +00:00
snipe
9bff2d0bbf Fixed indenting?
Signed-off-by: snipe <snipe@snipe.net>
2023-11-09 21:47:29 +00:00
snipe
9e1cfac995 Merge pull request #13857 from akemidx/bug/sc-20532
Bug Fix:  Undefined array key 266
2023-11-09 09:39:04 +00:00
Ivan Nieto Vivanco
7d30001ff8 Ignore the importer checkout if user is not found 2023-11-08 15:32:33 -06:00
Marcus Moore
49136a4d67 Add tests for color helper 2023-11-08 12:35:42 -08:00
akemidx
4382adce85 typo in error log message 2023-11-08 15:29:29 -05:00
akemidx
2051ac785d adding in error logging and code comments 2023-11-08 13:44:46 -05:00
akemidx
9cc89911f7 possibilitieeeeeeeeessss 2023-11-08 13:29:32 -05:00
akemidx
495521b70e adjusting for less than 0 possibility 2023-11-08 12:51:51 -05:00
snipe
6da0671143 Added comment
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:44:09 +00:00
snipe
a24d21e683 Added new unescaped alert box
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:41:05 +00:00
snipe
d2bf71327b Redirect /asset_tag/view to /asset_tag
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:40:56 +00:00
snipe
ef10f2e50e Use more correct route in redirect
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:40:38 +00:00
snipe
08153d418f Use new alert box and linked string
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:40:18 +00:00
snipe
3f8b63e053 Add linked text
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 14:40:02 +00:00
snipe
d2bfa9a3b6 Merge pull request #13852 from snipe/bug/sc-23959
Fixed URL to favicon on default blade (used in 404 messages)
2023-11-08 11:39:24 +00:00
snipe
26311e46b8 Fixed URL to favicon on default blade (used in 404 messages)
Signed-off-by: snipe <snipe@snipe.net>
2023-11-08 11:38:48 +00:00
snipe
b3a6cc633b Merge pull request #13769 from Godmartinz/location-dropdown-duplication
clarifies an address column under locations
2023-11-08 08:59:07 +00:00
snipe
bc9545ca96 Merge pull request #13823 from spencerrlongg/chore/sc-23922
New Translation Strings for Custom Asset Export
2023-11-08 08:57:20 +00:00
snipe
cf42eca198 Merge pull request #13814 from ntbutler-nbcs/feature-location-history
Added #13754 - asset history tab to locations view
2023-11-08 08:55:07 +00:00
snipe
44d064f094 Merge pull request #13842 from marcusmoore/bug/sc-23932
Fixed notes not saving to action log when licenses are checked in/out
2023-11-08 08:53:00 +00:00
snipe
3fb913669b Merge pull request #13847 from spencerrlongg/bug/sc-23914
Removed Refresh Button on Client Tables
2023-11-08 08:52:40 +00:00
snipe
1171141904 Merge pull request #13848 from marcusmoore/bug/sc-23949
Fixed missing import
2023-11-08 08:50:31 +00:00
Marcus Moore
eceb69122c Add missing import 2023-11-07 16:03:28 -08:00
akemidx
154e71de74 shifting wrap around logic back into 0-265 2023-11-07 17:43:51 -05:00
spencerrlongg
93101d36b8 removed refresh button on client tables 2023-11-07 15:07:24 -06:00
Marcus Moore
4fb349e326 Remove extra assertions 2023-11-06 14:04:52 -08:00
Marcus Moore
4d2790c3f4 Add tests for license checkout notes 2023-11-06 13:54:36 -08:00
Marcus Moore
a08e0bd547 Ensure notes are saved to the action log when licenses are checked in and out 2023-11-06 12:20:13 -08:00
snipe
92c7de3fb5 Merge pull request #13837 from snipe/features/add_employee_number_to_user_merge
Added employee number to user merge screen
2023-11-03 20:10:05 +00:00
snipe
d0cc42a068 Added employee number to user merge screeb
Signed-off-by: snipe <snipe@snipe.net>
2023-11-03 20:09:11 +00:00
snipe
8c316e0bd3 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2023-11-03 14:07:15 +00:00
snipe
7a638e4a65 RB-17457 - Object of class Illuminate\Database\Eloquent\Collection could not be converted to int
Signed-off-by: snipe <snipe@snipe.net>
2023-11-03 14:06:27 +00:00
Godfrey M
3b6a0d6525 allows multi location sync for ldap 2023-11-02 16:50:19 -07:00
snipe
4d65d09306 Removed trim on manager ID
Signed-off-by: snipe <snipe@snipe.net>
2023-11-02 14:50:40 +00:00
snipe
c5e1cdbcee Merge pull request #13828 from Godmartinz/ldap_location_sync
fixed ldap location sync
2023-11-02 12:20:01 +00:00
spencerrlongg
75532d9662 adds permission test 2023-11-01 16:49:59 -05:00
Godfrey M
13d3f85c62 fixes ldap location sync 2023-11-01 12:26:58 -07:00
spencerrlongg
938ec75aa7 clarifying note 2023-11-01 14:09:03 -05:00
spencerrlongg
c9604b896a nevermind 2023-11-01 13:46:32 -05:00
spencerrlongg
d167ec6dc0 unique undeleted recreated with laravel rule 2023-11-01 13:36:35 -05:00
spencerrlongg
e05af5216e formatting 2023-11-01 12:14:46 -05:00
spencerrlongg
3f834cb88f authorization tested in request 2023-11-01 11:43:53 -05:00
spencerrlongg
d971172cf3 rm unnecessary import 2023-11-01 11:34:15 -05:00
spencerrlongg
53bd5626c9 this works, need to write up pr 2023-11-01 11:33:29 -05:00
spencerrlongg
8f75bb8706 fix translation strings 2023-10-31 21:38:52 -05:00
spencerrlongg
b67b00dd82 this is a start, something up with asset_tag unique rule 2023-10-31 21:06:44 -05:00
spencerrlongg
d4b9f6a2a4 add not_array (not ready, doesn't catch for specific exception) 2023-10-31 21:00:22 -05:00
snipe
5073c3389f Merge pull request #13821 from snipe/bug/rb-17462
More graceful failure if ldap_results JSON decode isn’t set
2023-10-31 17:11:14 +00:00
snipe
f822f8b186 More graceful failure if ldap_results JSON decode isn’t set
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 17:09:18 +00:00
snipe
e381bf978f Merge pull request #13816 from snipe/bug/sc-23900
Fixed datetime displayed on license edit for fields that should be date
2023-10-31 16:59:12 +00:00
snipe
95e9002cd0 Merge pull request #13820 from inietov/fixes/attempt_to_read_property_fields
Fixed Attempt to read property fields on null [sc-23903]
2023-10-31 16:30:07 +00:00
snipe
22996895d8 Check for valid model
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 16:04:19 +00:00
Ivan Nieto Vivanco
9f9f8c173b Add condition to only run trough custom fields if a custom fieldset exist 2023-10-31 10:00:36 -06:00
snipe
e6810e8db9 Merge pull request #13819 from snipe/bug/check_for_valid_category_on_print
Check that the category exists on user print page
2023-10-31 15:59:54 +00:00
snipe
28641493ca Check that the category exists
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 15:58:59 +00:00
snipe
9a7e7bddcc Merge pull request #13818 from snipe/bug/sc-23902
Fixed validation handling on backup uploads
2023-10-31 15:33:26 +00:00
snipe
aa2a25dc19 Display the form errors correctly
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 15:30:49 +00:00
snipe
57372fc82a Correctly call the validator in the response if it fails
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 15:30:38 +00:00
snipe
ae90f9defa Merge pull request #13817 from snipe/feature/sc-23901
Added checkboxes to intentionally remove field values in bulk user edit
2023-10-31 12:54:07 +00:00
snipe
3495652827 Added checkboxes to intentionally remove field values in bulk user edit
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 12:52:53 +00:00
snipe
5779a63221 Undoing that thing I just did :(
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 12:28:57 +00:00
snipe
209627e2cc Force format if dates are given
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 12:25:00 +00:00
snipe
9fc1cbd7ae Change cast to include format
https://laravel.com/docs/8.x/eloquent-mutators#date-casting - this doesn’t seem to work tho?
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 12:24:48 +00:00
snipe
e405e27643 Merge pull request #13815 from snipe/feature/sc-23898
Add additional fields to Download All in Activity Report
2023-10-31 09:45:12 +00:00
snipe
96211cf73c Add additional fields to Download All in Activity Report
Signed-off-by: snipe <snipe@snipe.net>
2023-10-31 09:43:47 +00:00
Nathan Butler
79f380cbe7 Added asset history tab to locations view 2023-10-31 10:28:54 +11:00
snipe
518c43680d Merge pull request #13812 from snipe/bug/sc-23897
Handle redirect from after POST request from backup restore
2023-10-30 20:21:29 +00:00
snipe
e554a80589 Handle redirect from after POST request from backup restore
Signed-off-by: snipe <snipe@snipe.net>
2023-10-30 20:01:01 +00:00
snipe
f47a8664f9 Merge pull request #13811 from Godmartinz/missing_translation_string
add missing translation string
2023-10-30 19:40:51 +00:00
Godfrey M
97953ae082 add missing translation string 2023-10-30 12:17:55 -07:00
snipe
8e20071254 Merge pull request #13731 from snipe/improvements/trim_on_import
Added trim to import values
2023-10-30 14:17:37 +00:00
snipe
316c90c144 Update app/Importer/ItemImporter.php
Co-authored-by: Marcus Moore <contact@marcusmoore.io>
2023-10-30 14:17:27 +00:00
snipe
462d2d329a Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2023-10-30 14:12:38 +00:00
snipe
a6b713ae21 Add @mmanjos as a contributor 2023-10-30 14:12:23 +00:00
snipe
0d67ae27c2 Merge pull request #13810 from mmanjos/develop
Fixed #11179: APP_FORCE_TLS is missing from the example .env.docker
2023-10-30 14:11:54 +00:00
Matthew Manjos
bd894677ef add missing variable APP_FORCE_TLS to .env.docker 2023-10-30 10:09:44 -04:00
snipe
d404452f5a Merge pull request #13809 from snipe/bug/sc-23893
Added min_amt value to model detail page
2023-10-30 13:21:00 +00:00
snipe
8c46f4ef69 Added min_amt value to model detaul page
Signed-off-by: snipe <snipe@snipe.net>
2023-10-30 13:19:59 +00:00
snipe
740569791f Merge pull request #13803 from spencerrlongg/feature/13789-feature-request-add-user-phone-and-address-to-custom-asset-report
Added User Phone & Address Fields to Custom Asset Report
2023-10-30 12:43:20 +00:00
spencerrlongg
90eb3b86cf added user phone & address fields 2023-10-26 19:10:53 -05:00
snipe
10abecc5a6 Merge pull request #13802 from spencerrlongg/bug/13531-custom-asset-report-does-not-correct-report-eol-date
Fixes EOL on Custom Report
2023-10-26 21:43:13 +01:00
spencerrlongg
f12d44509e asset_eol_date should always be filled now 2023-10-26 14:39:02 -05:00
spencerrlongg
91b53bffa1 resolved 2023-10-26 14:23:31 -05:00
snipe
a2ba39c837 Removed required custom field from seeder
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 17:57:05 +01:00
snipe
aab7eb4a85 Merge pull request #13800 from snipe/fixes/bulk_edit_assets
Fixed FD-38641 - Bulk asset edit unable to update model_id, misc other bugs
2023-10-26 15:56:47 +01:00
snipe
7b2f49644e Use nothing instead of null to make it consistent
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 14:44:17 +01:00
snipe
305cd9b0b8 Use null instead of blank
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 14:42:49 +01:00
snipe
9bba0b764a Added clearer comments
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 14:38:39 +01:00
snipe
1ce95b6eac Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 14:34:27 +01:00
snipe
9c90f03142 Updated comment
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 14:34:18 +01:00
snipe
b4d3f51414 Merge pull request #13776 from Robert-Azelis/Robert-Azelis-EOL_fix_improvements
[FIX and improvements] EOL rate and EOL date
2023-10-26 13:36:25 +01:00
snipe
64219da0b4 Merge branch 'develop' into Robert-Azelis-EOL_fix_improvements 2023-10-26 13:36:18 +01:00
snipe
7b9259274c Merge pull request #13799 from inietov/fixes/unlogged_checkin_from_importer
Fixed #13795 Unlogged checkin action post updating the username of an asset through the CSV import
2023-10-26 13:33:51 +01:00
snipe
c1213e0abc Nicer handling for empty custom fields
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 09:41:44 +01:00
snipe
05de8a5731 Re-added missing break
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 09:21:27 +01:00
snipe
ff72c4fbaa Added nicer formatting for fields in log meta
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 09:17:28 +01:00
snipe
9d887484c8 Added encrypted custom field to seeder
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 07:41:25 +01:00
snipe
c4c47f2e8d Updated comment
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 06:51:14 +01:00
snipe
0cebccac95 Removed extra logging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 06:51:07 +01:00
snipe
c3b3aa4de6 Set one field to encrypted in the seeder
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 06:25:51 +01:00
snipe
3717d60170 Added two more custom fields/fieldsets to seeder
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 05:04:30 +01:00
snipe
6c996ac8fa Changed the seeder order to accomodate actionlog factory
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 05:03:59 +01:00
snipe
7042260871 More style polishing on custom fields in bulk edit
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 04:23:11 +01:00
snipe
2695576968 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 04:21:20 +01:00
snipe
f25ddee857 Removed debugging, use new model rules if model has changed
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 04:21:14 +01:00
snipe
541ba0c0ba Removed conditional add for model_id
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 04:20:52 +01:00
snipe
77d141d19c Don’t check the delete expected checkin by detault
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 03:56:02 +01:00
snipe
436192b836 Fixed seeder
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 02:56:34 +01:00
snipe
522aa96fcc Changed error variable name
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 02:36:18 +01:00
snipe
b87879f8e7 Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 02:35:53 +01:00
snipe
60e0e899bc Added show_in_requestable_list to factory
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 02:35:33 +01:00
snipe
6ebc01ca50 Bulk edit refactor
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 01:49:23 +01:00
snipe
4b6b36c639 Removed semicolon
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 01:49:14 +01:00
snipe
4a759f0a20 Added fingerprint icon for unique
Signed-off-by: snipe <snipe@snipe.net>
2023-10-26 01:49:01 +01:00
Ivan Nieto Vivanco
ca1845efd7 Only log checkin event if the assigned_to property is not empty 2023-10-25 16:41:31 -06:00
Ivan Nieto Vivanco
7ed58a177b Evaluate if the asset was assigned to a different user than current target to log a checkin event 2023-10-25 16:31:23 -06:00
snipe
d93e399295 Merge pull request #13797 from Godmartinz/employeeid_search_add
adds employee number as a searchable field to the asset table
2023-10-25 21:04:54 +01:00
Godfrey M
1b614c13fb ugh 2023-10-25 13:03:42 -07:00
Godfrey M
56fd41eee4 removed erroneous code 2023-10-25 13:03:12 -07:00
Godfrey M
33b45898b1 adds to advancedsearch 2023-10-25 12:59:58 -07:00
snipe
7509d57bf6 Merge pull request #13796 from snipe/feature/sc-23880
Added optional custom fields to requestable assets page
2023-10-25 20:57:54 +01:00
snipe
da08f0e26f Merge pull request #13584 from Godmartinz/Bulk_location_edit_fix_gh-13572
added an option to bulk edit location & fixed location bug
2023-10-25 20:57:18 +01:00
snipe
0feb56d931 Removed period
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 20:39:15 +01:00
snipe
a0cae77278 Fixed weird layout quirk on smaller screens
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 20:21:07 +01:00
snipe
0f97c0601b Nicer show/hide if encrypted, warn if already encrypted
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 20:11:06 +01:00
snipe
3916bac95f Added warning translation for encrypted fields
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 20:10:46 +01:00
snipe
6264d90459 Removed ending period for consistency
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:45:11 +01:00
snipe
57d3b2d2ef Check for encryption
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:44:10 +01:00
snipe
a202bf81fa Removed column widths, check for encryption and showable
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:44:02 +01:00
snipe
43da292d60 Used short translation for sr-only text
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:43:34 +01:00
snipe
ab3edae0b6 Reverted buttons (this re-introduces the n+1 problem.)
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:43:09 +01:00
snipe
887d013a39 Added listable fields in profile -> requested assets
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:42:33 +01:00
snipe
eb82bc290d Clarified translation
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:42:07 +01:00
snipe
8b9b46c562 Removed comment
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:41:59 +01:00
snipe
307f84dff8 Removed extraneous method
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 19:41:53 +01:00
snipe
3c6b79e807 Added new fields to blades
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:28:55 +01:00
snipe
e5bd78a722 Added show_in_requestable_list validation, casting
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:28:35 +01:00
snipe
23a6547ada Added custom fields, fixed n+1
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:28:14 +01:00
snipe
63a86c5622 Added field to controller
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:27:36 +01:00
snipe
933ce61efd Removed eager loading for defaultLoc, added requests loading, sorting
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:26:51 +01:00
snipe
88661a58a7 Make field nullable
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:26:31 +01:00
snipe
08693f09b0 Added requests() method on Assets
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:22:12 +01:00
snipe
3a37b80251 Added translations
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:21:58 +01:00
snipe
e7ef7f04f0 Removed periods for consistency
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:21:48 +01:00
snipe
c4adc3ccc8 Migration to add show_in_requestable_list boolean
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 17:18:37 +01:00
snipe
96f7f49b68 Fixed response docblock
Signed-off-by: snipe <snipe@snipe.net>
2023-10-25 16:40:49 +01:00
snipe
8c93e79397 Merge pull request #13787 from inietov/fixes/errorexception_undefined_array_key
Fixed ErrorException: Undefined array key in importer blade [sc-23864]
2023-10-25 07:47:48 +01:00
snipe
4d345168d6 Merge pull request #13786 from svpernova09/map-docker-logs-to-storage
Map Snipe-IT logs to storage logs folder
2023-10-25 07:20:22 +01:00
Ivan Nieto Vivanco
f2f3931fa9 Formatting correctly 2023-10-24 11:26:37 -06:00
Joe Ferguson
c919cd31fa Map Snipe-IT logs to storage logs folder
For docker-compose.yml usage to prevent error messages.
2023-10-24 11:31:02 -05:00
snipe
9d2d13bb59 Merge pull request #13784 from snipe/fixes/fixed_accessory_not_found_string
Fixed accessory not found string
2023-10-24 17:03:13 +01:00
snipe
944520bc5d Fixed notification formatting
Signed-off-by: snipe <snipe@snipe.net>
2023-10-24 17:02:16 +01:00
snipe
aae289e150 Added missing string
Signed-off-by: snipe <snipe@snipe.net>
2023-10-24 17:02:07 +01:00
snipe
c756849497 Removed debug line
Signed-off-by: snipe <snipe@snipe.net>
2023-10-24 17:01:51 +01:00
snipe
29f438123e Merge pull request #13777 from Robert-Azelis/Robert-Azelis-Cutom_Report_FIX
[FIX] Custom report - date fields error
2023-10-24 10:27:58 +01:00
Ivan Nieto Vivanco
0c5d54dfde Create the language string to show in the importer GUI indicating the file is empty 2023-10-23 22:09:17 -06:00
Ivan Nieto Vivanco
49f6eef9df Places a text indicating that the file is empty and display it as an info text 2023-10-23 21:57:35 -06:00
Ivan Nieto Vivanco
8f8e5cb723 Adds condition to only process CSV if is not empty 2023-10-23 21:47:10 -06:00
snipe
d5598b3322 Merge pull request #13778 from Robert-Azelis/Robert-Azelis-Dashboard_impr
[Improvement] Dashboard
2023-10-23 18:00:00 +01:00
Robert-Azelis
309f30f630 Update view.blade.php
display marker if date of EOL is expired and show real EOL rate as diff between purchase date and eol date
2023-10-22 18:38:59 +02:00
Robert-Azelis
04a867d12b Update eol_date.blade.php
read EOL date from database instead calculate on the base model EOL rate
2023-10-22 18:32:20 +02:00
Robert-Azelis
e62636b3a5 Update AssetModelPresenter.php
display name 'EOL rate' instead of 'EOL'
2023-10-22 18:27:53 +02:00
Robert-Azelis
4e20a241ce Update AssetPresenter.php
display name 'EOL rate' instead of 'EOL'
2023-10-22 18:26:19 +02:00
Robert-Azelis
30cc498a16 Update AssetObserver.php
update exlicit marker depends if model EOL > 0 , EOL = 0
2023-10-22 18:23:30 +02:00
Robert-Azelis
5ae09b791f Update AssetsTransformer.php
on list of assets display EOL rate in months if purchase date and eol date are set, otherwise null (not display anything)
2023-10-22 18:16:49 +02:00
Robert-Azelis
bd195a6911 Update AssetsController.php
when asset is updated control EOL date change sand set explicit marker depends to setup of model EOL rate
2023-10-22 18:11:19 +02:00
Robert-Azelis
58062ff9f5 Update AssetModelsController.php
if EOL of model has been changed and value is >0 assets will be updates with new EOL rate, date
if EOL of model has been changes and value is null or 0 then assets will be updates by null EOL rate, date
*asset with set expilicity market will not be updated
2023-10-22 17:56:38 +02:00
Robert-Azelis
7571d45d44 [Improvement[ Dashboard
If multicompany option is enabled much more important for admin is to see on dashboard list of companies instead of locations.  
In other way if multicompanies option is disabled then locations are displayed.
2023-10-22 17:41:33 +02:00
Robert-Azelis
47186b0abe [FIX] Custom report - date fields error
Custom report in v6.2.3 give Error if selected are fields:
Purchase Date, Checkout Date, Last Checkin Date, Expected Checkin Date
Reason is: date field format
2023-10-22 16:28:44 +02:00
Godfrey M
e0a4387b10 clarifies an address column under locations 2023-10-19 10:01:08 -07:00
snipe
38066bf162 Merge pull request #13758 from inietov/fixes/array_to_string_conversion_rb17355
Fixed ErrorException: Array to String Conversion rb17355
2023-10-18 19:09:13 +01:00
spencerrlongg
6b745930b5 what typo? 2023-10-18 13:00:13 -05:00
spencerrlongg
38de69b3da new validation rule 2023-10-18 12:41:24 -05:00
snipe
bc6f014fff Merge pull request #13762 from snipe/fixes/make_sure_order_number_is_not_array
Make sure order number is not an array
2023-10-18 14:37:18 +01:00
snipe
1be503b21a Make sure order number is not an array
Signed-off-by: snipe <snipe@snipe.net>
2023-10-18 14:36:16 +01:00
snipe
825c800d32 Merge pull request #13761 from snipe/fixes/check_for_valid_date_in_custom_report
Check for valid date in custom report
2023-10-18 14:18:27 +01:00
snipe
0c86b67ed6 Added error reporting to UI
Signed-off-by: snipe <snipe@snipe.net>
2023-10-18 14:13:35 +01:00
snipe
4d841dbc79 Use custom report validator
Signed-off-by: snipe <snipe@snipe.net>
2023-10-18 14:13:25 +01:00
snipe
f2fb5b43e3 Added custom report validator
Signed-off-by: snipe <snipe@snipe.net>
2023-10-18 14:12:52 +01:00
snipe
3747165f25 Merge pull request #13759 from snipe/fixes/better_handle_deleted_variables_in_api_call
Check for true/false explicitly on api table view
2023-10-18 13:05:57 +01:00
snipe
787a64d313 Check for true/false explicitly on api table view
Signed-off-by: snipe <snipe@snipe.net>
2023-10-18 13:05:06 +01:00
spencerrlongg
f7bb911b99 clean up 2023-10-17 19:06:53 -05:00
spencerrlongg
388e4c10c4 catch custom fields that are arrays 2023-10-17 19:03:58 -05:00
Ivan Nieto Vivanco
74ed32903f Adds condition to check if parameter is of the proper type 2023-10-17 15:48:51 -06:00
snipe
f2bc7d6173 Merge pull request #13757 from snipe/fixes/RB-17334_user_formatted_array
Fixed RB-17334 - ErrorException: Undefined variable $user_formatted_array
2023-10-17 22:34:52 +01:00
snipe
72fe94edb3 Fixed RB-17334 - ErrorException: Undefined variable $user_formatted_array
Signed-off-by: snipe <snipe@snipe.net>
2023-10-17 22:33:56 +01:00
snipe
3e54a9d561 Merge pull request #13756 from spencerrlongg/bug/label_logo_fix
Fixed #13652 - Labels Using Wrong Logo
2023-10-17 19:45:20 +01:00
spencerrlongg
4bdd070f7b formatting 2023-10-17 13:41:31 -05:00
spencerrlongg
277608d962 this seems to work 2023-10-17 13:30:51 -05:00
snipe
82c2a46ada Commented out noisy debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-16 23:24:03 +01:00
snipe
3f6554ee34 Use singleton for offset
Signed-off-by: snipe <snipe@snipe.net>
2023-10-16 23:23:43 +01:00
snipe
1e0512ad44 Merge pull request #13753 from snipe/fixes/check_for_valid_location
Check that the location is valid before debugging
2023-10-16 23:14:41 +01:00
snipe
23fa92f4ce Check that the location is valid before debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-16 23:13:38 +01:00
snipe
2218cff7bc Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2023-10-16 20:18:33 +01:00
snipe
b1f158ba5a Merge pull request #13749 from snipe/bug/sc-23473
Handle case where value is deleted in history
2023-10-13 19:58:19 +01:00
snipe
a92a9d7616 Removed duplicated deleted
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 19:53:11 +01:00
snipe
8f23a45cb3 Removed dummy text
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 19:49:03 +01:00
snipe
93cccf4f5f Handle case where value is deleted in history
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 19:32:09 +01:00
snipe
04f6f39588 Merge pull request #13748 from snipe/bug/sc-23830
Fixed #13723 - race condition on asset observer for older migration
2023-10-13 12:40:14 +01:00
snipe
ca1420c9bd Added the temp column back in
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:39:28 +01:00
snipe
2537d0fdaf Added comments
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:34:46 +01:00
snipe
b2aed7feea Removed temp column
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:30:26 +01:00
snipe
ea960c39bb Check if the eol_explicit column exists yet, add it if not
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:19:32 +01:00
snipe
0b39591d88 Add the eol_explicit column earlier to accomodate the observer
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:18:58 +01:00
snipe
c14a01eb8b Added comments to explain the potential race condition
Signed-off-by: snipe <snipe@snipe.net>
2023-10-13 12:18:19 +01:00
snipe
b96e77268a Merge pull request #13745 from spencerrlongg/bug/table_prefix_migration_fix
Resolves issue with migrations with table prefixes
2023-10-13 08:49:28 +01:00
spencerrlongg
f3bd23da3d rm whitespace 2023-10-12 14:53:17 -05:00
spencerrlongg
9b53b0fedc resolve issue with migrations with table prefixes 2023-10-12 14:50:12 -05:00
snipe
138ec33555 Merge pull request #13733 from marcusmoore/bug/sc-23453
Fixed notification logic to ensure check in and out emails are delivered
2023-10-11 12:00:37 +01:00
snipe
e99d307350 Merge pull request #13734 from marcusmoore/bug/sc-23661
Fixed the storing of group permissions when creating via API
2023-10-11 03:46:49 +01:00
Marcus Moore
417f9c21e4 Fix the storing of group permissions when creating via API 2023-10-10 17:51:29 -07:00
Marcus Moore
43b9e6401c Formatting 2023-10-10 15:18:55 -07:00
Marcus Moore
dae9e6d096 Improve try catch blocks 2023-10-10 15:18:37 -07:00
Marcus Moore
9ef598d07b Apply changes to exception handling for check outs to check ins 2023-10-10 15:16:12 -07:00
Marcus Moore
2a29566458 Catch all ClientExceptions on check out 2023-10-10 15:15:02 -07:00
Marcus Moore
ab3a3de59b Fire webhook notification after sending emails 2023-10-10 15:13:46 -07:00
Marcus Moore
47a77eabf2 Avoid logging error messages for webhook request failures 2023-10-10 15:06:08 -07:00
snipe
758d3aadb4 Added trim to import values
Signed-off-by: snipe <snipe@snipe.net>
2023-10-10 11:52:35 +01:00
snipe
f685ba01b6 Reversed order of find
Signed-off-by: snipe <snipe@snipe.net>
2023-10-09 16:43:14 +01:00
snipe
e3591dc756 Merge pull request #13728 from snipe/fixes/13725_for_dev
Fixes qty remaining requirements for component checkout via API
2023-10-09 16:34:40 +01:00
snipe
0fbe63d0cf Add @Singrity as a contributor 2023-10-09 16:33:00 +01:00
snipe
b9dc7f88d0 Fix qty requirements for Components API checkout
Signed-off-by: snipe <snipe@snipe.net>
2023-10-09 16:32:30 +01:00
snipe
f6527e0b42 Merge pull request #13727 from snipe/security/huntr_43206801-9862-48da-b379-e55e341d78bf
Set resend acceptance to POST
2023-10-09 16:15:27 +01:00
snipe
866bbe5e11 Removed extra debugging
Signed-off-by: snipe <snipe@snipe.net>
2023-10-09 16:15:14 +01:00
snipe
6d55d78280 Set resend acceptance to POST
Signed-off-by: snipe <snipe@snipe.net>
2023-10-09 16:13:41 +01:00
snipe
7a0d3f788f Merge pull request #13726 from snipe/fixes/tighter_checkin_on_log_match
Added tighter controls for matching log ID and item_id
2023-10-09 15:19:10 +01:00
snipe
e5f5802235 Added tighter controls for matching log ID and item_id
Signed-off-by: snipe <snipe@snipe.net>
2023-10-09 15:17:03 +01:00
snipe
d2dc3253ab Cast the request limit to intval before we try to abs()
Signed-off-by: snipe <snipe@snipe.net>
2023-10-07 11:34:37 +01:00
Marcus Moore
eb2edb7475 Add more default values for label preview 2023-09-28 13:40:51 -07:00
Marcus Moore
ed77e8768c Re-order options 2023-09-28 12:33:27 -07:00
Marcus Moore
3aa256ec02 Ensure non-checked out items can be displayed 2023-09-28 12:33:20 -07:00
Marcus Moore
36638feac4 Add assigned to as option 2023-09-28 11:37:02 -07:00
Marcus Moore
4675f02349 Format assigned to field 2023-09-28 11:35:20 -07:00
Marcus Moore
a47e36c1e5 Add custom fields as options 2023-09-27 16:51:53 -07:00
Marcus Moore
a4a25ab9d0 Remove a couple fields 2023-09-27 16:35:44 -07:00
Marcus Moore
518ae28a37 Improve option display values 2023-09-27 16:20:26 -07:00
Marcus Moore
74a891afbb WIP: Render select box for field options in label engine 2023-09-27 15:58:01 -07:00
Godfrey M
e73b16846e adds an option to bulk edit actual location for bulk edit and fixes update issue 2023-09-11 11:21:56 -07:00
1212 changed files with 297961 additions and 3955 deletions

View File

@@ -2961,6 +2961,42 @@
"contributions": [
"code"
]
},
{
"login": "Singrity",
"name": "Bogdan",
"avatar_url": "https://avatars.githubusercontent.com/u/58479551?v=4",
"profile": "http://@singrity",
"contributions": [
"code"
]
},
{
"login": "mmanjos",
"name": "mmanjos",
"avatar_url": "https://avatars.githubusercontent.com/u/3483684?v=4",
"profile": "https://github.com/mmanjos",
"contributions": [
"code"
]
},
{
"login": "Azooz2014",
"name": "Abdelaziz Faki",
"avatar_url": "https://avatars.githubusercontent.com/u/7429229?v=4",
"profile": "https://azooz2014.github.io/",
"contributions": [
"code"
]
},
{
"login": "bilias",
"name": "bilias",
"avatar_url": "https://avatars.githubusercontent.com/u/47315739?v=4",
"profile": "https://github.com/bilias",
"contributions": [
"code"
]
}
]
}

View File

@@ -159,6 +159,7 @@ LOG_CHANNEL=stderr
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC
APP_FORCE_TLS=false
GOOGLE_MAPS_API=
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600

View File

@@ -127,6 +127,17 @@ PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
# OPTIONAL: Digital Ocean Spaces File Settings
# --------------------------------------------
DIGITALOCEAN_SPACES_KEY=null
DIGITALOCEAN_SPACES_SECRET=null
DIGITALOCEAN_SPACES_ENDPOINT=https://region.digitaloceanspaces.com
DIGITALOCEAN_SPACES_REGION=null
DIGITALOCEAN_SPACES_BUCKET=null
# --------------------------------------------
# OPTIONAL: PRIVATE S3 Settings
# --------------------------------------------

View File

@@ -1,5 +1,5 @@
[![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)
[![All Contributors](https://img.shields.io/badge/all_contributors-326-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
[![All Contributors](https://img.shields.io/badge/all_contributors-330-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
## Snipe-IT - Open Source Asset Management System
@@ -145,7 +145,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -18,7 +18,7 @@ class LdapSync extends Command
*
* @var string
*/
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=} {--base_dn=} {--filter=} {--summary} {--json_summary}';
protected $signature = 'snipeit:ldap-sync {--location=} {--location_id=*} {--base_dn=} {--filter=} {--summary} {--json_summary}';
/**
* The console command description.
@@ -83,7 +83,16 @@ class LdapSync extends Command
$summary = [];
try {
if ($this->option('base_dn') != '') {
if ( $this->option('location_id') != '') {
foreach($this->option('location_id') as $location_id){
$location_ou= Location::where('id', '=', $location_id)->value('ldap_ou');
$search_base = $location_ou;
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
}
}
else if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
Log::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
@@ -106,17 +115,21 @@ class LdapSync extends Command
/* Determine which location to assign users to by default. */
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
if ($this->option('location') != '') {
if ($location = Location::where('name', '=', $this->option('location'))->first()) {
Log::debug('Location name ' . $this->option('location') . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
}
if ($this->option('location') != '') {
$location = Location::where('name', '=', $this->option('location'))->first();
Log::debug('Location name '.$this->option('location').' passed');
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
} elseif ($this->option('location_id') != '') {
$location = Location::where('id', '=', $this->option('location_id'))->first();
Log::debug('Location ID '.$this->option('location_id').' passed');
Log::debug('Importing to '.$location->name.' ('.$location->id.')');
} elseif ($this->option('location_id') != '') {
foreach($this->option('location_id') as $location_id) {
if ($location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
}
}
}
if (! isset($location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}

View File

@@ -7,6 +7,7 @@ use App\Models\CustomField;
use App\Models\Setting;
use Artisan;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Encryption\Encrypter;
class RotateAppKey extends Command
@@ -16,14 +17,17 @@ class RotateAppKey extends Command
*
* @var string
*/
protected $signature = 'snipeit:rotate-key';
protected $signature = 'snipeit:rotate-key
{previous_key? : The previous key to rotate from}
{--emergency : Emergency mode - rotate from .env APP_KEY to newly-generated one, modifying .env}
{--force : Skip interactive confirmation}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
protected $description = 'Rotates APP_KEY to a new value, optionally taking the previous key as an argument';
/**
* Create a new command instance.
@@ -42,26 +46,42 @@ class RotateAppKey extends Command
*/
public function handle()
{
if ($this->confirm("\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ")) {
//make sure they specify only exactly one of --emergency, or a filename. Not neither, and not both.
if ( (!$this->option('emergency') && !$this->argument('previous_key')) || ( $this->option('emergency') && $this->argument('previous_key'))) {
$this->error("Specify only one of --emergency, or an app key value, in order to rotate keys");
return 1;
}
if ( $this->option('emergency') ) {
$msg = "\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ";
} else {
$msg = "\n****************************************************\nTHIS WILL DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND RE-ENCRYPT THEM WITH YOUR\nAPP_KEY.\n\nThere is NO undo. \n\nMake SURE you have a database backup BEFORE running this command. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup? ";
}
if ($this->option('force') || $this->confirm($msg)) {
// Get the existing app_key and ciphers
// We put them in a variable since we clear the cache partway through here.
$old_app_key = config('app.key');
$cipher = config('app.cipher');
if ($this->option('emergency')) {
$old_app_key = config('app.key');
$cipher = config('app.cipher');
// Generate a new one
Artisan::call('key:generate', ['--show' => true]);
$new_app_key = Artisan::output();
// Generate a new one
Artisan::call('key:generate', ['--show' => true]);
$new_app_key = trim(Artisan::output());
// Clear the config cache
Artisan::call('config:clear');
// Clear the config cache
Artisan::call('config:clear');
$this->warn('Your app cipher is: '.$cipher);
$this->warn('Your old APP_KEY is: '.$old_app_key);
$this->warn('Your new APP_KEY is: '.$new_app_key);
// Write the new app key to the .env file
$this->writeNewEnvironmentFileWith($new_app_key);
} elseif ($this->argument('previous_key')) {
$old_app_key = $this->argument('previous_key');
$cipher = config('app.cipher'); // just a guess?
$new_app_key = config('app.key');
}
// Write the new app key to the .env file
$this->writeNewEnvironmentFileWith($new_app_key);
$this->warn('Your app cipher is: ' . $cipher);
$this->warn('Your old APP_KEY is: ' . $old_app_key);
$this->warn('Your new APP_KEY is: ' . $new_app_key);
// Manually create an old encrypter instance using the old app key
// and also create a new encrypter instance so we can re-crypt the field
@@ -75,8 +95,16 @@ class RotateAppKey extends Command
$assets = Asset::whereNotNull($field->db_column)->get();
foreach ($assets as $asset) {
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
$this->line('DECRYPTED: '.$field->db_column);
try {
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
$this->line('DECRYPTED: ' . $field->db_column);
} catch (DecryptException $e) {
$this->line('Could not decrypt '. $field->db_column.' using "old key" - skipping...');
continue;
} catch (\Exception $e) {
$this->error("Error decrypting ".$field->db_column.", reason: ".$e->getMessage().". Aborting key rotation");
throw $e;
}
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
$this->line('ENCRYPTED: '.$field->db_column);
$asset->save();
@@ -86,10 +114,14 @@ class RotateAppKey extends Command
// Handle the LDAP password if one is provided
$setting = Setting::first();
if ($setting->ldap_pword != '') {
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
$setting->save();
$this->warn('LDAP password has been re-encrypted.');
try {
$setting->ldap_pword = $oldEncrypter->decrypt($setting->ldap_pword);
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
$setting->save();
$this->warn('LDAP password has been re-encrypted.');
} catch(DecryptException $e) {
$this->warn("Unable to decrypt old LDAP password; skipping");
}
}
} else {
$this->info('This operation has been canceled. No changes have been made.');
@@ -106,7 +138,7 @@ class RotateAppKey extends Command
{
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
$this->keyReplacementPattern(),
'APP_KEY='.$key,
'APP_KEY="'.$key.'"',
file_get_contents($this->laravel->environmentFilePath())
));
}
@@ -118,7 +150,7 @@ class RotateAppKey extends Command
*/
protected function keyReplacementPattern()
{
$escaped = preg_quote('='.$this->laravel['config']['app.key'], '/');
$escaped = '="?'.preg_quote($this->laravel['config']['app.key'], '/').'"?';
return "/^APP_KEY{$escaped}/m";
}

View File

@@ -150,6 +150,11 @@ class Handler extends ExceptionHandler
return redirect()->guest('login');
}
protected function invalidJson($request, ValidationException $exception)
{
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200);
}
/**
* A list of the inputs that are never flashed for validation exceptions.

View File

@@ -73,10 +73,14 @@ class Helper
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.3]
* @return array
* @return string
*/
public static function defaultChartColors($index = 0)
public static function defaultChartColors(int $index = 0)
{
if ($index < 0) {
$index = 0;
}
$colors = [
'#008941',
'#FF4A46',
@@ -349,7 +353,19 @@ class Helper
$total_colors = count($colors);
if ($index >= $total_colors) {
$index = $index - $total_colors;
\Log::error('Status label count is '.$index.' and exceeds the allowed count of 266.');
//patch fix for array key overflow (color count starts at 1, array starts at 0)
$index = $index - $total_colors - 1;
//constraints to keep result in 0-265 range. This should never be needed, but if something happens
//to create this many status labels and it DOES happen, this will keep it from failing at least.
if($index < 0) {
$index = 0;
}
elseif($index >($total_colors - 1)) {
$index = $total_colors - 1;
}
}
return $colors[$index];

View File

@@ -146,9 +146,8 @@ class AccessoriesFilesController extends Controller
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
}
$file = 'private_uploads/accessories/'.$log->filename;

View File

@@ -18,31 +18,36 @@ class AccessoryCheckoutController extends Controller
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @param int $id
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($accessoryId)
public function create($id)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
if ($accessory->category) {
if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
$this->authorize('checkout', $accessory);
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'));
if ($accessory->category) {
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Return the checkout view
return view('accessories/checkout', compact('accessory'));
}
// Invalid category
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
}
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
// Not found
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
/**

View File

@@ -33,6 +33,7 @@ use TCPDF;
use Validator;
use Route;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
@@ -48,7 +49,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request, $audit = null)
{
@@ -295,7 +296,7 @@ class AssetsController extends Controller
}
if ($request->filled('order_number')) {
$assets->where('assets.order_number', '=', $request->get('order_number'));
$assets->where('assets.order_number', '=', strval($request->get('order_number')));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
@@ -346,7 +347,7 @@ class AssetsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
@@ -443,7 +444,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
@@ -474,7 +475,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
* @return \Illuminate\Http\JsonResponse
*/
public function selectlist(Request $request)
{
@@ -530,12 +531,12 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function store(ImageUploadRequest $request)
{
$this->authorize('create', Asset::class);
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
@@ -545,8 +546,7 @@ class AssetsController extends Controller
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset()); //yup, problem :/
// NO IT IS NOT!!! This is never firing; we SHOW the asset_tag you're going to get, so it *will* be filled in!
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
@@ -639,7 +639,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@@ -666,10 +666,11 @@ class AssetsController extends Controller
$request->offsetSet('image', $request->offsetGet('image_source'));
}
$asset = $request->handleImages($asset);
$asset = $request->handleImages($asset);
$model = AssetModel::find($asset->model_id);
// Update custom fields
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
@@ -720,7 +721,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
@@ -749,38 +750,28 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v5.1.18]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function restore(Request $request, $assetId = null)
{
// Get asset information
$asset = Asset::withTrashed()->find($assetId);
$this->authorize('delete', $asset);
if (isset($asset->id)) {
if ($asset = Asset::withTrashed()->find($assetId)) {
$this->authorize('delete', $asset);
if ($asset->deleted_at=='') {
$message = 'Asset was not deleted. No data was changed.';
} else {
$message = trans('admin/hardware/message.restore.success');
// Restore the asset
Asset::withTrashed()->where('id', $assetId)->restore();
$logaction = new Actionlog();
$logaction->item_type = Asset::class;
$logaction->item_id = $asset->id;
$logaction->created_at = date("Y-m-d H:i:s");
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restored');
if ($asset->deleted_at == '') {
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.asset')])), 200);
}
return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset, $request), $message));
if ($asset->restore()) {
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/hardware/message.restore.success')), 200);
}
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()])), 200);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/**
@@ -789,7 +780,7 @@ class AssetsController extends Controller
* @author [N. Butler]
* @param string $tag
* @since [v6.0.5]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkoutByTag(AssetCheckoutRequest $request, $tag)
{
@@ -805,7 +796,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkout(AssetCheckoutRequest $request, $asset_id)
{
@@ -889,7 +880,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkin(Request $request, $asset_id)
{
@@ -945,7 +936,7 @@ class AssetsController extends Controller
*
* @author [A. Janes] [<ajanes@adagiohealth.org>]
* @since [v6.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function checkinByTag(Request $request, $tag = null)
{
@@ -971,7 +962,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function audit(Request $request)
@@ -1032,24 +1023,54 @@ class AssetsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function requestable(Request $request)
{
$this->authorize('viewRequestable', Asset::class);
$allowed_columns = [
'name',
'asset_tag',
'serial',
'model_number',
'image',
'purchase_cost',
'expected_checkin',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
}
$assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier')
->with('location', 'assetstatus', 'assetlog', 'company','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests')
->requestableAssets();
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
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())) {
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
}
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = str_replace('custom_fields.', '', $request->input('sort'));
// This handles all the pivot sorting (versus the assets.* fields
// in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
switch ($request->input('sort')) {
case 'model':
$assets->OrderModels($order);
@@ -1057,17 +1078,19 @@ class AssetsController extends Controller
case 'model_number':
$assets->OrderModelNumber($order);
break;
case 'category':
$assets->OrderCategory($order);
break;
case 'manufacturer':
$assets->OrderManufacturer($order);
case 'location':
$assets->OrderLocation($order);
break;
default:
$assets->orderBy('assets.created_at', $order);
$assets->orderBy($column_sort, $order);
break;
}
// 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');
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();

View File

@@ -92,7 +92,7 @@ class CategoriesController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -56,7 +56,7 @@ class CompaniesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
$limit = app('api_limit_value');

View File

@@ -77,7 +77,7 @@ class ComponentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $components->count()) ? $components->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $components->count()) ? $components->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -263,7 +263,7 @@ class ComponentsController extends Controller
}
// Make sure there is at least one available to checkout
if ($component->numRemaining() <= $request->get('assigned_qty')) {
if ($component->numRemaining() < $request->get('assigned_qty')) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
}

View File

@@ -86,7 +86,7 @@ class ConsumablesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value');
$limit = app('api_limit_value');
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
@@ -263,9 +263,14 @@ class ConsumablesController extends Controller
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
\Log::debug('No enough remaining');
}
// Make sure there is a valid category
if (!$consumable->category){
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
}
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
if (!$user = User::find($request->input('assigned_to'))) {
// Return error message

View File

@@ -61,7 +61,7 @@ class DepartmentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -29,7 +29,7 @@ class DepreciationsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -36,7 +36,7 @@ class GroupsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -63,7 +63,7 @@ class GroupsController extends Controller
$group = new Group;
$group->name = $request->input('name');
$group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here
$group->permissions = json_encode($request->input('permissions')); // Todo - some JSON validation stuff here
if ($group->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $group, trans('admin/groups/message.create.success')));

View File

@@ -41,7 +41,7 @@ class LicenseSeatsController extends Controller
$total = $seats->count();
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $seats->count()) ? $seats->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $seats->count()) ? $seats->count() : app('api_offset_value');
if ($offset >= $total ){
$offset = 0;

View File

@@ -95,7 +95,7 @@ class LicensesController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -81,7 +81,7 @@ class LocationsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -6,9 +6,11 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ManufacturersTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class ManufacturersController extends Controller
@@ -62,7 +64,7 @@ class ManufacturersController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -159,6 +161,44 @@ class ManufacturersController extends Controller
}
/**
* Restore a given Manufacturer (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.4]
* @param int $id
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($id)
{
$this->authorize('delete', Manufacturer::class);
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
if ($manufacturer->deleted_at == '') {
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')])), 200);
}
if ($manufacturer->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restore');
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
}
// Check validation to make sure we're not restoring an item with the same unique attributes as a non-deleted one
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()])), 200);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
}
/**
* Gets a paginated collection for the select2 menus
*

View File

@@ -30,7 +30,7 @@ class PredefinedKitsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $kits->count()) ? $kits->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $kits->count()) ? $kits->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc';

View File

@@ -11,6 +11,7 @@ use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use DB;
class ProfileController extends Controller
@@ -48,14 +49,23 @@ class ProfileController extends Controller
{
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
$results = [];
$results = array();
$show_field = array();
$showable_fields = array();
$results['total'] = $checkoutRequests->count();
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
$showable_fields[] = $field->db_column_name();
}
}
foreach ($checkoutRequests as $checkoutRequest) {
// Make sure the asset and request still exist
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
$results['rows'][] = [
$assets = [
'image' => e($checkoutRequest->itemRequested()->present()->getImageUrl()),
'name' => e($checkoutRequest->itemRequested()->present()->name()),
'type' => e($checkoutRequest->itemType()),
@@ -64,7 +74,16 @@ class ProfileController extends Controller
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
foreach ($showable_fields as $showable_field_name) {
$show_field['custom_fields.'.$showable_field_name] = $checkoutRequest->itemRequested()->{$showable_field_name};
}
// Merge the plain asset data and the custom fields data
$results['rows'][] = array_merge($assets, $show_field);
}
}
return $results;

View File

@@ -56,7 +56,7 @@ class ReportsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $actionlogs->count()) ? $actionlogs->count() : app('api_offset_value');
$limit = app('api_limit_value');
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';

View File

@@ -52,7 +52,7 @@ class StatuslabelsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -94,7 +94,7 @@ class SuppliersController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $suppliers->count()) ? $suppliers->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -11,6 +11,7 @@ use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\License;
@@ -192,7 +193,7 @@ class UsersController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $users->count()) ? $users->count() : abs($request->input('offset'));
$offset = ($request->input('offset') > $users->count()) ? $users->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -688,17 +689,31 @@ class UsersController extends Controller
*/
public function restore($userId = null)
{
// Get asset information
$user = User::withTrashed()->find($userId);
$this->authorize('delete', $user);
if (isset($user->id)) {
// Restore the user
User::withTrashed()->where('id', $userId)->restore();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
if ($user = User::withTrashed()->find($userId)) {
$this->authorize('delete', $user);
if ($user->deleted_at == '') {
return response()->json(Helper::formatStandardApiResponse('error', trans('general.not_deleted', ['item_type' => trans('general.user')])), 200);
}
if ($user->restore()) {
$logaction = new Actionlog();
$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('restore');
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/users/message.restore.success')), 200);
}
// Check validation to make sure we're not restoring a user with the same username as an existing user
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()])), 200);
}
$id = $userId;
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
}
}

View File

@@ -4,7 +4,10 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
@@ -179,9 +182,14 @@ class AssetModelsController extends Controller
if ($model->save()) {
if ($model->wasChanged('eol')) {
$newEol = $model->eol;
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
if ($model->eol > 0) {
$newEol = $model->eol;
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
} elseif ($model->eol == 0) {
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('null')]);
}
}
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
}
@@ -204,7 +212,7 @@ class AssetModelsController extends Controller
$this->authorize('delete', AssetModel::class);
// Check if the model exists
if (is_null($model = AssetModel::find($modelId))) {
return redirect()->route('models.index')->with('error', trans('admin/models/message.not_found'));
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
if ($model->assets()->count() > 0) {
@@ -232,22 +240,42 @@ class AssetModelsController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $modelId
* @param int $id
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getRestore($modelId = null)
public function getRestore($id)
{
$this->authorize('create', AssetModel::class);
// Get user information
$model = AssetModel::withTrashed()->find($modelId);
if (isset($model->id)) {
$model->restore();
if ($model = AssetModel::withTrashed()->find($id)) {
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
if ($model->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset_model')]));
}
if ($model->restore()) {
$logaction = new Actionlog();
$logaction->item_type = User::class;
$logaction->item_id = $model->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_models = AssetModel::onlyTrashed()->count();
if ($deleted_models > 0) {
return redirect()->back()->with('success', trans('admin/models/message.restore.success'));
}
return redirect()->route('models.index')->with('success', trans('admin/models/message.restore.success'));
}
// Check validation
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset_model'), 'error' => $model->getErrors()->first()]));
}
return redirect()->back()->with('error', trans('admin/models/message.not_found'));
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}

View File

@@ -86,7 +86,7 @@ class AssetFilesController extends Controller
if (isset($asset->id)) {
$this->authorize('view', $asset);
if (! $log = Actionlog::find($fileId)) {
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}

View File

@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Support\Facades\Log;
use App\Models\Asset;
use App\Models\AssetModel;
@@ -137,7 +138,7 @@ class AssetsController extends Controller
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', $asset->present()->eol_date());
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
@@ -204,12 +205,9 @@ class AssetsController extends Controller
}
if ($success) {
// Redirect to the asset listing page
$minutes = 518400;
// dd( $_POST['options']);
// Cookie::queue(Cookie::make('optional_info', json_decode($_POST['options']), $minutes));
\Log::debug(e($asset->asset_tag));
return redirect()->route('hardware.index')
->with('success', trans('admin/hardware/message.create.success'));
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
}
@@ -309,14 +307,15 @@ class AssetsController extends Controller
$asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = $request->input('purchase_cost', null);
$asset->purchase_date = $request->input('purchase_date', null);
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && $asset->model->eol) {
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && ($asset->model->eol > 0)) {
$asset->purchase_date = $request->input('purchase_date', null);
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
$asset->eol_explicit = false;
} elseif ($request->filled('asset_eol_date')) {
$asset->asset_eol_date = $request->input('asset_eol_date', null);
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($asset->model->eol) {
if($months != $asset->model->eol) {
if($months != $asset->model->eol > 0) {
$asset->eol_explicit = true;
} else {
$asset->eol_explicit = false;
@@ -324,6 +323,9 @@ class AssetsController extends Controller
} else {
$asset->eol_explicit = true;
}
} elseif (!$request->filled('asset_eol_date') && (($asset->model->eol) == 0)) {
$asset->asset_eol_date = null;
$asset->eol_explicit = false;
}
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
@@ -794,21 +796,24 @@ class AssetsController extends Controller
*/
public function getRestore($assetId = null)
{
// Get asset information
$asset = Asset::withTrashed()->find($assetId);
$this->authorize('delete', $asset);
if (isset($asset->id)) {
// Restore the asset
Asset::withTrashed()->where('id', $assetId)->restore();
if ($asset = Asset::withTrashed()->find($assetId)) {
$this->authorize('delete', $asset);
$logaction = new Actionlog();
$logaction->item_type = Asset::class;
$logaction->item_id = $asset->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restored');
if ($asset->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.asset')]));
}
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
if ($asset->restore()) {
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_assets = Asset::onlyTrashed()->count();
if ($deleted_assets > 0) {
return redirect()->back()->with('success', trans('admin/hardware/message.restore.success'));
}
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
}
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.asset'), 'error' => $asset->getErrors()->first()]));
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));

View File

@@ -7,6 +7,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\Setting;
use App\View\Label;
use Illuminate\Http\Request;
@@ -23,6 +25,13 @@ class BulkAssetsController extends Controller
/**
* Display the bulk edit page.
*
* This method is super weird because it's kinda of like a controller within a controller.
* It's main function is to determine what the bulk action in, and then return a view with
* the information that view needs, be it bulk delete, bulk edit, restore, etc.
*
* This is something that made sense at the time, but sort of doesn't make sense now. A JS front-end to determine form
* action would make a lot more sense here and make things a lot more clear.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @internal param int $assetId
@@ -32,7 +41,10 @@ class BulkAssetsController extends Controller
public function edit(Request $request)
{
$this->authorize('view', Asset::class);
/**
* No asset IDs were passed
*/
if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
@@ -41,59 +53,52 @@ class BulkAssetsController extends Controller
$bulk_back_url = request()->headers->get('referer');
session(['bulk_back_url' => $bulk_back_url]);
$asset_ids = array_values(array_unique($request->input('ids')));
//custom fields logic
$asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) {
return $query->where('fieldset_id', '!=', null);
})->get();
$models = $asset_custom_field->unique('model_id');
$asset_ids = $request->input('ids');
$assets = Asset::with('assignedTo', 'location', 'model')->find($asset_ids);
$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);
$assets_found = Asset::find($asset_ids);
if ($assets_found->isEmpty()){
return redirect()->back();
}
return (new Label)
->with('assets', $assets_found)
->with('assets', $assets)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
case 'delete':
$this->authorize('delete', Asset::class);
$assets = Asset::with('assignedTo', 'location')->find($asset_ids);
$assets->each(function ($asset) {
$this->authorize('delete', $asset);
$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 = 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('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
@@ -112,30 +117,36 @@ class BulkAssetsController extends Controller
public function update(Request $request)
{
$this->authorize('update', Asset::class);
$error_bag = [];
$has_errors = 0;
$error_array = array();
// Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index');
if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if(Session::exists('ids')) {
$assets = Session::get('ids');
} elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) {
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
$assets = array_keys($request->input('ids'));
if ($request->anyFilled($custom_field_columns)) {
$custom_fields_present = true;
} else {
$custom_fields_present = false;
}
$assets = Asset::whereIn('id', array_keys($request->input('ids')))->get();
/**
* If ANY of these are filled, prepare to update the values on the assets.
*
* Additional checks will be needed for some of them to make sure the values
* make sense (for example, changing the status ID to something incompatible with
* its checkout status.
*/
if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
@@ -154,23 +165,32 @@ class BulkAssetsController extends Controller
|| ($request->anyFilled($custom_field_columns))
) {
foreach ($assets as $assetId) {
// Let's loop through those assets and build an update array
foreach ($assets as $asset) {
$this->update_array = [];
/**
* Leave out model_id and status here because we do math on that later. We have to do some extra
* validation and checks on those two.
*
* It's tempting to make these match the request check above, but some of these values require
* extra work to make sure the data makes sense.
*/
$this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('model_id')
->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable')
->conditionallyAddItem('status_id')
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date');
foreach ($custom_field_columns as $key => $custom_field_column) {
$this->conditionallyAddItem($custom_field_column);
}
}
/**
* Blank out fields that were requested to be blanked out via checkbox
*/
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
}
@@ -194,69 +214,150 @@ class BulkAssetsController extends Controller
}
}
/**
* We're trying to change the model ID - we need to do some extra checks here to make sure
* the custom field values work for the custom fieldset rules around this asset. Uniqueness
* and requiredness across the fieldset is particularly important, since those are
* fieldset-specific attributes.
*/
if ($request->filled('model_id')) {
$this->update_array['model_id'] = AssetModel::find($request->input('model_id'))->id;
}
/**
* We're trying to change the status ID - we need to do some extra checks here to
* make sure the status label type is one that makes sense for the state of the asset,
* for example, we shouldn't be able to make an asset archived if it's currently assigned
* to someone/something.
*/
if ($request->filled('status_id')) {
$updated_status = Statuslabel::find($request->input('status_id'));
// We cannot assign a non-deployable status type if the asset is already assigned.
// This could probably be added to a form request.
// If the asset isn't assigned, we don't care what the status is.
// Otherwise we need to make sure the status type is still a deployable one.
if (
($asset->assigned_to == '')
|| ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
) {
$this->update_array['status_id'] = $updated_status->id;
}
}
/**
* We're changing the location ID - figure out which location we should apply
* this change to:
*
* 0 - RTD location only
* 1 - location ID and RTD location ID
* 2 - location ID only
*
* Note: this is kinda dumb and we should just use human-readable values IMHO. - snipe
*/
if ($request->filled('rtd_location_id')) {
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '0')) {
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
}
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '1')) {
$this->update_array['location_id'] = $request->input('rtd_location_id');
$this->update_array['rtd_location_id'] = $request->input('rtd_location_id');
}
if (($request->filled('update_real_loc')) && (($request->input('update_real_loc')) == '2')) {
$this->update_array['location_id'] = $request->input('rtd_location_id');
}
}
/**
* ------------------------------------------------------------------------------
* ANYTHING that happens past this foreach
* WILL NOT BE logged in the edit log_meta data
* ------------------------------------------------------------------------------
*/
$changed = [];
$assetCollection = Asset::where('id' ,$assetId)->get();
foreach ($this->update_array as $key => $value) {
if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) {
$changed[$key]['old'] = $assetCollection->toArray()[0][$key];
if ($this->update_array[$key] != $asset->{$key}) {
$changed[$key]['old'] = $asset->{$key};
$changed[$key]['new'] = $this->update_array[$key];
}
}
$logAction = new Actionlog();
$logAction->item_type = Asset::class;
$logAction->item_id = $assetId;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->user_id = Auth::id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
if($custom_fields_present) {
$asset = Asset::find($assetId);
$assetCustomFields = $asset->model()->first()->fieldset;
if($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if (array_key_exists($field->db_column, $this->update_array)) {
$asset->{$field->db_column} = $this->update_array[$field->db_column];
$saved = $asset->save();
if(!$saved) {
$error_bag[] = $asset->getErrors();
}
continue;
} else {
$array = $this->update_array;
array_except($array, $field->db_column);
$asset->save($array);
}
if (!$asset->save()) {
$error_bag[] = $asset->getErrors();
}
}
/**
* Start all the custom fields shenanigans
*/
// Does the model have a fieldset?
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) {
$decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
/*
* Check if the decrypted existing value is different from one we just submitted
* and if not, pull it out of the object since it shouldn't really be updating at all.
* If we don't do this, it will try to re-encrypt it, and the same value encrypted two
* different times will have different values, so it will *look* like it was updated
* but it wasn't.
*/
if ($decrypted_old != $this->update_array[$field->db_column]) {
$asset->{$field->db_column} = \Crypt::encrypt($this->update_array[$field->db_column]);
} else {
/*
* Remove the encrypted custom field from the update_array, since nothing changed
*/
unset($this->update_array[$field->db_column]);
unset($asset->{$field->db_column});
}
/*
* These custom fields aren't encrypted, just carry on as usual
*/
} else {
if ((array_key_exists($field->db_column, $this->update_array)) && ($asset->{$field->db_column} != $this->update_array[$field->db_column])) {
// Check if this is an array, and if so, flatten it
if (is_array($this->update_array[$field->db_column])) {
$asset->{$field->db_column} = implode(', ', $this->update_array[$field->db_column]);
} else {
$asset->{$field->db_column} = $this->update_array[$field->db_column];
}
}
}
} // endforeach
}
} else {
Asset::find($assetId)->update($this->update_array);
}
}
if(!empty($error_bag)) {
$errors = [];
//find the customfield name from the name of the messagebag items
foreach ($error_bag as $key => $bag) {
foreach($bag->keys() as $key => $value) {
CustomField::where('db_column', $value)->get()->map(function($item) use (&$errors) {
$errors[] = $item->name;
});
// Check if it passes validation, and then try to save
if (!$asset->update($this->update_array)) {
// Build the error array
foreach ($asset->getErrors()->toArray() as $key => $message) {
for ($x = 0; $x < count($message); $x++) {
$error_array[$key][] = trans('general.asset') . ' ' . $asset->id . ': ' . $message[$x];
$has_errors++;
}
}
}
return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors));
}
} // end if saved
} // end asset foreach
if ($has_errors > 0) {
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
}
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
}
// no values given, nothing to update

View File

@@ -56,7 +56,6 @@ class LoginController extends Controller
parent::__construct();
$this->middleware('guest', ['except' => ['logout', 'postTwoFactorAuth', 'getTwoFactorAuth', 'getTwoFactorEnroll']]);
Session::put('backUrl', \URL::previous());
// $this->ldap = $ldap;
$this->saml = $saml;
}
@@ -82,7 +81,6 @@ class LoginController extends Controller
}
if (Setting::getSettings()->login_common_disabled == '1') {
\Log::debug('login_common_disabled is set to 1 - return a 403');
return view('errors.403');
}
@@ -123,7 +121,7 @@ class LoginController extends Controller
if ($user = Auth::user()) {
$user->last_login = \Carbon::now();
$user->save();
$user->saveQuietly();
}
} catch (\Exception $e) {
@@ -199,7 +197,7 @@ class LoginController extends Controller
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
$user->save();
$user->saveQuietly();
} // End if(!user)
return $user;
}
@@ -319,7 +317,7 @@ class LoginController extends Controller
if ($user = Auth::user()) {
$user->last_login = \Carbon::now();
$user->activated = 1;
$user->save();
$user->saveQuietly();
}
// Redirect to the users page
return redirect()->intended()->with('success', trans('auth/message.signin.success'));
@@ -371,7 +369,7 @@ class LoginController extends Controller
[-2, -2, -2, -2]
);
$user->save(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
$user->saveQuietly(); // make sure to save *AFTER* displaying the barcode, or else we might save a two_factor_secret that we never actually displayed to the user if the barcode fails
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
}
@@ -426,7 +424,7 @@ class LoginController extends Controller
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->save();
$user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);
return redirect()->route('home')->with('success', 'You are logged in!');

View File

@@ -96,8 +96,8 @@ class ComponentCheckinController extends Controller
$asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
if($backto == 'asset'){
return redirect()->route('hardware.view', $asset->id)->with('success',
if ($backto == 'asset'){
return redirect()->route('hardware.show', $asset->id)->with('success',
trans('admin/components/message.checkin.success'));
}

View File

@@ -20,25 +20,38 @@ class ComponentCheckoutController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ComponentCheckoutController::store() method that stores the data.
* @since [v3.0]
* @param int $componentId
* @param int $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($componentId)
public function create($id)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
// Redirect to the component management page with error
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
$this->authorize('checkout', $component);
// Make sure there is at least one available to checkout
if ($component->numRemaining() <= 0){
return redirect()->route('components.index')->with('error', trans('admin/components/message.checkout.unavailable'));
if ($component = Component::find($id)) {
$this->authorize('checkout', $component);
// Make sure the category is valid
if ($component->category) {
// Make sure there is at least one available to checkout
if ($component->numRemaining() <= 0){
return redirect()->route('components.index')
->with('error', trans('admin/components/message.checkout.unavailable'));
}
// Return the checkout view
return view('components/checkout', compact('component'));
}
// Invalid category
return redirect()->route('components.edit', ['component' => $component->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.component')]));
}
return view('components/checkout', compact('component'));
// Not found
return redirect()->route('components.index')->with('error', trans('admin/components/message.not_found'));
}
/**

View File

@@ -142,7 +142,7 @@ class ComponentsFilesController extends Controller
$this->authorize('view', $component);
$this->authorize('components.files', $component);
if (! $log = Actionlog::find($fileId)) {
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
@@ -18,25 +19,38 @@ class ConsumableCheckoutController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0]
* @param int $consumableId
* @param int $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($consumableId)
public function create($id)
{
if (is_null($consumable = Consumable::with('users')->find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
if ($consumable = Consumable::with('users')->find($id)) {
$this->authorize('checkout', $consumable);
// Make sure the category is valid
if ($consumable->category) {
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
// Return the checkout view
return view('consumables/checkout', compact('consumable'));
}
// Invalid category
return redirect()->route('consumables.edit', ['consumable' => $consumable->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]));
}
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
// Not found
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
$this->authorize('checkout', $consumable);
return view('consumables/checkout', compact('consumable'));
}
/**

View File

@@ -140,7 +140,7 @@ class ConsumablesFilesController extends Controller
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
if (! $log = Actionlog::find($fileId)) {
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}

View File

@@ -110,6 +110,7 @@ class CustomFieldsController extends Controller
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"user_id" => Auth::id()
]);
@@ -267,6 +268,7 @@ class CustomFieldsController extends Controller
$field->display_in_user_view = $display_in_user_view;
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));

View File

@@ -7,8 +7,10 @@ use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Labels\Label;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Setting;
use App\Models\Supplier;
use App\Models\User;
use App\View\Label as LabelView;
use Illuminate\Support\Facades\Storage;
@@ -33,18 +35,20 @@ class LabelsController extends Controller
$exampleAsset->name = 'JEN-867-5309';
$exampleAsset->asset_tag = '100001';
$exampleAsset->serial = 'SN9876543210';
$exampleAsset->asset_eol_date = '2025-01-01';
$exampleAsset->order_number = '12345';
$exampleAsset->purchase_date = '2023-01-01';
$exampleAsset->status_id = 1;
$exampleAsset->company = new Company();
$exampleAsset->company->id = 999999;
$exampleAsset->company->name = 'Test Company Limited';
$exampleAsset->company->image = 'company-image-test.png';
$exampleAsset->company = new Company([
'name' => 'Test Company Limited',
'phone' => '1-555-555-5555',
'email' => 'company@example.com',
]);
$exampleAsset->assignedto = new User();
$exampleAsset->assignedto->id = 999999;
$exampleAsset->assignedto->first_name = 'Test';
$exampleAsset->assignedto->last_name = 'Person';
$exampleAsset->assignedto->username = 'Test.Person';
$exampleAsset->assignedto->employee_num = '0123456789';
$exampleAsset->setRelation('assignedTo', new User(['first_name' => 'Luke', 'last_name' => 'Skywalker']));
$exampleAsset->defaultLoc = new Location(['name' => 'Building 1', 'phone' => '1-555-555-5555']);
$exampleAsset->location = new Location(['name' => 'Building 2', 'phone' => '1-555-555-5555']);
$exampleAsset->model = new AssetModel();
$exampleAsset->model->id = 999999;
@@ -53,6 +57,10 @@ class LabelsController extends Controller
$exampleAsset->model->manufacturer = new Manufacturer();
$exampleAsset->model->manufacturer->id = 999999;
$exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
$exampleAsset->model->manufacturer->support_email = 'support@test.com';
$exampleAsset->model->manufacturer->support_phone = '1-555-555-5555';
$exampleAsset->model->manufacturer->support_url = 'https://example.com';
$exampleAsset->supplier = new Supplier(['name' => 'Test Company Limited']);
$exampleAsset->model->category = new Category();
$exampleAsset->model->category->id = 999999;
$exampleAsset->model->category->name = 'Test Category';

View File

@@ -101,7 +101,7 @@ class LicenseCheckinController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('note')));
event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
if ($backTo == 'user') {
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut;
use App\Http\Controllers\Controller;
use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
@@ -21,23 +22,35 @@ class LicenseCheckoutController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param $licenseId
* @param $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($licenseId)
public function create($id)
{
// Check that the license is valid
if ($license = License::find($licenseId)) {
if ($license = License::find($id)) {
$this->authorize('checkout', $license);
// If the license is valid, check that there is an available seat
if ($license->avail_seats_count < 1) {
return redirect()->route('licenses.index')->with('error', 'There are no available seats for this license');
if ($license->category) {
// Make sure there is at least one available to checkout
if ($license->availCount()->count() < 1){
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'));
}
// Return the checkout view
return view('licenses/checkout', compact('license'));
}
return view('licenses/checkout', compact('license'));
// Invalid category
return redirect()->route('licenses.edit', ['license' => $license->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
}
// Not found
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
@@ -105,7 +118,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to;
}
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}
@@ -122,7 +135,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('note')));
event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return true;
}

View File

@@ -137,7 +137,7 @@ class LicenseFilesController extends Controller
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
if (! $log = Actionlog::find($fileId)) {
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Manufacturer;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
@@ -218,22 +222,37 @@ class ManufacturersController extends Controller
* @return Redirect
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($manufacturers_id)
public function restore($id)
{
$this->authorize('create', Manufacturer::class);
$manufacturer = Manufacturer::onlyTrashed()->where('id', $manufacturers_id)->first();
$this->authorize('delete', Manufacturer::class);
if ($manufacturer) {
if ($manufacturer = Manufacturer::withTrashed()->find($id)) {
if ($manufacturer->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.manufacturer')]));
}
// Not sure why this is necessary - it shouldn't fail validation here, but it fails without this, so....
$manufacturer->setValidating(false);
if ($manufacturer->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_manufacturers = Manufacturer::onlyTrashed()->count();
if ($deleted_manufacturers > 0) {
return redirect()->back()->with('success', trans('admin/manufacturers/message.success.restored'));
}
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.restore.success'));
}
return redirect()->back()->with('error', 'Could not restore.');
// Check validation to make sure we're not restoring an asset with the same asset tag (or unique attribute) as an existing asset
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()]));
}
return redirect()->back()->with('error', trans('admin/manufacturers/message.does_not_exist'));
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
}

View File

@@ -134,6 +134,7 @@ class ProfileController extends Controller
];
$validator = \Validator::make($request->all(), $rules);
$validator->after(function ($validator) use ($request, $user) {
if (! Hash::check($request->input('current_password'), $user->password)) {
$validator->errors()->add('current_password', trans('validation.custom.hashed_pass'));
@@ -159,12 +160,14 @@ class ProfileController extends Controller
});
if (! $validator->fails()) {
$user->password = Hash::make($request->input('password'));
$user->save();
$user->password = Hash::make($request->input('password'));
// We have to use saveQuietly here because for some reason this method was calling the User Oserver twice :(
$user->saveQuietly();
// Log the user out of other devices
Auth::logoutOtherDevices($request->input('password'));
return redirect()->route('account.password.index')->with('success', 'Password updated!');
return redirect()->route('account')->with('success', trans('passwords.password_change'));
}
return redirect()->back()->withInput()->withErrors($validator);

View File

@@ -23,6 +23,7 @@ use Input;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
/**
@@ -246,6 +247,9 @@ class ReportsController extends Controller
trans('general.action'),
trans('general.type'),
trans('general.item'),
trans('general.license_serial'),
trans('general.model_name'),
trans('general.model_no'),
'To',
trans('general.notes'),
'Changed',
@@ -288,6 +292,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,
$target_name,
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
@@ -403,11 +410,12 @@ class ReportsController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function postCustom(Request $request)
public function postCustom(CustomAssetReportRequest $request)
{
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
@@ -526,6 +534,30 @@ class ReportsController extends Controller
$header[] = trans('admin/users/table.title');
}
if ($request->filled('phone')) {
$header[] = trans('admin/users/table.phone');
}
if ($request->filled('user_address')) {
$header[] = trans('admin/reports/general.custom_export.user_address');
}
if ($request->filled('user_city')) {
$header[] = trans('admin/reports/general.custom_export.user_city');
}
if ($request->filled('user_state')) {
$header[] = trans('admin/reports/general.custom_export.user_state');
}
if ($request->filled('user_country')) {
$header[] = trans('admin/reports/general.custom_export.user_country');
}
if ($request->filled('user_zip')) {
$header[] = trans('admin/reports/general.custom_export.user_zip');
}
if ($request->filled('status')) {
$header[] = trans('general.status');
}
@@ -645,7 +677,7 @@ class ReportsController extends Controller
if (($request->filled('created_start')) && ($request->filled('created_end'))) {
$created_start = \Carbon::parse($request->input('created_start'))->startOfDay();
$created_end = \Carbon::parse($request->input('created_end'))->endOfDay();
$assets->whereBetween('assets.created_at', [$created_start, $created_end]);
}
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
@@ -656,22 +688,22 @@ class ReportsController extends Controller
}
if (($request->filled('checkin_date_start'))) {
$assets->whereBetween('last_checkin', [
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(),
]);
$assets->whereBetween('last_checkin', [
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(),
]);
}
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
$last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
$last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
$last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
$last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
}
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
@@ -742,7 +774,7 @@ class ReportsController extends Controller
}
if ($request->filled('eol')) {
$row[] = ($asset->purchase_date != '') ? $asset->present()->eol_date() : '';
$row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
}
if ($request->filled('order')) {
@@ -826,6 +858,54 @@ class ReportsController extends Controller
}
}
if ($request->filled('phone')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->phone : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_address')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->address : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_city')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->city : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_state')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->state : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_country')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->country : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('user_zip')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->zip : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
@@ -1043,27 +1123,34 @@ class ReportsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
public function sentAssetAcceptanceReminder($acceptanceId = null)
public function sentAssetAcceptanceReminder(Request $request)
{
$this->authorize('reports.view');
if (!$acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
\Log::debug('No pending acceptances');
// Redirect to the unaccepted assets report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$assetItem = $acceptance->checkoutable;
\Log::debug(print_r($assetItem, true));
if (is_null($acceptance->created_at)){
\Log::debug('No acceptance created_at');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} else {
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
if ($logItem_res->isEmpty()){
\Log::debug('Acceptance date mismatch');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$logItem = $logItem_res[0];
}
if(!$assetItem->assignedTo->locale){
if (!$assetItem->assignedTo->locale){
Notification::locale(Setting::getSettings()->locale)->send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)

View File

@@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest;
use App\Models\CustomField;
use App\Models\Group;
use App\Models\Setting;
use App\Models\Asset;
@@ -26,7 +27,7 @@ use Response;
use App\Http\Requests\SlackSettingsRequest;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Validator;
use Illuminate\Support\Facades\Validator;
/**
* This controller handles all actions related to Settings for
@@ -809,9 +810,10 @@ class SettingsController extends Controller
*/
public function getLabels()
{
$setting = Setting::getSettings();
return view('settings.labels', compact('setting'));
return view('settings.labels', [
'setting' => Setting::getSettings(),
'customFields' => CustomField::all(),
]);
}
/**
@@ -1248,13 +1250,11 @@ class SettingsController extends Controller
if (!$request->hasFile('file')) {
return redirect()->route('settings.backups.index')->with('error', 'No file uploaded');
} else {
$max_file_size = Helper::file_upload_max_size();
$rules = [
$validator = Validator::make($request->all(), [
'file' => 'required|mimes:zip|max:'.$max_file_size,
];
$validator = \Validator::make($request->all(), $rules);
]);
if ($validator->passes()) {
@@ -1265,7 +1265,7 @@ class SettingsController extends Controller
return redirect()->route('settings.backups.index')->with('success', 'File uploaded');
}
return redirect()->route('settings.backups.index')->withErrors($request->getErrors());
return redirect()->route('settings.backups.index')->withErrors($validator);
}

View File

@@ -125,10 +125,26 @@ class BulkUsersController extends Controller
];
}
/**
* Check to see if the user wants to actually blank out the values vs skip them
*/
if ($request->input('null_location_id')=='1') {
$this->update_array['location_id'] = null;
}
if ($request->input('null_department_id')=='1') {
$this->update_array['department_id'] = null;
}
if ($request->input('null_manager_id')=='1') {
$this->update_array['manager_id'] = null;
}
if ($request->input('null_company_id')=='1') {
$this->update_array['company_id'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}

View File

@@ -49,15 +49,19 @@ class LDAPImportController extends Controller
{
$this->authorize('update', User::class);
// Call Artisan LDAP import command.
$location_id = $request->input('location_id');
Artisan::call('snipeit:ldap-sync', ['--location_id' => $location_id, '--json_summary' => true]);
Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]);
// Collect and parse JSON summary.
$ldap_results_json = Artisan::output();
$ldap_results = json_decode($ldap_results_json, true);
if (!$ldap_results) {
return redirect()->back()->withInput()->with('error', trans('general.no_results'));
}
// Direct user to appropriate status page.
if ($ldap_results['error']) {
return redirect()->back()->withInput()->with('error', $ldap_results['error_message']);
}

View File

@@ -136,6 +136,11 @@ class UserFilesController extends Controller
*/
public function show($userId = null, $fileId = null)
{
if (empty($fileId)) {
return redirect()->route('users.show')->with('error', 'Invalid file request');
}
$user = User::find($userId);
// the license is valid
@@ -143,18 +148,20 @@ class UserFilesController extends Controller
$this->authorize('view', $user);
$log = Actionlog::find($fileId);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
}
return Storage::download('private_uploads/users/'.$log->filename);
}
return Storage::download('private_uploads/users/'.$log->filename);
return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found'));
}
// Redirect to the user management page if the user doesn't exist

View File

@@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Controllers\UserNotFoundException;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SaveUserRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Group;
use App\Models\Ldap;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\WelcomeNotification;
@@ -385,18 +385,35 @@ class UsersController extends Controller
*/
public function getRestore($id = null)
{
$this->authorize('update', User::class);
// Get user information
if (! User::onlyTrashed()->find($id)) {
return redirect()->route('users.index')->with('error', trans('admin/users/messages.user_not_found'));
if ($user = User::withTrashed()->find($id)) {
$this->authorize('delete', $user);
if ($user->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
}
if ($user->restore()) {
$logaction = new Actionlog();
$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('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_users = User::onlyTrashed()->count();
if ($deleted_users > 0) {
return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
}
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
}
// Check validation to make sure we're not restoring a user with the same username as an existing user
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
}
// Restore the user
if (User::withTrashed()->where('id', $id)->restore()) {
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
}
return redirect()->route('users.index')->with('error', 'User could not be restored.');
return redirect()->route('users.index')->with('error', trans('admin/users/message.does_not_exist'));
}
/**

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
class CustomAssetReportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'purchase_start' => 'date|date_format:Y-m-d|nullable',
'purchase_end' => 'date|date_format:Y-m-d|nullable',
'created_start' => 'date|date_format:Y-m-d|nullable',
'created_end' => 'date|date_format:Y-m-d|nullable',
'checkout_date_start' => 'date|date_format:Y-m-d|nullable',
'checkout_date_end' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_start' => 'date|date_format:Y-m-d|nullable',
'expected_checkin_end' => 'date|date_format:Y-m-d|nullable',
'checkin_date_start' => 'date|date_format:Y-m-d|nullable',
'checkin_date_end' => 'date|date_format:Y-m-d|nullable',
'last_audit_start' => 'date|date_format:Y-m-d|nullable',
'last_audit_end' => 'date|date_format:Y-m-d|nullable',
'next_audit_start' => 'date|date_format:Y-m-d|nullable',
'next_audit_end' => 'date|date_format:Y-m-d|nullable',
];
}
public function response(array $errors)
{
return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreAssetRequest extends ImageUploadRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', new Asset);
}
public function prepareForValidation(): void
{
//
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = array_merge(
(new Asset)->getRules(),
parent::rules(),
);
return $rules;
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace App\Http\Traits;
use App\Models\Setting;
trait UniqueSerialTrait
{
/**
* Prepare a unique_ids rule, adding a model identifier if required.
*
* @param array $parameters
* @param string $field
*
* @return string
*/
protected function prepareUniqueSerialRule($parameters, $field)
{
if ($settings = Setting::getSettings()) {
if ($settings->unique_serial == '1') {
return 'unique_undeleted:'.$this->table.','.$this->getKey();
}
}
}
}

View File

@@ -3,6 +3,7 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Setting;
use App\Models\Company;
@@ -10,6 +11,9 @@ use App\Models\Supplier;
use App\Models\Location;
use App\Models\AssetModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
class ActionlogsTransformer
{
@@ -69,9 +73,43 @@ class ActionlogsTransformer
if ($custom_field->db_column == $fieldname) {
if ($custom_field->field_encrypted == '1') {
$clean_meta[$fieldname]['old'] = "************";
$clean_meta[$fieldname]['new'] = "************";
if ($custom_field->field_encrypted == '1') {
// Unset these fields. We need to decrypt them, since even if the decrypted value
// didn't change, their value in the DB will, so we have to compare the unencrypted version
// to see if the values actually did change
unset($clean_meta[$fieldname]);
unset($clean_meta[$fieldname]);
$enc_old = '';
$enc_new = '';
try {
$enc_old = \Crypt::decryptString($this->clean_field($fieldata->old));
} catch (\Exception $e) {
\Log::debug('Could not decrypt field - maybe the key changed?');
}
try {
$enc_new = \Crypt::decryptString($this->clean_field($fieldata->new));
} catch (\Exception $e) {
\Log::debug('Could not decrypt field - maybe the key changed?');
}
if ($enc_old != $enc_new) {
\Log::debug('custom fields do not match');
$clean_meta[$fieldname]['old'] = "************";
$clean_meta[$fieldname]['new'] = "************";
// Display the changes if the user is an admin or superadmin
if (Gate::allows('admin')) {
$clean_meta[$fieldname]['old'] = ($enc_old) ? unserialize($enc_old): '';
$clean_meta[$fieldname]['new'] = ($enc_new) ? unserialize($enc_new): '';
}
}
}
}
@@ -178,15 +216,31 @@ class ActionlogsTransformer
if(array_key_exists('rtd_location_id',$clean_meta)) {
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". e($location->find($clean_meta['rtd_location_id']['old'])->name) : trans('general.unassigned');
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". e($location->find($clean_meta['rtd_location_id']['new'])->name) : trans('general.unassigned');
$oldRtd = $location->find($clean_meta['rtd_location_id']['old']);
$oldRtdName = $oldRtd ? e($oldRtd->name) : trans('general.deleted');
$newRtd = $location->find($clean_meta['rtd_location_id']['new']);
$newRtdName = $newRtd ? e($newRtd->name) : trans('general.deleted');
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : '';
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : '';
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
unset($clean_meta['rtd_location_id']);
}
if (array_key_exists('location_id', $clean_meta)) {
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ".e($location->find($clean_meta['location_id']['old'])->name): trans('general.unassigned');
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ".e($location->find($clean_meta['location_id']['new'])->name) : trans('general.unassigned');
$oldLocation = $location->find($clean_meta['location_id']['old']);
$oldLocationName = $oldLocation ? e($oldLocation->name) : trans('general.deleted');
$newLocation = $location->find($clean_meta['location_id']['new']);
$newLocationName = $newLocation ? e($newLocation->name) : trans('general.deleted');
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : '';
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : '';
$clean_meta['Current Location'] = $clean_meta['location_id'];
unset($clean_meta['location_id']);
}

View File

@@ -73,7 +73,7 @@ class AssetModelsTransformer
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')),
'delete' => (Gate::allows('delete', AssetModel::class) && ($assetmodel->assets_count == 0)),
'delete' => $assetmodel->isDeletable(),
'clone' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at == '')),
'restore' => (Gate::allows('create', AssetModel::class) && ($assetmodel->deleted_at != '')),
];

View File

@@ -7,7 +7,8 @@ use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Carbon\Carbon;
use Auth;
class AssetsTransformer
{
@@ -38,7 +39,7 @@ class AssetsTransformer
'byod' => ($asset->byod ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => (($asset->model) && ($asset->model->eol != '')) ? $asset->model->eol : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id,
@@ -146,7 +147,7 @@ class AssetsTransformer
'clone' => Gate::allows('create', Asset::class) ? true : false,
'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false,
'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false,
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class)) ? true : false,
'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false,
];
@@ -231,6 +232,29 @@ class AssetsTransformer
'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id),
];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
// Only display this if it's allowed via the custom field setting
if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) {
$value = $asset->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) {
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->db_column] = e($value);
}
$array['custom_fields'] = $fields_array;
}
} else {
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
}
$permissions_array['available_actions'] = [
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,

View File

@@ -79,7 +79,7 @@ class UsersTransformer
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),
'delete' => $user->isDeletable(),
'clone' => (Gate::allows('create', User::class) && ($user->deleted_at == '')),
'restore' => (Gate::allows('create', User::class) && ($user->deleted_at != '')),
];

View File

@@ -34,7 +34,7 @@ class AccessoryImporter extends ItemImporter
}
$this->log('Updating Accessory');
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$this->item['model_number'] = trim($this->findCsvMatch($row, "model_number"));
$accessory->update($this->sanitizeItemForUpdating($accessory));
$accessory->save();

View File

@@ -5,6 +5,9 @@ namespace App\Importer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use App\Models\User;
use App\Events\CheckoutableCheckedIn;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class AssetImporter extends ItemImporter
@@ -80,13 +83,13 @@ class AssetImporter extends ItemImporter
$this->log('No Matching Asset, Creating a new one');
$asset = new Asset;
}
$this->item['notes'] = $this->findCsvMatch($row, 'asset_notes');
$this->item['image'] = $this->findCsvMatch($row, 'image');
$this->item['requestable'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable')) == 1) ? '1' : 0;
$this->item['notes'] = trim($this->findCsvMatch($row, 'asset_notes'));
$this->item['image'] = trim($this->findCsvMatch($row, 'image'));
$this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? '1' : 0;
$asset->requestable = $this->item['requestable'];
$this->item['warranty_months'] = intval($this->findCsvMatch($row, 'warranty_months'));
$this->item['warranty_months'] = intval(trim($this->findCsvMatch($row, 'warranty_months')));
$this->item['model_id'] = $this->createOrFetchAssetModel($row);
$this->item['byod'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'byod')) == 1) ? '1' : 0;
$this->item['byod'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'byod'))) == 1) ? '1' : 0;
// If no status ID is found
@@ -141,7 +144,13 @@ class AssetImporter extends ItemImporter
// If we have a target to checkout to, lets do so.
//-- user_id is a property of the abstract class Importer, which this class inherits from and it's setted by
//-- the class that needs to use it (command importer or GUI importer inside the project).
if (isset($target)) {
if (isset($target) && ($target !== false)) {
if (!is_null($asset->assigned_to)){
if ($asset->assigned_to != $target->id){
event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), $asset->notes, date('Y-m-d H:i:s')));
}
}
$asset->fresh()->checkOut($target, $this->user_id, date('Y-m-d H:i:s'), null, $asset->notes, $asset->name);
}

View File

@@ -28,8 +28,8 @@ class ComponentImporter extends ItemImporter
{
$component = null;
$this->log('Creating Component');
$component = Component::where('name', $this->item['name'])
->where('serial', $this->item['serial'])
$component = Component::where('name', trim($this->item['name']))
->where('serial', trim($this->item['serial']))
->first();
if ($component) {

View File

@@ -26,7 +26,7 @@ class ConsumableImporter extends ItemImporter
*/
public function createConsumableIfNotExists($row)
{
$consumable = Consumable::where('name', $this->item['name'])->first();
$consumable = Consumable::where('name', trim($this->item['name']))->first();
if ($consumable) {
if (! $this->updating) {
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
@@ -41,9 +41,9 @@ class ConsumableImporter extends ItemImporter
}
$this->log('No matching consumable, creating one');
$consumable = new Consumable();
$this->item['model_number'] = $this->findCsvMatch($row, 'model_number');
$this->item['item_no'] = $this->findCsvMatch($row, 'item_number');
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$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();

View File

@@ -19,22 +19,76 @@ abstract class Importer
* Id of User performing import
* @var
*/
protected $user_id;
/**
* Are we updating items in the import
* @var bool
*/
protected $updating;
/**
* Default Map of item fields->csv names
*
* This has been moved into app/Http/Livewire/Importer.php to be more granular.
* @todo - remove references to this property since we don't use it anymore.
* This private variable is ONLY used for the cli-importer.
*
* @todo - find a way to make this less duplicative
* @var array
*/
private $defaultFieldMap = [
'asset_tag' => 'asset tag',
'activated' => 'activated',
'category' => 'category',
'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already.
'checkout_location' => 'checkout location',
'company' => 'company',
'item_name' => 'item name',
'item_number' => 'item number',
'image' => 'image',
'expiration_date' => 'expiration date',
'location' => 'location',
'notes' => 'notes',
'license_email' => 'licensed to email',
'license_name' => 'licensed to name',
'maintained' => 'maintained',
'manufacturer' => 'manufacturer',
'asset_model' => 'model name',
'model_number' => 'model number',
'order_number' => 'order number',
'purchase_cost' => 'purchase cost',
'purchase_date' => 'purchase date',
'purchase_order' => 'purchase order',
'qty' => 'quantity',
'reassignable' => 'reassignable',
'requestable' => 'requestable',
'seats' => 'seats',
'serial' => 'serial number',
'status' => 'status',
'supplier' => 'supplier',
'termination_date' => 'termination date',
'warranty_months' => 'warranty',
'full_name' => 'full name',
'email' => 'email',
'username' => 'username',
'address' => 'address',
'address2' => 'address2',
'city' => 'city',
'state' => 'state',
'country' => 'country',
'zip' => 'zip',
'jobtitle' => 'job title',
'employee_num' => 'employee number',
'phone_number' => 'phone number',
'first_name' => 'first name',
'last_name' => 'last name',
'department' => 'department',
'manager_name' => 'manager full name',
'manager_username' => 'manager username',
'min_amt' => 'minimum quantity',
'remote' => 'remote',
'vip' => 'vip',
];
/**
* Map of item fields->csv names
@@ -281,9 +335,11 @@ abstract class Importer
$user_array['email'] = User::generateEmailFromFullName($user_array['full_name']);
}
// Get some variables for $user_formatted_array in case we need them later
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
if (empty($user_array['first_name'])) {
// Get some fields for first name and last name based off of full name
$user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format);
$user_array['first_name'] = $user_formatted_array['first_name'];
$user_array['last_name'] = $user_formatted_array['last_name'];
}

View File

@@ -372,7 +372,7 @@ class ItemImporter extends Importer
if (empty($asset_statuslabel_name)) {
return null;
}
$status = Statuslabel::where(['name' => $asset_statuslabel_name])->first();
$status = Statuslabel::where(['name' => trim($asset_statuslabel_name)])->first();
if ($status) {
$this->log('A matching Status '.$asset_statuslabel_name.' already exists');
@@ -381,7 +381,7 @@ class ItemImporter extends Importer
}
$this->log('Creating a new status');
$status = new Statuslabel();
$status->name = $asset_statuslabel_name;
$status->name = trim($asset_statuslabel_name);
$status->deployable = 1;
$status->pending = 0;
@@ -420,7 +420,7 @@ class ItemImporter extends Importer
//Otherwise create a manufacturer.
$manufacturer = new Manufacturer();
$manufacturer->name = $item_manufacturer;
$manufacturer->name = trim($item_manufacturer);
$manufacturer->user_id = $this->user_id;
if ($manufacturer->save()) {

View File

@@ -55,19 +55,19 @@ class LicenseImporter extends ItemImporter
$this->log('No Matching License, Creating a new one');
$license = new License;
}
$asset_tag = $this->item['asset_tag'] = $this->findCsvMatch($row, 'asset_tag'); // used for checkout out to an asset.
$asset_tag = $this->item['asset_tag'] = trim($this->findCsvMatch($row, 'asset_tag')); // used for checkout out to an asset.
$this->item["expiration_date"] = null;
if ($this->findCsvMatch($row, "expiration_date")!='') {
$this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime($this->findCsvMatch($row, "expiration_date")));
$this->item["expiration_date"] = date("Y-m-d 00:00:01", strtotime(trim($this->findCsvMatch($row, "expiration_date"))));
}
$this->item['license_email'] = $this->findCsvMatch($row, 'license_email');
$this->item['license_name'] = $this->findCsvMatch($row, 'license_name');
$this->item['maintained'] = $this->findCsvMatch($row, 'maintained');
$this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order');
$this->item['order_number'] = $this->findCsvMatch($row, 'order_number');
$this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable');
$this->item['manufacturer'] = $this->createOrFetchManufacturer($this->findCsvMatch($row, 'manufacturer'));
$this->item['license_email'] = trim($this->findCsvMatch($row, 'license_email'));
$this->item['license_name'] = trim($this->findCsvMatch($row, 'license_name'));
$this->item['maintained'] = trim($this->findCsvMatch($row, 'maintained'));
$this->item['purchase_order'] = trim($this->findCsvMatch($row, 'purchase_order'));
$this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number'));
$this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable'));
$this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer')));
if($this->item['reassignable'] == "")
{

View File

@@ -53,21 +53,21 @@ class LocationImporter extends ItemImporter
}
// Pull the records from the CSV to determine their values
$this->item['name'] = $this->findCsvMatch($row, 'name');
$this->item['address'] = $this->findCsvMatch($row, 'address');
$this->item['address2'] = $this->findCsvMatch($row, 'address2');
$this->item['city'] = $this->findCsvMatch($row, 'city');
$this->item['state'] = $this->findCsvMatch($row, 'state');
$this->item['country'] = $this->findCsvMatch($row, 'country');
$this->item['zip'] = $this->findCsvMatch($row, 'zip');
$this->item['currency'] = $this->findCsvMatch($row, 'currency');
$this->item['ldap_ou'] = $this->findCsvMatch($row, 'ldap_ou');
$this->item['manager'] = $this->findCsvMatch($row, 'manager');
$this->item['manager_username'] = $this->findCsvMatch($row, 'manager_username');
$this->item['name'] = trim($this->findCsvMatch($row, 'name'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
$this->item['address2'] = trim($this->findCsvMatch($row, 'address2'));
$this->item['city'] = trim($this->findCsvMatch($row, 'city'));
$this->item['state'] = trim($this->findCsvMatch($row, 'state'));
$this->item['country'] = trim($this->findCsvMatch($row, 'country'));
$this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
$this->item['currency'] = trim($this->findCsvMatch($row, 'currency'));
$this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou'));
$this->item['manager'] = trim($this->findCsvMatch($row, 'manager'));
$this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username'));
$this->item['user_id'] = \Auth::user()->id;
if ($this->findCsvMatch($row, 'parent_location')) {
$this->item['parent_id'] = $this->createOrFetchLocation($this->findCsvMatch($row, 'parent_location'));
$this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location')));
}
if (!empty($this->item['manager'])) {

View File

@@ -42,32 +42,32 @@ class UserImporter extends ItemImporter
public function createUserIfNotExists(array $row)
{
// Pull the records from the CSV to determine their values
$this->item['id'] = $this->findCsvMatch($row, 'id');
$this->item['username'] = $this->findCsvMatch($row, 'username');
$this->item['first_name'] = $this->findCsvMatch($row, 'first_name');
$this->item['last_name'] = $this->findCsvMatch($row, 'last_name');
$this->item['email'] = $this->findCsvMatch($row, 'email');
$this->item['gravatar'] = $this->findCsvMatch($row, 'gravatar');
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
$this->item['website'] = $this->findCsvMatch($row, 'website');
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
$this->item['address'] = $this->findCsvMatch($row, 'address');
$this->item['city'] = $this->findCsvMatch($row, 'city');
$this->item['state'] = $this->findCsvMatch($row, 'state');
$this->item['country'] = $this->findCsvMatch($row, 'country');
$this->item['start_date'] = $this->findCsvMatch($row, 'start_date');
$this->item['end_date'] = $this->findCsvMatch($row, 'end_date');
$this->item['zip'] = $this->findCsvMatch($row, 'zip');
$this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0;
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
$this->item['vip'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'vip')) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'autoassign_licenses')) ==1 ) ? '1' : 0;
$this->item['id'] = trim($this->findCsvMatch($row, 'id'));
$this->item['username'] = trim($this->findCsvMatch($row, 'username'));
$this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
$this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
$this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar'));
$this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number'));
$this->item['website'] = trim($this->findCsvMatch($row, 'website'));
$this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
$this->item['city'] = trim($this->findCsvMatch($row, 'city'));
$this->item['state'] = trim($this->findCsvMatch($row, 'state'));
$this->item['country'] = trim($this->findCsvMatch($row, 'country'));
$this->item['start_date'] = trim($this->findCsvMatch($row, 'start_date'));
$this->item['end_date'] = trim($this->findCsvMatch($row, 'end_date'));
$this->item['zip'] = trim($this->findCsvMatch($row, 'zip'));
$this->item['activated'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'activated'))) == 1) ? '1' : 0;
$this->item['employee_num'] = trim($this->findCsvMatch($row, 'employee_num'));
$this->item['department_id'] = trim($this->createOrFetchDepartment(trim($this->findCsvMatch($row, 'department'))));
$this->item['manager_id'] = $this->fetchManager(trim($this->findCsvMatch($row, 'manager_first_name')), trim($this->findCsvMatch($row, 'manager_last_name')));
$this->item['remote'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'remote'))) == 1 ) ? '1' : 0;
$this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0;
$this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0;
$user_department = $this->findCsvMatch($row, 'department');
$user_department = trim($this->findCsvMatch($row, 'department'));
if ($this->shouldUpdateField($user_department)) {
$this->item['department_id'] = $this->createOrFetchDepartment($user_department);
}

View File

@@ -18,6 +18,7 @@ use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseSeatNotification;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Notification;
use Exception;
use Log;
@@ -41,14 +42,9 @@ class CheckoutableListener
/**
* Make a checkout acceptance and attach it in the notification
*/
$acceptance = $this->getCheckoutAcceptance($event);
$acceptance = $this->getCheckoutAcceptance($event);
try {
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event));
}
if (! $event->checkedOutTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
$this->getNotifiables($event),
@@ -60,8 +56,15 @@ class CheckoutableListener
$this->getCheckoutNotification($event, $acceptance)
);
}
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event));
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkout notification: ".$e->getMessage());
Log::error("Exception caught during checkout notification: " . $e->getMessage());
}
}
@@ -92,11 +95,6 @@ class CheckoutableListener
}
try {
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckinNotification($event));
}
// Use default locale
if (! $event->checkedOutTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
@@ -109,8 +107,15 @@ class CheckoutableListener
$this->getCheckinNotification($event)
);
}
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckinNotification($event));
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkin notification: ".$e->getMessage());
Log::error("Exception caught during checkin notification: " . $e->getMessage());
}
}

View File

@@ -69,7 +69,6 @@ class LogListener
$logaction->item()->associate($event->acceptance->checkoutable->license);
}
\Log::debug('New onCheckoutAccepted Listener fired. logaction: '.print_r($logaction, true));
$logaction->save();
}

View File

@@ -6,7 +6,6 @@ use App\Events\AssetCheckedOut;
use App\Events\CheckoutableCheckedOut;
use App\Exceptions\CheckoutNotAllowed;
use App\Helpers\Helper;
use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Searchable;
@@ -32,7 +31,7 @@ class Asset extends Depreciable
protected $presenter = \App\Presenters\AssetPresenter::class;
use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
public const LOCATION = 'location';
public const ASSET = 'asset';
@@ -72,8 +71,7 @@ class Asset extends Depreciable
protected $casts = [
'purchase_date' => 'date',
'asset_eol_date' => 'date',
'eol_explicit' => 'boolean',
'eol_explicit' => 'boolean',
'last_checkout' => 'datetime',
'last_checkin' => 'datetime',
'expected_checkin' => 'date',
@@ -92,7 +90,7 @@ class Asset extends Depreciable
protected $rules = [
'name' => 'max:255|nullable',
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL',
'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array',
'status_id' => 'required|integer|exists:status_labels,id',
'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable|digits_between:0,240',
@@ -101,9 +99,9 @@ class Asset extends Depreciable
'expected_checkin' => 'date|nullable',
'location_id' => 'exists:locations,id|nullable',
'rtd_location_id' => 'exists:locations,id|nullable',
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array',
'purchase_date' => 'date|date_format:Y-m-d|nullable',
'serial' => 'unique_serial|nullable',
'serial' => 'unique_undeleted:assets,serial|nullable',
'purchase_cost' => 'numeric|nullable|gte:0',
'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|nullable',
@@ -111,6 +109,7 @@ class Asset extends Depreciable
'byod' => 'boolean',
];
/**
* The attributes that are mass assignable.
*
@@ -213,16 +212,16 @@ class Asset extends Depreciable
$this->rules += $model->fieldset->validation_rules();
foreach ($this->model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
if ($this->model->fieldset){
foreach ($this->model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
}
}
}
return parent::save($params);
}
@@ -267,7 +266,7 @@ class Asset extends Depreciable
/**
* Determines if an asset is available for checkout.
* This checks to see if the it's checked out to an invalid (deleted) user
* This checks to see if it's checked out to an invalid (deleted) user
* OR if the assigned_to and deleted_at fields on the asset are empty AND
* that the status is deployable
*
@@ -754,7 +753,7 @@ class Asset extends Depreciable
}
/**
* Establishes the asset -> status relationship
* Establishes the asset -> license seats relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
@@ -790,7 +789,6 @@ class Asset extends Depreciable
}
/**
* Get the next autoincremented asset tag
*
@@ -923,6 +921,27 @@ class Asset extends Depreciable
return $cost;
}
/**
* -----------------------------------------------
* BEGIN MUTATORS
* -----------------------------------------------
**/
/**
* This sets the requestable to a boolean 0 or 1. This accounts for forms or API calls that
* explicitly pass the requestable field but it has a null or empty value.
*
* This will also correctly parse a 1/0 if "true"/"false" is passed.
*
* @param $value
* @return void
*/
public function setRequestableAttribute($value)
{
$this->attributes['requestable'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
@@ -953,6 +972,7 @@ class Asset extends Depreciable
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.employee_num', 'LIKE', '%'.$term.'%')
->orWhereMultipleColumns([
'assets_users.first_name',
'assets_users.last_name',

View File

@@ -6,6 +6,7 @@ use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
@@ -188,6 +189,21 @@ class AssetModel extends SnipeModel
return false;
}
/**
* Checks if the model is deletable
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.3.4]
* @return bool
*/
public function isDeletable()
{
return Gate::allows('delete', $this)
&& ($this->assets_count == 0)
&& ($this->deleted_at == '');
}
/**
* Get uploads for this model
*

View File

@@ -100,7 +100,8 @@ class Category extends SnipeModel
{
return Gate::allows('delete', $this)
&& ($this->itemCount() == 0);
&& ($this->itemCount() == 0)
&& ($this->deleted_at == '');
}
/**
@@ -247,6 +248,26 @@ class Category extends SnipeModel
}
}
/**
* -----------------------------------------------
* BEGIN MUTATORS
* -----------------------------------------------
**/
/**
* This sets the checkin_value to a boolean 0 or 1. This accounts for forms or API calls that
* explicitly pass the checkin_email field but it has a null or empty value.
*
* This will also correctly parse a 1/0 if "true"/"false" is passed.
*
* @param $value
* @return void
*/
public function setCheckinEmailAttribute($value)
{
$this->attributes['checkin_email'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES

View File

@@ -53,6 +53,12 @@ class CustomField extends Model
'field_encrypted' => 'nullable|boolean',
'auto_add_to_fieldsets' => 'boolean',
'show_in_listview' => 'boolean',
'show_in_requestable_list' => 'boolean',
'show_in_email' => 'boolean',
];
protected $casts = [
'show_in_requestable_list' => 'boolean',
];
/**
@@ -72,7 +78,8 @@ class CustomField extends Model
'display_in_user_view',
'auto_add_to_fieldsets',
'show_in_listview',
'show_in_email',
'show_in_requestable_list',
];
/**
@@ -243,8 +250,6 @@ class CustomField extends Model
/**
* Gets the DB column name.
*
* @todo figure out if this is still needed? I don't know WTF it's for.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return string

View File

@@ -92,6 +92,8 @@ 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';
}
return $rules;

View File

@@ -14,6 +14,14 @@ class FieldOption {
public function getValue(Asset $asset) {
$dataPath = collect(explode('.', $this->dataSource));
// assignedTo directly on the asset is a special case where
// we want to avoid returning the property directly
// and instead return the entity's presented name.
if ($dataPath[0] === 'assignedTo'){
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
}
return $dataPath->reduce(function ($myValue, $path) {
try { return $myValue ? $myValue->{$path} : ${$myValue}; }
catch (\Exception $e) { return $myValue; }
@@ -46,4 +54,4 @@ class FieldOption {
return $option;
}
}
}
}

View File

@@ -32,6 +32,7 @@ class License extends Depreciable
protected $guarded = 'id';
protected $table = 'licenses';
protected $casts = [
'purchase_date' => 'date',
'expiration_date' => 'date',

View File

@@ -77,7 +77,8 @@ class Manufacturer extends SnipeModel
&& ($this->assets()->count() === 0)
&& ($this->licenses()->count() === 0)
&& ($this->consumables()->count() === 0)
&& ($this->accessories()->count() === 0);
&& ($this->accessories()->count() === 0)
&& ($this->deleted_at == '');
}
public function assets()

View File

@@ -92,6 +92,10 @@ class Setting extends Model
'google_client_secret',
];
protected $casts = [
'label2_asset_logo' => 'boolean',
];
/**
* Get the app settings.
* Cache is expired on Setting model saved in EventServiceProvider.

View File

@@ -129,8 +129,20 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
'locale' => null, // see RFC5646
'timezone' => null, // see RFC6557
'active' => AttributeMapping::eloquent('activated'),
'active' => (new AttributeMapping())->setAdd(
function ($value, &$object) {
$object->activated = $value;
}
)->setReplace(
function ($value, &$object) {
$object->activated = $value;
}
)->setRead(
// this works as specified.
function (&$object) {
return (bool)$object->activated;
}
),
'password' => AttributeMapping::eloquent('password')->disableRead(),
// Multi-Valued Attributes

View File

@@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\HasApiTokens;
use Watson\Validating\ValidatingTrait;
@@ -201,6 +202,23 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->checkPermissionSection('superuser');
}
/**
* Checks if the user is deletable
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.3.4]
* @return bool
*/
public function isDeletable()
{
return Gate::allows('delete', $this)
&& ($this->assets()->count() === 0)
&& ($this->licenses()->count() === 0)
&& ($this->consumables()->count() === 0)
&& ($this->accessories()->count() === 0)
&& ($this->deleted_at == '');
}
/**
* Establishes the user -> company relationship

View File

@@ -26,7 +26,6 @@ class CheckinAccessoryNotification extends Notification
$this->admin = $checkedInby;
$this->note = $note;
$this->settings = Setting::getSettings();
\Log::debug('Constructor for notification fired');
}
/**

View File

@@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;

View File

@@ -11,7 +11,7 @@ use Carbon\Carbon;
class AssetObserver
{
/**
* Listen to the User created event.
* Listen to the Asset updating event. This fires automatically every time an existing asset is saved.
*
* @param Asset $asset
* @return void
@@ -22,6 +22,13 @@ class AssetObserver
$attributesOriginal = $asset->getRawOriginal();
$same_checkout_counter = false;
$same_checkin_counter = false;
$restoring_or_deleting = false;
// This is a gross hack to prevent the double logging when restoring an asset
if (array_key_exists('deleted_at', $attributes) && array_key_exists('deleted_at', $attributesOriginal)){
$restoring_or_deleting = (($attributes['deleted_at'] != $attributesOriginal['deleted_at']));
}
if (array_key_exists('checkout_counter', $attributes) && array_key_exists('checkout_counter', $attributesOriginal)){
$same_checkout_counter = (($attributes['checkout_counter'] == $attributesOriginal['checkout_counter']));
@@ -33,10 +40,10 @@ class AssetObserver
// If the asset isn't being checked out or audited, log the update.
// (Those other actions already create log entries.)
if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
if (($attributes['assigned_to'] == $attributesOriginal['assigned_to'])
&& ($same_checkout_counter) && ($same_checkin_counter)
&& ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null))
&& ($attributes['last_checkout'] == $attributesOriginal['last_checkout']))
&& ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting))
{
$changed = [];
@@ -120,18 +127,47 @@ class AssetObserver
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
/**
* Listen to the Asset deleting event.
*
* @param Asset $asset
* @return void
*/
public function restoring(Asset $asset)
{
$logAction = new Actionlog();
$logAction->item_type = Asset::class;
$logAction->item_id = $asset->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
$logAction->logaction('restore');
}
/**
* Executes every time an asset is saved.
*
* This matters specifically because any database fields affected here MUST already exist on
* the assets table (and/or any related models), or related migrations WILL fail.
*
* For example, if there is a database migration that's a bit older and modifies an asset, if the save
* fires before a field gets created in a later migration and that field in the later migration
* is used in this observer, it doesn't actually exist yet and the migration will break unless we
* use saveQuietly() in the migration which skips this observer.
*
* @see https://github.com/snipe/snipe-it/issues/13723#issuecomment-1761315938
*/
public function saving(Asset $asset)
{
//determine if calculated eol and then calculate it - this should only happen on a new asset
if(is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && !is_null($asset->model->eol)){
// determine if calculated eol and then calculate it - this should only happen on a new asset
if (is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && ($asset->model->eol > 0)){
$asset->asset_eol_date = $asset->purchase_date->addMonths($asset->model->eol)->format('Y-m-d');
$asset->eol_explicit = false;
}
//determine if explicit and set eol_explit to true
if(!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) {
if($asset->model->eol) {
// determine if explicit and set eol_explicit to true
if (!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) {
if($asset->model->eol > 0) {
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($months != $asset->model->eol) {
$asset->eol_explicit = true;
@@ -140,7 +176,7 @@ class AssetObserver
} elseif (!is_null($asset->asset_eol_date) && is_null($asset->purchase_date)) {
$asset->eol_explicit = true;
}
if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol))) {
if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol) || ($asset->model->eol == 0))) {
$asset->eol_explicit = true;
}

View File

@@ -0,0 +1,149 @@
<?php
namespace App\Observers;
use App\Models\Actionlog;
use App\Models\User;
use Auth;
class UserObserver
{
/**
* Listen to the User updating event. This fires automatically every time an existing asset is saved.
*
* @param User $user
* @return void
*/
public function updating(User $user)
{
// ONLY allow these fields to be stored
$allowed_fields = [
'email',
'activated',
'first_name',
'last_name',
'website',
'country',
'gravatar',
'location_id',
'phone',
'jobtitle',
'manager_id',
'employee_num',
'username',
'notes',
'company_id',
'ldap_import',
'locale',
'two_factor_enrolled',
'two_factor_optin',
'department_id',
'address',
'address2',
'city',
'state',
'zip',
'remote',
'start_date',
'end_date',
'autoassign_licenses',
'vip',
'password'
];
$changed = [];
foreach ($user->getRawOriginal() as $key => $value) {
// Make sure the info is in the allow fields array
if (in_array($key, $allowed_fields)) {
// Check and see if the value changed
if ($user->getRawOriginal()[$key] != $user->getAttributes()[$key]) {
$changed[$key]['old'] = $user->getRawOriginal()[$key];
$changed[$key]['new'] = $user->getAttributes()[$key];
// Do not store the hashed password in changes
if ($key == 'password') {
$changed['password']['old'] = '*************';
$changed['password']['new'] = '*************';
}
}
}
}
if (count($changed) > 0) {
$logAction = new Actionlog();
$logAction->item_type = User::class;
$logAction->item_id = $user->id;
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
$logAction->target_id = $user->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
}
}
/**
* Listen to the User created event, and increment
* the next_auto_tag_base value in the settings table when i
* a new asset is created.
*
* @param User $user
* @return void
*/
public function created(User $user)
{
$logAction = new Actionlog();
$logAction->item_type = User::class; // can we instead say $logAction->item = $asset ?
$logAction->item_id = $user->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
$logAction->logaction('create');
}
/**
* Listen to the User deleting event.
*
* @param User $user
* @return void
*/
public function deleting(User $user)
{
$logAction = new Actionlog();
$logAction->item_type = User::class;
$logAction->item_id = $user->id;
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
$logAction->target_id = $user->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
/**
* Listen to the User deleting event.
*
* @param User $user
* @return void
*/
public function restoring(User $user)
{
$logAction = new Actionlog();
$logAction->item_type = User::class;
$logAction->item_id = $user->id;
$logAction->target_type = User::class; // can we instead say $logAction->item = $asset ?
$logAction->target_id = $user->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
$logAction->logaction('restore');
}
}

View File

@@ -38,22 +38,63 @@ class ActionlogPresenter extends Presenter
public function icon()
{
$itemicon = 'fas fa-paperclip';
// User related icons
if ($this->itemType() == 'user') {
if ($this->itemType() == 'asset') {
return 'fas fa-barcode';
} elseif ($this->itemType() == 'accessory') {
return 'far fa-keyboard';
} elseif ($this->itemType() == 'consumable') {
return 'fas fa-tint';
} elseif ($this->itemType() == 'license') {
return 'far fa-save';
} elseif ($this->itemType() == 'component') {
return 'far fa-hdd';
} elseif ($this->itemType() == 'user') {
return 'fa-solid fa-people-arrows';
if ($this->actionType()=='create new') {
return 'fa-solid fa-user-plus';
}
if ($this->actionType()=='merged') {
return 'fa-solid fa-people-arrows';
}
if ($this->actionType()=='delete') {
return 'fa-solid fa-user-minus';
}
if ($this->actionType()=='delete') {
return 'fa-solid fa-user-minus';
}
if ($this->actionType()=='update') {
return 'fa-solid fa-user-pen';
}
return 'fa-solid fa-user';
}
// Everything else
if ($this->actionType()=='create new') {
return 'fa-solid fa-plus';
}
if ($this->actionType()=='delete') {
return 'fa-solid fa-user-xmark';
}
if ($this->actionType()=='update') {
return 'fa-solid fa-pen';
}
if ($this->actionType()=='restore') {
return 'fa-solid fa-trash-arrow-up';
}
if ($this->actionType()=='upload') {
return 'fas fa-paperclip';
}
if ($this->actionType()=='checkout') {
return 'fa-solid fa-rotate-left';
}
if ($this->actionType()=='checkin from') {
return 'fa-solid fa-rotate-right';
}
return 'fa-solid fa-rotate-right';
}
public function actionType()

View File

@@ -104,7 +104,7 @@ class AssetModelPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.eol'),
'title' => trans('admin/hardware/form.eol_rate'),
'visible' => true,
],
[

View File

@@ -173,7 +173,7 @@ class AssetPresenter extends Presenter
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('general.eol'),
'title' => trans('admin/hardware/form.eol_rate'),
],
[
'field' => 'asset_eol_date',

View File

@@ -106,7 +106,7 @@ class LocationPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/locations/table.address'),
'title' => trans('admin/locations/table.address2'),
'visible' => false,
],
[

View File

@@ -7,10 +7,12 @@ use App\Models\Asset;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\User;
use App\Models\Setting;
use App\Models\SnipeSCIMConfig;
use App\Observers\AccessoryObserver;
use App\Observers\AssetObserver;
use App\Observers\UserObserver;
use App\Observers\ComponentObserver;
use App\Observers\ConsumableObserver;
use App\Observers\LicenseObserver;
@@ -58,6 +60,7 @@ class AppServiceProvider extends ServiceProvider
Schema::defaultStringLength(191);
Asset::observe(AssetObserver::class);
User::observe(UserObserver::class);
Accessory::observe(AccessoryObserver::class);
Component::observe(ComponentObserver::class);
Consumable::observe(ConsumableObserver::class);

View File

@@ -33,18 +33,33 @@ class SettingsServiceProvider extends ServiceProvider
// Make sure the limit is actually set, is an integer and does not exceed system limits
\App::singleton('api_limit_value', function () {
$limit = config('app.max_results');
$int_limit = intval(request('limit'));
if ((abs(intval(request('limit'))) > 0) && (abs(request('limit')) <= config('app.max_results'))) {
$limit = abs(request('limit'));
if ((abs($int_limit) > 0) && ($int_limit <= config('app.max_results'))) {
$limit = abs($int_limit);
}
\Log::debug('Max in env: '.config('app.max_results'));
\Log::debug('Original requested limit: '.request('limit'));
\Log::debug('Modified limit: '.$limit);
\Log::debug('------------------------------');
// \Log::debug('Max in env: '.config('app.max_results'));
// \Log::debug('Original requested limit: '.request('limit'));
// \Log::debug('Int limit: '.$int_limit);
// \Log::debug('Modified limit: '.$limit);
// \Log::debug('------------------------------');
return $limit;
});
// Make sure the offset is actually set and is an integer
\App::singleton('api_offset_value', function () {
$offset = intval(request('offset'));
// \Log::debug('Original requested offset: '.request('offset'));
// \Log::debug('Modified offset: '.$offset);
// \Log::debug('------------------------------');
return $offset;
});
/**
* Set some common variables so that they're globally available.

View File

@@ -3,6 +3,7 @@
namespace App\Providers;
use App\Models\Department;
use App\Models\Setting;
use DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rule;
@@ -45,30 +46,87 @@ class ValidationServiceProvider extends ServiceProvider
return $validator->passes();
});
// Unique only if undeleted
// This works around the use case where multiple deleted items have the same unique attribute.
// (I think this is a bug in Laravel's validator?)
/**
* Unique only if undeleted.
*
* This works around the use case where multiple deleted items have the same unique attribute.
* (I think this is a bug in Laravel's validator?)
*
* $attribute is the FIELDNAME you're checking against
* $value is the VALUE of the item you're checking against the existing values in the fieldname
* $parameters[0] is the TABLE NAME you're querying
* $parameters[1] is the ID of the item you're querying - this makes it work on saving, checkout, etc,
* since it defaults to 0 if there is no item created yet (new item), but populates the ID if editing
*
* The UniqueUndeletedTrait prefills these parameters, so you can just use
* `unique_undeleted:table,fieldname` in your rules out of the box
*/
Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) {
$count = DB::table($parameters[0])->select('id')->where($attribute, '=', $value)->whereNull('deleted_at')->where('id', '!=', $parameters[1])->count();
// This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable
if (($parameters[0]=='assets') && ($attribute == 'serial') && (Setting::getSettings()->unique_serial != '1')) {
return true;
}
$count = DB::table($parameters[0])
->select('id')
->where($attribute, '=', $value)
->whereNull('deleted_at')
->where('id', '!=', $parameters[1])->count();
return $count < 1;
}
});
/**
* Unique if undeleted for two columns
*
* Same as unique_undeleted but taking the combination of two columns as unique constrain.
* This uses the Validator::replacer('two_column_unique_undeleted') below for nicer translations.
*
* $parameters[0] - the name of the first table we're looking at
* $parameters[1] - the ID (this will be 0 on new creations)
* $parameters[2] - the name of the second table we're looking at
* $parameters[3] - the value that the request is passing for the second table we're
* checking for uniqueness across
*
*/
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) {
$count = DB::table($parameters[0])
->select('id')->where($attribute, '=', $value)
->whereNull('deleted_at')
->where('id', '!=', $parameters[1])
->where($parameters[2], $parameters[3])->count();
return $count < 1;
}
});
// Unique if undeleted for two columns
// Same as unique_undeleted but taking the combination of two columns as unique constrain.
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) {
$count = DB::table($parameters[0])
->select('id')->where($attribute, '=', $value)
->whereNull('deleted_at')
->where('id', '!=', $parameters[1])
->where($parameters[2], $parameters[3])->count();
return $count < 1;
}
});
/**
* This is the validator replace static method that allows us to pass the $parameters of the table names
* into the translation string in validation.two_column_unique_undeleted for two_column_unique_undeleted
* validation messages.
*
* This is invoked automatically by Validator::extend('two_column_unique_undeleted') above and
* produces a translation like: "The name value must be unique across categories and category type."
*
* The $parameters passed coincide with the ones the two_column_unique_undeleted custom validator above
* uses, so $parameter[0] is the first table and so $parameter[2] is the second table.
*/
Validator::replacer('two_column_unique_undeleted', function($message, $attribute, $rule, $parameters) {
$message = str_replace(':table1', $parameters[0], $message);
$message = str_replace(':table2', $parameters[2], $message);
// Change underscores to spaces for a friendlier display
$message = str_replace('_', ' ', $message);
return $message;
});
// Prevent circular references
//
@@ -232,6 +290,10 @@ class ValidationServiceProvider extends ServiceProvider
return true;
}
});
Validator::extend('not_array', function ($attribute, $value, $parameters, $validator) {
return !is_array($value);
});
}
/**

View File

@@ -103,19 +103,12 @@ class Label implements View
$logo = null;
// Should we be trying to use a logo at all?
if ($settings->label2_asset_logo='1') {
// If we don't have a company image, fall back to the general site label image
if (!empty($settings->label_logo)) {
$logo = Storage::disk('public')->path('/'.e($settings->label_logo));
}
// If we have a company logo, use that first
if (($asset->company) && ($asset->company->image!='')) {
$logo = Storage::disk('public')->path('companies/'.e($asset->company->image));
}
// Should we use the assets assigned company logo? (A.K.A. "Is `Labels > Use Asset Logo` enabled?"), and do we have a company logo?
if ($settings->label2_asset_logo && $asset->company && $asset->company->image!='') {
$logo = Storage::disk('public')->path('companies/'.e($asset->company->image));
} elseif (!empty($settings->label_logo)) {
// Use the general site label logo, if available
$logo = Storage::disk('public')->path('/'.e($settings->label_logo));
}
if (!empty($logo)) {

View File

@@ -74,12 +74,16 @@
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^6.1"
},
"suggest": {
"ext-ldap": "*"
},
"require-dev": {
"brianium/paratest": "^6.6",
"fakerphp/faker": "^1.16",
"mockery/mockery": "^1.4",
"nunomaduro/larastan": "^1.0",
"nunomaduro/phpinsights": "^2.7",
"php-mock/php-mock-phpunit": "^2.8",
"phpunit/php-token-stream": "^3.1",
"phpunit/phpunit": "^9.0",
"squizlabs/php_codesniffer": "^3.5",

224
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "348f96db24a0f8dfb595ee38b38b34eb",
"content-hash": "f4f3b6b02d044ed3e54cdd509b01c3dc",
"packages": [
{
"name": "alek13/slack",
@@ -7011,16 +7011,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.14",
"version": "3.0.34",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef"
"reference": "56c79f16a6ae17e42089c06a2144467acc35348a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2f0b7af658cbea265cbb4a791d6c29a6613f98ef",
"reference": "2f0b7af658cbea265cbb4a791d6c29a6613f98ef",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a",
"reference": "56c79f16a6ae17e42089c06a2144467acc35348a",
"shasum": ""
},
"require": {
@@ -7032,6 +7032,7 @@
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
@@ -7100,7 +7101,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.14"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.34"
},
"funding": [
{
@@ -7116,7 +7117,7 @@
"type": "tidelift"
}
],
"time": "2022-04-04T05:15:45+00:00"
"time": "2023-11-27T11:13:31+00:00"
},
{
"name": "phpspec/prophecy",
@@ -14100,6 +14101,213 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "php-mock/php-mock",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock.git",
"reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock/zipball/6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d",
"reference": "6240b6f0a76d7b9d1ee4d70e686a7cc711619a9d",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0 || ^8.0",
"phpunit/php-text-template": "^1 || ^2 || ^3"
},
"replace": {
"malkusch/php-mock": "*"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0",
"squizlabs/php_codesniffer": "^3.5"
},
"suggest": {
"php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock."
},
"type": "library",
"autoload": {
"files": [
"autoload.php"
],
"psr-4": {
"phpmock\\": [
"classes/",
"tests/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"WTFPL"
],
"authors": [
{
"name": "Markus Malkusch",
"email": "markus@malkusch.de",
"homepage": "http://markus.malkusch.de",
"role": "Developer"
}
],
"description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.",
"homepage": "https://github.com/php-mock/php-mock",
"keywords": [
"BDD",
"TDD",
"function",
"mock",
"stub",
"test",
"test double",
"testing"
],
"support": {
"issues": "https://github.com/php-mock/php-mock/issues",
"source": "https://github.com/php-mock/php-mock/tree/2.4.1"
},
"funding": [
{
"url": "https://github.com/michalbundyra",
"type": "github"
}
],
"time": "2023-06-12T20:48:52+00:00"
},
{
"name": "php-mock/php-mock-integration",
"version": "2.2.1",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock-integration.git",
"reference": "04f4a8d5442ca457b102b5204673f77323e3edb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/04f4a8d5442ca457b102b5204673f77323e3edb5",
"reference": "04f4a8d5442ca457b102b5204673f77323e3edb5",
"shasum": ""
},
"require": {
"php": ">=5.6",
"php-mock/php-mock": "^2.4",
"phpunit/php-text-template": "^1 || ^2 || ^3"
},
"require-dev": {
"phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10"
},
"type": "library",
"autoload": {
"psr-4": {
"phpmock\\integration\\": "classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"WTFPL"
],
"authors": [
{
"name": "Markus Malkusch",
"email": "markus@malkusch.de",
"homepage": "http://markus.malkusch.de",
"role": "Developer"
}
],
"description": "Integration package for PHP-Mock",
"homepage": "https://github.com/php-mock/php-mock-integration",
"keywords": [
"BDD",
"TDD",
"function",
"mock",
"stub",
"test",
"test double"
],
"support": {
"issues": "https://github.com/php-mock/php-mock-integration/issues",
"source": "https://github.com/php-mock/php-mock-integration/tree/2.2.1"
},
"funding": [
{
"url": "https://github.com/michalbundyra",
"type": "github"
}
],
"time": "2023-02-13T09:51:29+00:00"
},
{
"name": "php-mock/php-mock-phpunit",
"version": "2.8.0",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock-phpunit.git",
"reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/56edee85ad3232caa0202f98f2a3c899ab16bdb7",
"reference": "56edee85ad3232caa0202f98f2a3c899ab16bdb7",
"shasum": ""
},
"require": {
"php": ">=7",
"php-mock/php-mock-integration": "^2.2.1",
"phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17"
},
"require-dev": {
"mockery/mockery": "^1.3.6"
},
"type": "library",
"autoload": {
"files": [
"autoload.php"
],
"psr-4": {
"phpmock\\phpunit\\": "classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"WTFPL"
],
"authors": [
{
"name": "Markus Malkusch",
"email": "markus@malkusch.de",
"homepage": "http://markus.malkusch.de",
"role": "Developer"
}
],
"description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.",
"homepage": "https://github.com/php-mock/php-mock-phpunit",
"keywords": [
"BDD",
"TDD",
"function",
"mock",
"phpunit",
"stub",
"test",
"test double",
"testing"
],
"support": {
"issues": "https://github.com/php-mock/php-mock-phpunit/issues",
"source": "https://github.com/php-mock/php-mock-phpunit/tree/2.8.0"
},
"funding": [
{
"url": "https://github.com/michalbundyra",
"type": "github"
}
],
"time": "2023-10-30T07:06:12+00:00"
},
{
"name": "php-parallel-lint/php-parallel-lint",
"version": "v1.3.2",
@@ -16600,5 +16808,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View File

@@ -82,6 +82,27 @@ $config = [
'visibility' => 'private'
],
'digitalocean_private' => [
'driver' => 's3',
'key' => env('DIGITALOCEAN_SPACES_KEY'),
'secret' => env('DIGITALOCEAN_SPACES_SECRET'),
'endpoint' => env('DIGITALOCEAN_SPACES_ENDPOINT'),
'region' => env('DIGITALOCEAN_SPACES_REGION'),
'bucket' => env('DIGITALOCEAN_SPACES_BUCKET'),
'visibility' => 'private'
],
'digitalocean' => [
'driver' => 's3',
'key' => env('DIGITALOCEAN_SPACES_KEY'),
'secret' => env('DIGITALOCEAN_SPACES_SECRET'),
'endpoint' => env('DIGITALOCEAN_SPACES_ENDPOINT'),
'region' => env('DIGITALOCEAN_SPACES_REGION'),
'bucket' => env('DIGITALOCEAN_SPACES_BUCKET'),
'visibility' => 'public'
],
'rackspace' => [
'driver' => 'rackspace',
'username' => env('RACKSPACE_USERNAME'),

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v6.2.2',
'full_app_version' => 'v6.2.2 - build 11714-ga95fae0e9',
'build_version' => '11714',
'app_version' => 'v6.2.4-pre',
'full_app_version' => 'v6.2.4-pre - build 12090-g776b16d37',
'build_version' => '12090',
'prerelease_version' => '',
'hash_version' => 'ga95fae0e9',
'full_hash' => 'v6.2.2-85-ga95fae0e9',
'branch' => 'master',
'hash_version' => 'g776b16d37',
'full_hash' => 'v6.2.4-pre-329-g776b16d37',
'branch' => 'develop',
);

View File

@@ -38,7 +38,7 @@ class ActionlogFactory extends Factory
{
return $this->state(function () {
$target = User::inRandomOrder()->first();
$asset = Asset::RTD()->inRandomOrder()->first();
$asset = Asset::inRandomOrder()->RTD()->first();
$asset->update(
[

View File

@@ -26,6 +26,7 @@ class CustomFieldFactory extends Factory
'format' => '',
'element' => 'text',
'auto_add_to_fieldsets' => '0',
'show_in_requestable_list' => '0',
];
}
@@ -66,6 +67,7 @@ class CustomFieldFactory extends Factory
return [
'name' => 'CPU',
'help_text' => 'The speed of the processor on this device.',
'show_in_requestable_list' => '1',
];
});
}
@@ -79,4 +81,28 @@ class CustomFieldFactory extends Factory
];
});
}
public function testEncrypted()
{
return $this->state(function () {
return [
'name' => 'Test Encrypted',
'field_encrypted' => '1',
'help_text' => 'This is a sample encrypted field.',
];
});
}
public function testCheckbox()
{
return $this->state(function () {
return [
'name' => 'Test Checkbox',
'help_text' => 'This is a sample checkbox.',
'field_values' => "One\nTwo\nThree",
'element' => 'checkbox',
];
});
}
}

View File

@@ -17,19 +17,30 @@ class AddEolDateOnAssetsTable extends Migration
{
Schema::table('assets', function (Blueprint $table) {
if (!Schema::hasColumn('assets', 'asset_eol_date')) {
$table->date('asset_eol_date')->after('purchase_date')->nullable()->default(null);
}
// This is a temporary shim so we don't have to modify the asset observer for migrations where
// there is a large version difference. (See the AssetObserver notes). This column gets created
// later in 2023_07_13_052204_denormalized_eol_and_add_column_for_explicit_date_to_assets.php
// but we have to temporarily create it now so the save method below doesn't break
if (!Schema::hasColumn('assets', 'eol_explicit')) {
$table->boolean('eol_explicit')->default(false)->after('asset_eol_date');
}
});
// Chunk the model query to get the models that do have an EOL date
// We use saveQuietly() here to skip the AssetObserver, since it modifies fields
// that do not yet exist on the assets table.
AssetModel::whereNotNull('eol')->chunk(10, function ($models) {
foreach ($models as $model) {
foreach ($model->assets as $asset) {
if ($asset->purchase_date!='') {
$asset->asset_eol_date = $asset->present()->eol_date();
$asset->save();
$asset->saveQuietly();
}
}

View File

@@ -3,6 +3,7 @@
use App\Models\Asset;
use Carbon\CarbonImmutable;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
@@ -18,7 +19,9 @@ class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
public function up()
{
Schema::table('assets', function (Blueprint $table) {
$table->boolean('eol_explicit')->default(false)->after('asset_eol_date');
if (!Schema::hasColumn('assets', 'eol_explicit')) {
$table->boolean('eol_explicit')->default(false)->after('asset_eol_date');
}
});
@@ -48,7 +51,7 @@ class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
->whereNotNull('model_id')
->join('models', 'assets.model_id', '=', 'models.id')
->update([
'asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL models.eol MONTH)')
'asset_eol_date' => $this->eolUpdateExpression(),
]);
}
@@ -64,4 +67,22 @@ class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
$table->dropColumn('eol_explicit');
});
}
/**
* This method returns the correct database expression for either
* mysql, postgres, or sqlite depending on the driver being used.
*/
private function eolUpdateExpression(): Expression
{
if (DB::getDriverName() === 'sqlite') {
return DB::raw("DATE(purchase_date, '+' || (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id) || ' months')");
}
if (DB::getDriverName() === 'pgsql') {
return DB::raw("date(purchase_date + interval '1 month' * (SELECT eol FROM " . DB::getTablePrefix() . "models WHERE models.id = assets.model_id))");
}
// Default to MySQL's method
return DB::raw('DATE_ADD(purchase_date, INTERVAL ' . DB::getTablePrefix() . 'models.eol MONTH)');
}
}

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