Compare commits

...

290 Commits

Author SHA1 Message Date
snipe
6f233bc218 Get more of the select2 ajax lists working 2025-11-03 15:38:43 +00:00
snipe
448f4b94af Remove classes 2025-11-03 14:03:31 +00:00
snipe
7c58fb93a9 Fixed test 2025-11-01 02:53:47 +00:00
snipe
80e4e4a40e Updated more form fields 2025-11-01 02:45:59 +00:00
snipe
7161d56ae2 Prod assets for some reason 2025-10-31 19:07:17 +00:00
snipe
1e85c7b575 Put the form-control-static back since it didn’t cut down the number of files :( 2025-10-31 19:05:36 +00:00
snipe
03130f0947 Try to undo the asset stuff for the PR 2025-10-31 19:03:50 +00:00
snipe
4812e25e5c Added name awareness in child elements 2025-10-31 19:03:19 +00:00
snipe
e6c49da11c Use new blade for textarea 2025-10-31 19:02:54 +00:00
snipe
6f3ee1914a Updated maintenances screen 2025-10-31 19:02:39 +00:00
snipe
9232ee781e Updated validation JS for select2 2025-10-31 19:02:25 +00:00
snipe
3d675d375c Re-add the form static so it’s less noisy for this PR 2025-10-31 19:02:02 +00:00
snipe
d2edc77197 Debugging - remove later 2025-10-31 19:01:42 +00:00
snipe
53716cbe90 Still not working (pre-selecting the ID of the asset) 2025-10-31 19:00:55 +00:00
snipe
8ce3fe0df0 Pass name to input 2025-10-31 16:21:41 +00:00
snipe
affee0a990 Handle unfilled data 2025-10-31 16:21:22 +00:00
snipe
6d5f515f48 Make select2 ajax a little more flexible 2025-10-31 12:23:08 +00:00
snipe
d96844498f This doesn’t actually work yet 2025-10-31 12:22:56 +00:00
snipe
08609d4f56 Moar refactorings 2025-10-30 22:26:21 +00:00
snipe
be344c440f Different approach - rely on nested slots 2025-10-30 21:30:10 +00:00
snipe
96cc2c9ee9 Move form into its own component 2025-10-30 16:35:15 +00:00
snipe
fb1a89442c Removed p-static class 2025-10-30 14:10:06 +00:00
snipe
359e0197bf Start building out the box+form stuff 2025-10-30 14:02:33 +00:00
snipe
6dbb836a01 Merge pull request #18125 from grokability/form-row-again
Added form row component
2025-10-29 16:33:15 +00:00
snipe
3426afe5a8 Account for input styles 2025-10-29 16:15:22 +00:00
snipe
4bbf923eb6 Smaller default row for textarea 2025-10-29 16:11:56 +00:00
snipe
e2c3480194 Apply form rows to manufacturers 2025-10-29 16:11:47 +00:00
snipe
73159076f6 Better handle input groups in js validator 2025-10-29 16:11:37 +00:00
snipe
90d040573d Added regular link icon 2025-10-29 16:07:16 +00:00
snipe
155481a442 Merge pull request #18120 from grokability/small-form-fixes-settings
Fixed form label alignments in settings section
2025-10-29 11:16:59 +00:00
snipe
2f31bfc5fe Fixed some HTML labels in settings 2025-10-29 11:13:07 +00:00
snipe
8d0c88dc74 Merge pull request #18116 from akemidx/auditwarningthreshold
Fixed #17329 Audit Warning Threshold could be negative
2025-10-28 20:44:58 +00:00
snipe
07256fd833 Merge pull request #18044 from mohammad-ahmadi1/ISSUE-17004-fix-db-dump-ssl-issue
fix: update mysqldump options to use --ssl-mode=DISABLED for modern versions
2025-10-28 20:37:08 +00:00
snipe
776cd43a58 Change text string for item (versus asset) 2025-10-28 20:36:05 +00:00
akemidx
acb5309aab min = 0 2025-10-28 16:32:21 -04:00
snipe
c68f81db3c Merge pull request #18113 from Godmartinz/customfieldvalue-in-importer-fix
Adds #17433  `is_null` check to import handler for custom fields
2025-10-28 20:27:22 +00:00
snipe
ac8e341b37 Merge pull request #18115 from marcusmoore/fixes/rb-20449-remove-admin-from-acceptance-email
Fixed #18112 - fix consumable and license acceptances
2025-10-28 20:26:59 +00:00
snipe
36122b3966 Added BACKUP_TIME_LIMIT to env example 2025-10-28 20:26:23 +00:00
akemidx
79bcf472f0 greater than or equal to zero 2025-10-28 16:13:33 -04:00
Marcus Moore
55d86da846 Remove references to administrator in acceptance notification 2025-10-28 13:07:00 -07:00
Godfrey M
e4f8c1ba3f fix custom field value conditional check 2025-10-28 12:24:35 -07:00
Godfrey M
c36236b7dc add is_null acheck to import hanlder for custom fields 2025-10-28 11:37:38 -07:00
snipe
63994333d0 Merge pull request #18111 from grokability/#16914-better-ldap-sync-phrasing
Fixed #16914: better ldap sync phrasing
2025-10-28 15:51:10 +00:00
snipe
da4c7d8934 One more tweak 2025-10-28 15:47:56 +00:00
snipe
186721eca0 Added breadcrumbs 2025-10-28 15:42:59 +00:00
snipe
f53d939c86 Better explanation for location sync, nicer look 2025-10-28 15:42:53 +00:00
snipe
23e6909708 Merge pull request #18110 from grokability/#18107-normalize-to-strings
Fixed #18107: normalize "to" strings
2025-10-28 15:03:16 +00:00
snipe
cf421fe1c1 Added user-specific “to” 2025-10-28 15:02:15 +00:00
snipe
4d80e806e4 Use “-“ instead of “to” string, added placeholders 2025-10-28 15:02:03 +00:00
snipe
60a7b7f7ff Merge pull request #18109 from grokability/audit-improvements
Limit the upcoming audit email to 30 records, added optional --with-output
2025-10-28 14:31:55 +00:00
snipe
90263eab06 Added note for —with-option flag 2025-10-28 14:20:40 +00:00
snipe
9d8f251fc4 Limit the email to 30 records, added optional --with-output 2025-10-28 14:15:15 +00:00
snipe
2b4986571c Merge pull request #18108 from uberbrady/fix_ldap_tests
Fixed - LDAP test needs to be fixed to match new behavior
2025-10-28 12:49:11 +00:00
Brady Wetherington
890d13bd52 LDAP test needs to be fixed to match new behavior 2025-10-28 12:30:30 +00:00
snipe
e698e71137 Merge pull request #18100 from marcusmoore/fixes/20318-license-seat-display-name
Added null safe operator in case of missing license
2025-10-28 09:29:25 +00:00
snipe
d064a5530a Merge remote-tracking branch 'origin/master' into develop 2025-10-28 02:10:07 +00:00
snipe
ab4fbf6c19 Merge pull request #18105 from grokability/ldap-fast-find-and-bind
Possible fix for 504 gateway timeout on unreachable LDAP server
2025-10-28 02:09:41 +00:00
snipe
728afa8361 Possible fix for 504 gateway timeout on unreachable LDAP server 2025-10-27 23:45:12 +00:00
snipe
b77019c16e Merge remote-tracking branch 'origin/develop' 2025-10-27 19:32:28 +00:00
snipe
6703448b80 Merge pull request #18102 from marcusmoore/fixes/rb-20434-undefined-permissions-variable
Fixed issue when viewing user that does not have permissions set
2025-10-27 19:31:54 +00:00
Marcus Moore
776ba19a1f Define default permissions array 2025-10-27 12:28:55 -07:00
Marcus Moore
1f499e0d44 Add null safe operator in case of missing license 2025-10-27 10:47:55 -07:00
snipe
0a6eb61103 Merge remote-tracking branch 'origin/develop' 2025-10-27 13:20:18 +00:00
snipe
32a2eed5ec Fixed #18075 - make require_serial boolean in API transformer 2025-10-27 13:19:37 +00:00
snipe
40a70d39d0 Merge remote-tracking branch 'origin/develop' 2025-10-27 12:42:40 +00:00
snipe
5697054e98 Merge pull request #18099 from grokability/fix-cjk-for-acceptance-translated-strings
Fixed #18097 - check for CJK in field labels as well as content
2025-10-27 12:41:59 +00:00
snipe
def5969e1c Fixed #18097 - check for CJK in field labels as well as content 2025-10-27 12:34:30 +00:00
snipe
78a418630d Merge remote-tracking branch 'origin/develop' 2025-10-27 12:21:25 +00:00
snipe
6d76e7b2d4 Added dumb pverride for reports :( 2025-10-27 12:21:16 +00:00
snipe
f052c8b44c Merge pull request #18076 from uberbrady/move_traits_into_directories
Moved Traits into its directory and modify the FCO's to point to them
2025-10-27 11:43:31 +00:00
snipe
0e6991d56d Merge remote-tracking branch 'origin/develop' 2025-10-27 11:42:05 +00:00
snipe
06eab5f8a4 Merge pull request #18093 from Godmartinz/fix-warranty-part-of-expiring-asset-query
Fixed expiring warranties not being included in the expiring alerts notification
2025-10-27 11:41:51 +00:00
snipe
4a481e79c4 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-10-27 11:14:05 +00:00
snipe
ee4abbcbaa Updated dev assets 2025-10-27 11:12:13 +00:00
snipe
dcc82d742f Fixed RB-20430 - 500 on LDAP if baseDN is not set 2025-10-27 11:09:24 +00:00
snipe
19cb2089d7 Merge pull request #18098 from grokability/dependabot/github_actions/develop/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-27 10:47:59 +00:00
snipe
04923b06b0 Merge pull request #18078 from grokability/groups-ui-improvements
Groups UI improvements, ability to add users from the group edit screen
2025-10-27 10:47:11 +00:00
dependabot[bot]
e16755d491 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 08:19:26 +00:00
snipe
742b0769a4 Use translation string 2025-10-26 12:10:08 +00:00
snipe
df68dca9dc Warn if user has individual permission overrides 2025-10-25 18:57:17 +01:00
snipe
4a5bf78d58 Merge branch 'develop' into groups-ui-improvements 2025-10-25 18:31:22 +01:00
snipe
7947237489 Double check the admin status when toggling the superadmin 2025-10-25 18:29:52 +01:00
snipe
1115205164 Normalize the JS 2025-10-25 18:20:22 +01:00
snipe
d5d01136c4 Fixed js errors 2025-10-25 17:57:59 +01:00
snipe
3d47277614 Added cursor style 2025-10-25 17:57:44 +01:00
snipe
b937bea04f Working, but there’s a bit of a jitter I need to fix 2025-10-25 15:59:47 +01:00
snipe
fff14632bc Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-10-25 14:59:31 +01:00
snipe
9bdf1a620f Built assets 2025-10-25 14:58:27 +01:00
snipe
60099aa989 FAFOing on disclosure arrows being remembered 2025-10-25 14:56:01 +01:00
snipe
f3976e5dd8 Merge pull request #18094 from Godmartinz/custom_field_color_fixx
Fixed fieldset colors on dark themes
2025-10-23 20:32:05 +01:00
Godfrey M
d7d6893304 fix Custom Fields section header font color 2025-10-23 12:05:13 -07:00
Godfrey M
99549ce805 rewrite query for expired warranties on assets, concat queries" 2025-10-23 11:01:51 -07:00
snipe
7eb15fe04d Merge pull request #18091 from uberbrady/fix_backup_durations
Moved import time limit inside class, added new backup time limit
2025-10-23 18:51:35 +01:00
Godfrey M
e2019a13ab rewrite expired assets collection query 2025-10-23 10:49:58 -07:00
Brady Wetherington
4b7a06761a Moved import time limit inside class, added new backup time limit 2025-10-23 16:05:09 +01:00
snipe
8f4a1f5801 Moved JS and styles into js and css files 2025-10-23 13:24:26 +01:00
snipe
891bec9cdb Styling fixes 2025-10-23 13:06:45 +01:00
Godfrey M
c5252ea583 skip in one more spot 2025-10-22 12:23:14 -07:00
Godfrey M
82d553c180 skip test if sqllite 2025-10-22 12:19:13 -07:00
Godfrey M
71e34355b9 remove unwanted changes 2025-10-22 11:51:00 -07:00
Godfrey M
2bad8c72e4 fixes warranty part of expiring alerts query 2025-10-22 11:43:54 -07:00
Godfrey M
6134ca01ac Merge remote-tracking branch 'origin/develop' into develop 2025-10-22 10:48:05 -07:00
snipe
be8193ebff Fixed inherit permissions 2025-10-22 15:00:04 +01:00
snipe
430ee46645 Small tweaks to inherit js 2025-10-22 13:41:59 +01:00
snipe
76fbbf29e8 Replace generate text with icon button 2025-10-22 13:37:45 +01:00
snipe
3108159d95 Jitter CSS tweaks 2025-10-22 12:37:39 +01:00
snipe
f282a1ead7 Removed duplicated permissions js code 2025-10-22 12:03:55 +01:00
snipe
324bc4957d Merge pull request #18080 from Godmartinz/add-null-safe-operator-to-manager
Fixes admin alerts when a location doesnt have a manager
2025-10-22 11:56:59 +01:00
snipe
c3b5c4dfae Moved the logic into the permissions partial, added translations 2025-10-21 22:10:09 +01:00
Godfrey M
4ab5d97e86 add nullsafe operator to location manager 2025-10-21 14:06:08 -07:00
Godfrey M
b4696ef11e added alert_email to send conditional in listener 2025-10-21 13:11:21 -07:00
snipe
294ffb72a4 Translations!!! 2025-10-21 20:36:09 +01:00
snipe
8c534d29d3 WTF tower?? 2025-10-21 19:36:28 +01:00
snipe
d6cb262f9d Bumped version 2025-10-21 19:22:36 +01:00
snipe
5211e2ae20 Some UI refinement 2025-10-21 19:22:36 +01:00
snipe
90832fd1ad Checked check 2025-10-21 19:22:36 +01:00
snipe
889cbc69e1 Starter improvements - needs testing 2025-10-21 19:22:36 +01:00
snipe
15948370d4 Updated assets 2025-10-21 19:22:24 +01:00
snipe
63c5177b37 Merge pull request #18077 from Godmartinz/acceptance-notif-fixes-pt2
Adds admin to decline notification, fix asset and model name translations on asset notification
2025-10-21 18:31:28 +01:00
Godfrey M
4fbfaf6b9f add admin to decline, fix asset and model name translations 2025-10-21 10:22:34 -07:00
Brady Wetherington
9e68497b63 Moved Traits into directory and modify the users to point to them 2025-10-21 16:45:58 +01:00
snipe
0b9e13bf1e Merge remote-tracking branch 'origin/develop' 2025-10-21 12:58:06 +01:00
snipe
485d343e0f Merge pull request #18058 from uberbrady/ldap_fetch_dn_before_login_FIXED
FIXED - perform Ldap fetch for dn (Distinguished Name) before logging-in (fixed)
2025-10-21 12:56:31 +01:00
snipe
07f0e8a3be Merge pull request #18070 from Godmartinz/fix-admin-in-user-acceptance-notif
Fixes `admin` parameter acceptance notifications
2025-10-21 12:54:03 +01:00
snipe
cc5afb1cd8 Merge pull request #18074 from grokability/smaller-audit-image-fix
Fixed audit images not displaying inline
2025-10-21 12:51:20 +01:00
snipe
c69f1c0890 Smaller fix for missing audit images 2025-10-21 12:43:59 +01:00
Godfrey M
a8733bdedf remove nullsafe operator 2025-10-20 11:01:38 -07:00
Godfrey M
4222e4eb51 add nullsafe operator after admin 2025-10-20 11:00:59 -07:00
Godfrey M
5db4441f5c fix admin param in user accepted notification 2025-10-20 10:51:24 -07:00
snipe
2bcfe97211 Merge remote-tracking branch 'origin/develop' 2025-10-20 16:03:55 +01:00
snipe
4e74c97c84 Merge pull request #18066 from smarsching/issue-18065
Fixed #18065: Ensure that private_key_bits is always an int
2025-10-20 13:51:42 +01:00
Sebastian Marsching
71a46c9bd6 Fixed #18065: ensure that private_key_bits is always an int. 2025-10-18 18:39:43 +02:00
snipe
1a7c7fdebf Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/mix-manifest.json
2025-10-17 18:37:18 +01:00
snipe
65ff3d414a Bumped version 2025-10-17 18:34:54 +01:00
snipe
9fa38b70c8 Bumped version 2025-10-17 18:26:44 +01:00
snipe
470172f53f Re-run assets 2025-10-17 18:26:05 +01:00
snipe
81261d9e36 Put the session check back in - possible fix for #18004 2025-10-17 18:25:50 +01:00
snipe
3ab2e20119 Merge remote-tracking branch 'origin/develop' 2025-10-17 17:12:47 +01:00
snipe
b773d576ea Updated cipher-base library 2025-10-17 17:12:31 +01:00
snipe
23feb64b5a Fixed potential XSS on locations 2025-10-17 17:07:57 +01:00
snipe
87fe9d9d3d Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.css.map
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-contrast.css
#	public/css/dist/skins/skin-contrast.css.map
#	public/css/dist/skins/skin-contrast.min.css
#	public/mix-manifest.json
2025-10-17 17:00:10 +01:00
snipe
b1ef3f51cb Merge pull request #18064 from grokability/new-fieldsets
Use new fieldset component
2025-10-17 16:58:17 +01:00
snipe
e1b6488f8e Use new fieldset component 2025-10-17 16:55:36 +01:00
snipe
a472dede2b Merge pull request #18063 from grokability/improved-fieldset-UI-and-better-help-test-for-ldap-mapping
New legend styles and additional help hints for LDAP
2025-10-17 15:45:05 +01:00
snipe
263cc3f7a1 New styles and additional help hints for LDAP 2025-10-17 15:29:24 +01:00
Brady Wetherington
598612d4bf Got tests to pass? Not very happy about it, tbh. 2025-10-16 19:11:49 +01:00
Brady Wetherington
a07d83e583 Improved find-and-bind for complex LDAP directory structures 2025-10-16 19:11:49 +01:00
snipe
d355812433 Updated assets 2025-10-16 19:06:22 +01:00
snipe
1afc14f5ab Merge remote-tracking branch 'origin/develop' 2025-10-16 19:04:30 +01:00
snipe
8947b667ae Fixed status labels skin for visited link buttons 2025-10-16 19:04:15 +01:00
snipe
6b41796d44 Fixed restore button on asset view page 2025-10-16 19:04:01 +01:00
snipe
6efe8eb55b Small fix for high contrast skin 2025-10-16 18:33:33 +01:00
snipe
4c8f8918e8 Merge remote-tracking branch 'origin/develop' 2025-10-16 15:49:59 +01:00
snipe
4cb5bb1855 Merge pull request #18057 from grokability/user-selector-fix
Fixed #18053 - user selector missing on page load without cookies
2025-10-16 15:49:39 +01:00
snipe
eb007e025a Fixed #18053 - user selector missing on page load without cookies 2025-10-16 15:47:25 +01:00
snipe
18aefa9dee Merge remote-tracking branch 'origin/develop' 2025-10-16 15:10:29 +01:00
snipe
2ec540bd36 Updated language translations 2025-10-16 15:10:17 +01:00
snipe
1374ec408a Merge pull request #18019 from akemidx/emdashcharfix
Fixed #17363 - Emdash Character Encoding error
2025-10-16 15:07:31 +01:00
snipe
8f8fed2b79 Merge remote-tracking branch 'origin/develop' 2025-10-16 14:33:30 +01:00
snipe
a1e6f01fe9 Fixed updating notes in bulk edit 2025-10-16 14:33:21 +01:00
snipe
d7496f22e5 Merge remote-tracking branch 'origin/develop' 2025-10-16 14:04:44 +01:00
snipe
7de25a1c37 Merge pull request #18056 from grokability/add-expected-checkin-to-view-assets
Added expected_checkin to user’s View Assigned page
2025-10-16 14:04:31 +01:00
snipe
78da89340c Added expected_checkin to user’s View Assigned page 2025-10-16 13:59:59 +01:00
snipe
60606115fe Merge remote-tracking branch 'origin/develop' 2025-10-16 12:46:47 +01:00
snipe
57b49fc31c Check for array key for license report/formatters 2025-10-16 12:46:17 +01:00
snipe
6e90c8f6e6 Bumped hash for master 2025-10-16 11:28:37 +01:00
snipe
a7ff2595a5 Bumped hash 2025-10-16 11:28:01 +01:00
snipe
1edea6abef Fixed RB-20347 - ambigious field in custom fields 2025-10-16 11:27:25 +01:00
snipe
a3bd58bda6 Foundation 2025-10-16 11:17:48 +01:00
snipe
3339d1999f Merge remote-tracking branch 'origin/develop' 2025-10-16 11:12:56 +01:00
akemidx
df3e8ec0f3 cleaning up notes 2025-10-15 17:35:25 -04:00
snipe
2dbec867d9 Merge pull request #18038 from boteroTradebe/develop
Fixed LDAP sync not syncing all user fields
2025-10-15 17:25:33 +01:00
Mohammad Ahmadi
3fc651d659 fix: update mysqldump options to use --ssl-mode=DISABLED for modern versions 2025-10-15 00:30:53 +02:00
snipe
b91d23023d Fix blade template syntax for accessory fields 2025-10-14 13:45:58 +01:00
boteroTradebe
cc234c60b8 Update LdapSync.php to populate/update user fields
Added missing IF statements for address, city, state, and ZIP code. Previously, this information would be pulled via LDAP sync but would not update the user data due to the missing IF statements for these attributes.
2025-10-13 13:42:11 -05:00
snipe
f1883c8004 Merge remote-tracking branch 'origin/develop' 2025-10-13 14:48:46 +01:00
snipe
79f6ddc8ee Merge pull request #18036 from grokability/better-messaging-for-bulk-deletes
Fixed #18034 - Shows success message on partial bulk delete success
2025-10-13 14:30:26 +01:00
snipe
89f439a18d Shows success message on partial bulk delete success 2025-10-13 14:25:05 +01:00
snipe
79eb5bfad9 Merge pull request #18035 from grokability/fixes-bulk-error-notification
Fixed bulk error display to be more consistent and correct HTML
2025-10-13 14:02:11 +01:00
snipe
3a36b7dafd Fixed display to be more consistent and correct HTML 2025-10-13 13:57:04 +01:00
snipe
1fe2fd9891 Merge pull request #17573 from spencerrlongg/feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers
Fixed #8709 - Bulk Deletion of Categories, Suppliers, Manufa...
2025-10-13 13:20:13 +01:00
snipe
5fdb999ece Merge branch 'develop' into feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers 2025-10-13 13:07:53 +01:00
snipe
5a38d9c2b6 Merge pull request #18029 from grokability/dependabot/github_actions/develop/github/codeql-action-4
Bump github/codeql-action from 3 to 4
2025-10-13 12:00:49 +01:00
snipe
b1c7dc6cbb Merge remote-tracking branch 'origin/develop' 2025-10-13 11:32:19 +01:00
snipe
b21b2ac41c Merge pull request #18032 from grokability/move-asset-private-files
Added migration to move asset model private uploads
2025-10-13 11:31:55 +01:00
snipe
1396c597ef Move files 2025-10-13 11:19:25 +01:00
dependabot[bot]
46af72ee4e Bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 08:19:22 +00:00
snipe
e3e8f553c8 Merge remote-tracking branch 'origin/develop' 2025-10-09 18:39:13 +01:00
snipe
549928d3d1 Updated assets 2025-10-09 18:38:59 +01:00
snipe
abefec9628 Upgraded jdpdf to ^3.0.3 2025-10-09 18:37:59 +01:00
snipe
b483eeded4 Merge remote-tracking branch 'origin/develop' 2025-10-09 11:24:47 +01:00
snipe
fab85dafa8 Added employee number and email to acceptance PDF 2025-10-09 11:24:16 +01:00
snipe
955faed919 Merge pull request #18018 from grokability/advanced-user-search
Normalize advanced search
2025-10-09 11:04:28 +01:00
snipe
3ff516180d Merge remote-tracking branch 'origin/develop' 2025-10-09 10:56:08 +01:00
snipe
0c8ca6d6b0 Search on status label name 2025-10-09 10:54:11 +01:00
snipe
a5de077e04 Merge pull request #17975 from marcusmoore/replace-date-and-time-display-macros
Removed date and time display format form macros
2025-10-09 04:54:05 +01:00
akemidx
c0a99d6b52 notes 2025-10-08 19:20:32 -04:00
Marcus Moore
a1e65cd897 Merge branch 'develop' into replace-date-and-time-display-macros
# Conflicts:
#	resources/macros/macros.php
2025-10-08 13:10:56 -07:00
snipe
5d65f1ffc5 Merge pull request #17976 from marcusmoore/17204-replace-form-digit-separator
Fixed #17204 - replace Form::digit_separator macro
2025-10-08 21:01:11 +01:00
snipe
b7193a06fd Normalize advanced search 2025-10-08 20:51:35 +01:00
akemidx
cba090f8eb notes 2025-10-08 15:50:00 -04:00
snipe
0d6baa1081 Merge remote-tracking branch 'origin/develop' 2025-10-08 15:29:02 +01:00
snipe
bc60d796a3 Fixed RB-20329 - ambiguous clause on deleted_at 2025-10-08 15:28:48 +01:00
snipe
85a208526b Merge remote-tracking branch 'origin/develop' 2025-10-08 10:38:08 +01:00
snipe
c3ac0a750d Added licenses checkin permission 2025-10-08 10:37:58 +01:00
snipe
0f111127a5 Merge pull request #18005 from marcusmoore/fixes/drop-index-migration
Fixed exception when rolling back migrations
2025-10-08 05:26:22 +01:00
snipe
cd266a6bef Merge remote-tracking branch 'origin/develop' 2025-10-08 05:13:14 +01:00
snipe
0f4945621c Merge pull request #18009 from grokability/#18002-audit-image-location
Fixed #18002 - use correct path for audit images to determine validity
2025-10-08 05:12:54 +01:00
snipe
9a477f227b Fixed #18002 - use correct path for audit images to determine validity 2025-10-08 05:09:04 +01:00
akemidx
3c81257325 fix? 2025-10-07 20:23:46 -04:00
Marcus Moore
218fe9ebdc Allow Laravel to calculate index name 2025-10-07 16:04:12 -07:00
spencerrlongg
24bb45ab97 change translation 2025-10-07 13:26:08 -05:00
snipe
85f39de540 Merge pull request #18001 from grokability/#17670-fixes-advanced-search-on-assets
Fixed #17670 - advanced search on relationships not working
2025-10-07 17:35:05 +01:00
snipe
5cc3277e2d Merge pull request #18003 from uberbrady/fix_file_upload_base64
Fixed [FD-50921] - base64-encoded image files for asset creation was broken
2025-10-07 17:34:53 +01:00
snipe
d5ef7f3204 Merge remote-tracking branch 'origin/develop' 2025-10-07 17:29:24 +01:00
snipe
cb47d01f51 Fixed variable name in location print 2025-10-07 17:29:12 +01:00
Brady Wetherington
ee499c1385 Fix base64-encoded image files for asset creation; add test 2025-10-07 16:00:16 +01:00
snipe
4ae4af5c10 Fixed #17670 - advanced search on relationships not working 2025-10-07 15:39:09 +01:00
snipe
b0f5fe7e25 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:32:33 +01:00
snipe
fd32585efc Added notes back to audit email 2025-10-07 14:32:24 +01:00
snipe
c675bb7252 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:29:21 +01:00
snipe
e65d11d71e Pulled incorrect button label 2025-10-07 14:29:10 +01:00
snipe
c2680334f1 Clearer title on maintenances within asset context 2025-10-07 14:28:38 +01:00
snipe
1217a02ec1 Merge pull request #17992 from marcusmoore/17963-undelete-files-command
Added command to remove invalid "upload deleted" entries from the action log
2025-10-07 14:17:56 +01:00
snipe
143a091f45 Merge remote-tracking branch 'origin/develop' 2025-10-07 14:15:59 +01:00
snipe
49ecc93dbe Merge pull request #17999 from grokability/updated-audit-report
Improved upcoming audit email layout and cli feedback
2025-10-07 14:15:44 +01:00
snipe
1735fb6bed Improved audit email 2025-10-07 14:08:33 +01:00
snipe
8fefc11b4d Merge remote-tracking branch 'origin/develop' 2025-10-07 12:09:32 +01:00
snipe
1a3b22171c Merge pull request #17997 from grokability/#17924-add-url-to-maintenances
Fixed #17924 - added url to maintenances
2025-10-07 12:09:06 +01:00
snipe
5e773be260 Enhanced tests 2025-10-07 12:05:37 +01:00
snipe
c317a1dc8b Added url to factory 2025-10-07 12:03:35 +01:00
snipe
9401ffc83c Added link to view 2025-10-07 11:55:32 +01:00
snipe
479a852446 Fixed #17924 - added url to maintenances 2025-10-07 11:49:10 +01:00
snipe
9188fb03f0 Merge remote-tracking branch 'origin/develop' 2025-10-07 10:49:59 +01:00
snipe
36bb91cbc3 Merge pull request #17995 from grokability/#17910-show-counts-on-mobile
Fixed #17910 - added counts to mobile view for assets
2025-10-07 10:47:41 +01:00
snipe
56e5ab8bc6 Fixed #17910 - added counts to mobile view for assets 2025-10-07 10:41:03 +01:00
snipe
607eb6ca03 Merge remote-tracking branch 'origin/develop' 2025-10-07 09:42:59 +01:00
snipe
ad745cc84b Fixed #17911 - updated language string 2025-10-07 09:42:30 +01:00
Marcus Moore
5a13d5ea6f Update command description 2025-10-06 15:23:55 -07:00
Marcus Moore
0b065eb7fe Improve command 2025-10-06 15:20:43 -07:00
Marcus Moore
e2c0e4bc66 WIP build out command 2025-10-06 15:14:15 -07:00
Marcus Moore
655b0e6778 Scaffold command 2025-10-06 14:59:25 -07:00
Marcus Moore
cd785c9fc3 Remove name_display_format macro that was accidentally re-added 2025-10-06 13:06:52 -07:00
Marcus Moore
23885f5166 Merge branch 'develop' into 17204-replace-form-digit-separator
# Conflicts:
#	resources/macros/macros.php
2025-10-06 13:04:34 -07:00
Marcus Moore
689d5a2d58 Remove digit_separator form macro 2025-10-02 15:39:28 -07:00
Marcus Moore
b2e9eb866c Replace digit_separator form macro
Fixes #17204
2025-10-02 15:38:59 -07:00
Marcus Moore
c8b7782d1d Remove date_display_format and time_display_format macros 2025-10-02 15:27:20 -07:00
Marcus Moore
673f936689 Replace Form:: time_display_format on localization screen 2025-10-02 15:26:31 -07:00
Marcus Moore
2ca0d39e51 Replace Form:: date_display_format on localization screen 2025-10-02 15:21:51 -07:00
spencerrlongg
b9f4dc1e9d translation 2025-10-02 12:00:52 -05:00
akemidx
b082fb6692 investigation and notes 2025-10-01 18:21:25 -04:00
akemidx
a384245368 investigation and notes 2025-09-30 16:33:33 -04:00
spencerrlongg
32882f81e7 replace box-default 2025-09-29 15:52:43 -05:00
spencerrlongg
36f5099932 added new counts and throw new exceptions and catch them 2025-09-29 15:16:41 -05:00
spencerrlongg
1d24b7985b another translation change 2025-08-27 17:25:17 -05:00
spencerrlongg
526bb2c650 more translation changes 2025-08-27 17:23:46 -05:00
spencerrlongg
c450c0ddb8 lots of translation changes 2025-08-27 17:17:18 -05:00
spencerrlongg
51f6927076 add details block for more than 3 errors to notification 2025-08-26 21:50:08 -05:00
spencerrlongg
1d88cf443f revert SuppliersController as well 2025-08-25 18:45:31 -05:00
spencerrlongg
7b6c0c3a40 Revert "tests passing, needs some manual testing"
This reverts commit fdb0651bf4.
2025-08-25 18:42:11 -05:00
spencerrlongg
fdb0651bf4 tests passing, needs some manual testing 2025-08-25 18:25:36 -05:00
spencerrlongg
c39d484611 rm unused import, new prop 2025-08-25 16:30:53 -05:00
spencerrlongg
c42996429f rm unused import 2025-08-25 14:10:12 -05:00
spencerrlongg
a091baf5a6 component finally working 2025-08-25 14:06:56 -05:00
spencerrlongg
643d44af22 change \Throwable to \Exceptionm, add missing report()s 2025-08-20 23:09:59 -05:00
spencerrlongg
b934f43db0 rename all exceptions 2025-08-20 22:58:39 -05:00
spencerrlongg
e33b1b6c90 fixed maintenances 2025-08-12 13:34:40 -05:00
Spencer Long
30520297e8 Merge branch 'develop' into feature/8709-bulk-deletion-of-asset-categories-suppliers-manufacturers 2025-08-12 11:58:11 -06:00
spencerrlongg
78ca1d1335 some cleanup 2025-08-11 21:07:08 -05:00
spencerrlongg
6159ee8c2c category done! 2025-08-11 20:16:43 -05:00
spencerrlongg
5cd5392958 manufacturer completed, just categories left 2025-08-11 19:42:09 -05:00
spencerrlongg
0dcdfc5d14 fix tests after routing change 2025-08-11 19:11:40 -05:00
spencerrlongg
d0e068f1c0 suppliers completely done, rinse and repeat for the other two 2025-08-11 19:04:36 -05:00
spencerrlongg
3eefeec4ce partials made, need to figure out all this jquery, button disabled 2025-08-09 22:40:00 -05:00
spencerrlongg
b61419c1ce oop, revert delete 2025-08-09 22:19:28 -05:00
spencerrlongg
f590fcffbc some tests, a component i probably won't use, beginning of front end 2025-08-09 22:16:23 -05:00
spencerrlongg
7cdfaa93ec a couple more tests and cleanup 2025-07-10 16:45:15 -05:00
spencerrlongg
59ccc70303 bulk changes that should make this work 2025-07-10 16:02:39 -05:00
spencerrlongg
f1584b722d work on bulk tests, switching branches to check something 2025-07-10 15:29:42 -05:00
spencerrlongg
4d8c5a86a4 routes added, tests scratched but need writing 2025-07-09 23:01:58 -05:00
spencerrlongg
5f835aa009 delete unused imports 2025-07-07 18:07:26 -05:00
spencerrlongg
d5ca543719 start introducing parent exception 2025-07-07 18:05:23 -05:00
spencerrlongg
db63ad1cf4 add image check to supplier action 2025-06-17 18:13:37 -05:00
spencerrlongg
5da79cd5ca destroy manufacturer action, bulk manufacturer controller 2025-06-12 17:11:45 -05:00
spencerrlongg
13c971b171 some refinements, bulk categories controller 2025-06-12 15:19:49 -05:00
spencerrlongg
1cee7e43ed more (reusable) exceptions, a couple notes 2025-06-12 00:47:57 -05:00
spencerrlongg
5e81c63d6e some notes and things moved 2025-06-11 11:39:14 -05:00
spencerrlongg
17456482d6 category destroy action 2025-06-04 21:42:58 -05:00
spencerrlongg
a0431e1912 supplier actions working 2025-06-04 20:44:18 -05:00
spencerrlongg
ee5aac8008 supplier destroy action creaated and a lot scratched out 2025-06-03 20:25:07 -05:00
679 changed files with 7979 additions and 3275 deletions

View File

@@ -190,6 +190,7 @@ APP_ALLOW_INSECURE_HOSTS=false
GOOGLE_MAPS_API=
LDAP_MEM_LIM=500M
LDAP_TIME_LIM=600
BACKUP_TIME_LIMIT=600
IMPORT_TIME_LIMIT=600
IMPORT_MEMORY_LIMIT=500M
REPORT_TIME_LIMIT=12000

View File

@@ -30,10 +30,10 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4

View File

@@ -52,6 +52,6 @@ jobs:
# Upload the SARIF file generated in the previous step
- name: Upload SARIF results file
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif

View File

@@ -82,7 +82,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -81,7 +81,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -67,7 +67,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Actions\Categories;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Category;
use Illuminate\Support\Facades\Storage;
class DestroyCategoryAction
{
/**
* @throws ItemStillHasAssets
* @throws ItemStillHasAssetModels
* @throws ItemStillHasComponents
* @throws ItemStillHasAccessories
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Category $category): bool
{
$category->loadCount([
'assets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
'models as models_count'
]);
if ($category->assets_count > 0) {
throw new ItemStillHasAssets($category);
}
if ($category->accessories_count > 0) {
throw new ItemStillHasAccessories($category);
}
if ($category->consumables_count > 0) {
throw new ItemStillHasConsumables($category);
}
if ($category->components_count > 0) {
throw new ItemStillHasComponents($category);
}
if ($category->licenses_count > 0) {
throw new ItemStillHasLicenses($category);
}
if ($category->models_count > 0) {
throw new ItemStillHasAssetModels($category);
}
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
return true;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Actions\Manufacturers;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Manufacturer;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class DeleteManufacturerAction
{
/**
* @throws ItemStillHasAssets
* @throws ItemStillHasComponents
* @throws ItemStillHasAccessories
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Manufacturer $manufacturer): bool
{
$manufacturer->loadCount([
'assets as assets_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
]);
if ($manufacturer->assets_count > 0) {
throw new ItemStillHasAssets($manufacturer);
}
if ($manufacturer->accessories_count > 0) {
throw new ItemStillHasAccessories($manufacturer);
}
if ($manufacturer->consumables_count > 0) {
throw new ItemStillHasConsumables($manufacturer);
}
if ($manufacturer->components_count > 0) {
throw new ItemStillHasComponents($manufacturer);
}
if ($manufacturer->licenses_count > 0) {
throw new ItemStillHasLicenses($manufacturer);
}
if ($manufacturer->image) {
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
Log::info($e);
}
}
$manufacturer->delete();
//dd($manufacturer);
return true;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Actions\Suppliers;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Models\Supplier;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasLicenses;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class DestroySupplierAction
{
/**
*
* @throws ItemStillHasLicenses
* @throws ItemStillHasAssets
* @throws ItemStillHasMaintenances
* @throws ItemStillHasAccessories
* @throws ItemStillHasConsumables
* @throws ItemStillHasComponents
*/
static function run(Supplier $supplier): bool
{
$supplier->loadCount([
'maintenances as maintenances_count',
'assets as assets_count',
'licenses as licenses_count',
'accessories as accessories_count',
'consumables as consumables_count',
'components as components_count',
]);
if ($supplier->assets_count > 0) {
throw new ItemStillHasAssets($supplier);
}
if ($supplier->maintenances_count > 0) {
throw new ItemStillHasMaintenances($supplier);
}
if ($supplier->licenses_count > 0) {
throw new ItemStillHasLicenses($supplier);
}
if ($supplier->accessories_count > 0) {
throw new ItemStillHasAccessories($supplier);
}
if ($supplier->consumables_count > 0) {
throw new ItemStillHasConsumables($supplier);
}
if ($supplier->components_count > 0) {
throw new ItemStillHasComponents($supplier);
}
if ($supplier->image) {
try {
Storage::disk('public')->delete('suppliers/'.$supplier->image);
} catch (\Exception $e) {
Log::info($e->getMessage());
}
}
$supplier->delete();
return true;
}
}

View File

@@ -317,9 +317,21 @@ class LdapSync extends Command
if($ldap_map["jobtitle"] != null){
$user->jobtitle = $item['jobtitle'];
}
if($ldap_map["address"] != null){
$user->address = $item['address'];
}
if($ldap_map["city"] != null){
$user->city = $item['city'];
}
if($ldap_map["state"] != null){
$user->state = $item['state'];
}
if($ldap_map["country"] != null){
$user->country = $item['country'];
}
if($ldap_map["zip"] != null){
$user->zip = $item['zip'];
}
if($ldap_map["dept"] != null){
$user->department_id = $department->id;
}

View File

@@ -8,8 +8,6 @@ use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Helper\ProgressIndicator;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
/**
* Class ObjectImportCommand
@@ -52,6 +50,9 @@ class ObjectImportCommand extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
$this->progressIndicator = new ProgressIndicator($this->output);
$filename = $this->argument('filename');

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Models\Actionlog;
use Illuminate\Console\Command;
class RemoveInvalidUploadDeleteActionLogItems extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:remove-invalid-upload-delete-action-log-items';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Permanently remove invalid "upload deleted" action log items that have a null filename. This command can potentially result in deleted files being "resurrected" in the UI.';
/**
* Execute the console command.
*/
public function handle()
{
$invalidLogs = Actionlog::query()
->where('action_type', 'upload deleted')
->whereNull('filename')
->withTrashed()
->get();
$this->info("{$invalidLogs->count()} invalid log items found.");
if ($invalidLogs->count() === 0) {
return 0;
}
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn($log) => [
$log->id,
$log->action_type,
$log->item_type,
$log->item_id,
$log->created_at,
$log->deleted_at,
])->toArray());
if ($this->confirm("Do you wish to remove {$invalidLogs->count()} log items?")) {
$invalidLogs->each(fn($log) => $log->forceDelete());
}
return 0;
}
}

