Compare commits

...

309 Commits

Author SHA1 Message Date
snipe
93014d7f34 Better handle backups in S3
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 14:58:32 +00:00
snipe
759e3794df Small upgrader UI fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 14:57:28 +00:00
snipe
c50b14763f Merge pull request #16403 from snipe/#16402_each_localization
Fixed #16402 - localize "each" string in components tab on asset view
2025-03-04 13:35:08 +00:00
snipe
369a68fe57 Fixed #16402 - Localize “each” in string
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 13:33:04 +00:00
snipe
83855d44d0 Merge pull request #16251 from Godmartinz/Audit_Checkin_warning_fix
refactors audit notification to mail, adds test, adds  alerts check to scheduler
2025-03-04 12:51:42 +00:00
snipe
6f847294ed Merge pull request #15911 from Fiala06/patch-1
Fixed duplicate entries preventing LDAP sync from continuing
2025-03-04 12:49:54 +00:00
snipe
d556d1c6e7 Merge pull request #16150 from Godmartinz/add-translations-to-settings
Adds Translation strings to General and Branding Settings
2025-03-04 12:48:21 +00:00
snipe
3bb94e98f0 Merge pull request #16398 from marcusmoore/bug/sc-28535
Avoid using authenticated user's email address in email partial
2025-03-04 12:47:10 +00:00
Marcus Moore
8f5f6f3502 Avoid using authenticated user's email address in email partial 2025-03-03 16:28:08 -08:00
snipe
b3792bfa00 Merge pull request #16396 from marcusmoore/chore/migrate-checkbox-helpers-pt10
Replace calls to Form::checkbox pt10
2025-03-03 20:52:54 +00:00
Marcus Moore
40f7257723 Replace call to Form::checkbox 2025-03-03 12:44:50 -08:00
snipe
8486256142 Merge pull request #16381 from marcusmoore/chore/migrate-checkbox-helpers-pt9
Replace calls to Form::checkbox pt9
2025-03-03 20:28:21 +00:00
snipe
7f36750e33 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2025-03-03 19:54:14 +00:00
snipe
de046db106 Add @azmcnutt as a contributor 2025-03-03 19:53:30 +00:00
snipe
eb1d27a5bc Merge pull request #16379 from azmcnutt/feature/settings_ldap_invert_active_flag
Feature/settings ldap invert active flag
2025-03-03 19:52:57 +00:00
Godfrey M
cc0b9f404a merged develop, fix conflicts 2025-02-27 15:38:31 -08:00
Marcus Moore
70332696c6 Fix test by passing in required properties 2025-02-27 15:23:17 -08:00
Marcus Moore
7a9b5d61b0 Replace another Form::checkbox 2025-02-27 13:25:32 -08:00
Marcus Moore
5876259893 Replace another Form::checkbox 2025-02-27 13:19:26 -08:00
Marcus Moore
8755c54edc Replace Form::checkbox 2025-02-27 13:13:39 -08:00
Marcus Moore
014f3b7652 Cast to boolean 2025-02-27 13:12:01 -08:00
Marcus Moore
3a2579b205 WIP: replace Form::checkbox 2025-02-27 13:08:02 -08:00
James M
149474bfe3 Update general.php
FIX: Spelling error
2025-02-27 12:42:47 -07:00
James M
b2b768dede Merge branch 'snipe:develop' into develop 2025-02-27 12:25:07 -07:00
snipe
a9ed9e2a7f Merge pull request #16378 from snipe/wrap_pdf_table_results
Wrap long text in PDF export in tables
2025-02-27 19:06:34 +00:00
snipe
ce8523b00a Fixed wrapping
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:58:18 +00:00
snipe
7076a68d35 Wrap table results in PDF
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:38:10 +00:00
James M
112112d258 Feat: #14926 LDAP Active Flag - Add config option to make False = Enable 2025-02-27 10:52:12 -07:00
snipe
3928c8afe9 Merge pull request #16376 from uberbrady/improve_safety_csv_charset_detection
Add some safeties around the charset-detection and transliteration
2025-02-27 16:26:44 +00:00
snipe
23ce54e80c Make sure we’re saving the last_login in 2FA auths
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 16:17:59 +00:00
Brady Wetherington
646e3e8df5 Complete failed-transliteration test, clean up error, new translation string 2025-02-27 16:10:56 +00:00
snipe
30c4e9dbf7 Use formatter for created_at on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:48:24 +00:00
snipe
27fc30a881 Nicer button layout on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:43:53 +00:00
snipe
8ac7cda4ee Merge pull request #16366 from marcusmoore/chore/migrate-checkbox-helpers-pt7
Replace calls to Form::checkbox pt7
2025-02-27 15:01:31 +00:00
snipe
6f04d314a8 Merge pull request #16367 from marcusmoore/chore/migrate-checkbox-helpers-pt8
Replace calls to Form::checkbox pt8
2025-02-27 15:01:19 +00:00
snipe
1051b1d16d Merge pull request #16375 from snipe/fixes_16371_name_not_included_in_reminder_emails
Fixed #16371 - incorrect count and missing name in acceptance reminder email
2025-02-27 15:00:04 +00:00
snipe
115bb94704 Merge pull request #16156 from marcusmoore/acceptance-reminder-subject
Added "Reminder" to subject line of follow up asset checkout emails
2025-02-27 14:55:59 +00:00
snipe
25807cc62f Fixed constructor
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:48 +00:00
snipe
cd1d1b2d3e Fixed count
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:40 +00:00
Brady Wetherington
6dcd3bfd30 Add some safeties around the charset-detection and transliteration 2025-02-27 13:44:31 +00:00
snipe
b8799f8038 Bumped bash
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 12:21:28 +00:00
Marcus Moore
a0dc056da8 Replace Form::checkbox on label settings page 2025-02-26 16:33:07 -08:00
Marcus Moore
27aeb518ff Replace Form::checkbox on general settings page 2025-02-26 16:18:00 -08:00
snipe
dc619bb0dc Merge pull request #16365 from marcusmoore/chore/migrate-checkbox-helpers-pt6
Replace calls to Form::checkbox pt6
2025-02-26 23:54:50 +00:00
Marcus Moore
245a16c377 Replace Form::checkbox on branding settings page 2025-02-26 15:37:07 -08:00
Marcus Moore
de3c1d159f Replace Form::checkbox on branding settings page 2025-02-26 15:35:17 -08:00
Marcus Moore
af6d9e4a00 Replace Form::checkbox on custom report pages 2025-02-26 15:15:48 -08:00
snipe
8c8af3062e Merge pull request #16354 from snipe/dont_checkin_for_pending_on_asset_update
Allow pending as an asset status type that does not automatically check the asset in
2025-02-26 21:05:11 +00:00
snipe
2ff47edb94 Merge pull request #16361 from Godmartinz/acceptancer_reminder_unlisted_email_info
Fixed acceptance reminder command lag on users with no associated email
2025-02-26 20:33:02 +00:00
snipe
d923d29bad Merge pull request #16360 from spencerrlongg/bug/sc-28537
Add Safety Around Bulk Status Update
2025-02-26 20:32:19 +00:00
Godfrey M
899119ae2d changes output to a table 2025-02-26 12:30:19 -08:00
snipe
e031de8e49 Merge pull request #16363 from snipe/added_a_few_more_no_interaction_calls
Added `--no-interaction` to additional passport commands
2025-02-26 20:19:37 +00:00
snipe
a81c520d93 Follow up for #16341
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 20:15:09 +00:00
Godfrey M
8352e81228 fix unaccepted reminder test 2025-02-26 11:50:08 -08:00
Godfrey M
fd0174ff32 remove unwanted changes to livewire 2025-02-26 11:31:41 -08:00
Godfrey M
cc26aa02b2 fix acceptance reminder command no email list 2025-02-26 11:25:21 -08:00
snipe
616f3558dd Update example env
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 19:14:25 +00:00
spencerrlongg
d6e266cec1 make super safe 2025-02-26 13:11:49 -06:00
snipe
31516d7f24 Merge pull request #16356 from snipe/added_fields_for_model_search
Added name, model_number and notes for strict search
2025-02-26 12:46:13 +00:00
snipe
e79af255aa Added name, model_number and notes for strict search
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 12:43:54 +00:00
snipe
5b0d7f4064 One more chonk
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 12:01:50 +00:00
snipe
2f6af10c5d Bumped chunk for custom report
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 11:59:34 +00:00
snipe
afabda9235 Remove greater than 0 for alert threshold - fixes FD-47040
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 11:38:33 +00:00
snipe
1618c9ae8e Changed confirmation message
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:53:41 +00:00
snipe
5344ef4a1a Allow pending as an asset status that does not automatically check the asset in
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:46:38 +00:00
snipe
16420b1e00 Audit Log Number under Days to next Audit [sc-28530]
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:25:07 +00:00
snipe
62f66e724e Drop errors to warnings to stop pooping on rollbar
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 09:54:13 +00:00
snipe
9b0ea51d35 Moved composer clear commands to after composer install per #16334
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 09:37:31 +00:00
snipe
992214fc66 Check for adminuser withTrashed
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 08:25:22 +00:00
snipe
93dab12461 Merge pull request #16342 from snipe/nicer_upgrade_script
Nicer upgrade.php UI
2025-02-26 07:16:28 +00:00
snipe
ea0f105180 Merge pull request #16347 from marcusmoore/chore/migrate-checkbox-helpers-pt4
Replace calls to Form::checkbox pt4
2025-02-26 07:05:38 +00:00
snipe
5b6da0c1e8 Merge pull request #16348 from marcusmoore/chore/migrate-checkbox-helpers-pt5
Replace calls to Form::checkbox pt5
2025-02-26 07:05:22 +00:00
snipe
4d7655bbe1 Merge pull request #16346 from marcusmoore/chore/migrate-checkbox-helpers-pt3
Replace calls to Form::checkbox pt3
2025-02-26 07:04:27 +00:00
snipe
5e3855ee5b Merge pull request #16345 from marcusmoore/fixes/update-custom-fields
Fixed renaming custom fields
2025-02-26 07:03:40 +00:00
Marcus Moore
e01226a174 Replace Form::checkbox on saml settings page 2025-02-25 17:31:05 -08:00
Marcus Moore
f9ccf32af4 Replace Form::checkbox on ldap settings page 2025-02-25 17:22:44 -08:00
Marcus Moore
2c5170a218 Replace Form::checkbox on security settings page 2025-02-25 17:09:25 -08:00
Marcus Moore
15f842e2dc Replace Form::checkbox on bulk user edit page 2025-02-25 16:00:46 -08:00
Marcus Moore
46b31dfe14 Replace Form::checkbox on user create and edit page 2025-02-25 14:32:39 -08:00
Marcus Moore
d88c79366c Replace Form::checkbox on google settings page 2025-02-25 14:27:07 -08:00
Marcus Moore
de330a47cd Replace Form::checkbox on asset tag settings page 2025-02-25 14:25:02 -08:00
Marcus Moore
ecd7dc2094 Replace Form::checkbox on alert settings page 2025-02-25 14:23:26 -08:00
Marcus Moore
510946e0eb Replace Form::checkbox in logo upload partial 2025-02-25 14:20:43 -08:00
Marcus Moore
37e4a13979 Replace Form::checkbox in image upload partial 2025-02-25 14:15:12 -08:00
Marcus Moore
c73d64cdbc Remove manual doctrine mapping for enums 2025-02-25 13:37:23 -08:00
snipe
4a0410d969 Uncomment git stash
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 16:55:39 +00:00
snipe
668b9f8fb9 Nicer upgrade.php UI
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 16:51:32 +00:00
snipe
037d2d9e84 Merge pull request #16341 from uberbrady/fix_passport_install_no_interaction
Fixes #16331 - Don't make passport:install command require user input
2025-02-25 16:43:49 +00:00
Brady Wetherington
bce2007b97 Fixes #16331 - Don't make passport:install command require user input 2025-02-25 16:38:40 +00:00
Brady Wetherington
09a5e5b1bd Whoops! Need minimum 8.2, not 8.1 (probably mis-merge?) 2025-02-25 14:40:45 +00:00
snipe
7b4f4b6b7f Merge pull request #16337 from joakimbergros/develop
Fixed #16173: `useraccountcontrol` was not included in the ldap query attributes
2025-02-25 13:49:38 +00:00
Joakim Bergros
5c66334017 Added a check to see if the user has specified that is an ActiveDirectory server in the configuration before adding the useraccountcontrol attribute to the ldap query. 2025-02-25 14:22:22 +01:00
Joakim Bergros
ae82051b73 Fixed #16173: useraccountcontrol was not included in the ldap query attributes.
`$results` did not include the `useraccountcontrol` and thus rendered the fallback logic void when `active_flag` was blank.

 Added a condition to check if `active_flag` is blank and only then add `useraccountcontrol` to the ldap query since it is then a requirement in accordance with "we respect the userAccountControl attribute" text in the `admin/ldap` route.