View File

@@ -107,7 +107,7 @@ class SendExpirationAlerts extends Command
trans('general.name') => $item->name,
trans('general.purchase_date') => $item->purchase_date_formatted,
trans('admin/licenses/form.expiration') => $item->expires_formatted_date,
trans('mail.expires') => $item->expires_diff_for_humans,
trans('mail.expires') => $item->expires_formatted_date ? $item->expires_diff_for_humans : '',
trans('admin/licenses/form.termination_date') => $item->terminates_formatted_date,
trans('mail.terminates') => $item->terminates_diff_for_humans
])

View File

@@ -16,7 +16,7 @@ class SendUpcomingAuditReport extends Command
*
* @var string
*/
protected $signature = 'snipeit:upcoming-audits';
protected $signature = 'snipeit:upcoming-audits {--with-output : Display the results in a table in your console in addition to sending the email}';
/**
* The console command description.
@@ -47,21 +47,69 @@ 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');
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(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
$assets_query = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'asc')->with('supplier');
$asset_count = $assets_query->count();
$this->info(number_format($asset_count) . ' assets must be audited on or before ' . $interval_date);
if (!$this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
if ($asset_count > 0) {
$assets_for_email = $assets_query->limit(30)->get();
// Send a rollup to the admin, if settings dictate
if ($settings->alert_email != '') {
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets_for_email, $settings->audit_warning_days, $asset_count));
$this->info('Audit notification sent to: ' . $settings->alert_email);
} else {
$this->info('There is no admin alert email set so no email will be sent.');
}
if ($this->option('with-output')) {
// Get the full list if the user wants output in the console
$assets_for_output = $assets_query->limit(null)->get();
$this->table(
[
trans('general.id'),
trans('general.name'),
trans('general.last_audit'),
trans('general.next_audit_date'),
trans('mail.Days'),
trans('mail.supplier'),
trans('mail.assigned_to'),
],
$assets_for_output->map(fn($item) => [
trans('general.id') => $item->id,
trans('general.name') => $item->display_name,
trans('general.last_audit') => $item->last_audit_formatted_date,
trans('general.next_audit_date') => $item->next_audit_formatted_date,
trans('mail.Days') => round($item->next_audit_diff_in_days),
trans('mail.supplier') => $item->supplier ? $item->supplier->name : '',
trans('mail.assigned_to') => $item->assignedTo ? $item->assignedTo->display_name : '',
])
);
}
} else {
$this->info('There are no assets due for audit in the next ' . $interval . ' days.');
}
}
}

View File

@@ -37,6 +37,8 @@ class SystemBackup extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('BACKUP_TIME_LIMIT', 600)); //600 seconds = 10 minutes
if ($this->option('filename')) {
$filename = $this->option('filename');

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAccessories extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAssetModels extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasAssets extends ItemStillHasChildren
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasChildren extends Exception
{
//public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
//{
// trans()
//
//}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasComponents extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasConsumables extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasLicenses extends ItemStillHasChildren
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ItemStillHasMaintenances extends ItemStillHasChildren
{
//
}

View File

@@ -40,6 +40,8 @@ class IconHelper
return 'fa-solid fa-trash-arrow-up';
case 'external-link':
return 'fa fa-external-link';
case 'link':
return 'fa fa-link';
case 'email':
return 'fa-regular fa-envelope';
case 'phone':
@@ -140,7 +142,7 @@ class IconHelper
case 'more-files':
return 'fa-solid fa-laptop-file';
case 'maintenances':
return 'fas fa-wrench';
return 'fa-solid fa-screwdriver-wrench';
case 'seats':
return 'far fa-list-alt';
case 'globe-us':
@@ -195,6 +197,10 @@ class IconHelper
case 'note':
case 'notes':
return 'fas fa-sticky-note';
case 'tip':
return 'fa-solid fa-lightbulb';
case 'highlight':
return 'fa-solid fa-highlighter';
}
}
}

View File

@@ -116,8 +116,6 @@ class AcceptanceController extends Controller
$item = $acceptance->checkoutable_type::find($acceptance->checkoutable_id);
// If signatures are required, make sure we have one
if (Setting::getSettings()->require_accept_signature == '1') {
@@ -157,16 +155,16 @@ class AcceptanceController extends Controller
'accepted_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'declined_date' => Helper::getFormattedDateObject(now()->format('Y-m-d H:i:s'), 'datetime', false),
'assigned_to' => $assigned_user->display_name,
'email' => $assigned_user->email,
'employee_num' => $assigned_user->employee_num,
'site_name' => $settings->site_name,
'company_name' => $item->company?->name?? $settings->site_name,
'signature' => (($sig_filename && array_key_exists('1', $encoded_image))) ? $encoded_image[1] : null,
'logo' => ($encoded_logo) ?? null,
'date_settings' => $settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
'qty' => $acceptance->qty ?? 1,
];
if ($request->input('asset_acceptance') == 'accepted') {

View File

@@ -54,6 +54,15 @@ class AccessoriesController extends Controller
'notes',
'checkouts_count',
'qty',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'supplier',
'manufacturer',
];
@@ -61,10 +70,23 @@ class AccessoriesController extends Controller
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
->withCount('checkouts as checkouts_count');
if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search'));
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$accessories->ByFilter($filter);
} elseif ($request->filled('search')) {
$accessories->TextSearch($request->input('search'));
}
if ($request->filled('company_id')) {
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
}

View File

@@ -54,6 +54,12 @@ class AssetModelsController extends Controller
'deleted_at',
'updated_at',
'require_serial',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'manufacturer',
'category',
];
$assetmodels = AssetModel::select([
@@ -81,6 +87,24 @@ class AssetModelsController extends Controller
->withCount('assignedAssets as assets_assigned_count')
->withCount('archivedAssets as assets_archived_count');
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assetmodels->ByFilter($filter);
} elseif ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search'));
}
if ($request->input('status')=='deleted') {
$assetmodels->onlyTrashed();
}

View File

@@ -116,6 +116,22 @@ class AssetsController extends Controller
'asset_eol_date',
'requestable',
'jobtitle',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'model',
'location',
'rtd_location',
'category',
'status_label',
'manufacturer',
'supplier',
'jobtitle',
'assigned_to',
'created_by',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
@@ -132,6 +148,7 @@ class AssetsController extends Controller
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
$assets = Asset::select('assets.*')
@@ -166,7 +183,7 @@ class AssetsController extends Controller
// Search custom fields by column name
foreach ($all_custom_fields as $field) {
if ($request->filled($field->db_column_name()) && $field->db_column_name()) {
$assets->where($field->db_column_name(), '=', $request->input($field->db_column_name()));
$assets->where('assets.'.$field->db_column_name(), '=', $request->input($field->db_column_name()));
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers\Api;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasChildren;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\CategoriesTransformer;
@@ -61,6 +63,23 @@ class CategoriesController extends Controller
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$categories->ByFilter($filter);
} elseif ($request->filled('search')) {
$categories->TextSearch($request->input('search'));
}
/*
* This checks to see if we should override the Admin Setting to show archived assets in list.
* We don't currently use it within the Snipe-IT GUI, but will be useful for API integrations where they
@@ -74,10 +93,6 @@ class CategoriesController extends Controller
$categories = $categories->withCount('showableAssets as assets_count');
}
if ($request->filled('search')) {
$categories = $categories->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$categories->where('name', '=', $request->input('name'));
}
@@ -211,17 +226,21 @@ class CategoriesController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id) : JsonResponse
public function destroy(Category $category): JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
if (! $category->isDeletable()) {
try {
DestroyCategoryAction::run(category: $category);
} catch (ItemStillHasChildren $e) {
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.assoc_items', ['asset_type'=>$category->category_type]))
Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['asset_type' => $category->category_type]))
);
} catch (\Exception $e) {
report($e);
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))
);
}
$category->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
}

View File

@@ -45,16 +45,40 @@ class ComponentsController extends Controller
'qty',
'image',
'notes',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'manufacturer',
'supplier',
];
$components = Component::select('components.*')
->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'));
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$components->ByFilter($filter);
} elseif ($request->filled('search')) {
$components->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$components->where('name', '=', $request->input('name'));
}

View File

@@ -31,10 +31,53 @@ class ConsumablesController extends Controller
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer')
->withCount('users as consumables_users_count');
if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search')));
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'category',
'supplier',
'manufacturer',
];
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$consumables->ByFilter($filter);
} elseif ($request->filled('search')) {
$consumables->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$consumables->where('name', '=', $request->input('name'));
}
@@ -96,25 +139,6 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderByCreatedBy($order);
break;
default:
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image'
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$consumables = $consumables->orderBy($sort, $order);
break;

View File

@@ -52,6 +52,10 @@ class MaintenancesController extends Controller
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
}
if ($request->filled('url')) {
$maintenances->where('maintenances.url', '=', $request->input('url'));
}
if ($request->filled('asset_maintenance_type')) {
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
}

View File

@@ -2,6 +2,13 @@
namespace App\Http\Controllers\Api;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ManufacturersTransformer;
@@ -184,19 +191,19 @@ class ManufacturersController extends Controller
* @since [v4.0]
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy(Manufacturer $manufacturer): JsonResponse
{
$this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
$this->authorize('delete', $manufacturer);
if ($manufacturer->isDeletable()) {
$manufacturer->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
try {
DeleteManufacturerAction::run($manufacturer);
} catch (ItemStillHasChildren $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')])));
} catch (\Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.assoc_users')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/manufacturers/message.delete.success')));
}
/**

View File

@@ -2,6 +2,13 @@
namespace App\Http\Controllers\Api;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\SelectlistTransformer;
@@ -191,27 +198,40 @@ class SuppliersController extends Controller
* @since [v4.0]
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy(Supplier $supplier): JsonResponse
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
$this->authorize('delete', $supplier);
if ($supplier->assets_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_assets', [
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasMaintenances $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_maintenances', [
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasLicenses $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_licenses', [
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasAccessories $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_accessories', [
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasConsumables $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_consumables', [
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
])));
} catch (ItemStillHasComponents $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.assoc_components', [
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
])));
} catch (\Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
if ($supplier->maintenances_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])));
}
if ($supplier->licenses_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count])));
}
$supplier->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/suppliers/message.delete.success')));
}

View File

@@ -103,9 +103,75 @@ class UsersController extends Controller
'managedLocations as manages_locations_count'
]);
$allowed_columns =
[
'last_name',
'first_name',
'display_name',
'email',
'jobtitle',
'username',
'employee_num',
'groups',
'activated',
'created_at',
'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
'assets_count',
'licenses_count',
'consumables_count',
'accessories_count',
'manages_users_count',
'manages_locations_count',
'phone',
'mobile',
'address',
'city',
'state',
'country',
'zip',
'id',
'ldap_import',
'two_factor_optin',
'two_factor_enrolled',
'remote',
'vip',
'start_date',
'end_date',
'autoassign_licenses',
'website',
'locale',
'notes',
'employee_num',
if ($request->filled('search') != '') {
$users = $users->TextSearch($request->input('search'));
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
// BUT we account for them in the ordering switch down at the end of this method
// DO NOT ADD ANYTHING TO THIS LIST WITHOUT CHECKING THE ORDERING SWITCH BELOW!
'company',
'location',
'department',
'manager',
'created_by',
];
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$users->ByFilter($filter);
} elseif ($request->filled('search')) {
$users->TextSearch($request->input('search'));
}
if ($request->filled('activated')) {
@@ -286,49 +352,6 @@ class UsersController extends Controller
$users->orderBy('first_name', $order);
break;
default:
$allowed_columns =
[
'last_name',
'first_name',
'display_name',
'email',
'jobtitle',
'username',
'employee_num',
'groups',
'activated',
'created_at',
'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
'assets_count',
'licenses_count',
'consumables_count',
'accessories_count',
'manages_users_count',
'manages_locations_count',
'phone',
'mobile',
'address',
'city',
'state',
'country',
'zip',
'id',
'ldap_import',
'two_factor_optin',
'two_factor_enrolled',
'remote',
'vip',
'start_date',
'end_date',
'autoassign_licenses',
'website',
'locale',
'notes',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
$users = $users->orderBy($sort, $order);
break;

View File

@@ -240,10 +240,6 @@ class BulkAssetsController extends Controller
$custom_fields_to_null[str_replace('null', '', $key)] = $value;
}
if (! $request->filled('ids') || count($request->input('ids')) == 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
@@ -274,6 +270,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('company_id'))
|| ($request->filled('status_id'))
|| ($request->filled('model_id'))
|| ($request->filled('notes'))
|| ($request->filled('next_audit_date'))
|| ($request->filled('asset_eol_date'))
|| ($request->filled('null_name'))

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Category;
use Illuminate\Http\Request;
class BulkCategoriesController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Category::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$category = Category::find($id);
if (is_null($category)) {
$errors[] = trans('admin/categories/message.does_not_exist');
continue;
}
try {
DestroyCategoryAction::run(category: $category);
$success_count++;
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasAssetModels) {
$errors[] = trans('general.bulk_delete_associations.assoc_asset_models_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasAssets) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasComponents) {
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasConsumables) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);
} catch (ItemStillHasLicenses) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $category->name, 'item' => trans('general.category')]);;
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('categories.index')->with('success', trans_choice('admin/categories/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('categories.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.bulk_success'));
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
class BulkManufacturersController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Manufacturer::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$manufacturer = Manufacturer::find($id);
if (is_null($manufacturer)) {
$errors[] = trans('admin/manufacturers/message.does_not_exist');
continue;
}
try {
DeleteManufacturerAction::run(manufacturer: $manufacturer);
$success_count++;
} catch (ItemStillHasAssets $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_accessories_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasConsumables $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasComponents $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_components_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);
} catch (ItemStillHasLicenses $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses_no_count', ['item_name' => $manufacturer->name, 'item' => trans('general.manufacturer')]);;
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('manufacturers.index')->with('success', trans_choice('admin/manufacturers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('manufacturers.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.bulk_success'));
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Models\Supplier;
use Illuminate\Http\Request;
class BulkSuppliersController extends Controller
{
public function destroy(Request $request)
{
$this->authorize('delete', Supplier::class);
$errors = [];
$success_count = 0;
foreach ($request->ids as $id) {
$supplier = Supplier::find($id);
if (is_null($supplier)) {
$errors[] = trans('admin/suppliers/message.delete.not_found');
continue;
}
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_assets', ['asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasMaintenances $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasLicenses $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasAccessories $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_accessories', ['accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasConsumables $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_consumables', ['consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (ItemStillHasComponents $e) {
$errors[] = trans('general.bulk_delete_associations.assoc_components', ['components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier'), 'item_name' => $supplier->name]);
} catch (\Exception $e) {
report($e);
$errors[] = trans('general.something_went_wrong');
}
}
if (count($errors) > 0) {
if ($success_count > 0) {
return redirect()->route('suppliers.index')->with('success', trans_choice('admin/suppliers/message.delete.partial_success', $success_count, ['count' => $success_count]))->with('multi_error_messages', $errors);
}
return redirect()->route('suppliers.index')->with('multi_error_messages', $errors);
} else {
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.bulk_success'));
}
}
}

View File

@@ -2,6 +2,14 @@
namespace App\Http\Controllers;
use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssetModels;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Category;
@@ -143,20 +151,18 @@ class CategoriesController extends Controller
* @since [v1.0]
* @param int $categoryId
*/
public function destroy($categoryId) : RedirectResponse
public function destroy(Category $category): RedirectResponse
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
try {
DestroyCategoryAction::run($category);
} catch (ItemStillHasChildren $e) {
return redirect()->route('categories.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.category')]));
} catch (\Exception $e) {
report($e);
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.delete.error'));
}
if (! $category->isDeletable()) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.assoc_items', ['asset_type'=> $category->category_type]));
}
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}