[`elseif' will become true when `active_flag` is blank](b141945add/app/Console/Commands/LdapSync.php (L364))
2025-02-25 13:55:53 +01:00
snipe
c3a2e81afd Updated version
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 12:18:22 +00:00
snipe
18c7cbbbbb Merge pull request #16336 from snipe/change_order_in_upgrade_script
Fixed #16334 - Changed composer order in upgrade script
2025-02-25 12:09:15 +00:00
snipe
3718f08c72 Changed composer order in upgrade script
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 12:05:00 +00:00
snipe
b7821a69b6 Added link to the google apps script repo
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 11:07:04 +00:00
snipe
8049f21068 Updated laravel version in readme
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 11:02:00 +00:00
snipe
8140110bf9 Chnage dev docker env to en-US from en
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 10:57:38 +00:00
snipe
a4587f6322 Merge pull request #16335 from snipe/added_label_test
Added label test
2025-02-25 10:40:46 +00:00
snipe
163e19f1e9 Added label test
Signed-off-by: snipe <snipe@snipe.net>
2025-02-25 10:37:06 +00:00
snipe
8a40c3ecb4 Merge pull request #16325 from spencerrlongg/bug/sc-28520
Remove remaining hardware references in favor of new RMB
2025-02-25 10:09:35 +00:00
spencerrlongg
d949a9689f fix all 2025-02-24 22:35:47 -06:00
snipe
27a7c6fd10 Merge pull request #16324 from marcusmoore/chore/migrate-checkbox-helpers-p2
Replace calls to Form::checkbox pt2
2025-02-25 02:33:02 +00:00
Marcus Moore
4d55a67628 Merge branch 'develop' into chore/migrate-checkbox-helpers-p2 2025-02-24 16:37:59 -08:00
snipe
9d453226bd Merge pull request #16321 from spencerrlongg/bug/sc-28515
Changed Parameter Name for RMB
2025-02-25 00:17:37 +00:00
snipe
cd26694767 Merge pull request #16322 from marcusmoore/fixes/fix-test-edit-route
Fixed edit routes in tests
2025-02-25 00:17:10 +00:00
Marcus Moore
a26cc2ced1 Use .edit routes instead of .update 2025-02-24 15:55:05 -08:00
spencerrlongg
1fd81d17a3 route model binding change 2025-02-24 17:53:41 -06:00
snipe
134045f1ec Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2025-02-24 23:46:55 +00:00
snipe
b6b6a3eec7 Remove req for 0 min
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 23:46:50 +00:00
snipe
b62714d702 Merge pull request #16062 from snipe/remove_mcrypt
Removed references to mcrypt
2025-02-24 23:22:32 +00:00
Brady Wetherington
271bcc66bf Remove lib-mcrypt as a dependency 2025-02-24 23:19:08 +00:00
snipe
ced560daa9 Add @jostrander as a contributor 2025-02-24 23:16:27 +00:00
snipe
b395c9130a Merge remote-tracking branch 'origin/master' into develop 2025-02-24 23:06:46 +00:00
snipe
ed46a757fa Merge pull request #16320 from jostrander/fix/location-edit
Fixed: fix 500 on edit locations page by referencing current location
2025-02-24 23:03:01 +00:00
Jesse Ostrander
6a8a41b389 fix: reference to item location on location edit method 2025-02-24 17:34:18 -05:00
snipe
4800f4c853 Merge branch 'develop' into remove_mcrypt 2025-02-24 22:12:56 +00:00
snipe
c79ff49c85 Add @addex12 as a contributor 2025-02-24 20:30:04 +00:00
Marcus Moore
e88bba51bb Merge branch 'develop' into acceptance-reminder-subject 2025-02-24 11:55:50 -08:00
Marcus Moore
f97211f6cd Remove unused language string 2025-02-24 11:45:58 -08:00
Marcus Moore
027c2b3627 Change subject to "You have Unaccepted Assets." 2025-02-24 11:45:23 -08:00
snipe
ee85a392e7 Merge pull request #16316 from snipe/v8_final_merge
V8 final merge
2025-02-24 19:18:12 +00:00
Brady Wetherington
6c2d367124 Tweak version number and hashes 2025-02-24 18:38:53 +00:00
Brady Wetherington
982cfeca32 Merge branch 'develop' 2025-02-24 18:37:41 +00:00
snipe
5fa4f85c20 Merge pull request #16106 from marcusmoore/bug/sc-27960
Fixed asset show page erroring when asset not associated with model
2025-02-24 17:16:12 +00:00
snipe
43b8c0bf7b Merge pull request #16313 from snipe/localizations/2024-02-24
Updated strings, added Oromo
2025-02-24 14:51:45 +00:00
snipe
54b664d679 Updated strings, added Oromo
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 14:50:23 +00:00
snipe
bf2355a297 Fixed showfile path
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 14:34:10 +00:00
snipe
2bf4ec0ae8 A few more small tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 14:24:15 +00:00
Brady Wetherington
9555825a67 Update minimum PHP version 2025-02-24 14:03:57 +00:00
snipe
0bea07e2f9 Merge pull request #16312 from snipe/small_cleanups
Small cleanups before release
2025-02-24 13:53:28 +00:00
snipe
22f7a9ddd4 Fixed stray tag
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:46:33 +00:00
snipe
77b417bcea Passed user to showfile
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:43:39 +00:00
snipe
76e3c00d2d Removed footer on tables that didn’t need it
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:41:49 +00:00
snipe
77add8abbc Added strings
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:39:07 +00:00
snipe
b53957268a Added warning if deleted
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:38:55 +00:00
snipe
ba49fc554d Moved warning to be consistent with other FCOs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:38:47 +00:00
snipe
e60462b751 Added/updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:38:28 +00:00
snipe
a5009aa7df Added withTrashed() to handle soft deletes with RMB
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:38:06 +00:00
snipe
e589adbe80 Updated model route
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 13:31:09 +00:00
snipe
0501c5f53c Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 11:15:03 +00:00
snipe
6a9247ba8a Cast token policies to int
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 11:14:49 +00:00
snipe
fcf84bf63f Fixed test namespace
Signed-off-by: snipe <snipe@snipe.net>
2025-02-24 11:09:54 +00:00
snipe
cebb9d034c Merge pull request #16305 from snipe/bug/sc-28425
Fixed #16262 - Check for quantity before allowing component deletion
2025-02-23 14:17:36 +00:00
snipe
dd2b570db5 Added tighter constraints on deleting components
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 14:11:39 +00:00
snipe
2bee4532ec Added qty error string
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 13:21:58 +00:00
snipe
96248e0023 Removed soft delete from test
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 13:21:49 +00:00
snipe
157801242d Added API test, renamed test to match filter
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 13:21:36 +00:00
snipe
7b151cf692 Added test
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 13:17:20 +00:00
snipe
50c88df4cc Merge pull request #16304 from snipe/security/snyk-updates-less-papaparse
Updated less and papaparse
2025-02-23 12:51:38 +00:00
snipe
d87a6868cc Updated less and papaparse
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 12:50:09 +00:00
snipe
201f487853 Merge pull request #16303 from snipe/chore/sc-28493
Added tooltip for currency in table
2025-02-23 12:21:04 +00:00
snipe
069860d65f Added tooltip for currency in table
Signed-off-by: snipe <snipe@snipe.net>
2025-02-23 12:19:18 +00:00
snipe
afa7391080 Merge pull request #16299 from snipe/added_checkin_checkout_indicator_for_custom_fields
Added checkin/checkout indicators on custom field table
2025-02-22 19:34:04 +00:00
snipe
07cf203018 Add checkin/checkout indicators on custom field table
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 19:32:34 +00:00
snipe
5f561452d2 Merge pull request #16298 from uberbrady/tooltips_custom_fields
Add tooltips to custom fields display, especially for icons
2025-02-22 19:17:28 +00:00
Brady Wetherington
cbe04e8514 Add tooltips to custom fields display, especially for icons 2025-02-22 19:13:09 +00:00
snipe
9b44dfd9b6 Merge pull request #16297 from snipe/add_field_to_checkin_checkout
Fixed #6188 - Added custom fields to checkin/checkout screens
2025-02-22 19:09:51 +00:00
snipe
9abf3029ef Compact the method just a little
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 19:02:41 +00:00
snipe
19fb45f488 Refactor into method
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:57:18 +00:00
snipe
e983ee4d0f Tweaked icon position
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:47:12 +00:00
snipe
40acd48eff Tweaked some style
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:19:09 +00:00
snipe
f5eedb8d23 Added RMB and include $item so the asset fields are populated
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:13:12 +00:00
snipe
172df0d220 Save new custom field values
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:12:46 +00:00
snipe
c611cb5612 Updated loggable to check for custom fields that were passed for meta
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:12:30 +00:00
snipe
da77ddd447 Added fields to custom field edit form
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:11:56 +00:00
snipe
c727633e6b Added language strings
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:11:42 +00:00
snipe
10834cf638 Added migration
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:11:35 +00:00
snipe
42eda089e2 Added include for custom fields partial
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:11:27 +00:00
snipe
2434c82a75 Added if/else for custom fields model partial
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:11:04 +00:00
snipe
1b9d046f5f Fixed test for RMB
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:08:25 +00:00
snipe
a0a0727b5c Corrected route breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 18:08:13 +00:00
snipe
917b9f0293 Merge pull request #16296 from snipe/added_select_unselect_tooltip_to_presenters
Added select/unselect tooltip to presenters, added translation
2025-02-22 14:32:04 +00:00
snipe
bb70250007 Added select/unselect tooltip to presenters, added translation
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 14:30:49 +00:00
snipe
346aeeda71 Merge pull request #16287 from uberbrady/better_hover_titles
Fixes: [sc-28423] Better tooltips on lists, Bootstrap style
2025-02-22 14:24:02 +00:00
snipe
84a29378cd Merge pull request #16295 from snipe/#16282_adds_accessories_tab_to_assets
Fixed #16282 - adds accessories tab to assets
2025-02-22 14:21:32 +00:00
snipe
0cd0abe8bb Fixed #16282 - adds accessories tab to assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 14:20:21 +00:00
snipe
7db11dc12b Merge pull request #16277 from Godmartinz/bulk_delete_asset_bug
Fixes deletion of assigned assets through bulk delete
2025-02-22 12:59:37 +00:00
snipe
e0221301ff Increased text limit on notes
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 12:56:25 +00:00
snipe
b0fa059a28 Merge pull request #16285 from marcusmoore/bug/sc-28148
Re-added ability to add notes to assets
2025-02-22 12:55:18 +00:00
snipe
3fb00a9fa9 Merge pull request #16293 from uberbrady/fix_default_docker_image
Updated default Dockerfile to handle newer PHP versions
2025-02-22 12:52:37 +00:00
snipe
3ef8b047ba Merge pull request #16294 from snipe/bug/sc-28474
Don’t delete avatar file on soft-delete
2025-02-22 12:52:18 +00:00
snipe
7a77de6ffe Don’t delete avatar file on soft-delete
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 12:49:04 +00:00
Brady Wetherington
5e85891b10 Updated default Dockerfile to handle newer PHP versions 2025-02-22 12:43:30 +00:00
snipe
934da0f630 Merge pull request #16291 from uberbrady/fix_tests_and_migrations_v8
Fix tests and fix migrations to always explicitly include nullable()
2025-02-22 12:21:56 +00:00
snipe
cca46ea9e0 Merge pull request #16292 from snipe/add_email_list_assigned_to_bulk
Added ability to send user inventory via bulk UI
2025-02-22 12:20:01 +00:00
snipe
bde034593b Updateed string
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 12:17:04 +00:00
Brady Wetherington
7e1295bac7 Fix tests and fix migrations to always explicitly include nullable() 2025-02-22 12:15:12 +00:00
snipe
d1cc0fcfac Added ability to send user inventory via bulk UI
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 12:07:54 +00:00
snipe
056fbefb16 Merge pull request #16290 from snipe/update_php_versions_for_tests
Removed php 8.1, added 8.4
2025-02-22 11:11:53 +00:00
snipe
83a848980f Removed php 8.1, added 8.4
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 11:11:01 +00:00
snipe
7b930357e4 Cast years to int
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 10:51:27 +00:00
snipe
bdab052f40 Fixed test name
Signed-off-by: snipe <snipe@snipe.net>
2025-02-22 10:51:16 +00:00
Brady Wetherington
2d1e9b237f Better tooltips on list views, in Bootstrap style 2025-02-21 13:23:19 +00:00
Brady Wetherington
1cff49116e Bump version numbers and upgrade requirements 2025-02-21 11:49:25 +00:00
Brady Wetherington
38c5f23a52 Merge branch 'v8' into develop 2025-02-21 11:15:44 +00:00
Marcus Moore
c6bee0c47a Fix test 2025-02-20 13:24:01 -08:00
Marcus Moore
ae7d7f7d22 Merge branch 'develop' into bug/sc-28148
# Conflicts:
#	routes/web.php
2025-02-20 13:23:55 -08:00
Marcus Moore
96ab828cec Fix indent 2025-02-20 13:05:42 -08:00
Marcus Moore
43f679a554 Revert some changes from #15525 2025-02-20 12:59:16 -08:00
Marcus Moore
f1bf9fcf5c Implement test 2025-02-20 12:51:08 -08:00
Marcus Moore
992b4c7d86 Remove old comment 2025-02-20 12:50:03 -08:00
Marcus Moore
39bb2c2adb Implement tests 2025-02-20 12:49:58 -08:00
Marcus Moore
41129829e6 Add frontend required validation 2025-02-20 12:43:51 -08:00
Marcus Moore
44503fc423 Use translation 2025-02-20 12:41:56 -08:00
Marcus Moore
0190ccea27 Finish implementing test 2025-02-20 12:40:05 -08:00
snipe
5b63eb5a15 Merge pull request #16272 from snipe/experiments/breadcrumbs
Experimental WIP - added breadcrumbs, route model binding for resource routes
2025-02-20 19:21:58 +00:00
snipe
54cc427ef5 Updated tests for new RMB behavior
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 18:28:46 +00:00
snipe
e0beed0fa3 Reapplied accessory gate
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 18:28:01 +00:00
snipe
d4b24e5e57 Exception handler exceptions - exception inception!
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 18:27:45 +00:00
snipe
671b64bc6b Fixed RMB for kits and fields
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 15:01:17 +00:00
snipe
75db0d9e66 Fixed parameter for audit
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 15:00:57 +00:00
snipe
b891992351 Nice language for audit/checkin screens accounting for null
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 15:00:35 +00:00
snipe
ddb72bf4d0 Added breadcrumbs to licenses routes
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 15:00:08 +00:00
snipe
6f071722a2 Fixed kit crimbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:59:50 +00:00
snipe
be9bd93588 More crombs, removed scan route that we don’t use
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:59:10 +00:00
snipe
44a7bdbfb5 Moved SAML gui settings routes to web.php
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:58:17 +00:00
snipe
0341335d14 Additional crombs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:57:48 +00:00
snipe
76f038d142 RMB for custom fields
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:57:18 +00:00
snipe
3150ad50c9 RMB for audits
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 14:57:07 +00:00
snipe
80394ef788 Revert delete because route model binding + resources routes + soft deletes = sux0r
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 12:29:37 +00:00
snipe
8a70f09dd3 Temp “fix” RMB for soft-deleted users
Signed-off-by: snipe <snipe@snipe.net>
2025-02-20 12:19:31 +00:00
Godfrey M
a275391557 adds test for deleting assigned asset 2025-02-19 15:59:57 -08:00
Godfrey M
eaacf29d57 fix tests 2025-02-19 15:11:16 -08:00
snipe
441fdaa54a Merge pull request #16278 from marcusmoore/bug/sc-27234
Added better error handling around rendering barcodes
2025-02-19 20:54:24 +00:00
Marcus Moore
759c8c8f5c Catch TypeError in addition to Exceptions when rendering barcodes 2025-02-19 12:36:50 -08:00
snipe
6242296930 Remove the “View” part of the breadcrumb
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 19:30:13 +00:00
snipe
d330ef9919 Added crumbs and RMB for custom fields
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 18:23:16 +00:00
snipe
0708af7d07 Made the help ring slightly less goofy looking
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 18:22:50 +00:00
Godfrey M
a534b488b2 prevents deletion of assigned assets in bulk deletion 2025-02-19 10:09:02 -08:00
snipe
3171c1b1c9 Added breadcrumbs for bulkaudit, requested, and import history
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 17:40:47 +00:00
snipe
2f2b879d98 Language refinement
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 17:40:12 +00:00
snipe
039f483ec8 Check for notes and cost before displaying them
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 16:57:24 +00:00
snipe
7f665e0005 De-uglifying the maintenances detail page
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 16:34:48 +00:00
snipe
292819afa5 Added maintenances crumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:29:06 +00:00
snipe
7a94b09017 Use MLB for kit group
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:07:14 +00:00
snipe
6b46d12d67 Added breadcrumbs on kits (sort of)
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:05:29 +00:00
snipe
e264907157 Added breadcrumbs on more web routes
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:05:12 +00:00
snipe
32b89e937c Fixed routes
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:04:56 +00:00
snipe
0e1dc8745c Wider custom report
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:04:45 +00:00
snipe
129d44cc70 Made icon possible for dashboard/home
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:04:34 +00:00
snipe
11b746d801 UI tweak
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:04:19 +00:00
snipe
545445d584 Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:04:07 +00:00
snipe
4d72fd8667 Added additional resource crumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:03:53 +00:00
snipe
fca62a14f9 Fixed asset presenter to link to the company directly
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:03:29 +00:00
snipe
c8f2d9806a Use route model binding on kits
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:03:15 +00:00
snipe
1ee5f28fca Added angle-right and house icon
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 14:02:59 +00:00
snipe
24a17b09a5 Fixed a few more tests and strings
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 09:03:41 +00:00
snipe
a617c0545e WIP license checkin
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:39:25 +00:00
snipe
eb7e24eeb0 Asset checkout with route model binding
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:39:16 +00:00
snipe
22c58678c4 License checkout controller with route model binding
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:38:55 +00:00
snipe
4beaf6b3ec Added item
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:38:40 +00:00
snipe
4c1856b91f Fixed wonky quote in HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:37:58 +00:00
snipe
9d299340da Checkout license modifications (this doesn’t work yet
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:37:39 +00:00
snipe
457228d033 Route model binding on more asset methods
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:37:06 +00:00
snipe
63f4e3cc89 More breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 08:36:52 +00:00
snipe
cd7135ea77 More test fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 06:19:50 +00:00
snipe
347eb2bdee Fixed route parameters and tests to match
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 05:03:56 +00:00
snipe
ecc0e76e7b Fixed route parameters for helper
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 05:03:39 +00:00
snipe
35a89de6dd Fixed handler for reports
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 05:03:26 +00:00
snipe
82d8e1b0a9 Added item back into edit
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 04:18:57 +00:00
snipe
5a7ffa5664 Sure, whatever I guess.
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 04:18:40 +00:00
snipe
55abd242f2 Don’t know why I need to compact this?
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 04:07:09 +00:00
snipe
217d65f64c More changes to the error message formatting
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 03:47:51 +00:00
snipe
fa20eb4965 Add exception for hardware vs assets :(
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 03:41:01 +00:00
snipe
560311b883 Added ->with(‘item’) back in
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 03:37:34 +00:00
snipe
3954ff20b4 More tweaks to the controllers, more breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 03:29:31 +00:00
snipe
989082d719 Handle the not found errors with route model binding
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 03:26:44 +00:00
snipe
644527c5a6 More resources to breadcrumb provider
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 02:49:21 +00:00
snipe
f7d213052a Route model binding for resource groups
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 02:49:05 +00:00
snipe
972bd1ef83 Tinkering with routes for breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-02-19 00:37:09 +00:00
Marcus Moore
cf0ce1c5ea Start to implement tests 2025-02-18 16:24:52 -08:00
Marcus Moore
3d69721af0 Scaffold tests 2025-02-18 16:16:00 -08:00
Marcus Moore
1995e1399e Start recording ip, user agent, and source 2025-02-18 16:04:53 -08:00
Marcus Moore
79d1147f20 Add validation 2025-02-18 15:46:52 -08:00
Marcus Moore
53c084c6c0 WIP: begin to store notes 2025-02-18 15:43:58 -08:00
Marcus Moore
c165e6f400 Formatting 2025-02-18 15:20:22 -08:00
Marcus Moore
25215fd3c9 WIP: begin migrating notes to ui controller 2025-02-18 15:20:06 -08:00
Godfrey M
4c43a06eee add overdue asset to test 2025-02-13 11:19:49 -08:00
Godfrey M
25c8449e86 remove unused 2025-02-13 11:14:18 -08:00
Godfrey M
13e1f4a127 adds mailable 2025-02-13 11:09:39 -08:00
Godfrey M
ed96fd766c refactors audit notification to mail, adds test, ads check to scheduler 2025-02-13 11:08:53 -08:00
Marcus Moore
77635c3737 Migrate Form::checkboxes on license edit view 2025-02-06 17:05:35 -08:00
Marcus Moore
b937b7e767 Migrate Form::checkboxes on asset history import page 2025-02-06 17:01:09 -08:00
Marcus Moore
7cbb3f7e07 Add assertion 2025-01-30 11:48:55 -08:00
Marcus Moore
7e9c564d0b Simplify test 2025-01-30 11:47:43 -08:00
Marcus Moore
fc88b2487f Extract method 2025-01-30 11:44:37 -08:00
Marcus Moore
e94ee48f74 Extract helper 2025-01-30 10:37:11 -08:00
Marcus Moore
6a4a5d1380 Add translation 2025-01-30 10:35:31 -08:00
Marcus Moore
ab9e9b66d2 Reduce complexity 2025-01-29 16:27:18 -08:00
Marcus Moore
c15c338ffd Merge if/else 2025-01-29 16:25:37 -08:00
Marcus Moore
d1197d015c Add another case scenario 2025-01-29 16:24:43 -08:00
Marcus Moore
ce31ce477e Inline additional variables 2025-01-29 16:16:47 -08:00
Marcus Moore
78f9292555 Inline variable 2025-01-29 16:15:27 -08:00
Marcus Moore
4e7c6bd2cf Fix relationship 2025-01-29 16:14:09 -08:00
Marcus Moore
70aed45bfe Improve naming 2025-01-29 15:56:20 -08:00
Marcus Moore
e2805f4033 Add "Reminder" to subject line 2025-01-29 15:36:45 -08:00
Marcus Moore
d254a40e0a Scaffold tests 2025-01-29 15:21:10 -08:00
Marcus Moore
fdcb891cbb Improve test case 2025-01-29 15:20:56 -08:00
Godfrey M
16d322d70e fix translation 2025-01-29 10:38:30 -08:00
Godfrey M
2163312997 adds translations for branding and general settings 2025-01-29 10:35:26 -08:00
Godfrey M
0dfb71cfe5 added some translations to branding and general setrting 2025-01-28 11:56:10 -08:00
Marcus Moore
31097bdc37 Formatting 2025-01-21 10:25:53 -08:00
Marcus Moore
e733d2a5d0 Backfill test 2025-01-21 10:24:15 -08:00
Marcus Moore
6a35fa7ba3 Account for missing asset model 2025-01-21 10:14:27 -08:00
Marcus Moore
370666b5bd Fix indenting 2025-01-21 10:12:56 -08:00
snipe
99526cfc2c Remove mcrypt and legacy recrypter
Signed-off-by: snipe <snipe@snipe.net>
2025-01-13 19:54:00 +00:00
Fiala06
bdb0e6c2a3 Update LdapSync.php
Fix for duplicate entries preventing the sync from continuing. 

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '13178-6' for key 'PRIMARY' (Connection: mysql, SQL: insert into users_groups (group_id, user_id) values (6, 13178))
2024-12-02 11:07:37 -08:00
873 changed files with 284350 additions and 3337 deletions

View File

@@ -3271,6 +3271,33 @@
"contributions": [
"code"
]
},
{
"login": "addex12",
"name": "Adugna Gizaw",
"avatar_url": "https://avatars.githubusercontent.com/u/18550946?v=4",
"profile": "https://orbalia.pythonanywhere.com/",
"contributions": [
"translation"
]
},
{
"login": "jostrander",
"name": "Jesse Ostrander",
"avatar_url": "https://avatars.githubusercontent.com/u/760989?v=4",
"profile": "https://github.com/jostrander",
"contributions": [
"code"
]
},
{
"login": "azmcnutt",
"name": "James M",
"avatar_url": "https://avatars.githubusercontent.com/u/31522486?v=4",
"profile": "https://github.com/azmcnutt",
"contributions": [
"code"
]
}
]
}

View File

@@ -16,7 +16,7 @@ APP_DEBUG=true
APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
APP_LOCALE=en
APP_LOCALE=en-US
MAX_RESULTS=500
# --------------------------------------------

View File

@@ -99,7 +99,7 @@ PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
BS_TABLE_STORAGE=localStorage
BS_TABLE_DEEPLINK=true
# --------------------------------------------

View File

@@ -25,9 +25,9 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
name: PHP ${{ matrix.php-version }}
@@ -67,7 +67,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit

View File

@@ -21,9 +21,10 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
name: PHP ${{ matrix.php-version }}
@@ -64,7 +65,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit

View File

@@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1.1"
- "8.3"
name: PHP ${{ matrix.php-version }}

View File

@@ -3,8 +3,8 @@
"DOC2": "In other words, what you see locally are the requirements for your _current_ install",
"DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version",
"DOC4": "You should really just ignore it and run upgrade.php. Really",
"php_min_version": "8.1.0",
"php_max_major_minor": "8.3",
"php_max_wontwork": "8.4.0",
"current_snipeit_version": "7.0"
"php_min_version": "8.2.0",
"php_max_major_minor": "8.4",
"php_max_wontwork": "8.5.0",
"current_snipeit_version": "8.0"
}

View File

@@ -53,7 +53,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM ubuntu:24.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference:
@@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \
apache2 \
apache2-bin \
libapache2-mod-php8.1 \
php8.1-curl \
php8.1-ldap \
php8.1-mysql \
php8.1-gd \
php8.1-xml \
php8.1-mbstring \
php8.1-zip \
php8.1-bcmath \
php8.1-redis \
libapache2-mod-php8.3 \
php8.3-curl \
php8.3-ldap \
php8.3-mysql \
php8.3-gd \
php8.3-xml \
php8.3-mbstring \
php8.3-zip \
php8.3-bcmath \
php8.3-redis \
php-memcached \
patch \
curl \
@@ -40,8 +40,7 @@ autoconf \
libc-dev \
libldap-common \
pkg-config \
libmcrypt-dev \
php8.1-dev \
php8.3-dev \
ca-certificates \
unzip \
dnsutils \
@@ -51,18 +50,13 @@ dnsutils \
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
RUN php go-pear.phar
RUN pecl install mcrypt
RUN bash -c "echo extension=/usr/lib/php/20210902/mcrypt.so > /etc/php/8.1/mods-available/mcrypt.ini"
RUN phpenmod mcrypt
RUN phpenmod gd
RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/cli/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/cli/php.ini
RUN useradd -m --uid 1000 --gid 50 docker
RUN useradd -m --uid 10000 --gid 50 docker
RUN echo export APACHE_RUN_USER=docker >> /etc/apache2/envvars
RUN echo export APACHE_RUN_GROUP=staff >> /etc/apache2/envvars

View File

@@ -7,7 +7,7 @@
This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc.
It is built on [Laravel 10](http://laravel.com).
It is built on [Laravel 11](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
@@ -94,6 +94,8 @@ Since the release of the JSON REST API, several third-party developers have been
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks.
-----
### Join the Community!

View File

@@ -125,6 +125,10 @@ class LdapSync extends Command
*/
$attributes = array_values(array_filter($ldap_map));
if (Setting::getSettings()->is_ad === 1 && is_null($ldap_map['active_flag'])) {
$attributes[] = 'useraccountcontrol';
}
$results = Ldap::findLdapUsers($search_base, -1, $filter, $attributes);
} catch (\Exception $e) {
@@ -357,9 +361,15 @@ class LdapSync extends Command
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
$user->activated = !($filter_var ?? $boolean_cast);
}else{
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
}
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
@@ -424,8 +434,12 @@ class LdapSync extends Command
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
// Check if the relationship already exists
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
}
}
//updates assets location based on user's location
if ($user->wasChanged('location_id')) {
foreach ($user->assets as $asset) {

View File

@@ -1,157 +0,0 @@
<?php
namespace App\Console\Commands;
use App\LegacyEncrypter\McryptEncrypter;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Setting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class RecryptFromMcrypt extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:legacy-recrypt
{--force : Force a re-crypt of encrypted data from MCRYPT.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command allows upgrading users to de-encrypt their deprecated mcrypt encrypted fields and re-encrypt them using the current OpenSSL encryption.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Check and see if they have a legacy app key listed in their .env
// If not, we can try to use the current APP_KEY if looks like it's old
$legacy_key = env('LEGACY_APP_KEY');
$key_parts = explode(':', $legacy_key);
$legacy_cipher = env('LEGACY_CIPHER', 'rijndael-256');
$errors = [];
if (! $legacy_key) {
$this->error('ERROR: You do not have a LEGACY_APP_KEY set in your .env file. Please locate your old APP_KEY and ADD a line to your .env file like: LEGACY_APP_KEY=YOUR_OLD_APP_KEY');
return false;
}
// Do some basic legacy app key length checks
if (strlen($legacy_key) == 32) {
$legacy_length_check = true;
} elseif (array_key_exists('1', $key_parts) && (strlen($key_parts[1]) == 44)) {
$legacy_key = base64_decode($key_parts[1], true);
$legacy_length_check = true;
} else {
$legacy_length_check = false;
}
// Check that the app key is 32 characters
if ($legacy_length_check === true) {
$this->comment('INFO: Your LEGACY_APP_KEY looks correct. Okay to continue.');
} else {
$this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters or base64 followed by 44 characters for later versions). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.');
return false;
}
$this->error('================================!!!! WARNING !!!!================================');
$this->error('================================!!!! WARNING !!!!================================');
$this->comment("This tool will attempt to decrypt your old Snipe-IT (mcrypt, now deprecated) encrypted data and re-encrypt it using OpenSSL. \n\nYou should only continue if you have backed up any and all old APP_KEYs and have backed up your data.");
$force = ($this->option('force')) ? true : false;
if ($force || ($this->confirm('Are you SURE you wish to continue?'))) {
$backup_file = 'backups/env-backups/'.'app_key-'.date('Y-m-d-gis');
try {
Storage::disk('local')->put($backup_file, 'APP_KEY: '.config('app.key'));
Storage::disk('local')->append($backup_file, 'LEGACY_APP_KEY: '.$legacy_key);
} catch (\Exception $e) {
$this->info('WARNING: Could not backup app keys');
}
if ($legacy_cipher) {
$mcrypter = new McryptEncrypter($legacy_key, $legacy_cipher);
} else {
$mcrypter = new McryptEncrypter($legacy_key);
}
$settings = Setting::getSettings();
if ($settings->ldap_pword == '') {
$this->comment('INFO: No LDAP password found. Skipping... ');
} else {
$decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword);
$settings->ldap_pword = Crypt::encrypt($decrypted_ldap_pword);
$settings->save();
}
/** @var CustomField[] $custom_fields */
$custom_fields = CustomField::where('field_encrypted', '=', 1)->get();
$this->comment('INFO: Retrieving encrypted custom fields...');
$query = Asset::withTrashed();
foreach ($custom_fields as $custom_field) {
$this->comment('FIELD TO RECRYPT: '.$custom_field->name.' ('.$custom_field->db_column.')');
$query->orWhereNotNull($custom_field->db_column);
}
// Get all assets with a value in any of the fields that were encrypted
/** @var Asset[] $assets */
$assets = $query->get();
$bar = $this->output->createProgressBar(count($assets));
foreach ($assets as $asset) {
foreach ($custom_fields as $encrypted_field) {
$columnName = $encrypted_field->db_column;
// Make sure the value isn't null
if ($asset->{$columnName} != '') {
// Try to decrypt the payload using the legacy app key
try {
$decrypted_field = $mcrypter->decrypt($asset->{$columnName});
$asset->{$columnName} = Crypt::encrypt($decrypted_field);
$this->comment($decrypted_field);
} catch (\Exception $e) {
$errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage();
}
}
}
$asset->save();
$bar->advance();
}
$bar->finish();
if (count($errors) > 0) {
$this->comment("\n\n");
$this->error("The decrypter encountered some errors: \n");
foreach ($errors as $error) {
$this->error($error);
}
}
}
}
}

View File

@@ -64,28 +64,43 @@ class SendAcceptanceReminder extends Command
->groupBy(function($item) {
return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
});
$no_email_list= [];
foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
// The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user.
// Collapsing and flattening the collection doesn't work above.
$acceptance = $unacceptedAssetGroup[0]['acceptance'];
$locale = $acceptance->assignedTo?->locale;
$email = $acceptance->assignedTo?->email;
if(!$email){
$this->info($acceptance->assignedTo?->present()->fullName().' has no email address.');
$no_email_list[] = [
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->present()->fullName(),
];
} else {
$count++;
}
$item_count = $unacceptedAssetGroup->count();
if ($locale && $email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count)));
}
$count++;
}
$this->info($count.' users notified.');
$headers = ['ID', 'Name'];
$rows = [];
foreach ($no_email_list as $user) {
$rows[] = [$user['id'], $user['name']];
}
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
return 0;
}

View File

@@ -2,13 +2,12 @@
namespace App\Console\Commands;
use App\Mail\SendUpcomingAuditMail;
use App\Models\Asset;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class SendUpcomingAuditReport extends Command
{
@@ -48,19 +47,19 @@ class SendUpcomingAuditReport extends Command
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->all();
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use App\Models\Setting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
if(Setting::getSettings()->alerts_enabled === 1) {
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
$schedule->command('saml:clear_expired_nonces')->weekly();
}

View File

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

View File

@@ -122,6 +122,27 @@ class Handler extends ExceptionHandler
}
// This is traaaaash but it handles models that are not found while using route model binding :(
// The only alternative is to set that at *each* route, which is crazypants
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
$model_name = last(explode('\\', $e->getModel()));
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
// Sigh.
if ($route == 'assets.index') {
$route = 'hardware.index';
} elseif ($route == 'reporttemplates.index') {
$route = 'reports/custom';
} elseif ($route == 'assetmodels.index') {
$route = 'models.index';
} elseif ($route == 'predefinedkits.index') {
$route = 'kits.index';
}
return redirect()
->route($route)
->withError(trans('general.generic_model_not_found', ['model' => $model_name]));
}
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {

View File

@@ -1520,11 +1520,11 @@ class Helper
if ($redirect_option == 'target') {
switch ($checkout_to_type) {
case 'user':
return route('users.show', ['user' => $request->assigned_user]);
return route('users.show', $request->assigned_user);
case 'location':
return route('locations.show', ['location' => $request->assigned_location]);
return route('locations.show', $request->assigned_location);
case 'asset':
return route('hardware.show', ['hardware' => $request->assigned_asset]);
return route('hardware.show', $request->assigned_asset);
}
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));

View File

@@ -59,6 +59,8 @@ class IconHelper
return 'fas fa-cog';
case 'angle-left':
return 'fas fa-angle-left';
case 'angle-right':
return 'fas fa-angle-right';
case 'warning':
return 'fas fa-exclamation-triangle';
case 'kits':
@@ -184,6 +186,8 @@ class IconHelper
return 'fa-regular fa-id-card';
case 'department' :
return 'fa-solid fa-building-user';
case 'home' :
return 'fa-solid fa-house';
case 'note':
case 'notes':
return 'fas fa-sticky-note';

View File

@@ -95,16 +95,10 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
*/
public function edit($accessoryId = null) : View | RedirectResponse
public function edit(Accessory $accessory) : View | RedirectResponse
{
if ($item = Accessory::find($accessoryId)) {
$this->authorize($item);
return view('accessories.edit', compact('item'))->with('category_type', 'accessory');
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
$this->authorize('update', Accessory::class);
return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
}
/**
@@ -114,19 +108,12 @@ class AccessoriesController extends Controller
* @param int $accessoryId
* @since [v6.0]
*/
public function getClone($accessoryId = null) : View | RedirectResponse
public function getClone(Accessory $accessory) : View | RedirectResponse
{
$this->authorize('create', Accessory::class);
// Check if the asset exists
if (is_null($accessory_to_clone = Accessory::find($accessoryId))) {
// Redirect to the asset management page
return redirect()->route('accessories.index')
->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryId]));
}
$accessory = clone $accessory_to_clone;
$accessory = clone $accessory;
$accessory->id = null;
$accessory->location_id = null;
@@ -142,9 +129,9 @@ class AccessoriesController extends Controller
* @param ImageUploadRequest $request
* @param int $accessoryId
*/
public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse
public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse
{
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) {
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) {
$this->authorize($accessory);
@@ -231,14 +218,10 @@ class AccessoriesController extends Controller
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
*/
public function show($accessoryID = null) : View | RedirectResponse
public function show(Accessory $accessory) : View | RedirectResponse
{
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID);
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id);
$this->authorize('view', $accessory);
if (isset($accessory->id)) {
return view('accessories.view', compact('accessory'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID]));
return view('accessories.view', compact('accessory'));
}
}

View File

@@ -77,6 +77,18 @@ class AssetModelsController extends Controller
$assetmodels->onlyTrashed();
}
if ($request->filled('name')) {
$assetmodels = $assetmodels->where('models.name', '=', $request->input('name'));
}
if ($request->filled('model_number')) {
$assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number'));
}
if ($request->filled('notes')) {
$assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes'));
}
if ($request->filled('category_id')) {
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
}

View File

@@ -1230,7 +1230,10 @@ class AssetsController extends Controller
{
$this->authorize('view', Asset::class);
$this->authorize('view', $asset);
$accessory_checkouts = AccessoryCheckout::AssetsAssigned()->with('adminuser')->with('accessories');
$accessory_checkouts = AccessoryCheckout::AssetsAssigned()
->where('assigned_to', $asset->id)
->with('adminuser')
->with('accessories');
$offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -1239,6 +1242,8 @@ class AssetsController extends Controller
$accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
}
/**
* Generate asset labels by tag
*

View File

@@ -48,7 +48,8 @@ class ComponentsController extends Controller
];
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer');
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
@@ -197,6 +198,11 @@ class ComponentsController extends Controller
$this->authorize('delete', Component::class);
$component = Component::findOrFail($id);
$this->authorize('delete', $component);
if ($component->numCheckedOut() > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.delete.error_qty')));
}
$component->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.delete.success')));

View File

@@ -66,25 +66,41 @@ class ImportController extends Controller
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = iconv($encoding, 'UTF-8', $file_contents);
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
if ($tmpresults !== false) {
if (function_exists('iconv')) {
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = false;
try {
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
} catch (\Exception $e) {
$transliterated = false; //blank out the partially-decoded string
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
),
422
);
}
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
$transliterated = null; //save on memory?
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
if ($tmpresults !== false) {
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
}
}
}
$file_contents = null; //try to save on memory, I guess?
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$file_contents = null; //try to save on memory, I guess?
try {
$import->header_row = $reader->fetchOne(0);

View File

@@ -1,43 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Events\NoteAdded;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class NotesController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'note' => 'required|string|max:500',
'type' => [
'required',
Rule::in(['asset']),
],
]);
// This can be made dynamic by using $request->input('type') to determine which model type to add the note to.
// For now, we are only placing this on Assets
$item = Asset::findOrFail($request->input("id"));
$this->authorize('update', $item);
event(new NoteAdded($item, Auth::user(), $validated['note']));
return response()->json(Helper::formatStandardApiResponse('success'));
}
public function update(Request $request)
{
}
public function destroy(Request $request)
{
}
}

View File

@@ -290,10 +290,12 @@ class StatuslabelsController extends Controller
/**
* Returns a boolean response based on whether the status label
* is one that is deployable.
* is one that is deployable or pending.
*
* This is used by the hardware create/edit view to determine whether
* we should provide a dropdown of users for them to check the asset out to.
* we should provide a dropdown of users for them to check the asset out to,
* and whether we show a warning that the asset will be checked in if it's already
* assigned but the status is changed to one that isn't pending or deployable
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
@@ -301,7 +303,7 @@ class StatuslabelsController extends Controller
public function checkIfDeployable($id) : string
{
$statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') {
if (($statuslabel->getStatuslabelType() == 'pending') || ($statuslabel->getStatuslabelType() == 'deployable')) {
return '1';
}

View File

@@ -139,19 +139,12 @@ class AssetMaintenancesController extends Controller
* @version v1.0
* @since [v1.8]
*/
public function edit($assetMaintenanceId = null) : View | RedirectResponse
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -161,7 +154,7 @@ class AssetMaintenancesController extends Controller
return view('asset_maintenances/edit')
->with('selectedAsset', null)
->with('assetMaintenanceType', $assetMaintenanceType)
->with('item', $assetMaintenance);
->with('item', $maintenance);
}
/**
@@ -174,24 +167,20 @@ class AssetMaintenancesController extends Controller
* @version v1.0
* @since [v1.8]
*/
public function update(Request $request, $assetMaintenanceId = null) : View | RedirectResponse
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
if ((!$maintenance->asset) || ($maintenance->asset->deleted_at!='')) {
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
} elseif (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$maintenance->supplier_id = $request->input('supplier_id');
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id'));
@@ -200,39 +189,39 @@ class AssetMaintenancesController extends Controller
}
// Save the asset maintenance data
$assetMaintenance->asset_id = $request->input('asset_id');
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$maintenance->asset_id = $request->input('asset_id');
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->title = $request->input('title');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
if (($assetMaintenance->completion_date == null)
if (($maintenance->completion_date == null)
) {
if (($assetMaintenance->asset_maintenance_time !== 0)
|| (! is_null($assetMaintenance->asset_maintenance_time))
if (($maintenance->asset_maintenance_time !== 0)
|| (! is_null($maintenance->asset_maintenance_time))
) {
$assetMaintenance->asset_maintenance_time = null;
$maintenance->asset_maintenance_time = null;
}
}
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
if (($maintenance->completion_date !== null)
&& ($maintenance->start_date !== '')
&& ($maintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
$startDate = Carbon::parse($maintenance->start_date);
$completionDate = Carbon::parse($maintenance->completion_date);
$maintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
if ($maintenance->save()) {
// Redirect to the new asset maintenance page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.edit.success'));
->with('success', trans('admin/asset_maintenances/message.edit.success'));
}
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
}
/**
@@ -271,19 +260,13 @@ class AssetMaintenancesController extends Controller
* @version v1.0
* @since [v1.8]
*/
public function show($assetMaintenanceId) : View | RedirectResponse
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
{
$this->authorize('view', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
return view('asset_maintenances/view')->with('assetMaintenance', $assetMaintenance);
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
}
}

View File

@@ -109,16 +109,11 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @param int $modelId
*/
public function edit($modelId = null) : View | RedirectResponse
public function edit(AssetModel $model) : View | RedirectResponse
{
$this->authorize('update', AssetModel::class);
if ($item = AssetModel::find($modelId)) {
$category_type = 'asset';
return view('models/edit', compact('item', 'category_type'))->with('depreciation_list', Helper::depreciationList());
}
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
$category_type = 'asset';
return view('models/edit', compact('category_type'))->with('item', $model)->with('depreciation_list', Helper::depreciationList());
}
@@ -133,16 +128,11 @@ class AssetModelsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(StoreAssetModelRequest $request, $modelId) : RedirectResponse
public function update(StoreAssetModelRequest $request, AssetModel $model) : RedirectResponse
{
$this->authorize('update', AssetModel::class);
if (is_null($model = AssetModel::find($modelId))) {
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
$model = $request->handleImages($model);
$model->depreciation_id = $request->input('depreciation_id');
$model->eol = $request->input('eol');
$model->name = $request->input('name');
@@ -188,28 +178,16 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @param int $modelId
*/
public function destroy($modelId) : RedirectResponse
public function destroy(AssetModel $model) : RedirectResponse
{
$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.does_not_exist'));
}
if ($model->assets()->count() > 0) {
// Throw an error that this model is associated with assets
return redirect()->route('models.index')->with('error', trans('admin/models/message.assoc_users'));
}
if ($model->image) {
try {
Storage::disk('public')->delete('models/'.$model->image);
$model->update(['image' => null]);
} catch (\Exception $e) {
Log::info($e);
}
}
// Delete the model
$model->delete();
@@ -267,16 +245,10 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @param int $modelId
*/
public function show($modelId = null) : View | RedirectResponse
public function show(AssetModel $model) : View | RedirectResponse
{
$this->authorize('view', AssetModel::class);
$model = AssetModel::withTrashed()->find($modelId);
if (isset($model->id)) {
return view('models/view', compact('model'));
}
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
return view('models/view', compact('model'));
}
/**
@@ -286,23 +258,20 @@ class AssetModelsController extends Controller
* @since [v1.0]
* @param int $modelId
*/
public function getClone($modelId = null) : View | RedirectResponse
public function getClone(AssetModel $model) : View | RedirectResponse
{
$this->authorize('create', AssetModel::class);
// Check if the model exists
if (is_null($model_to_clone = AssetModel::find($modelId))) {
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
}
$model = clone $model_to_clone;
$cloned_model = clone $model;
$model->id = null;
$model->deleted_at = null;
// Show the page
return view('models/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
->with('model_id', $model_to_clone->id)
->with('clone_model', $model_to_clone);
->with('model_id', $model->id)
->with('clone_model', $cloned_model);
}
@@ -321,7 +290,7 @@ class AssetModelsController extends Controller
/**
* Returns a view that allows the user to bulk edit model attrbutes
* Returns a view that allows the user to bulk edit model attributes
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7]

View File

@@ -58,11 +58,9 @@ class AssetModelsFilesController extends Controller
* @param int $fileId
* @since [v1.0]
*/
public function show($modelId = null, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$model = AssetModel::find($modelId);
// the asset is valid
if (isset($model->id)) {
$this->authorize('view', $model);
if (! $log = Actionlog::find($fileId)) {
@@ -87,12 +85,6 @@ class AssetModelsFilesController extends Controller
}
return StorageHelper::downloader($file);
}
// Prepare the error message
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', $error);
}
/**
@@ -103,29 +95,21 @@ class AssetModelsFilesController extends Controller
* @param int $fileId
* @since [v1.0]
*/
public function destroy($modelId = null, $fileId = null) : RedirectResponse
public function destroy(AssetModel $model, $fileId = null) : RedirectResponse
{
$model = AssetModel::find($modelId);
$this->authorize('update', $model);
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($model->id)) {
$this->authorize('update', $model);
$log = Actionlog::find($fileId);
if ($log) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
$this->authorize('update', $model);
$log = Actionlog::find($fileId);
if ($log) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}

View File

@@ -27,18 +27,12 @@ class AssetCheckinController extends Controller
* @param string $backto
* @since [v1.0]
*/
public function create($assetId, $backto = null) : View | RedirectResponse
public function create(Asset $asset, $backto = null) : View | RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkin', $asset);
// This asset is already checked in, redirect
if (is_null($asset->assignedTo)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in'));
}
@@ -47,7 +41,11 @@ class AssetCheckinController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets');
return view('hardware/checkin', compact('asset'))
->with('item', $asset)
->with('statusLabel_list', Helper::statusLabelList())
->with('backto', $backto)
->with('table_name', 'Assets');
}
/**
@@ -91,6 +89,9 @@ class AssetCheckinController extends Controller
$asset->status_id = e($request->get('status_id'));
}
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkin');
$this->migrateLegacyLocations($asset);
$asset->location_id = $asset->rtd_location_id;
@@ -128,6 +129,9 @@ class AssetCheckinController extends Controller
session()->put('redirect_option', $request->get('redirect_option'));
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkin');
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues));

View File

@@ -26,27 +26,25 @@ class AssetCheckoutController extends Controller
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function create($assetId) : View | RedirectResponse
public function create(Asset $asset) : View | RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('checkout', $asset);
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
return redirect()->route('hardware.show', $asset)
->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'))
->with('statusLabel_list', Helper::deployableStatusLabelList())
->with('table_name', 'Assets');
->with('table_name', 'Assets')
->with('item', $asset);
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available'));
return redirect()->route('hardware.index')
->with('error', trans('admin/hardware/message.checkout.not_available'));
}
/**
@@ -68,7 +66,7 @@ class AssetCheckoutController extends Controller
$this->authorize('checkout', $asset);
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
return redirect()->route('hardware.show', $asset)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
$admin = auth()->user();
@@ -91,6 +89,7 @@ class AssetCheckoutController extends Controller
$asset->status_id = $request->get('status_id');
}
if(!empty($asset->licenseseats->all())){
if(request('checkout_to_type') == 'user') {
foreach ($asset->licenseseats as $seat){
@@ -100,23 +99,26 @@ class AssetCheckoutController extends Controller
}
}
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkout');
$settings = \App\Models\Setting::getSettings();
// We have to check whether $target->company_id is null here since locations don't have a company yet
if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
if ($target->company_id != $asset->company_id){
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
return redirect()->route('hardware.checkout.create', $asset)->with('error', trans('general.error_user_company'));
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.checkout.success'));
}
// Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
return redirect()->route("hardware.checkout.create", $asset)->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
} catch (ModelNotFoundException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors());
} catch (CheckoutNotAllowed $e) {

View File

@@ -26,11 +26,8 @@ class AssetFilesController extends Controller
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $assetId = null) : RedirectResponse
public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse
{
if (! $asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('update', $asset);
@@ -59,31 +56,28 @@ class AssetFilesController extends Controller
* @param int $fileId
* @since [v1.0]
*/
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
if ($asset = Asset::find($assetId)) {
$this->authorize('view', $asset);
$this->authorize('view', $asset);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found'));
}
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found'));
}
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
@@ -95,23 +89,20 @@ class AssetFilesController extends Controller
* @param int $fileId
* @since [v1.0]
*/
public function destroy($assetId = null, $fileId = null) : RedirectResponse
public function destroy(Asset $asset, $fileId = null) : RedirectResponse
{
if ($asset = Asset::find($assetId)) {
$this->authorize('update', $asset);
$rel_path = 'private_uploads/assets';
$this->authorize('update', $asset);
$rel_path = 'private_uploads/assets';
if ($log = Actionlog::find($fileId)) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
if ($log = Actionlog::find($fileId)) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
}

View File

@@ -30,6 +30,7 @@ use Illuminate\Http\Response;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use TypeError;
/**
* This class controls all actions related to assets for
@@ -201,7 +202,7 @@ class AssetsController extends Controller
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
$successes[] = "<a href='" . route('hardware.show', ['hardware' => $asset->id]) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
} else {
$failures[] = join(",", $asset->getErrors()->all());
@@ -222,7 +223,7 @@ class AssetsController extends Controller
//the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed
//and re-translated
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)]));
} else {
//multi-success
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
@@ -240,20 +241,14 @@ class AssetsController extends Controller
* Returns a view that presents a form to edit an existing asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function edit($assetId = null) : View | RedirectResponse
public function edit(Asset $asset) : View | RedirectResponse
{
if (! $item = Asset::find($assetId)) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
//Handles company checks and permissions.
$this->authorize($item);
return view('hardware/edit', compact('item'))
$this->authorize($asset);
return view('hardware/edit')
->with('item', $asset)
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList());
}
@@ -267,15 +262,14 @@ class AssetsController extends Controller
* @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/
public function show($assetId = null) : View | RedirectResponse
public function show(Asset $asset) : View | RedirectResponse
{
$asset = Asset::withTrashed()->find($assetId);
$this->authorize('view', $asset);
$settings = Setting::getSettings();
if (isset($asset)) {
$audit_log = Actionlog::where('action_type', '=', 'audit')
->where('item_id', '=', $assetId)
->where('item_id', '=', $asset->id)
->where('item_type', '=', Asset::class)
->orderBy('created_at', 'DESC')->first();
@@ -291,7 +285,7 @@ class AssetsController extends Controller
$qr_code = (object) [
'display' => $settings->qr_code == '1',
'url' => route('qr_code/hardware', $asset->id),
'url' => route('qr_code/hardware', $asset),
];
return view('hardware/view', compact('asset', 'qr_code', 'settings'))
@@ -308,14 +302,9 @@ class AssetsController extends Controller
* @since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse
public function update(ImageUploadRequest $request, Asset $asset) : RedirectResponse
{
// Check if the asset exists
if (! $asset = Asset::find($assetId)) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize($asset);
$asset->status_id = $request->input('status_id', null);
@@ -351,15 +340,14 @@ class AssetsController extends Controller
$status = Statuslabel::find($request->input('status_id'));
// This is a non-deployable status label - we should check the asset back in.
if (($status && $status->getStatuslabelType() != 'deployable') && ($target = $asset->assignedTo)) {
// This is an archived or undeployable - we should check the asset back in.
// Pending is allowed here
if (($status) && (($status->getStatuslabelType() != 'pending') && ($status->getStatuslabelType() != 'deployable')) && ($target = $asset->assignedTo)) {
$originalValues = $asset->getRawOriginal();
$asset->assigned_to = null;
$asset->assigned_type = null;
$asset->accepted = null;
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update', date('Y-m-d H:i:s'), $originalValues));
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
}
if ($asset->assigned_to == '') {
@@ -430,7 +418,7 @@ class AssetsController extends Controller
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->save()) {
return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets'))
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success', trans('admin/hardware/message.update.success'));
}
@@ -531,12 +519,12 @@ class AssetsController extends Controller
* @param int $assetId
* @since [v1.0]
*/
public function getQrCode($assetId = null) : Response | BinaryFileResponse | string | bool
public function getQrCode(Asset $asset) : Response | BinaryFileResponse | string | bool
{
$settings = Setting::getSettings();
if (($settings->qr_code == '1') && ($settings->label2_2d_type !== 'none')) {
$asset = Asset::withTrashed()->find($assetId);
if ($asset) {
$size = Helper::barcodeDimensions($settings->label2_2d_type);
$qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png';
@@ -590,7 +578,7 @@ class AssetsController extends Controller
file_put_contents($barcode_file, $barcode_obj->getPngData());
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
} catch (\Exception|TypeError $e) {
Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
@@ -877,12 +865,11 @@ class AssetsController extends Controller
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
public function audit($id)
public function audit(Asset $asset)
{
$settings = Setting::getSettings();
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
$asset = Asset::findOrFail($id);
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
@@ -901,7 +888,7 @@ class AssetsController extends Controller
}
public function auditStore(UploadFileRequest $request, $id)
public function auditStore(UploadFileRequest $request, Asset $asset)
{
$this->authorize('audit', Asset::class);
@@ -916,8 +903,6 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::findOrFail($id);
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the

View File

@@ -358,7 +358,11 @@ class BulkAssetsController extends Controller
* to someone/something.
*/
if ($request->filled('status_id')) {
$updated_status = Statuslabel::find($request->input('status_id'));
try {
$updated_status = Statuslabel::findOrFail($request->input('status_id'));
} catch (ModelNotFoundException $e) {
return redirect($bulk_back_url)->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
// We cannot assign a non-deployable status type if the asset is already assigned.
// This could probably be added to a form request.
@@ -366,7 +370,7 @@ class BulkAssetsController extends Controller
// 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')
|| ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1')
) {
$this->update_array['status_id'] = $updated_status->id;
}
@@ -525,21 +529,31 @@ class BulkAssetsController extends Controller
$this->authorize('delete', Asset::class);
$bulk_back_url = route('hardware.index');
if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
$assetIds = $request->get('ids');
if ($request->filled('ids')) {
$assets = Asset::find($request->get('ids'));
foreach ($assets as $asset) {
$asset->delete();
} // endforeach
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
// no values given, nothing to update
if(empty($assetIds)) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
}
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
$assignedAssets = Asset::whereIn('id', $assetIds)->whereNotNull('assigned_to')->get();
if($assignedAssets->isNotEmpty()) {
//if assets are checked out, return a list of asset tags that would need to be checked in first.
$assetTags = $assignedAssets->pluck('asset_tag')->implode(', ');
return redirect($bulk_back_url)->with('error', trans_choice('admin/hardware/message.delete.assigned_to_error', $assignedAssets->count(), ['asset_tag' => $assetTags] ));
}
foreach (Asset::wherein('id', $assetIds)->get() as $asset) {
$asset->delete();
}
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
// no values given, nothing to update
}
/**

View File

@@ -206,6 +206,7 @@ class LoginController extends Controller
$user->password = bcrypt($request->input('password'));
}
$user->last_login = \Carbon::now();
$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.
@@ -432,6 +433,7 @@ class LoginController extends Controller
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->last_login = \Carbon::now();
$user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);

View File

@@ -88,14 +88,10 @@ class CategoriesController extends Controller
* @param int $categoryId
* @since [v1.0]
*/
public function edit($categoryId = null) : RedirectResponse | View
public function edit(Category $category) : RedirectResponse | View
{
$this->authorize('update', Category::class);
if (is_null($item = Category::find($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
return view('categories/edit', compact('item'))
return view('categories/edit')->with('item', $category)
->with('category_types', Helper::categoryTypeList());
}
@@ -108,19 +104,10 @@ class CategoriesController extends Controller
* @param int $categoryId
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $categoryId = null) : RedirectResponse
public function update(ImageUploadRequest $request, Category $category) : RedirectResponse
{
$this->authorize('update', Category::class);
if (is_null($category = Category::find($categoryId))) {
// Redirect to the categories management page
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
// Update the category data
$category->name = $request->input('name');
// If the item count is > 0, we disable the category type in the edit. Disabled items
// don't POST, so if the category_type is blank we just set it to the default.
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type') && ($category->itemCount() > 0))) {
@@ -181,10 +168,10 @@ class CategoriesController extends Controller
* @param $id
* @since [v1.8]
*/
public function show($id) : View | RedirectResponse
public function show(Category $category) : View | RedirectResponse
{
$this->authorize('view', Category::class);
if ($category = Category::find($id)) {
if ($category->category_type == 'asset') {
$category_type = 'hardware';
$category_type_route = 'assets';
@@ -199,8 +186,5 @@ class CategoriesController extends Controller
return view('categories/view', compact('category'))
->with('category_type', $category_type)
->with('category_type_route', $category_type_route);
}
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist'));
}
}

View File

@@ -80,16 +80,10 @@ final class CompaniesController extends Controller
* @since [v1.8]
* @param int $companyId
*/
public function edit($companyId) : View | RedirectResponse
public function edit(Company $company) : View | RedirectResponse
{
if (is_null($item = Company::find($companyId))) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.does_not_exist'));
}
$this->authorize('update', $item);
return view('companies/edit')->with('item', $item);
$this->authorize('update', $company);
return view('companies/edit')->with('item', $company);
}
/**
@@ -100,14 +94,10 @@ final class CompaniesController extends Controller
* @param ImageUploadRequest $request
* @param int $companyId
*/
public function update(ImageUploadRequest $request, $companyId) : RedirectResponse
public function update(ImageUploadRequest $request, Company $company) : RedirectResponse
{
if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist'));
}
$this->authorize('update', $company);
$company->name = $request->input('name');
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
@@ -158,15 +148,9 @@ final class CompaniesController extends Controller
->with('success', trans('admin/companies/message.delete.success'));
}
public function show($id) : View | RedirectResponse
public function show(Company $company) : View | RedirectResponse
{
$this->authorize('view', Company::class);
if (is_null($company = Company::find($id))) {
return redirect()->route('companies.index')
->with('error', trans('admin/companies/message.not_found'));
}
return view('companies/view')->with('company', $company);
}
}

View File

@@ -107,15 +107,13 @@ class ComponentsController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($componentId = null)
public function edit(Component $component)
{
if ($item = Component::find($componentId)) {
$this->authorize('update', $item);
return view('components/edit', compact('item'))->with('category_type', 'component');
}
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
$this->authorize('update', $component);
return view('components/edit')
->with('item', $component)
->with('category_type', 'component');
}
@@ -130,11 +128,8 @@ class ComponentsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v3.0]
*/
public function update(ImageUploadRequest $request, $componentId = null)
public function update(ImageUploadRequest $request, Component $component)
{
if (is_null($component = Component::find($componentId))) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
}
$min = $component->numCheckedOut();
$validator = Validator::make($request->all(), [
'qty' => "required|numeric|min:$min",
@@ -201,6 +196,10 @@ class ComponentsController extends Controller
}
}
if ($component->numCheckedOut() > 0) {
return redirect()->route('components.index')->with('error', trans('admin/components/message.delete.error_qty'));
}
$component->delete();
return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success'));
@@ -216,17 +215,9 @@ class ComponentsController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null)
public function show(Component $component)
{
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('view', $component);
return view('components/view', compact('component'));
}
// Redirect to the user management page
return redirect()->route('components.index')
->with('error', trans('admin/components/message.does_not_exist'));
}
}

View File

@@ -104,15 +104,13 @@ class ConsumablesController extends Controller
* @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0]
*/
public function edit($consumableId = null) : View | RedirectResponse
public function edit(Consumable $consumable) : View | RedirectResponse
{
if ($item = Consumable::find($consumableId)) {
$this->authorize($item);
$this->authorize($consumable);
return view('consumables/edit')
->with('item', $consumable)
->with('category_type', 'consumable');
return view('consumables/edit', compact('item'))->with('category_type', 'consumable');
}
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
/**
@@ -126,11 +124,8 @@ class ConsumablesController extends Controller
* @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0]
*/
public function update(StoreConsumableRequest $request, $consumableId = null)
public function update(StoreConsumableRequest $request, Consumable $consumable)
{
if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
}
$min = $consumable->numCheckedOut();
$validator = Validator::make($request->all(), [
@@ -202,16 +197,11 @@ class ConsumablesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null)
public function show(Consumable $consumable)
{
$consumable = Consumable::withCount('users as users_consumables')->find($consumableId);
$consumable = Consumable::withCount('users as users_consumables')->find($consumable->id);
$this->authorize($consumable);
if (isset($consumable->id)) {
return view('consumables/view', compact('consumable'));
}
return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.does_not_exist'));
return view('consumables/view', compact('consumable'));
}
public function clone(Consumable $consumable) : View

View File

@@ -104,6 +104,8 @@ class CustomFieldsController extends Controller
"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),
"display_checkin" => $request->get("display_checkin", 0),
"display_checkout" => $request->get("display_checkout", 0),
"created_by" => auth()->id()
]);
@@ -193,10 +195,8 @@ class CustomFieldsController extends Controller
* @param int $id
* @since [v4.0]
*/
public function edit(Request $request, $id) : View | RedirectResponse
public function edit(Request $request, CustomField $field) : View | RedirectResponse
{
if ($field = CustomField::find($id)) {
$this->authorize('update', $field);
$fieldsets = CustomFieldset::get();
$customFormat = '';
@@ -210,11 +210,7 @@ class CustomFieldsController extends Controller
'fieldsets' => $fieldsets,
'predefinedFormats' => Helper::predefined_formats(),
]);
}
return redirect()->route("fields.index")
->with("error", trans('admin/custom_fields/message.field.invalid'));
}
@@ -229,13 +225,9 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(CustomFieldRequest $request, $id) : RedirectResponse
public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
{
$field = CustomField::find($id);
$this->authorize('update', $field);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
@@ -256,6 +248,8 @@ class CustomFieldsController extends Controller
$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);
$field->display_checkin = $request->get("display_checkin", 0);
$field->display_checkout = $request->get("display_checkout", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));

View File

@@ -35,10 +35,12 @@ class CustomFieldsetsController extends Controller
* @param int $id
* @since [v1.8]
*/
public function show($id) : View | RedirectResponse
public function show(CustomFieldset $fieldset) : View | RedirectResponse
{
$cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
->where('id', '=', $fieldset->id)
->orderBy('id', 'ASC')
->first();
$this->authorize('view', $cfset);
@@ -122,16 +124,10 @@ class CustomFieldsetsController extends Controller
* @param int $id
* @since [v6.0.14]
*/
public function edit($id) : View | RedirectResponse
public function edit(CustomFieldset $fieldset) : View | RedirectResponse
{
$this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
}
/**
@@ -141,23 +137,18 @@ class CustomFieldsetsController extends Controller
* @param int $id
* @since [v6.0.14]
*/
public function update(Request $request, $id) : RedirectResponse
public function update(Request $request, CustomFieldset $fieldset) : RedirectResponse
{
$this->authorize('create', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
$fieldset->name = $request->input('name');
if ($fieldset->save()) {
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
$fieldset->name = $request->input('name');
if ($fieldset->save()) {
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated'));
}
return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id]));
return redirect()->back()->withInput()->withErrors($fieldset->getErrors());
}
/**

View File

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

View File

@@ -73,17 +73,10 @@ class DepartmentsController extends Controller
* @param int $id
* @since [v4.0]
*/
public function show($id) : View | RedirectResponse
public function show(Department $department) : View | RedirectResponse
{
$department = Department::find($id);
$this->authorize('view', $department);
if (isset($department->id)) {
return view('departments/view', compact('department'));
}
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
return view('departments/view', compact('department'));
}
/**
@@ -139,15 +132,10 @@ class DepartmentsController extends Controller
* @param int $departmentId
* @since [v1.0]
*/
public function edit($departmentId = null) : View | RedirectResponse
public function edit(Department $department) : View | RedirectResponse
{
if (is_null($item = Department::find($departmentId))) {
return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
}
$this->authorize('update', $item);
return view('departments/edit', compact('item'));
$this->authorize('update', $department);
return view('departments/edit')->with('item', $department);
}
/**
@@ -158,11 +146,8 @@ class DepartmentsController extends Controller
* @param int $departmentId
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $id) : RedirectResponse
public function update(ImageUploadRequest $request, Department $department) : RedirectResponse
{
if (is_null($department = Department::find($id))) {
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
}
$this->authorize('update', $department);

View File

@@ -95,17 +95,11 @@ class DepreciationsController extends Controller
* @param int $depreciationId
* @since [v1.0]
*/
public function edit($depreciationId = null) : RedirectResponse | View
public function edit(Depreciation $depreciation) : RedirectResponse | View
{
// Check if the depreciation exists
if (is_null($item = Depreciation::find($depreciationId))) {
// Redirect to the blogs management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$this->authorize('update', $item);
return view('depreciations/edit', compact('item'));
$this->authorize('update', $depreciation);
return view('depreciations/edit')->with('item', $depreciation);
}
/**
@@ -117,17 +111,10 @@ class DepreciationsController extends Controller
* @param int $depreciationId
* @since [v1.0]
*/
public function update(Request $request, $depreciationId = null) : RedirectResponse
public function update(Request $request, Depreciation $depreciation) : RedirectResponse
{
// Check if the depreciation exists
if (is_null($depreciation = Depreciation::find($depreciationId))) {
// Redirect to the blogs management page
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
}
$this->authorize('update', $depreciation);
// Depreciation data
$depreciation->name = $request->input('name');
$depreciation->months = $request->input('months');
@@ -191,12 +178,12 @@ class DepreciationsController extends Controller
* @param int $depreciationId
* @since [v1.0]
*/
public function show($id) : View | RedirectResponse
public function show(Depreciation $depreciation) : View | RedirectResponse
{
$depreciation = Depreciation::withCount('assets as assets_count')
->withCount('models as models_count')
->withCount('licenses as licenses_count')
->find($id);
->find($depreciation->id);
$this->authorize('view', $depreciation);

View File

@@ -79,19 +79,12 @@ class GroupsController extends Controller
* @param int $id
* @since [v1.0]
*/
public function edit($id) : View | RedirectResponse
public function edit(Group $group) : View | RedirectResponse
{
$group = Group::find($id);
if ($group) {
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}
/**
@@ -102,11 +95,8 @@ class GroupsController extends Controller
* @param int $id
* @since [v1.0]
*/
public function update(Request $request, $id = null) : RedirectResponse
public function update(Request $request, Group $group) : RedirectResponse
{
if (! $group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
}
$group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
$group->notes = $request->input('notes');
@@ -151,14 +141,8 @@ class GroupsController extends Controller
* @param $id
* @since [v4.0.11]
*/
public function show($id) : View | RedirectResponse
public function show(Group $group) : View | RedirectResponse
{
$group = Group::find($id);
if ($group) {
return view('groups/view', compact('group'));
}
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
return view('groups/view', compact('group'));
}
}

View File

@@ -4,10 +4,8 @@ namespace App\Http\Controllers\Kits;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\PredefinedKit;
use App\Models\Asset;
use App\Models\PredefinedLicence;
use App\Models\PredefinedModel;
use App\Models\PredefinedKit;
use App\Models\User;
use App\Services\PredefinedKitCheckoutService;
use Illuminate\Http\Request;
@@ -35,12 +33,9 @@ class CheckoutKitController extends Controller
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
* @return \Illuminate\Contracts\View\View View to checkout
*/
public function showCheckout($kit_id)
public function showCheckout(PredefinedKit $kit)
{
$this->authorize('checkout', Asset::class);
$kit = PredefinedKit::findOrFail($kit_id);
return view('kits/checkout')->with('kit', $kit);
}

View File

@@ -76,17 +76,15 @@ class PredefinedKitsController extends Controller
* @param int $kit_id
* @return \Illuminate\Contracts\View\View
*/
public function edit($kit_id = null)
public function edit(PredefinedKit $kit)
{
$this->authorize('update', PredefinedKit::class);
if ($kit = PredefinedKit::find($kit_id)) {
return view('kits/edit')
->with('item', $kit)
->with('models', $kit->models)
->with('licenses', $kit->licenses);
}
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
}
/**
@@ -98,15 +96,9 @@ class PredefinedKitsController extends Controller
* @param int $kit_id
* @return \Illuminate\Http\RedirectResponse
*/
public function update(ImageUploadRequest $request, $kit_id = null)
public function update(ImageUploadRequest $request, PredefinedKit $kit)
{
$this->authorize('update', PredefinedKit::class);
// Check if the kit exists
if (is_null($kit = PredefinedKit::find($kit_id))) {
// Redirect to the kits management page
return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none'));
}
$kit->name = $request->input('name');
if ($kit->save()) {
@@ -153,9 +145,9 @@ class PredefinedKitsController extends Controller
* @param int $modelId
* @return \Illuminate\Contracts\View\View
*/
public function show($kit_id = null)
public function show(PredefinedKit $kit)
{
return $this->edit($kit_id);
return $this->edit($kit);
}
/**

View File

@@ -28,16 +28,11 @@ class LicenseCheckinController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($seatId = null, $backTo = null)
public function create(LicenseSeat $licenseSeat, $backTo = null)
{
// Check if the asset exists
if (is_null($licenseSeat = LicenseSeat::find($seatId)) || is_null($license = License::find($licenseSeat->license_id))) {
// Redirect to the asset management page with error
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
$license = License::find($licenseSeat->license_id);
$this->authorize('checkout', $license);
return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo);
}

View File

@@ -28,33 +28,24 @@ class LicenseCheckoutController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create($id)
public function create(License $license)
{
$this->authorize('checkout', $license);
if ($license = License::find($id)) {
if ($license->category) {
$this->authorize('checkout', $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'));
// 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'));
}
// Invalid category
return redirect()->route('licenses.edit', ['license' => $license->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
// Return the checkout view
return view('licenses/checkout', compact('license'));
}
// Not found
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
// Invalid category
return redirect()->route('licenses.edit', ['license' => $license->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')]));
}

View File

@@ -121,13 +121,10 @@ class LicensesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($licenseId = null)
public function edit(License $license)
{
if (is_null($item = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $item);
$this->authorize('update', $license);
$maintained_list = [
'' => 'Maintained',
@@ -135,7 +132,8 @@ class LicensesController extends Controller
'0' => 'No',
];
return view('licenses/edit', compact('item'))
return view('licenses/edit')
->with('item', $license)
->with('depreciation_list', Helper::depreciationList())
->with('maintained_list', $maintained_list);
}
@@ -153,11 +151,9 @@ class LicensesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, $licenseId = null)
public function update(Request $request, License $license)
{
if (is_null($license = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
$this->authorize('update', $license);
@@ -201,10 +197,10 @@ class LicensesController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($licenseId)
public function destroy(License $license)
{
// Check if the license exists
if (is_null($license = License::find($licenseId))) {
if (is_null($license = License::find($license->id))) {
// Redirect to the license management page
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found'));
}
@@ -238,14 +234,9 @@ class LicensesController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($licenseId = null)
public function show(License $license)
{
$license = License::with('assignedusers')->find($licenseId);
if (!$license) {
return redirect()->route('licenses.index')
->with('error', trans('admin/licenses/message.does_not_exist'));
}
$license = License::with('assignedusers')->find($license->id);
$users_count = User::where('autoassign_licenses', '1')->count();
$total_seats_count = $license->totalSeatsByLicenseID();
@@ -267,10 +258,10 @@ class LicensesController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\RedirectResponse | \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getClone($licenseId = null)
public function getClone($licenseId = null) : \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
{
if (is_null($license_to_clone = License::find($licenseId))) {
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));

View File

@@ -97,15 +97,10 @@ class LocationsController extends Controller
* @param int $locationId
* @since [v1.0]
*/
public function edit($locationId = null) : View | RedirectResponse
public function edit(Location $location) : View | RedirectResponse
{
$this->authorize('update', Location::class);
// Check if the location exists
if (is_null($item = Location::find($locationId))) {
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
return view('locations/edit', compact('item'));
return view('locations/edit')->with('item', $location);
}
/**
@@ -117,15 +112,10 @@ class LocationsController extends Controller
* @param int $locationId
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $locationId = null) : RedirectResponse
public function update(ImageUploadRequest $request, Location $location) : RedirectResponse
{
$this->authorize('update', Location::class);
// Check if the location exists
if (is_null($location = Location::find($locationId))) {
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
// Update the location data
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
$location->currency = $request->input('currency', '$');
@@ -194,7 +184,7 @@ class LocationsController extends Controller
* @param int $id
* @since [v1.0]
*/
public function show($id = null) : View | RedirectResponse
public function show(Location $location) : View | RedirectResponse
{
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
@@ -202,7 +192,7 @@ class LocationsController extends Controller
->withCount('children as children_count')
->withCount('users as users_count')
->withTrashed()
->find($id);
->find($location->id);
if (isset($location->id)) {
return view('locations/view', compact('location'));

View File

@@ -85,18 +85,10 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @since [v1.0]
*/
public function edit($manufacturerId = null) : View | RedirectResponse
public function edit(Manufacturer $manufacturer) : View | RedirectResponse
{
// Handles manufacturer checks and permissions.
$this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists
if (! $item = Manufacturer::find($manufacturerId)) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
// Show the page
return view('manufacturers/edit', compact('item'));
return view('manufacturers/edit')->with('item', $manufacturer);
}
/**
@@ -108,16 +100,10 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $manufacturerId = null) : RedirectResponse
public function update(ImageUploadRequest $request, Manufacturer $manufacturer) : RedirectResponse
{
$this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists
if (is_null($manufacturer = Manufacturer::find($manufacturerId))) {
// Redirect to the manufacturer page
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist'));
}
// Save the data
$manufacturer->name = $request->input('name');
$manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url');
@@ -185,18 +171,10 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @since [v1.0]
*/
public function show($manufacturerId = null) : View | RedirectResponse
public function show(Manufacturer $manufacturer) : View | RedirectResponse
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::find($manufacturerId);
if (isset($manufacturer->id)) {
return view('manufacturers/view', compact('manufacturer'));
}
$error = trans('admin/manufacturers/message.does_not_exist');
// Redirect to the user management page
return redirect()->route('manufacturers.index')->with('error', $error);
return view('manufacturers/view', compact('manufacturer'));
}
/**

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class NotesController extends Controller
{
public function store(Request $request)
{
$this->authorize('update', Asset::class);
$validated = $request->validate([
'id' => 'required',
'note' => 'required|string|max:50000',
'type' => [
'required',
Rule::in(['asset']),
],
]);
$item = Asset::findOrFail($validated['id']);
$this->authorize('update', $item);
$logaction = new Actionlog;
$logaction->item_id = $item->id;
$logaction->item_type = get_class($item);
$logaction->note = $validated['note'];
$logaction->created_by = Auth::id();
$logaction->logaction('note added');
return redirect()
->route('hardware.show', $validated['id'])
->withFragment('history')
->with('success', trans('general.note_added'));
}
}

View File

@@ -266,7 +266,7 @@ class ReportsController extends Controller
$actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser')
->orderBy('created_at', 'DESC')
->chunk(20, function ($actionlogs) use ($handle) {
->chunk(500, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
$count = 0;
@@ -748,7 +748,7 @@ class ReportsController extends Controller
}
Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
@@ -1175,18 +1175,13 @@ class ReportsController extends Controller
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
// Only send notification if assigned
if ($locale && $email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)));
}
if ($email == ''){
if (is_null($email) || $email === '') {
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}

3
app/Http/Controllers/SettingsController.php Executable file → Normal file
View File

@@ -256,7 +256,7 @@ class SettingsController extends Controller
Artisan::call('migrate', ['--force' => true]);
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('migrate', ['--path' => 'vendor/laravel/passport/database/migrations', '--force' => true]);
Artisan::call('passport:install');
Artisan::call('passport:install', ['--no-interaction' => true]);
}
return view('setup/migrate')
@@ -851,6 +851,7 @@ class SettingsController extends Controller
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
$setting->ldap_version = $request->input('ldap_version', 3);
$setting->ldap_active_flag = $request->input('ldap_active_flag');
$setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag');
$setting->ldap_emp_num = $request->input('ldap_emp_num');
$setting->ldap_email = $request->input('ldap_email');
$setting->ldap_manager = $request->input('ldap_manager');

View File

@@ -26,14 +26,10 @@ class StatuslabelsController extends Controller
return view('statuslabels.index');
}
public function show($id) : View | RedirectResponse
public function show(Statuslabel $statuslabel) : View | RedirectResponse
{
$this->authorize('view', Statuslabel::class);
if ($statuslabel = Statuslabel::find($id)) {
return view('statuslabels.view')->with('statuslabel', $statuslabel);
}
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
return view('statuslabels.view')->with('statuslabel', $statuslabel);
}
/**
@@ -91,20 +87,15 @@ class StatuslabelsController extends Controller
*
* @param int $statuslabelId
*/
public function edit($statuslabelId = null) : View | RedirectResponse
public function edit(Statuslabel $statuslabel) : View | RedirectResponse
{
$this->authorize('update', Statuslabel::class);
// Check if the Statuslabel exists
if (is_null($item = Statuslabel::find($statuslabelId))) {
// Redirect to the blogs management page
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
$use_statuslabel_type = $item->getStatuslabelType();
$statuslabel_types = ['' => trans('admin/hardware/form.select_statustype')] + ['undeployable' => trans('admin/hardware/general.undeployable')] + ['pending' => trans('admin/hardware/general.pending')] + ['archived' => trans('admin/hardware/general.archived')] + ['deployable' => trans('admin/hardware/general.deployable')];
return view('statuslabels/edit', compact('item', 'statuslabel_types'))->with('use_statuslabel_type', $use_statuslabel_type);
return view('statuslabels/edit', compact('statuslabel_types'))
->with('item', $statuslabel)
->with('use_statuslabel_type', $statuslabel);
}
/**
@@ -112,14 +103,9 @@ class StatuslabelsController extends Controller
*
* @param int $statuslabelId
*/
public function update(Request $request, $statuslabelId = null) : RedirectResponse
public function update(Request $request, Statuslabel $statuslabel) : RedirectResponse
{
$this->authorize('update', Statuslabel::class);
// Check if the Statuslabel exists
if (is_null($statuslabel = Statuslabel::find($statuslabelId))) {
// Redirect to the blogs management page
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
if (! $request->filled('statuslabel_types')) {
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);

View File

@@ -77,17 +77,10 @@ class SuppliersController extends Controller
*
* @param int $supplierId
*/
public function edit($supplierId = null) : View | RedirectResponse
public function edit(Supplier $supplier) : View | RedirectResponse
{
$this->authorize('update', Supplier::class);
// Check if the supplier exists
if (is_null($item = Supplier::find($supplierId))) {
// Redirect to the supplier page
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
}
// Show the page
return view('suppliers/edit', compact('item'));
return view('suppliers/edit')->with('item', $supplier);
}
/**
@@ -95,14 +88,9 @@ class SuppliersController extends Controller
*
* @param int $supplierId
*/
public function update($supplierId, ImageUploadRequest $request) : RedirectResponse
public function update(ImageUploadRequest $request, Supplier $supplier) : RedirectResponse
{
$this->authorize('update', Supplier::class);
if (is_null($supplier = Supplier::find($supplierId))) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
}
// Save the data
$supplier->name = request('name');
$supplier->address = request('address');
@@ -163,15 +151,10 @@ class SuppliersController extends Controller
* @param null $supplierId
* @internal param int $assetId
*/
public function show($supplierId = null) : View | RedirectResponse
public function show(Supplier $supplier) : View | RedirectResponse
{
$this->authorize('view', Supplier::class);
$supplier = Supplier::find($supplierId);
return view('suppliers/view', compact('supplier'));
if (isset($supplier->id)) {
return view('suppliers/view', compact('supplier'));
}
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist'));
}
}

View File

@@ -15,6 +15,7 @@ use App\Models\ConsumableAssignment;
use App\Models\Consumable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -52,6 +53,28 @@ class BulkUsersController extends Controller
return view('users/bulk-edit', compact('users'))
->with('groups', Group::pluck('name', 'id'));
// bulk send assigned inventory
} elseif ($request->input('bulk_actions') == 'send_assigned') {
$this->authorize('update', User::class);
$users_without_email = 0;
foreach ($users as $user) {
if (empty($user->email)) {
$users_without_email++;
} else {
$user->notify((new CurrentInventory($user)));
}
}
if ($users_without_email == 0) {
return redirect()->back()->with('success', trans_choice('admin/users/general.users_notified', $users->count()));
} else {
return redirect()->back()->with('warning', trans_choice('admin/users/general.users_notified_warning', $users->count(), ['no_email' => $users_without_email]));
}
// bulk delete, display the bulk delete confirmation form
} elseif ($request->input('bulk_actions') == 'delete') {
$this->authorize('delete', User::class);

View File

@@ -22,43 +22,34 @@ class UserFilesController extends Controller
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
*/
public function store(UploadFileRequest $request, $userId = null)
public function store(UploadFileRequest $request, User $user)
{
$user = User::find($userId);
$destinationPath = config('app.private_uploads').'/users';
$this->authorize('update', $user);
$files = $request->file('file');
if (isset($user->id)) {
$this->authorize('update', $user);
$logActions = [];
$files = $request->file('file');
if (is_null($files)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
$logAction->item_id = $user->id;
$logAction->item_type = User::class;
$logAction->created_by = auth()->id();
$logAction->note = $request->input('notes');
$logAction->target_id = null;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->filename = $file_name;
$logAction->action_type = 'uploaded';
if (! $logAction->save()) {
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
}
$logActions[] = $logAction;
}
// dd($logActions);
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
if (is_null($files)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
$logAction->item_id = $user->id;
$logAction->item_type = User::class;
$logAction->created_by = auth()->id();
$logAction->note = $request->input('notes');
$logAction->target_id = null;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->filename = $file_name;
$logAction->action_type = 'uploaded';
if (! $logAction->save()) {
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
@@ -110,7 +101,7 @@ class UserFilesController extends Controller
* @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($userId = null, $fileId = null)
public function show(User $user, $fileId = null)
{
@@ -118,29 +109,21 @@ class UserFilesController extends Controller
return redirect()->route('users.show')->with('error', 'Invalid file request');
}
if ($user = User::find($userId)) {
$this->authorize('view', $user);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
}
// The log record doesn't exist somehow
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
return redirect()->back()->with('error', trans('general.file_not_found'));
}
// Redirect to the user management page if the user doesn't exist
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
// The log record doesn't exist somehow
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
}
}

View File

@@ -182,11 +182,11 @@ class UsersController extends Controller
* @internal param int $id
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit($id)
public function edit(User $user)
{
$this->authorize('update', User::class);
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($id);
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id);
if ($user) {
@@ -198,7 +198,7 @@ class UsersController extends Controller
$userPermissions = Helper::selectedPermissionsArray($permissions, $user->permissions);
$permissions = $this->filterDisplayable($permissions);
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'))->with('item', $user);
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'));
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
@@ -324,7 +324,7 @@ class UsersController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy(DeleteUserRequest $request, $id = null)
public function destroy(DeleteUserRequest $request, $id)
{
$this->authorize('delete', User::class);
@@ -333,13 +333,6 @@ class UsersController extends Controller
$this->authorize('delete', $user);
if ($user->delete()) {
if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
try {
Storage::disk('public')->delete('avatars/' . $user->avatar);
} catch (\Exception $e) {
Log::debug($e);
}
}
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
}
}
@@ -398,23 +391,18 @@ class UsersController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($userId = null)
public function show(User $user)
{
// Make sure the user can view users at all
$this->authorize('view', User::class);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($userId);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user);
if ($user) {
$userlog = $user->userlog->load('item');
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
}
@@ -428,7 +416,7 @@ class UsersController extends Controller
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getClone(Request $request, $id = null)
public function getClone(Request $request, User $user)
{
$this->authorize('create', User::class);
@@ -438,7 +426,7 @@ class UsersController extends Controller
app('request')->request->set('permissions', $permissions);
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id);
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user_to_clone);

View File

@@ -24,7 +24,7 @@ class DeleteUserRequest extends FormRequest
public function prepareForValidation(): void
{
$user_to_delete = User::withTrashed()->find(request()->route('user'));
$user_to_delete = User::withTrashed()->with('managesUsers')->find(request()->route('user'));
if ($user_to_delete) {
$this->merge([
@@ -61,7 +61,8 @@ class DeleteUserRequest extends FormRequest
public function messages(): array
{
$user_to_delete = User::withTrashed()->find(request()->route('user'));
$user_to_delete = User::withTrashed()->with('managesUsers')->find(request()->route('user'));
$messages = [];
if ($user_to_delete) {

View File

@@ -26,9 +26,9 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
'alert_threshold' => 'numeric|nullable|gt:0',
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
'due_checkin_days' => 'numeric|nullable|gt:0',
'audit_interval' => 'numeric|nullable|gt:0',
];

View File

@@ -140,13 +140,13 @@ class ActionlogsTransformer
} else {
if ($actionlog->item) {
if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
$file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'accessory') {
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') {
$file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
$file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]);
}
}
}

View File

@@ -309,6 +309,7 @@ class AssetsTransformer
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,
],
'assigned_to' => $accessory_checkout->assigned_to,
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [

View File

@@ -62,7 +62,7 @@ class ComponentsTransformer
'checkout' => Gate::allows('checkout', Component::class),
'checkin' => Gate::allows('checkin', Component::class),
'update' => Gate::allows('update', Component::class),
'delete' => Gate::allows('delete', Component::class),
'delete' => $component->isDeletable(),
];
$array += $permissions_array;

View File

@@ -100,6 +100,7 @@ class LocationsTransformer
$array = [
'id' => $accessory_checkout->id,
'assigned_to' => $accessory_checkout->assigned_to,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,

View File

@@ -1,81 +0,0 @@
<?php
namespace App\LegacyEncrypter;
use Illuminate\Contracts\Encryption\DecryptException;
abstract class BaseEncrypter
{
/**
* The encryption key.
*
* @var string
*/
protected $key;
/**
* Create a MAC for the given value.
*
* @param string $iv
* @param string $value
* @return string
*/
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
/**
* Get the JSON array from the given payload.
*
* @param string $payload
* @return array
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function getJsonPayload($payload)
{
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if (! $payload || $this->invalidPayload($payload)) {
throw new DecryptException('The payload is invalid.');
}
if (! $this->validMac($payload)) {
throw new DecryptException('The MAC is invalid.');
}
return $payload;
}
/**
* Verify that the encryption payload is valid.
*
* @param array|mixed $data
* @return bool
*/
protected function invalidPayload($data)
{
return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']);
}
/**
* Determine if the MAC for the given payload is valid.
*
* @param array $payload
* @return bool
*
* @throws \RuntimeException
*/
protected function validMac(array $payload)
{
$bytes = random_bytes(16);
$calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true);
return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac);
}
}

View File

@@ -1,214 +0,0 @@
<?php
namespace App\LegacyEncrypter;
use Exception;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
use Illuminate\Contracts\Encryption\EncryptException;
use RuntimeException;
/**
* @deprecated since version 5.1. Use Illuminate\Encryption\Encrypter.
*/
class McryptEncrypter extends BaseEncrypter implements EncrypterContract
{
/**
* The algorithm used for encryption.
*
* @var string
*/
protected $cipher;
/**
* The block size of the cipher.
*
* @var int
*/
protected $block;
/**
* Create a new encrypter instance.
*
* @param string $key
* @param string $cipher
* @return void
*
* @throws \RuntimeException
*/
public function __construct($key, $cipher = MCRYPT_RIJNDAEL_128)
{
$key = (string) $key;
if (static::supported($key, $cipher)) {
$this->key = $key;
$this->cipher = $cipher;
$this->block = mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC);
} else {
throw new RuntimeException('The only supported ciphers are MCRYPT_RIJNDAEL_128 and MCRYPT_RIJNDAEL_256.');
}
}
/**
* Determine if the given key and cipher combination is valid.
*
* @param string $key
* @param string $cipher
* @return bool
*/
public static function supported($key, $cipher)
{
return defined('MCRYPT_RIJNDAEL_128') &&
($cipher === MCRYPT_RIJNDAEL_128 || $cipher === MCRYPT_RIJNDAEL_256);
}
/**
* Encrypt the given value.
*
* @param string $value
* @return string
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function encrypt($value, $serialize = true)
{
$iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer());
$value = base64_encode($this->padAndMcrypt($value, $iv));
// Once we have the encrypted value we will go ahead base64_encode the input
// vector and create the MAC for the encrypted value so we can verify its
// authenticity. Then, we'll JSON encode the data in a "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
/**
* Pad and use mcrypt on the given value and input vector.
*
* @param string $value
* @param string $iv
* @return string
*/
protected function padAndMcrypt($value, $iv)
{
$value = $this->addPadding(serialize($value));
return mcrypt_encrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv);
}
/**
* Decrypt the given value.
*
* @param string $payload
* @return string
*/
public function decrypt($payload, $unserialize = true)
{
$payload = $this->getJsonPayload($payload);
// We'll go ahead and remove the PKCS7 padding from the encrypted value before
// we decrypt it. Once we have the de-padded value, we will grab the vector
// and decrypt the data, passing back the unserialized from of the value.
$value = base64_decode($payload['value']);
$iv = base64_decode($payload['iv']);
return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv)));
}
/**
* Run the mcrypt decryption routine for the value.
*
* @param string $value
* @param string $iv
* @return string
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function mcryptDecrypt($value, $iv)
{
try {
return mcrypt_decrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv);
} catch (Exception $e) {
throw new DecryptException($e->getMessage());
}
}
/**
* Add PKCS7 padding to a given value.
*
* @param string $value
* @return string
*/
protected function addPadding($value)
{
$pad = $this->block - (strlen($value) % $this->block);
return $value.str_repeat(chr($pad), $pad);
}
/**
* Remove the padding from the given value.
*
* @param string $value
* @return string
*/
protected function stripPadding($value)
{
$pad = ord($value[($len = strlen($value)) - 1]);
return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value;
}
/**
* Determine if the given padding for a value is valid.
*
* @param string $pad
* @param string $value
* @return bool
*/
protected function paddingIsValid($pad, $value)
{
$beforePad = strlen($value) - $pad;
return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad);
}
/**
* Get the IV size for the cipher.
*
* @return int
*/
protected function getIvSize()
{
return mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC);
}
/**
* Get the random data source available for the OS.
*
* @return int
*/
protected function getRandomizer()
{
if (defined('MCRYPT_DEV_URANDOM')) {
return MCRYPT_DEV_URANDOM;
}
if (defined('MCRYPT_DEV_RANDOM')) {
return MCRYPT_DEV_RANDOM;
}
mt_srand();
return MCRYPT_RAND;
}
}

View File

@@ -115,7 +115,7 @@ class CheckoutableListener
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail') );
} catch (Exception $e) {
Log::error(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
@@ -211,7 +211,7 @@ class CheckoutableListener
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::error(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,

View File

@@ -17,7 +17,6 @@ use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Events\LicenseCheckedIn;
use App\Events\LicenseCheckedOut;
use App\Events\NoteAdded;
use App\Models\Actionlog;
use App\Models\User;
use App\Models\LicenseSeat;
@@ -129,23 +128,6 @@ class LogListener
}
/**
* Note is added to action log
*
*/
public function onNoteAdded(NoteAdded $event)
{
$logaction = new Actionlog();
$logaction->item_id = $event->itemNoteAddedOn->id;
$logaction->item_type = get_class($event->itemNoteAddedOn);
$logaction->note = $event->note; //this is the received alphanumeric text from the box
$logaction->created_by = $event->noteAddedBy->id;
$logaction->action_type = 'note_added';
$logaction->save();
}
/**
* Register the listeners for the subscriber.
*

View File

@@ -12,11 +12,11 @@ class CategoryEditForm extends Component
public $originalSendCheckInEmailValue;
public $requireAcceptance;
public bool $requireAcceptance;
public $sendCheckInEmail;
public bool $sendCheckInEmail;
public $useDefaultEula;
public bool $useDefaultEula;
public function mount()
{

View File

@@ -20,10 +20,12 @@ class CheckoutAssetMail extends Mailable
{
use Queueable, SerializesModels;
private bool $firstTimeSending;
/**
* Create a new message instance.
*/
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note, bool $firstTimeSending = true)
{
$this->item = $asset;
$this->admin = $checkedOutBy;
@@ -36,6 +38,8 @@ class CheckoutAssetMail extends Mailable
$this->last_checkout = '';
$this->expected_checkin = '';
$this->firstTimeSending = $firstTimeSending;
if ($this->item->last_checkout) {
$this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
@@ -56,7 +60,7 @@ class CheckoutAssetMail extends Mailable
return new Envelope(
from: $from,
subject: trans('mail.Asset_Checkout_Notification'),
subject: $this->getSubject(),
);
}
@@ -107,4 +111,13 @@ class CheckoutAssetMail extends Mailable
{
return [];
}
private function getSubject(): string
{
if ($this->firstTimeSending) {
return trans('mail.Asset_Checkout_Notification');
}
return trans('mail.unaccepted_asset_reminder');
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class SendUpcomingAuditMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct($params, $threshold)
{
$this->assets = $params;
$this->threshold = $threshold;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$from = new Address(config('mail.from.address'), config('mail.from.name'));
return new Envelope(
from: $from,
subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'notifications.markdown.upcoming-audits',
with: [
'assets' => $this->assets,
'threshold' => $this->threshold,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -19,9 +19,10 @@ class UnacceptedAssetReminderMail extends Mailable
*/
public function __construct($checkout_info, $count)
{
$this->count = $count;
$this->target = $checkout_info['acceptance']?->assignedTo;
$this->acceptance = $checkout_info['acceptance'];
$this->target = $checkout_info?->assignedTo;
$this->acceptance = $checkout_info;
}
/**

View File

@@ -57,7 +57,7 @@ class AccessoryCheckout extends Model
*/
public function adminuser()
{
return $this->hasOne(\App\Models\User::class, 'created_by');
return $this->hasOne(\App\Models\User::class, 'id', 'created_by');
}
/**
@@ -118,7 +118,7 @@ class AccessoryCheckout extends Model
$query->where('assigned_type', '=', Location::class);
}
public function scopeAssetAssigned(Builder $query): void
public function scopeAssetsAssigned(Builder $query): void
{
$query->where('assigned_type', '=', Asset::class);
}

View File

@@ -293,16 +293,22 @@ class Actionlog extends SnipeModel
public function daysUntilNextAudit($monthInterval = 12, $asset = null)
{
$now = Carbon::now();
$last_audit_date = $this->created_at;
$next_audit = $last_audit_date->addMonth($monthInterval);
$next_audit_days = $now->diffInDays($next_audit);
$last_audit_date = $this->created_at; // this is the action log's created at, not the asset itself
$next_audit = $last_audit_date->addMonth($monthInterval); // this actually *modifies* the $last_audit_date
$next_audit_days = round($now->diffInDays($next_audit, true));
$override_default_next = $next_audit;
// Override the default setting for interval if the asset has its own next audit date
if (($asset) && ($asset->next_audit_date)) {
$override_default_next = \Carbon::parse($asset->next_audit_date);
$next_audit_days = $override_default_next->diffInDays($now);
$override_default_next = Carbon::parse($asset->next_audit_date);
$next_audit_days = round($override_default_next->diffInDays($now, true));
}
// Show as negative number if the next audit date is before the audit date we're looking at
if ($this->created_at > $override_default_next) {
$next_audit_days = '-'.$next_audit_days;
}
return $next_audit_days;
}

View File

@@ -413,6 +413,17 @@ class Asset extends Depreciable
return $this->rules;
}
public function customFieldsForCheckinCheckout($checkin_checkout) {
// Check to see if any of the custom fields were included on the form and if they have any values
if (($this->model) && ($this->model->fieldset) && ($this->model->fieldset->fields)) {
foreach ($this->model->fieldset->fields as $field) {
if (($field->{$checkin_checkout} == 1) && (request()->has($field->db_column))){
$this->{$field->db_column} = request()->get($field->db_column);
}
}
}
}
/**
* Establishes the asset -> depreciation relationship
@@ -523,6 +534,18 @@ class Asset extends Depreciable
return $this->morphMany(self::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed();
}
/**
* Establishes the accessory -> asset assignment relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assignedAccessories()
{
return $this->morphMany(\App\Models\AccessoryCheckout::class, 'assigned', 'assigned_type', 'assigned_to');
}
/**
* Get the asset's location based on the assigned user

View File

@@ -234,7 +234,7 @@ class AssetModel extends SnipeModel
*/
public function adminuser()
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed();
}

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 Watson\Validating\ValidatingTrait;
/**
@@ -104,6 +105,13 @@ class Component extends SnipeModel
];
public function isDeletable()
{
return Gate::allows('delete', $this)
&& ($this->numCheckedOut() === 0)
&& ($this->deleted_at == '');
}
/**
* Establishes the components -> action logs -> uploads relationship
*
@@ -234,13 +242,24 @@ class Component extends SnipeModel
// In case there are elements checked out to assets that belong to a different company
// than this asset and full multiple company support is on we'll remove the global scope,
// so they are included in the count.
foreach ($this->assets()->withoutGlobalScope(new CompanyableScope)->get() as $checkout) {
$checkedout += $checkout->pivot->assigned_qty;
}
return $checkedout;
return $this->uncontrainedAssets->sum('pivot.assigned_qty');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*
* This allows us to get the assets with assigned components without the company restriction
*/
public function uncontrainedAssets() {
return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')
->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note')
->withoutGlobalScope(new CompanyableScope);
}
/**
* Check how many items within a component are remaining
*

View File

@@ -149,11 +149,6 @@ class CustomField extends Model
return true;
}
// This is just a dumb thing we have to include because Laraval/Doctrine doesn't
// play well with enums or a table that EVER had enums. :(
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
// Rename the field if the name has changed
Schema::table(self::$table_name, function ($table) use ($custom_field) {
$table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug());

View File

@@ -34,7 +34,11 @@ trait Loggable
*/
public function logCheckout($note, $target, $action_date = null, $originalValues = [])
{
$log = new Actionlog;
$fields_array = [];
$log = $this->determineLogItemType($log);
if (auth()->user()) {
$log->created_by = auth()->id();
@@ -55,6 +59,7 @@ trait Loggable
$log->target_type = get_class($target);
$log->target_id = $target->id;
// Figure out what the target is
if ($log->target_type == Location::class) {
$log->location_id = $target->id;
@@ -64,6 +69,21 @@ trait Loggable
$log->location_id = $target->location_id;
}
if (static::class == Asset::class) {
if ($asset = Asset::find($log->item_id)) {
// add the custom fields that were changed
if ($asset->model->fieldset) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_checkout == 1) {
$fields_array[$field->db_column] = $asset->{$field->db_column};
}
}
}
}
}
$log->note = $note;
$log->action_date = $action_date;
@@ -72,7 +92,10 @@ trait Loggable
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
@@ -119,6 +142,8 @@ trait Loggable
{
$log = new Actionlog;
$fields_array = [];
if($target != null){
$log->target_type = get_class($target);
$log->target_id = $target->id;
@@ -135,6 +160,16 @@ trait Loggable
if (static::class == Asset::class) {
if ($asset = Asset::find($log->item_id)) {
$asset->increment('checkin_counter', 1);
// add the custom fields that were changed
if ($asset->model->fieldset) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->display_checkin == 1) {
$fields_array[$field->db_column] = $asset->{$field->db_column};
}
}
}
}
}
}
@@ -152,9 +187,14 @@ trait Loggable
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
$array_to_flip = array_keys($fields_array);
$array_to_flip = array_merge($array_to_flip, ['action_date','name','status_id','location_id','expected_checkin']);
$originalValues = array_intersect_key($originalValues, array_flip($array_to_flip));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');

View File

@@ -20,6 +20,7 @@ class AssetAuditPresenter extends Presenter
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',

View File

@@ -22,6 +22,15 @@ class AssetMaintenancesPresenter extends Presenter
'title' => trans('general.id'),
'visible' => false,
], [
'field' => 'title',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.name'),
'visible' => true,
'formatter' => 'maintenancesLinkFormatter',
],
[
'field' => 'company',
'searchable' => true,
'sortable' => true,
@@ -81,12 +90,6 @@ class AssetMaintenancesPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.asset_maintenance_type'),
], [
'field' => 'title',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/asset_maintenances/form.title'),
], [
'field' => 'start_date',
'searchable' => true,

View File

@@ -15,6 +15,7 @@ class AssetModelPresenter extends Presenter
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',

View File

@@ -21,6 +21,7 @@ class AssetPresenter extends Presenter
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
], [
'field' => 'id',
'searchable' => false,
@@ -35,7 +36,7 @@ class AssetPresenter extends Presenter
'switchable' => true,
'title' => trans('general.company'),
'visible' => false,
'formatter' => 'assetCompanyObjFilterFormatter',
'formatter' => 'companiesLinkObjFormatter',
], [
'field' => 'name',
'searchable' => true,
@@ -352,6 +353,74 @@ class AssetPresenter extends Presenter
return json_encode($layout);
}
public static function assignedAccessoriesDataTableLayout()
{
$layout = [
[
'field' => 'id',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
],
[
'field' => 'accessory',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.accessory'),
'visible' => true,
'formatter' => 'accessoriesLinkObjFormatter',
],
[
'field' => 'image',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.image'),
'visible' => true,
'formatter' => 'imageFormatter',
],
[
'field' => 'note',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.notes'),
'visible' => true,
],
[
'field' => 'created_at',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/hardware/table.checkout_date'),
'visible' => true,
'formatter' => 'dateDisplayFormatter',
],
[
'field' => 'created_by',
'searchable' => false,
'sortable' => false,
'title' => trans('general.created_by'),
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
],
[
'field' => 'available_actions',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'accessoriesInOutFormatter',
],
];
return json_encode($layout);
}
/**
* Generate html link to this items name.
* @return string

View File

@@ -102,13 +102,13 @@ class LocationPresenter extends Presenter
'titleTooltip' => trans('general.people'),
'visible' => true,
'class' => 'css-house-user',
// 'data-tooltip' => true, - not working, but I want to try to use regular tooltips here
], [
'field' => 'currency',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.currency'),
'title' => trans('general.currency_text'),
'titleTooltip' => trans('general.currency_text'),
'visible' => true,
'class' => 'css-currency',
], [

View File

@@ -28,7 +28,7 @@ class StatusLabelPresenter extends Presenter
'switchable' => false,
'title' => trans('general.name'),
'visible' => true,
'formatter' => 'statuslabelsAssetLinkFormatter',
'formatter' => 'statuslabelsLinkFormatter',
],[
'field' => 'type',
'searchable' => false,

View File

@@ -24,6 +24,7 @@ class UserPresenter extends Presenter
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',

View File

@@ -87,9 +87,9 @@ class AuthServiceProvider extends ServiceProvider
]);
$this->registerPolicies();
Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::tokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years')));
Passport::refreshTokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years')));
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years')));
Passport::cookie(config('passport.cookie_name'));

View File

@@ -0,0 +1,555 @@
<?php namespace App\Providers;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\Department;
use App\Models\Depreciation;
use App\Models\Group;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\PredefinedKit;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Support\ServiceProvider;
use Tabuna\Breadcrumbs\Breadcrumbs;
use Tabuna\Breadcrumbs\Trail;
class BreadcrumbsServiceProvider extends ServiceProvider
{
/**
* Handles the resource routes for first-class objects
*
* @return void
*/
public function boot()
{
// Default home
Breadcrumbs::for('home', fn (Trail $trail) =>
$trail->push('<x-icon type="home" /><span class="sr-only">'.trans('general.dashboard').'</span>', route('home'))
);
/**
* Asset Breadcrumbs
*/
if ((request()->is('hardware*')) && (request()->status!='')) {
Breadcrumbs::for('hardware.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.assets'), route('hardware.index'))
->push(request()->status.' '.trans('general.assets'), route('hardware.index', ['status' => request()->status]))
);
} else {
Breadcrumbs::for('hardware.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.assets'), route('hardware.index'))
);
}
Breadcrumbs::for('hardware.create', fn (Trail $trail) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('general.create'), route('hardware.create'))
);
Breadcrumbs::for('hardware.show', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push($asset->present()->fullName(), route('hardware.show', $asset))
);
Breadcrumbs::for('hardware.edit', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $asset->asset_tag]), route('hardware.edit', $asset))
);
/**
* Asset Model Breadcrumbs
*/
if ((request()->is('models*')) && (request()->status=='deleted')) {
Breadcrumbs::for('models.index', fn (Trail $trail) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('general.asset_models'), route('models.index'))
->push(trans('general.deleted_models'), route('models.index', ['status' => request()->status]))
);
} else {
Breadcrumbs::for('models.index', fn (Trail $trail) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('general.asset_models'), route('models.index'))
);
}
Breadcrumbs::for('models.create', fn (Trail $trail) =>
$trail->parent('models.index', route('models.index'))
->push(trans('general.create'), route('models.create'))
);
Breadcrumbs::for('models.show', fn (Trail $trail, AssetModel $model) =>
$trail->parent('models.index', route('models.index'))
->push($model->name, route('models.show', $model))
);
Breadcrumbs::for('models.edit', fn (Trail $trail, AssetModel $model) =>
$trail->parent('models.index', route('models.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $model->name]), route('models.edit', $model))
);
/**
* Accessories Breadcrumbs
*/
Breadcrumbs::for('accessories.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.accessories'), route('accessories.index'))
);
Breadcrumbs::for('accessories.create', fn (Trail $trail) =>
$trail->parent('accessories.index', route('accessories.index'))
->push(trans('general.create'), route('accessories.create'))
);
Breadcrumbs::for('accessories.show', fn (Trail $trail, Accessory $accessory) =>
$trail->parent('accessories.index', route('accessories.index'))
->push($accessory->name, route('accessories.show', $accessory))
);
Breadcrumbs::for('accessories.edit', fn (Trail $trail, Accessory $accessory) =>
$trail->parent('accessories.index', route('accessories.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $accessory->name]), route('accessories.edit', $accessory))
);
/**
* Categories Breadcrumbs
*/
Breadcrumbs::for('categories.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.categories'), route('categories.index'))
);
Breadcrumbs::for('categories.create', fn (Trail $trail) =>
$trail->parent('categories.index', route('categories.index'))
->push(trans('general.create'), route('categories.create'))
);
Breadcrumbs::for('categories.show', fn (Trail $trail, Category $category) =>
$trail->parent('categories.index', route('categories.index'))
->push($category->name, route('categories.show', $category))
);
Breadcrumbs::for('categories.edit', fn (Trail $trail, Category $category) =>
$trail->parent('categories.index', route('categories.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $category->name]), route('categories.edit', $category))
);
/**
* Company Breadcrumbs
*/
Breadcrumbs::for('companies.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.companies'), route('companies.index'))
);
Breadcrumbs::for('companies.create', fn (Trail $trail) =>
$trail->parent('companies.index', route('companies.index'))
->push(trans('general.create'), route('companies.create'))
);
Breadcrumbs::for('companies.show', fn (Trail $trail, Company $company) =>
$trail->parent('companies.index', route('companies.index'))
->push($company->name, route('companies.show', $company))
);
Breadcrumbs::for('companies.edit', fn (Trail $trail, Company $company) =>
$trail->parent('companies.index', route('companies.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $company->name]), route('companies.edit', $company))
);
/**
* Components Breadcrumbs
*/
Breadcrumbs::for('components.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.components'), route('components.index'))
);
Breadcrumbs::for('components.create', fn (Trail $trail) =>
$trail->parent('components.index', route('components.index'))
->push(trans('general.create'), route('components.create'))
);
Breadcrumbs::for('components.show', fn (Trail $trail, Component $component) =>
$trail->parent('components.index', route('components.index'))
->push($component->name, route('components.show', $component))
);
Breadcrumbs::for('components.edit', fn (Trail $trail, Component $component) =>
$trail->parent('components.index', route('components.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $component->name]), route('components.edit', $component))
);
/**
* Consumables Breadcrumbs
*/
Breadcrumbs::for('consumables.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.consumables'), route('consumables.index'))
);
Breadcrumbs::for('consumables.create', fn (Trail $trail) =>
$trail->parent('consumables.index', route('consumables.index'))
->push(trans('general.create'), route('consumables.create'))
);
Breadcrumbs::for('consumables.show', fn (Trail $trail, Consumable $consumable) =>
$trail->parent('consumables.index', route('consumables.index'))
->push($consumable->name, route('consumables.show', $consumable))
);
Breadcrumbs::for('consumables.edit', fn (Trail $trail, Consumable $consumable) =>
$trail->parent('consumables.index', route('consumables.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $consumable->name]), route('consumables.edit', $consumable))
);
/**
* Custom fields Breadcrumbs
*/
Breadcrumbs::for('fields.index', fn (Trail $trail) =>
$trail->parent('models.index', route('models.index'))
->push(trans('admin/custom_fields/general.custom_fields'), route('fields.index'))
);
Breadcrumbs::for('fields.create', fn (Trail $trail) =>
$trail->parent('fields.index', route('fields.index'))
->push(trans('general.create'), route('fields.create'))
);
Breadcrumbs::for('fields.edit', fn (Trail $trail, CustomField $field) =>
$trail->parent('fields.index', route('fields.index'))
->push($field->name, route('fields.edit', $field))
);
/**
* Custom fieldsets Breadcrumbs
*/
Breadcrumbs::for('fieldsets.create', fn (Trail $trail) =>
$trail->parent('fields.index', route('fields.index'))
->push(trans('general.create'), route('fieldsets.create'))
);
Breadcrumbs::for('fieldsets.show', fn (Trail $trail, CustomFieldset $fieldset) =>
$trail->parent('fields.index', route('fields.index'))
->push($fieldset->name, route('fields.index'))
);
Breadcrumbs::for('fieldsets.edit', fn (Trail $trail, CustomFieldset $fieldset) =>
$trail->parent('fields.index', route('fields.index'))
->push($fieldset->name, route('fieldsets.edit', $fieldset))
);
/**
* Department Breadcrumbs
*/
Breadcrumbs::for('departments.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.departments'), route('departments.index'))
);
Breadcrumbs::for('departments.create', fn (Trail $trail) =>
$trail->parent('departments.index', route('departments.index'))
->push(trans('general.create'), route('departments.create'))
);
Breadcrumbs::for('departments.show', fn (Trail $trail, Department $department) =>
$trail->parent('departments.index', route('departments.index'))
->push($department->name, route('home'))
);
Breadcrumbs::for('departments.edit', fn (Trail $trail, Department $department) =>
$trail->parent('departments.index', route('departments.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $department->name]), route('departments.edit', $department))
);
/**
* Department Breadcrumbs
*/
Breadcrumbs::for('depreciations.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.depreciations'), route('depreciations.index'))
);
Breadcrumbs::for('depreciations.create', fn (Trail $trail) =>
$trail->parent('depreciations.index', route('depreciations.index'))
->push(trans('general.create'), route('depreciations.create'))
);
Breadcrumbs::for('depreciations.show', fn (Trail $trail, Depreciation $depreciation) =>
$trail->parent('depreciations.index', route('depreciations.index'))
->push($depreciation->name, route('depreciations.show', $depreciation))
);
Breadcrumbs::for('depreciations.edit', fn (Trail $trail, Depreciation $depreciation) =>
$trail->parent('depreciations.index', route('depreciations.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $depreciation->name]), route('depreciations.edit', $depreciation))
);
/**
* Groups Breadcrumbs
*/
Breadcrumbs::for('groups.index', fn (Trail $trail) =>
$trail->parent('settings.index', route('settings.index'))
->push(trans('general.groups'), route('groups.index'))
);
Breadcrumbs::for('groups.create', fn (Trail $trail) =>
$trail->parent('groups.index', route('groups.index'))
->push(trans('general.create'), route('groups.create'))
);
Breadcrumbs::for('groups.show', fn (Trail $trail, Group $group) =>
$trail->parent('groups.index', route('groups.index'))
->push($group->name, route('groups.show', $group))
);
Breadcrumbs::for('groups.edit', fn (Trail $trail, Group $group) =>
$trail->parent('groups.index', route('groups.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $group->name]), route('groups.edit', $group))
);
/**
* Licenses Breadcrumbs
*/
Breadcrumbs::for('licenses.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.licenses'), route('licenses.index'))
);
Breadcrumbs::for('licenses.create', fn (Trail $trail) =>
$trail->parent('licenses.index', route('licenses.index'))
->push(trans('general.create'), route('licenses.create'))
);
Breadcrumbs::for('licenses.show', fn (Trail $trail, License $license) =>
$trail->parent('licenses.index', route('licenses.index'))
->push($license->name, route('licenses.show', $license))
);
Breadcrumbs::for('licenses.edit', fn (Trail $trail, License $license) =>
$trail->parent('licenses.index', route('licenses.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $license->name]), route('licenses.edit', $license))
);
/**
* Locations Breadcrumbs
*/
Breadcrumbs::for('locations.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.locations'), route('locations.index'))
);
Breadcrumbs::for('locations.create', fn (Trail $trail) =>
$trail->parent('locations.index', route('locations.index'))
->push(trans('general.create'), route('locations.create'))
);
Breadcrumbs::for('locations.show', fn (Trail $trail, Location $location) =>
$trail->parent('locations.index', route('locations.index'))
->push($location->name, route('locations.show', $location))
);
Breadcrumbs::for('locations.edit', fn (Trail $trail, Location $location) =>
$trail->parent('locations.index', route('locations.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $location->name]), route('locations.edit', $location))
);
/**
* Maintenances Breadcrumbs
*/
Breadcrumbs::for('maintenances.index', fn (Trail $trail) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('general.maintenances'), route('maintenances.index'))
);
Breadcrumbs::for('maintenances.create', fn (Trail $trail) =>
$trail->parent('maintenances.index', route('maintenances.index'))
->push(trans('general.create'), route('maintenances.create'))
);
Breadcrumbs::for('maintenances.show', fn (Trail $trail, AssetMaintenance $maintenance) =>
$trail->parent('maintenances.index', route('locations.index'))
->push($maintenance->title, route('maintenances.show', $maintenance))
);
Breadcrumbs::for('manufacturers.edit', fn (Trail $trail, Manufacturer $manufacturer) =>
$trail->parent('manufacturers.index', route('manufacturers.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $manufacturer->name]), route('manufacturers.edit', $manufacturer))
);
/**
* Manufacturers Breadcrumbs
*/
Breadcrumbs::for('manufacturers.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.manufacturers'), route('manufacturers.index'))
);
Breadcrumbs::for('manufacturers.create', fn (Trail $trail) =>
$trail->parent('manufacturers.index', route('manufacturers.index'))
->push(trans('general.create'), route('manufacturers.create'))
);
Breadcrumbs::for('manufacturers.show', fn (Trail $trail, Manufacturer $manufacturer) =>
$trail->parent('manufacturers.index', route('manufacturers.index'))
->push($manufacturer->name, route('home'))
);
Breadcrumbs::for('manufacturers.edit', fn (Trail $trail, Manufacturer $manufacturer) =>
$trail->parent('manufacturers.index', route('manufacturers.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $manufacturer->name]), route('manufacturers.edit', $manufacturer))
);
/**
* Predefined Kits Breadcrumbs
*/
Breadcrumbs::for('kits.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.kits'), route('kits.index'))
);
Breadcrumbs::for('kits.create', fn (Trail $trail) =>
$trail->parent('kits.index', route('kits.index'))
->push(trans('general.create'), route('kits.create'))
);
Breadcrumbs::for('kits.show', fn (Trail $trail, PredefinedKit $kit) =>
$trail->parent('kits.index', route('kits.index'))
->push($kit->name, route('kits.show', $kit))
);
Breadcrumbs::for('kits.edit', fn (Trail $trail, PredefinedKit $kit) =>
$trail->parent('kits.index', route('kits.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $kit->name]), route('kits.edit', $kit))
);
/**
* Status Labels Breadcrumbs
*/
Breadcrumbs::for('statuslabels.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.status_labels'), route('statuslabels.index'))
);
Breadcrumbs::for('statuslabels.create', fn (Trail $trail) =>
$trail->parent('statuslabels.index', route('statuslabels.index'))
->push(trans('general.create'), route('statuslabels.create'))
);
Breadcrumbs::for('statuslabels.show', fn (Trail $trail, Statuslabel $statuslabel) =>
$trail->parent('statuslabels.index', route('statuslabels.index'))
->push($statuslabel->name, route('statuslabels.show', $statuslabel))
);
Breadcrumbs::for('statuslabels.edit', fn (Trail $trail, Statuslabel $statuslabel) =>
$trail->parent('statuslabels.index', route('statuslabels.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $statuslabel->name]), route('statuslabels.edit', $statuslabel))
);
/**
* Settings Breadcrumbs
*/
Breadcrumbs::for('settings.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.admin'), route('settings.index'))
);
/**
* Suppliers Breadcrumbs
*/
Breadcrumbs::for('suppliers.index', fn (Trail $trail) =>
$trail->parent('home', route('home'))
->push(trans('general.suppliers'), route('suppliers.index'))
);
Breadcrumbs::for('suppliers.create', fn (Trail $trail) =>
$trail->parent('suppliers.index', route('suppliers.index'))
->push(trans('general.create'), route('suppliers.create'))
);
Breadcrumbs::for('suppliers.show', fn (Trail $trail, Supplier $supplier) =>
$trail->parent('suppliers.index', route('suppliers.index'))
->push($supplier->name, route('home'))
);
Breadcrumbs::for('suppliers.edit', fn (Trail $trail, Supplier $supplier) =>
$trail->parent('suppliers.index', route('suppliers.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $supplier->name]), route('suppliers.edit', $supplier))
);
/**
* Users Breadcrumbs
*/
if ((request()->is('users*')) && (request()->status=='deleted')) {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
->push(trans('general.deleted_users'), route('users.index'))
);
} else {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
);
}
Breadcrumbs::for('users.create', fn (Trail $trail) =>
$trail->parent('users.index', route('users.index'))
->push(trans('general.create'), route('users.create'))
);
Breadcrumbs::for('users.show', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->username, route('users.show', $user))
);
Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $user->name]), route('users.edit', $user))
);
}
}

View File

@@ -49,10 +49,6 @@ class SamlServiceProvider extends ServiceProvider
'uses' => 'Auth\SamlController@login', ]
);
Route::group(['prefix' => 'admin', 'middleware' => ['web', 'auth', 'authorize:superuser']], function () {
Route::get('saml', ['as' => 'settings.saml.index', 'uses' => 'SettingsController@getSamlSettings']);
Route::post('saml', ['as' => 'settings.saml.save', 'uses' => 'SettingsController@postSamlSettings']);
});
});
}

View File

@@ -140,8 +140,8 @@ class Label implements View
$barcode2DTarget = $asset->serial;
break;
case 'hardware_id':
default:
$barcode2DTarget = route('hardware.show', ['hardware' => $asset->id]);
default:
$barcode2DTarget = route('hardware.show', $asset);
break;
}
$assetData->put('barcode2d', (object)[

View File

@@ -70,6 +70,7 @@
"rollbar/rollbar-laravel": "^8.0",
"spatie/laravel-backup": "^8.8",
"spatie/laravel-ignition": "^2.0",
"tabuna/breadcrumbs": "^4.2",
"tecnickcom/tc-lib-barcode": "^1.15",
"tecnickcom/tcpdf": "^6.5",
"unicodeveloper/laravel-password": "^1.0",

68
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": "8229576658d67211b967e98b5629224f",
"content-hash": "87742cc73d186f6f47627ad498cf0fe5",
"packages": [
{
"name": "alek13/slack",
@@ -11011,6 +11011,72 @@
],
"time": "2024-11-08T15:48:14+00:00"
},
{
"name": "tabuna/breadcrumbs",
"version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/tabuna/breadcrumbs.git",
"reference": "1d9047306f67e7fcc86fc7e608f1432f247636da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tabuna/breadcrumbs/zipball/1d9047306f67e7fcc86fc7e608f1432f247636da",
"reference": "1d9047306f67e7fcc86fc7e608f1432f247636da",
"shasum": ""
},
"require": {
"ext-json": "*",
"laravel/framework": "^10.0|^11.0",
"laravel/serializable-closure": "^1.0|^2.0",
"php": "^8.1"
},
"require-dev": {
"orchestra/testbench": "^8.0|^9.0",
"phpunit/php-code-coverage": "^10.|^11.0",
"phpunit/phpunit": "^10.5|^11.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Breadcrumbs": "Tabuna\\Breadcrumbs\\Breadcrumbs"
},
"providers": [
"Tabuna\\Breadcrumbs\\BreadcrumbsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Tabuna\\Breadcrumbs\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alexandr Chernyaev",
"email": "bliz48rus@gmail.com"
},
{
"name": "Dwight Watson",
"email": "dwight@studentservices.com.au"
},
{
"name": "Dave James Miller",
"email": "dave@davejamesmiller.com"
}
],
"description": "An easy way to add breadcrumbs to your Laravel app.",
"support": {
"issues": "https://github.com/tabuna/breadcrumbs/issues",
"source": "https://github.com/tabuna/breadcrumbs/tree/4.2.1"
},
"time": "2024-11-26T12:21:27+00:00"
},
{
"name": "tecnickcom/tc-lib-barcode",
"version": "1.18.4",

View File

@@ -242,7 +242,7 @@ return [
|
*/
'min_php' => '8.1.2',
'min_php' => '8.2.0',
/*
@@ -316,6 +316,7 @@ return [
App\Providers\LivewireServiceProvider::class,
App\Providers\MacroServiceProvider::class,
App\Providers\SamlServiceProvider::class,
App\Providers\BreadcrumbsServiceProvider::class,
],

View File

@@ -105,4 +105,17 @@ $config = [
// (by default, the PUBLIC_FILESYSTEM DISK is 'local_public', in the public/uploads directory)
$config['disks']['public'] = $config['disks'][env('PUBLIC_FILESYSTEM_DISK','local_public')];
// Handle the 'backup' disk for S3 consideration
if (env('PRIVATE_FILESYSTEM_DISK') == 's3_private') {
$config['disks']['backup']['driver'] = 's3';
$config['disks']['backup']['key'] = $config['disks']['s3_private']['key'];
$config['disks']['backup']['secret'] = $config['disks']['s3_private']['secret'];
$config['disks']['backup']['region'] = $config['disks']['s3_private']['region'];
$config['disks']['backup']['bucket'] = $config['disks']['s3_private']['bucket'];
$config['disks']['backup']['url'] = $config['disks']['s3_private']['url'];
$config['disks']['backup']['root'] = $config['disks']['s3_private']['root'];
$config['disks']['backup']['visibility'] = 'private';
}
return $config;

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v7.1.16',
'full_app_version' => 'v7.1.16 - build 16564-gfb857ccf5',
'build_version' => '16564',
'app_version' => 'v8.0.2',
'full_app_version' => 'v8.0.2 - build 17048-g44dd06161',
'build_version' => '17048',
'prerelease_version' => '',
'hash_version' => 'gfb857ccf5',
'full_hash' => 'v7.1.16-510-gfb857ccf5',
'hash_version' => 'g44dd06161',
'full_hash' => 'v8.0.2-54-g44dd06161',
'branch' => 'develop',
);

View File

@@ -27,6 +27,10 @@ class CheckoutAcceptanceFactory extends Factory
public function configure(): static
{
return $this->afterCreating(function (CheckoutAcceptance $acceptance) {
if ($acceptance->checkoutable instanceof Asset) {
$this->createdAssociatedActionLogEntry($acceptance);
}
if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) {
$acceptance->checkoutable->update([
'assigned_to' => $acceptance->assigned_to_id,
@@ -51,4 +55,23 @@ class CheckoutAcceptanceFactory extends Factory
'declined_at' => null,
]);
}
public function accepted()
{
return $this->state([
'accepted_at' => now()->subDay(),
'declined_at' => null,
]);
}
private function createdAssociatedActionLogEntry(CheckoutAcceptance $acceptance): void
{
$acceptance->checkoutable->assetlog()->create([
'action_type' => 'checkout',
'target_id' => $acceptance->assigned_to_id,
'target_type' => get_class($acceptance->assignedTo),
'item_id' => $acceptance->checkoutable_id,
'item_type' => $acceptance->checkoutable_type,
]);
}
}

View File

@@ -12,8 +12,8 @@ class AddSoftDeletedToLog extends Migration
*/
public function up()
{
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
//$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
//$platform->registerDoctrineTypeMapping('enum', 'string');
Schema::table('asset_logs', function ($table) {
$table->string('asset_type', 100)->nullable()->change();

View File

@@ -14,7 +14,7 @@ class ChangeLicenseNotesType extends Migration
{
//
Schema::table('licenses', function ($table) {
$table->text('notes')->change();
$table->text('notes')->nullable()->change();
});
}

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