View File

@@ -45,6 +45,7 @@ abstract class Controller extends BaseController
'accessories' => Accessory::class,
'maintenances' => Maintenance::class,
'assets' => Asset::class,
'audits' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
@@ -58,6 +59,7 @@ abstract class Controller extends BaseController
'accessories' => 'private_uploads/accessories/',
'maintenances' => 'private_uploads/maintenances/',
'assets' => 'private_uploads/assets/',
'audits' => 'private_uploads/audits/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
@@ -71,6 +73,7 @@ abstract class Controller extends BaseController
'accessories' => 'accessory',
'maintenances' => 'maintenance',
'assets' => 'asset',
'audits' => 'audits',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',

View File

@@ -65,6 +65,7 @@ class GroupsController extends Controller
$group->notes = $request->input('notes');
if ($group->save()) {
$group->users()->sync($request->input('associated_users'));
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
}
@@ -88,7 +89,10 @@ class GroupsController extends Controller
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
$associated_users = $group->users()->get();
//dd($associated_users->toArray());
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))->with('associated_users', $associated_users);
}
/**
@@ -105,8 +109,10 @@ class GroupsController extends Controller
$group->permissions = json_encode($request->input('permission'));
$group->notes = $request->input('notes');
if (! config('app.lock_passwords')) {
if ($group->save()) {
$group->users()->sync($request->input('associated_users'));
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.update'));
}

View File

@@ -68,6 +68,12 @@ class MaintenancesController extends Controller
{
$this->authorize('update', Asset::class);
\Log::error(print_r($request->input('selected_assets[]'), true));
if (!$request->filled('selected_assets')) {
return redirect()->back()->withInput()->with('error', 'No assets were selected.');
}
$assets = Asset::whereIn('id', $request->input('selected_assets'))->get();
// Loop through the selected assets
@@ -78,6 +84,7 @@ class MaintenancesController extends Controller
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$maintenance->url = $request->input('url');
// Save the asset maintenance data
$maintenance->asset_id = $asset->id;
@@ -152,6 +159,7 @@ class MaintenancesController extends Controller
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
$maintenance->url = $request->input('url');
// Todo - put this in a getter/setter?

View File

@@ -2,6 +2,14 @@
namespace App\Http\Controllers;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Manufacturer;
@@ -157,32 +165,18 @@ class ManufacturersController extends Controller
* @param int $manufacturerId
* @since [v1.0]
*/
public function destroy($manufacturerId) : RedirectResponse
public function destroy(Manufacturer $manufacturer): RedirectResponse
{
$this->authorize('delete', Manufacturer::class);
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.not_found'));
$this->authorize('delete', $manufacturer);
try {
DeleteManufacturerAction::run($manufacturer);
} catch (ItemStillHasChildren $e) {
return redirect()->route('manufacturers.index')->with('error', trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')]));
} catch (\Exception $e) {
report($e);
return redirect()->route('manufacturers.index')->with('error', trans('general.something_went_wrong'));
}
if (! $manufacturer->isDeletable()) {
return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.assoc_users'));
}
if ($manufacturer->image) {
try {
Storage::disk('public')->delete('manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
Log::info($e);
}
}
// Soft delete the manufacturer if active, permanent delete if is already deleted
if ($manufacturer->deleted_at === null) {
$manufacturer->delete();
} else {
$manufacturer->forceDelete();
}
// Redirect to the manufacturers management page
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.delete.success'));
}

View File

@@ -436,10 +436,8 @@ class ReportsController extends Controller
// Open output stream
$handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
}
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
$header = [];

View File

@@ -2,10 +2,18 @@
namespace App\Http\Controllers;
use App\Actions\Suppliers\DestroySupplierAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasLicenses;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Supplier;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\MessageBag;
/**
* This controller handles all actions related to Suppliers for
@@ -118,30 +126,41 @@ class SuppliersController extends Controller
*
* @param int $supplierId
*/
public function destroy($supplierId) : RedirectResponse
public function destroy(Supplier $supplier): RedirectResponse
{
$this->authorize('delete', Supplier::class);
if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
try {
DestroySupplierAction::run(supplier: $supplier);
} catch (ItemStillHasAssets $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_assets', [
'asset_count' => (int) $supplier->assets_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasMaintenances $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_maintenances', [
'asset_maintenances_count' => $supplier->asset_maintenances_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasLicenses $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_licenses', [
'licenses_count' => (int) $supplier->licenses_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasAccessories $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_accessories', [
'accessories_count' => (int) $supplier->accessories_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasConsumables $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_consumables', [
'consumables_count' => (int) $supplier->consumables_count, 'item' => trans('general.supplier')
]));
} catch (ItemStillHasComponents $e) {
return redirect()->route('suppliers.index')->with('error', trans('general.bulk_delete_associations.assoc_components', [
'components_count' => (int) $supplier->components_count, 'item' => trans('general.supplier')
]));
} catch (\Exception $e) {
report($e);
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.error'));
}
if ($supplier->assets_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]));
}
if ($supplier->maintenances_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]));
}
if ($supplier->licenses_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_licenses', ['licenses_count' => (int) $supplier->licenses_count]));
}
$supplier->delete();
return redirect()->route('suppliers.index')->with('success',
trans('admin/suppliers/message.delete.success')
);
return redirect()->route('suppliers.index')->with('success', trans('admin/suppliers/message.delete.success'));
}
/**
@@ -154,6 +173,5 @@ class SuppliersController extends Controller
{
$this->authorize('view', Supplier::class);
return view('suppliers/view', compact('supplier'));
}
}

View File

@@ -109,7 +109,7 @@ class SettingsSamlRequest extends FormRequest
];
$pkey = openssl_pkey_new([
'private_key_bits' => config('app.saml_key_size'),
'private_key_bits' => (int) config('app.saml_key_size'),
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);

View File

@@ -26,6 +26,7 @@ class StoreAssetRequest extends ImageUploadRequest
public function prepareForValidation(): void
{
parent::prepareForValidation(); // call ImageUploadRequest thing
// Guard against users passing in an array for company_id instead of an integer.
// If the company_id is not an integer then we simply use what was
// provided to be caught by model level validation later.

View File

@@ -32,7 +32,7 @@ class StoreNotificationSettings extends FormRequest
],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
'audit_warning_days' => 'numeric|nullable|gte:0',
'due_checkin_days' => 'numeric|nullable|gt:0',
'audit_interval' => 'numeric|nullable|gt:0',
];

View File

@@ -2,6 +2,7 @@
namespace App\Http\Traits;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
@@ -38,20 +39,13 @@ trait ConvertsBase64ToFiles
if (!$base64Contents) {
return;
}
// autogenerate filenames
if ($filename == 'auto'){
$header = explode(';', $base64Contents, 2)[0];
// Grab the image type from the header while we're at it.
$filename = $key . '.' . substr($header, strpos($header, '/')+1);
}
// Generate a temporary path to store the Base64 contents
$tempFilePath = tempnam(sys_get_temp_dir(), $filename);
// Store the contents using a stream, or by decoding manually
// Store the contents using a stream, or throw an Error (which doesn't do anything?)
if (Str::startsWith($base64Contents, 'data:') && count(explode(',', $base64Contents)) > 1) {
$source = fopen($base64Contents, 'r');
$source = fopen($base64Contents, 'r'); // PHP has special processing for "data:" URL's
$destination = fopen($tempFilePath, 'w');
stream_copy_to_stream($source, $destination);
@@ -59,7 +53,8 @@ trait ConvertsBase64ToFiles
fclose($source);
fclose($destination);
} else {
file_put_contents($tempFilePath, base64_decode($base64Contents, true));
// TODO - to get a better error message here, can we maybe do something with modifying the errorBag?
throw new ValidationException("Need Base64 URL starting with 'data:'"); // This doesn't actually throw?
}
$uploadedFile = new UploadedFile($tempFilePath, $filename, null, null, true);

View File

@@ -149,6 +149,7 @@ class ActionlogsTransformer
'filename' => $actionlog->filename,
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_path()),
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
'mediatype' => StorageHelper::getMediaType($actionlog->uploads_file_path()),
] : null,
'item' => ($actionlog->item) ? [

View File

@@ -68,7 +68,7 @@ class AssetModelsTransformer
'default_fieldset_values' => $default_field_values,
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false,
'require_serial' => $assetmodel->require_serial,
'require_serial' => ($assetmodel->require_serial == '1') ? true : false,
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_by' => ($assetmodel->adminuser) ? [
'id' => (int) $assetmodel->adminuser->id,

View File

@@ -66,6 +66,7 @@ class MaintenancesTransformer
'id' => $assetmaintenance->supplier->id,
'name'=> e($assetmaintenance->supplier->name)
] : null,
'url' => ($assetmaintenance->url) ? e($assetmaintenance->url) : null,
'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost),
'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type),
'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'),

View File

@@ -44,7 +44,7 @@ class AssetImporter extends ItemImporter
foreach ($this->customFields as $customField) {
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
if ($customFieldValue) {
if (!is_null($customFieldValue)) {
if ($customField->field_encrypted == 1) {
$this->item['custom_fields'][$customField->db_column_name()] = Crypt::encrypt($customFieldValue);
$this->log('Custom Field '.$customField->name.': '.Crypt::encrypt($customFieldValue));

View File

@@ -87,7 +87,7 @@ class CheckoutAssetMail extends Mailable
$name = $this->target->assignedto?->display_name;
}
else if($this->target instanceof Location){
$name = $this->target->manager->name;
$name = $this->target->manager?->name;
}
// Check if the item has custom fields associated with it

View File

@@ -17,10 +17,11 @@ class SendUpcomingAuditMail extends Mailable
/**
* Create a new message instance.
*/
public function __construct($params, $threshold)
public function __construct($params, $threshold, $total)
{
$this->assets = $params;
$this->threshold = $threshold;
$this->total = $total;
}
/**
@@ -32,7 +33,7 @@ class SendUpcomingAuditMail extends Mailable
return new Envelope(
from: $from,
subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]),
subject: trans_choice('mail.upcoming-audits', $this->total, ['count' => $this->total, 'threshold' => $this->threshold]),
);
}
@@ -49,6 +50,7 @@ class SendUpcomingAuditMail extends Mailable
with: [
'assets' => $this->assets,
'threshold' => $this->threshold,
'total' => $this->total,
],
);
}

View File

@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Models\Traits\Acceptable;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -390,7 +391,91 @@ class Accessory extends SnipeModel
* BEGIN QUERY SCOPES
* -----------------------------------------------
**/
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'name') {
$query->where('accessories.name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'notes') {
$query->where('accessories.notes', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'model_number') {
$query->where('accessories.model_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'order_number') {
$query->where('accessories.order_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'purchase_cost') {
$query->where('accessories.purchase_cost', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'location') {
$query->whereHas(
'location', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'manufacturer') {
$query->whereHas(
'manufacturer', function ($query) use ($search_val) {
$query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'supplier') {
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'category') {
$query->whereHas(
'category', function ($query) use ($search_val) {
$query->where('categories.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'company') {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}
}
}
);
}
/**
* Query builder scope to order on created_by name

View File

@@ -478,6 +478,10 @@ class Actionlog extends SnipeModel
$object = 'models';
}
if ($this->action_type == 'audit') {
$object = 'audits';
}
return route('ui.files.show', [
'object_type' => $object,
'id' => $this->item_id,
@@ -493,6 +497,10 @@ class Actionlog extends SnipeModel
return 'private_uploads/eula-pdfs/'.$this->filename;
}
if ($this->action_type == 'audit') {
return 'private_uploads/audits/'.$this->filename;
}
switch ($this->item_type) {
case Accessory::class:
return 'private_uploads/accessories/'.$this->filename;

View File

@@ -9,6 +9,8 @@ use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Requestable;
use App\Models\Traits\Searchable;
use App\Presenters\AssetPresenter;
use App\Presenters\Presentable;
@@ -308,6 +310,54 @@ class Asset extends Depreciable
}
protected function lastAuditFormattedDate(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => Helper::getFormattedDateObject($this->last_audit_date, 'datetime', false)
);
}
protected function lastAuditDiff(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $this->warrantyExpires ? round((Carbon::now()->diffInDays($this->warrantyExpires))) : null,
);
}
protected function lastAuditDiffForHumans(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['last_audit_date'] ? Carbon::parse($attributes['last_audit_date'])->diffForHumans() : null,
);
}
protected function nextAuditFormattedDate(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => Helper::getFormattedDateObject($this->next_audit_date, 'date', false)
);
}
protected function nextAuditDiffInDays(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['next_audit_date'] ? Carbon::now()->diffInDays($attributes['next_audit_date']) : null,
);
}
protected function nextAuditDiffForHumans(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['next_audit_date'] ? Carbon::parse($attributes['next_audit_date'])->diffForHumans() : null,
);
}
protected function eolDate(): Attribute
{
@@ -326,6 +376,7 @@ class Asset extends Depreciable
}
protected function eolFormattedDate(): Attribute
{
return Attribute:: make(
@@ -350,6 +401,12 @@ class Asset extends Depreciable
}
protected function expectedCheckinFormattedDate(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => array_key_exists('expected_checkin', $attributes) ? Helper::getFormattedDateObject($attributes['expected_checkin'], 'date', false) : null,
);
}
/**
* Establishes the asset -> company relationship
@@ -882,25 +939,37 @@ class Asset extends Depreciable
*/
public static function getExpiringWarrantyOrEol($days = 30)
{
return self::where('archived', '=', '0')
$now = now();
$end = now()->addDays($days);
$expired_assets = self::query()
->where('archived', '=', '0')
->NotArchived()
->whereNull('deleted_at')
->where(function ($query) use ($days) {
// Check for manual asset EOL first
$query->where(function ($query) use ($days) {
$query->whereNotNull('asset_eol_date')
->whereBetween('asset_eol_date', [Carbon::now(), Carbon::now()->addDays($days)]);
// Otherwise use the warranty months + purchase date + threshold
})->orWhere(function ($query) use ($days) {
$query->whereNotNull('purchase_date')
->whereNotNull('warranty_months')
->whereBetween('purchase_date', [Carbon::now(), Carbon::now()->addMonths('assets.warranty_months')->addDays($days)]);
});
})
->orderBy('asset_eol_date', 'ASC')
->orderBy('purchase_date', 'ASC')
->whereNotNull('asset_eol_date')
->whereBetween('asset_eol_date', [$now, $end])
->get();
$assets_with_warranties = self::query()
->where('archived', '=', '0')
->NotArchived()
->whereNull('deleted_at')
->whereNotNull('purchase_date')
->whereNotNull('warranty_months')
->get();
$expired_warranties = $assets_with_warranties->filter(function ($asset) use ($now, $end) {
$expiration_window = Carbon::parse($asset->purchase_date)->addMonths((int) $asset->warranty_months);
return $expiration_window->betweenIncluded($now, $end);
});
return $expired_assets->concat($expired_warranties)
->unique('id')
->sortBy([
['asset_eol_date', 'ASC'],
['purchase_date', 'ASC']
])
->values();
}
@@ -1832,16 +1901,32 @@ class Asset extends Depreciable
);
}
if ($fieldname =='assigned_to') {
if ($fieldname == 'assigned_to') {
$query->whereHasMorph(
'assignedTo', [User::class], function ($query) use ($search_val) {
$query->where(
function ($query) use ($search_val) {
$query->where('users.first_name', 'LIKE', '%'.$search_val.'%')
->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%');
->orWhere('users.last_name', 'LIKE', '%'.$search_val.'%')
->orWhere('users.username', 'LIKE', '%'.$search_val.'%');
}
);
}
)->orWhereHasMorph(
'assignedTo', [Location::class], function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
)->orWhereHasMorph(
'assignedTo', [Asset::class], function ($query) use ($search_val) {
$query->where(
function ($query) use ($search_val) {
// Don't use the asset table prefix here because it will pull from the original asset,
// not the subselect we're doing here to get the assigned asset
$query->where('name', 'LIKE', '%'.$search_val.'%')
->orWhere('asset_tag', 'LIKE', '%'.$search_val.'%');
}
);
}
);
}
@@ -1881,51 +1966,44 @@ class Asset extends Depreciable
}
if ($fieldname == 'model') {
$query->where(
function ($query) use ($search_val) {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.name', 'LIKE', '%'.$search_val.'%');
}
);
}
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'model_number') {
$query->where(
function ($query) use ($search_val) {
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.model_number', 'LIKE', '%'.$search_val.'%');
}
);
}
$query->whereHas(
'model', function ($query) use ($search_val) {
$query->where('models.model_number', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'company') {
$query->where(
function ($query) use ($search_val) {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'supplier') {
$query->where(
function ($query) use ($search_val) {
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'status_label') {
$query->whereHas(
'assetstatus', function ($query) use ($search_val) {
$query->where('status_labels.name', 'LIKE', '%'.$search_val.'%');
}
);
}

View File

@@ -2,16 +2,18 @@
namespace App\Models;
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Requestable;
use App\Models\Traits\Searchable;
use App\Presenters\AssetModelPresenter;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
use \App\Presenters\AssetModelPresenter;
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
/**
* Model for Asset Models. Asset Models contain higher level
@@ -256,6 +258,57 @@ class AssetModel extends SnipeModel
* -----------------------------------------------
**/
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'name') {
$query->where('models.name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'notes') {
$query->where('models.notes', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'model_number') {
$query->where('models.model_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'category') {
$query->whereHas(
'category', function ($query) use ($search_val) {
$query->where('categories.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'manufacturer') {
$query->whereHas(
'manufacturer', function ($query) use ($search_val) {
$query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
}
}
);
}
/**
* scopeInCategory
* Get all models that are in the array of category ids

View File

@@ -290,6 +290,35 @@ class Category extends SnipeModel
* -----------------------------------------------
**/
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'name') {
$query->where('categories.name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'category_type') {
$query->where('categories.category_type', 'LIKE', '%' . $search_val . '%');
}
}
}
);
}
/**
* Query builder scope for whether or not the category requires acceptance
*

View File

@@ -190,6 +190,11 @@ class CheckoutAcceptance extends Model
}
$pdf->Ln();
// Check for CJK in the translation string for date. (This is a good proxy for the rest of the document)
Helper::hasRtl(trans('general.date')) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk(trans('general.date')) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(trans('general.date') . ': ' . Helper::getFormattedDateObject(now(), 'datetime', false), true, 0, true, 0, '');
if ($data['company_name'] != null) {
@@ -210,7 +215,10 @@ class CheckoutAcceptance extends Model
if (($data['qty'] != null) && ($data['qty'] > 1)) {
$pdf->writeHTML(trans('general.qty').': '.e($data['qty']), true, 0, true, 0, '');
}
$pdf->writeHTML(trans('general.assignee').': '.e($data['assigned_to']), true, 0, true, 0, '');
$pdf->writeHTML(trans('general.assignee').': '.e($data['assigned_to']) . ($data['employee_num'] ? ' ('.$data['employee_num'].')' : ''), true, 0, true, 0, '');
if ($data['email'] != null) {
$pdf->writeHTML(trans('general.email').': '.e($data['email']), true, 0, true, 0, '');
}
$pdf->Ln();
$pdf->writeHTML('<hr>', true, 0, true, 0, '');
@@ -221,7 +229,6 @@ class CheckoutAcceptance extends Model
foreach ($eula_lines as $eula_line) {
Helper::hasRtl($eula_line) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk($eula_line) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(Helper::parseEscapedMarkedown($eula_line), true, 0, true, 0, '');
}
$pdf->Ln();
@@ -236,8 +243,11 @@ class CheckoutAcceptance extends Model
$pdf->Ln();
}
Helper::hasRtl(trans('general.notes')) ? $pdf->setRTL(true) : $pdf->setRTL(false);
Helper::isCjk(trans('general.notes')) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
if ($data['note'] != null) {
Helper::isCjk($data['note']) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
Helper::isCjk(trans('general.notes')) ? $pdf->SetFont('cid0cs', '', 9) : $pdf->SetFont('dejavusans', '', 8, '', true);
$pdf->writeHTML(trans('general.notes') . ': ' . e($data['note']), true, 0, true, 0, '');
$pdf->Ln();
}

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -321,6 +322,97 @@ class Component extends SnipeModel
* -----------------------------------------------
**/
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'name') {
$query->where('components.name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'notes') {
$query->where('components.notes', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'model_number') {
$query->where('components.model_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'order_number') {
$query->where('components.order_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'serial') {
$query->where('components.serial', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'serial') {
$query->where('components.serial', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'purchase_cost') {
$query->where('components.purchase_cost', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'location') {
$query->whereHas(
'location', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'manufacturer') {
$query->whereHas(
'manufacturer', function ($query) use ($search_val) {
$query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'supplier') {
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'category') {
$query->whereHas(
'category', function ($query) use ($search_val) {
$query->where('categories.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'company') {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}
}
}
);
}
/**
* Query builder scope to order on company

View File

@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Models\Traits\Acceptable;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\ConsumablePresenter;
use App\Presenters\Presentable;
@@ -345,6 +346,99 @@ class Consumable extends SnipeModel
* -----------------------------------------------
**/
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'name') {
$query->where('consumables.name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'notes') {
$query->where('consumables.notes', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'model_number') {
$query->where('consumables.model_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'order_number') {
$query->where('consumables.order_number', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'item_no') {
$query->where('consumables.item_no', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'serial') {
$query->where('consumables.serial', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'purchase_cost') {
$query->where('consumables.purchase_cost', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'location') {
$query->whereHas(
'location', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'manufacturer') {
$query->whereHas(
'manufacturer', function ($query) use ($search_val) {
$query->where('manufacturers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'supplier') {
$query->whereHas(
'supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'category') {
$query->whereHas(
'category', function ($query) use ($search_val) {
$query->where('categories.name', 'LIKE', '%'.$search_val.'%');
}
);
}
if ($fieldname == 'company') {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%'.$search_val.'%');
}
);
}
}
}
);
}
/**
* Query builder scope to order on company
*

View File

@@ -104,6 +104,78 @@ class Ldap extends Model
return $connection;
}
/**
* Finds user via Admin search *first*, and _then_ try to bind as that user, returning the user attributes on success,
* or false on failure. This enables login when the DN is harder to programmatically 'guess' due to having users in
* various different OU's or other LDAP entities.
*/
public static function findAndBindMultiOU(string $baseDn, string $filterQuery, string $password, int $slow_failure = 3): array|false
{
/**
* If you *don't* set the slow_failure variable, do note that we might permit timing attacks in here - if
* your find results come back 'slow' when a user *does* exist, but fast if they *don't* exist, then you
* can use this to enumerate users.
*
* Even if that's *not* true, we still might have an issue: if we don't find the user, then we don't even _try_
* to bind as them. Again, that could permit a timing attack.
*
* Instead of checking every little thing, we just wrap everything in a try/catch in order to unify the
* 'slow_failure' treatment. All failures are re-raised as exceptions so that all failures exit from the
* same place.
*/
$connection = null;
$admin_conn = null;
try {
/**
* First we get an 'admin' connection, which will need search permissions. That was already a requirement
* here, so that's not a big lift. But it _is_ possible to configure LDAP to only login, and *not* to be
* able to import lists of users. In that case, this function *will not work* - and you should use the
* legacy 'findAndBindUserLdap' method, below. Otherwise, it looks like this would attempt an anonymous
* bind - which you might want, but you probably don't.
*
**/
$admin_conn = self::connectToLdap();
self::bindAdminToLdap($admin_conn);
$results = ldap_search($admin_conn, $baseDn, $filterQuery);
$entry_count = ldap_count_entries($admin_conn, $results);
if ($entry_count != 1) {
throw new \Exception('Wrong number of entries found: ' . $entry_count);
}
$entry = ldap_first_entry($admin_conn, $results);
$user = ldap_get_attributes($admin_conn, $entry);
$userDn = ldap_get_dn($admin_conn, $entry);
if (!$userDn) {
throw new \Exception("No user DN found");
}
\Log::debug("FOUND DN IS: $userDn");
// The temptation now is to do ldap_unbind on the $admin_conn, but that gets handled in the 'finally' below.
// I don't know if that means a separate 'connection' is maintained to the LDAP server or not, and would
// definitely prefer to not do that if we can avoid it. But I don't know enough about the LDAP protocol to
// be certain that that happens.
//now we try to log in (bind) as that found user
$connection = self::connectToLdap();
$bind_results = ldap_bind($connection, $userDn, $password);
if (!$bind_results) {
throw new \Exception("Unable to bind as user");
}
return array_change_key_case($user);
} catch (\Exception $e) {
\Log::debug("Exception on fast find-and-bind: " . $e->getMessage());
if ($slow_failure) {
sleep($slow_failure);
}
return false; //TODO - make this null instead for a slightly nicer type signature
} finally {
if ($admin_conn) {
ldap_unbind($admin_conn);
}
if ($connection) {
ldap_unbind($connection);
}
}
}
/**
* Binds/authenticates the user to LDAP, and returns their attributes.
@@ -147,25 +219,27 @@ class Ldap extends Model
Log::debug('Filter query: '.$filterQuery);
// only try this if we have an Admin username set; otherwise use the 'legacy' method
if (($settings->ldap_uname) && ($baseDn)) {
// in the fallowing call, we pick a slow-failure of 0 because we might need to fall through to 'legacy'
$fast_bind = self::findAndBindMultiOU($baseDn, $filterQuery, $password, 0);
if ($fast_bind) {
\Log::debug("Fast bind worked");
return $fast_bind;
}
\Log::debug("Fast bind failed; falling through to legacy bind");
}
if (! $ldapbind = @ldap_bind($connection, $userDn, $password)) {
Log::debug("Status of binding user: $userDn to directory: (directly!) ".($ldapbind ? "success" : "FAILURE"));
if (! $ldapbind = self::bindAdminToLdap($connection)) {
/*
* TODO PLEASE:
*
* this isn't very clear, so it's important to note: the $ldapbind value is never correctly returned - we never 'return true' from self::bindAdminToLdap() (the function
* just "falls off the end" without ever explictly returning 'true')
*
* but it *does* have an interesting side-effect of checking for the LDAP password being incorrectly encrypted with the wrong APP_KEY, so I'm leaving it in for now.
*
* If it *did* correctly return 'true' on a succesful bind, it would _probably_ allow users to log in with an incorrect password. Which would be horrible!
*
* Let's definitely fix this at the next refactor!!!!
*
*/
Log::debug("Status of binding Admin user: $userDn to directory instead: ".($ldapbind ? "success" : "FAILURE"));
return false;
// replicate the old bad-decryption-key detection behavior here
try {
Crypt::decrypt(Setting::getSettings()->ldap_pword);
} catch (\Exception $e) {
throw new \Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.');
}
//regardless of anything else; stuff isn't working. Return false.
return false;
}
if (! $results = ldap_search($connection, $baseDn, $filterQuery)) {

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Carbon\Carbon;
@@ -760,7 +761,7 @@ class License extends Depreciable
->orWhere(function ($query) {
$query->whereDate('expiration_date', '<=', Carbon::now());
})
->whereNull('deleted_at');
->whereNull('licenses.deleted_at');
}
/**

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\CompanyableChildTrait;
use App\Models\Traits\Loggable;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Presenters\Presentable;
@@ -75,7 +76,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
protected function displayName(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $this->license->name,
get: fn(mixed $value) => $this->license?->name,
);
}

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\CompanyableChildTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -38,6 +39,7 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'completion_date' => 'date_format:Y-m-d|nullable|after_or_equal:start_date',
'notes' => 'string|nullable',
'cost' => 'numeric|nullable|gte:0|max:99999999999999999.99',
'url' => 'nullable|url|max:255',
];
@@ -57,6 +59,7 @@ class Maintenance extends SnipeModel implements ICompanyableChild
'asset_maintenance_time',
'notes',
'cost',
'url',
];
use Searchable;

View File

@@ -20,6 +20,13 @@ class SnipeModel extends Model
$this->attributes['purchase_date'] = $value;
}
protected function purchaseDateForDatepicker(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => array_key_exists('purchase_date', $attributes) ? Carbon::parse($attributes['purchase_date'])->format('Y-m-d') : null,
);
}
protected function purchaseDateFormatted(): Attribute
{
@@ -32,7 +39,7 @@ class SnipeModel extends Model
protected function expiresDiffInDays(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['expiration_date'] ? Carbon::now()->diffInDays($attributes['expiration_date']) : null,
get: fn(mixed $value, array $attributes) => array_key_exists('expiration_date', $attributes) ? Carbon::now()->diffInDays($attributes['expiration_date']) : null,
);
}
@@ -40,14 +47,14 @@ class SnipeModel extends Model
protected function expiresDiffForHumans(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['expiration_date'] ? Carbon::parse($attributes['expiration_date'])->diffForHumans() : null,
get: fn(mixed $value, array $attributes) => array_key_exists('expiration_date', $attributes) ? Carbon::parse($attributes['expiration_date'])->diffForHumans() : null,
);
}
protected function expiresFormattedDate(): Attribute
{
return Attribute:: make(
get: fn(mixed $value, array $attributes) => $attributes['expiration_date'] ? Helper::getFormattedDateObject($attributes['expiration_date'], 'date', false) : null,
get: fn(mixed $value, array $attributes) => array_key_exists('expiration_date', $attributes) ? Helper::getFormattedDateObject($attributes['expiration_date'], 'date', false) : null,
);
}

View File

@@ -1,8 +1,14 @@
<?php
namespace App\Models;
namespace App\Models\Traits;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\AuditNotification;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;

View File

@@ -1,8 +1,10 @@
<?php
namespace App\Models;
namespace App\Models\Traits;
use Illuminate\Support\Facades\Auth;
use App\Models\CheckoutRequest;
use App\Models\User;
// $asset->requests
// $asset->isRequestedBy($user)

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\CompanyableTrait;
use App\Models\Traits\HasUploads;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use App\Presenters\UserPresenter;
@@ -224,6 +225,26 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false;
}
public function hasIndividualPermissions()
{
$permissions = [];
if (is_object($this->permissions)) {
$permissions = json_decode(json_encode($this->permissions), true);
}
if (is_string($this->permissions)) {
$permissions = json_decode($this->permissions, true);
}
foreach ($permissions as $permission) {
if ($permission != 0) {
return true;
}
}
return false;
}
/**
* Internally check the user permission for the given section
*
@@ -863,6 +884,141 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return new \stdClass;
}
/**
* Query builder scope to search on text filters for complex Bootstrap Tables API
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $filter JSON array of search keys and terms
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeByFilter($query, $filter)
{
return $query->where(
function ($query) use ($filter) {
foreach ($filter as $fieldname => $search_val) {
if ($fieldname == 'first_name') {
$query->where('users.first_name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'last_name') {
$query->where('users.last_name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'display_name') {
$query->where('users.display_name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'name') {
$query->where('users.last_name', 'LIKE', '%' . $search_val . '%')
->orWhere('users.first_name', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'username') {
$query->where('users.username', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'email') {
$query->where('users.email', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'phone') {
$query->where('users.phone', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'mobile') {
$query->where('users.mobile', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'phone') {
$query->where('users.phone', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'jobtitle') {
$query->where('users.jobtitle', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'created_at') {
$query->where('users.created_at', '=', '%' . $search_val . '%');
}
if ($fieldname == 'updated_at') {
$query->where('users.updated_at', '=', '%' . $search_val . '%');
}
if ($fieldname == 'start_date') {
$query->where('users.start_date', '=', '%' . $search_val . '%');
}
if ($fieldname == 'end_date') {
$query->where('users.end_date', '=', '%' . $search_val . '%');
}
if ($fieldname == 'employee_num') {
$query->where('users.employee_num', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'locale') {
$query->where('users.locale', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'address') {
$query->where('users.address', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'state') {
$query->where('users.state', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'zip') {
$query->where('users.zip', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'country') {
$query->where('users.country', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'vip') {
$query->where('users.vip', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'remote') {
$query->where('users.remote', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'start_date') {
$query->where('users.purchase_date', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'notes') {
$query->where('users.notes', 'LIKE', '%' . $search_val . '%');
}
if ($fieldname == 'location') {
$query->whereHas(
'location', function ($query) use ($search_val) {
$query->where('locations.name', 'LIKE', '%' . $search_val . '%');
}
);
}
if ($fieldname == 'company') {
$query->whereHas(
'company', function ($query) use ($search_val) {
$query->where('companies.name', 'LIKE', '%' . $search_val . '%');
}
);
}
}
}
);
}
/**
* Query builder scope to search user by name with spaces in it.
* We don't use the advancedTextSearch() scope because that searches

View File

@@ -34,7 +34,6 @@ use Illuminate\Notifications\Notification;
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->note = $params['note'] ?? null;
$this->admin = $params['admin'] ?? null;
}
@@ -77,7 +76,6 @@ use Illuminate\Notifications\Notification;
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans('mail.acceptance_asset_accepted'),
])

View File

@@ -32,7 +32,6 @@ use Illuminate\Notifications\Notification;
$this->settings = Setting::getSettings();
$this->file = $params['file'] ?? null;
$this->qty = $params['qty'] ?? null;
$this->admin = $params['admin'] ?? null;
}
/**
@@ -70,7 +69,6 @@ use Illuminate\Notifications\Notification;
'accepted_date' => $this->accepted_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'admin' => $this->admin,
'qty' => $this->qty,
'intro_text' => trans_choice('mail.acceptance_asset_accepted_to_user', $this->qty, ['qty' => $this->qty, 'site_name' => $this->settings->site_name]),
])

View File

@@ -31,6 +31,7 @@ class AcceptanceAssetDeclinedNotification extends Notification
$this->company_name = $params['company_name'];
$this->settings = Setting::getSettings();
$this->qty = $params['qty'] ?? null;
$this->admin = $params['admin'] ?? null;
}
/**
@@ -70,7 +71,8 @@ class AcceptanceAssetDeclinedNotification extends Notification
'declined_date' => $this->declined_date,
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'qty' => $this->qty,
'qty' => $this->qty,
'admin' => $this->admin,
'intro_text' => trans('mail.acceptance_asset_declined'),
])
->subject(trans('mail.acceptance_asset_declined'));

View File

@@ -93,8 +93,8 @@ class CheckoutAssetNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
trans('general.to_user') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by_user') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {

View File

@@ -53,7 +53,7 @@ class AssetModelPresenter extends Presenter
],
[
'field' => 'manufacturer',
'searchable' => false,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.manufacturer'),
@@ -62,7 +62,7 @@ class AssetModelPresenter extends Presenter
],
[
'field' => 'model_number',
'searchable' => false,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/models/table.modelnumber'),
@@ -130,7 +130,7 @@ class AssetModelPresenter extends Presenter
],
[
'field' => 'category',
'searchable' => false,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.category'),

View File

@@ -14,6 +14,11 @@ class CategoryPresenter extends Presenter
public static function dataTableLayout()
{
$layout = [
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',
'searchable' => false,
@@ -92,7 +97,7 @@ class CategoryPresenter extends Presenter
'formatter' => 'usersLinkObjFormatter',
], [
'field' => 'created_at',
'searchable' => true,
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.created_at'),
@@ -100,7 +105,7 @@ class CategoryPresenter extends Presenter
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'updated_at',
'searchable' => true,
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.updated_at'),

View File

@@ -131,7 +131,7 @@ class ComponentPresenter extends Presenter
'class' => 'text-right',
], [
'field' => 'total_cost',
'searchable' => true,
'searchable' => false,
'sortable' => true,
'title' => trans('general.total_cost'),
'footerFormatter' => 'sumFormatterQuantity',

View File

@@ -111,6 +111,12 @@ class MaintenancesPresenter extends Presenter
'sortable' => true,
'title' => trans('admin/maintenances/form.completion_date'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'url',
'searchable' => true,
'sortable' => true,
'title' => trans('general.url'),
'formatter' => 'externalLinkFormatter',
], [
'field' => 'notes',
'searchable' => true,

View File

@@ -14,7 +14,11 @@ class ManufacturerPresenter extends Presenter
public static function dataTableLayout()
{
$layout = [
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',
'searchable' => false,

View File

@@ -13,6 +13,11 @@ class SupplierPresenter extends Presenter
public static function dataTableLayout()
{
$layout = [
[
'field' => 'checkbox',
'checkbox' => true,
'titleTooltip' => trans('general.select_all_none'),
],
[
'field' => 'id',
'searchable' => false,

View File

@@ -24,6 +24,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Blade;
/**
* This service provider handles setting the observers on models
@@ -42,6 +43,9 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(UrlGenerator $url)
{
/**
* This is a workaround for proxies/reverse proxies that don't always pass the proper headers.
*
@@ -76,6 +80,9 @@ class AppServiceProvider extends ServiceProvider
Consumable::observe(ConsumableObserver::class);
License::observe(LicenseObserver::class);
Setting::observe(SettingObserver::class);
// https://laravel.com/docs/11.x/blade#html-entity-encoding
Blade::withoutDoubleEncoding();
}
/**

View File

@@ -18,9 +18,10 @@ $dump_options = [
//'add_extra_option' => '--optionname=optionvalue',
];
// Some versions of mysql do not support the --skip-ssl option and will fail if it is even set
// For modern versions of mysqldump, use --ssl-mode=DISABLED
if (env('DB_DUMP_SKIP_SSL') == 'true') {
$dump_options['skip_ssl'] = true;
// Correctly add the option as a string to the 'add_extra_option' key.
$dump_options['add_extra_option'] = '--ssl-mode=DISABLED';
}

View File

@@ -9,11 +9,9 @@
return [
'Global' => [
'Superuser' => [
[
'permission' => 'superuser',
'label' => 'Super User',
'note' => 'Determines whether the user has full access to all aspects of the admin. This setting overrides any more specific permissions throughout the system. ',
'display' => true,
],
],
@@ -21,17 +19,13 @@ return [
'Admin' => [
[
'permission' => 'admin',
'label' => '',
'note' => 'Determines whether the user has access to most aspects of the admin. ',
'display' => true,
],
],
'CSV Import' => [
'Import' => [
[
'permission' => 'import',
'label' => '',
'note' => 'This will allow users to import even if access to users, assets, etc is denied elsewhere.',
'display' => true,
],
],
@@ -39,8 +33,6 @@ return [
'Reports' => [
[
'permission' => 'reports.view',
'label' => 'View',
'note' => 'Determines whether the user has the ability to view reports.',
'display' => true,
],
],
@@ -48,68 +40,48 @@ return [
'Assets' => [
[
'permission' => 'assets.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => false,
],
[
'permission' => 'assets.checkin',
'label' => 'Checkin ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.audit',
'label' => 'Audit ',
'note' => 'Allows the user to mark an asset as physically inventoried.',
'display' => true,
],
[
'permission' => 'assets.view.requestable',
'label' => 'View Requestable Assets',
'note' => '',
'display' => true,
],
[
'permission' => 'assets.view.encrypted_custom_fields',
'label' => 'View and Modify Encrypted Custom Fields',
'note' => '',
'display' => true,
],
@@ -118,44 +90,30 @@ return [
'Accessories' => [
[
'permission' => 'accessories.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.checkin',
'label' => 'Checkin ',
'note' => '',
'display' => true,
],
[
'permission' => 'accessories.files',
'label' => 'View and Modify Accessory Files',
'note' => '',
'display' => true,
],
@@ -164,38 +122,26 @@ return [
'Consumables' => [
[
'permission' => 'consumables.view',
'label' => 'View',
'note' => '',
'display' => true,
],
[
'permission' => 'consumables.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'consumables.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'consumables.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
[
'permission' => 'consumables.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => true,
],
[
'permission' => 'consumables.files',
'label' => 'View and Modify Consumable Files',
'note' => '',
'display' => true,
],
],
@@ -204,44 +150,34 @@ return [
'Licenses' => [
[
'permission' => 'licenses.view',
'label' => 'View',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.checkin',
'display' => true,
],
[
'permission' => 'licenses.keys',
'label' => 'View License Keys',
'note' => '',
'display' => true,
],
[
'permission' => 'licenses.files',
'label' => 'View and Modify License Files',
'note' => '',
'display' => true,
],
],
@@ -250,44 +186,30 @@ return [
'Components' => [
[
'permission' => 'components.view',
'label' => 'View',
'note' => '',
'display' => true,
],
[
'permission' => 'components.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'components.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'components.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
[
'permission' => 'components.checkout',
'label' => 'Checkout ',
'note' => '',
'display' => true,
],
[
'permission' => 'components.checkin',
'label' => 'Checkin ',
'note' => '',
'display' => true,
],
[
'permission' => 'components.files',
'label' => 'View and Modify Component Files',
'note' => '',
'display' => true,
],
@@ -296,26 +218,18 @@ return [
'Kits' => [
[
'permission' => 'kits.view',
'label' => 'View ',
'note' => 'These are predefined kits that can be used to quickly checkout assets, licenses, etc.',
'display' => true,
],
[
'permission' => 'kits.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'kits.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'kits.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -323,26 +237,18 @@ return [
'Users' => [
[
'permission' => 'users.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'users.create',
'label' => 'Create Users',
'note' => '',
'display' => true,
],
[
'permission' => 'users.edit',
'label' => 'Edit Users',
'note' => '',
'display' => true,
],
[
'permission' => 'users.delete',
'label' => 'Delete Users',
'note' => '',
'display' => true,
],
@@ -351,26 +257,18 @@ return [
'Models' => [
[
'permission' => 'models.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'models.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'models.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'models.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
@@ -379,26 +277,18 @@ return [
'Categories' => [
[
'permission' => 'categories.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'categories.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'categories.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'categories.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -406,26 +296,18 @@ return [
'Departments' => [
[
'permission' => 'departments.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'departments.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'departments.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'departments.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -433,26 +315,18 @@ return [
'Status Labels' => [
[
'permission' => 'statuslabels.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'statuslabels.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'statuslabels.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'statuslabels.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -460,26 +334,18 @@ return [
'Custom Fields' => [
[
'permission' => 'customfields.view',
'label' => 'View',
'note' => '',
'display' => true,
],
[
'permission' => 'customfields.create',
'label' => 'Create',
'note' => '',
'display' => true,
],
[
'permission' => 'customfields.edit',
'label' => 'Edit',
'note' => '',
'display' => true,
],
[
'permission' => 'customfields.delete',
'label' => 'Delete',
'note' => '',
'display' => true,
],
],
@@ -487,26 +353,18 @@ return [
'Suppliers' => [
[
'permission' => 'suppliers.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'suppliers.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'suppliers.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'suppliers.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -515,26 +373,18 @@ return [
'Manufacturers' => [
[
'permission' => 'manufacturers.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'manufacturers.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'manufacturers.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'manufacturers.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -542,26 +392,18 @@ return [
'Depreciations' => [
[
'permission' => 'depreciations.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'depreciations.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'depreciations.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'depreciations.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -569,26 +411,18 @@ return [
'Locations' => [
[
'permission' => 'locations.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'locations.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'locations.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'locations.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
@@ -596,66 +430,46 @@ return [
'Companies' => [
[
'permission' => 'companies.view',
'label' => 'View ',
'note' => '',
'display' => true,
],
[
'permission' => 'companies.create',
'label' => 'Create ',
'note' => '',
'display' => true,
],
[
'permission' => 'companies.edit',
'label' => 'Edit ',
'note' => '',
'display' => true,
],
[
'permission' => 'companies.delete',
'label' => 'Delete ',
'note' => '',
'display' => true,
],
],
'Self' => [
'User (Self) Accounts' => [
[
'permission' => 'self.two_factor',
'label' => 'Two-Factor Authentication',
'note' => 'The user may disable/enable two-factor authentication themselves if two-factor is enabled and set to selective.',
'display' => true,
],
[
'permission' => 'self.api',
'label' => 'Create API Keys',
'note' => 'The user create personal API keys to utilize the REST API.',
'display' => true,
],
[
'permission' => 'self.edit_location',
'label' => 'Profile Edit Location',
'note' => 'The user may update their own location in their profile. Note that this is not affected by any additional Users permissions you grant to this user or group.',
'display' => true,
],
[
'permission' => 'self.checkout_assets',
'label' => 'Self-Checkout',
'note' => 'This user may check out assets that are marked for self-checkout.',
'display' => true,
],
[
'permission' => 'self.view_purchase_cost',
'label' => 'View Purchase-Cost Column',
'note' => 'This user can see the purchase cost column of items assigned to them.',
'display' => true,
],

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v8.3.3',
'full_app_version' => 'v8.3.3 - build 20061-g884d2a955',
'build_version' => '20061',
'app_version' => 'v8.3.4',
'full_app_version' => 'v8.3.4 - build 20218-g3ab2e2011',
'build_version' => '20218',
'prerelease_version' => '',
'hash_version' => 'g884d2a955',
'full_hash' => 'v8.3.3-154-g884d2a955',
'hash_version' => 'g3ab2e2011',
'full_hash' => 'v8.3.4-149-g3ab2e2011',
'branch' => 'master',
);

View File

@@ -31,6 +31,7 @@ class MaintenanceFactory extends Factory
'start_date' => $this->faker->date(),
'is_warranty' => $this->faker->boolean(),
'notes' => $this->faker->paragraph(),
'url' => $this->faker->url(),
];
}
}

View File

@@ -22,7 +22,7 @@ return new class extends Migration
public function down(): void
{
Schema::table('action_logs', function (Blueprint $table) {
$table->dropIndex('deleted_at');
$table->dropIndex(['deleted_at']);
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('maintenances', function (Blueprint $table) {
if (!Schema::hasColumn('maintenances', 'url')) {
$table->text('url')->after('name')->nullable()->default(null);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maintenances', function (Blueprint $table) {
if (Schema::hasColumn('maintenances', 'url')) {
$table->dropColumn('url');
}
});
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Storage;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$assetmodelfiles = Storage::allFiles('private_uploads/assetmodels');
foreach ($assetmodelfiles as $file) {
Storage::writeStream('private_uploads/models/' . basename($file),
Storage::readStream($file)
);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

104
package-lock.json generated
View File

@@ -26,6 +26,7 @@
"jquery-ui": "^1.14.1",
"jquery-validation": "^1.21.0",
"jquery.iframe-transport": "^1.0.0",
"jspdf": "^3.0.3",
"jspdf-autotable": "^5.0.2",
"less": "^4.2.2",
"less-loader": "^6.0",
@@ -2193,6 +2194,12 @@
"@types/node": "*"
}
},
"node_modules/@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
"license": "MIT"
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"dev": true,
@@ -2258,6 +2265,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/ws": {
"version": "8.5.10",
"dev": true,
@@ -2742,16 +2756,6 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
"node_modules/atob": {
"version": "2.1.2",
"license": "(MIT OR Apache-2.0)",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/autoprefixer": {
"version": "10.4.19",
"dev": true,
@@ -3343,16 +3347,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"license": "(MIT OR Apache-2.0)",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/buffer": {
"version": "4.9.2",
"dev": true,
@@ -3649,11 +3643,17 @@
}
},
"node_modules/cipher-base": {
"version": "1.0.4",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz",
"integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1",
"to-buffer": "^1.2.2"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/ckeditor": {
@@ -4617,10 +4617,14 @@
}
},
"node_modules/dompurify": {
"version": "2.5.8",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
"integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
"optional": true
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true,
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/domutils": {
"version": "2.8.0",
@@ -5106,6 +5110,23 @@
"version": "2.1.0",
"license": "MIT"
},
"node_modules/fast-png": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
"license": "MIT",
"dependencies": {
"@types/pako": "^2.0.3",
"iobuffer": "^5.3.2",
"pako": "^2.1.0"
}
},
"node_modules/fast-png/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"license": "MIT"
@@ -6233,6 +6254,12 @@
"node": ">= 0.10"
}
},
"node_modules/iobuffer": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
"license": "MIT"
},
"node_modules/ion-rangeslider": {
"version": "2.3.1",
"license": "MIT",
@@ -6769,19 +6796,19 @@
}
},
"node_modules/jspdf": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
"integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"@babel/runtime": "^7.26.9",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.6",
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^2.5.4",
"dompurify": "^3.2.4",
"html2canvas": "^1.0.0-rc.5"
}
},
@@ -10272,9 +10299,10 @@
"license": "MIT"
},
"node_modules/to-buffer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
"integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz",
"integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==",
"license": "MIT",
"dependencies": {
"isarray": "^2.0.5",
"safe-buffer": "^5.2.1",

View File

@@ -46,6 +46,7 @@
"jquery-ui": "^1.14.1",
"jquery-validation": "^1.21.0",
"jquery.iframe-transport": "^1.0.0",
"jspdf": "^3.0.3",
"jspdf-autotable": "^5.0.2",
"less": "^4.2.2",
"less-loader": "^6.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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