Compare commits

...

109 Commits

Author SHA1 Message Date
snipe
e4f06b0ca8 One last time
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 15:43:48 +01:00
snipe
2f093c0e82 Added early exist on step 4 as well
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 15:41:39 +01:00
snipe
5d9dc0e74d Put decrypt in a try/catch
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 15:33:26 +01:00
snipe
adc3a34929 Fixed copy for encrypted custom fields
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 14:38:36 +01:00
snipe
cb2ffe6b3f Updated translations
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 14:02:39 +01:00
snipe
b3e3d01672 Fixed LDAP icon spacing
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 13:32:02 +01:00
snipe
4a6520fc78 Fixed address field
Signed-off-by: snipe <snipe@snipe.net>
2025-08-27 13:30:07 +01:00
snipe
75ab6c9b13 Merge pull request #17723 from uberbrady/improve_ldap_certificate_ignoring
Improve ldap certificate ignoring
2025-08-27 13:29:33 +01:00
snipe
2f77fcb526 Merge pull request #17724 from Godmartinz/checkout2location_email_fix
Fixes #17642 Checkouts to location email for Assets and Accessories
2025-08-27 13:02:57 +01:00
Brady Wetherington
60604c3481 With the new SSL stuff, we are calling ldap_set_option() one more time now 2025-08-27 12:25:39 +01:00
Godfrey M
671c113cd2 add coma to translation" 2025-08-26 16:07:04 -07:00
Godfrey M
8a74d21ede fixes checkout emails to location for assets and accessories" 2025-08-26 16:00:26 -07:00
Godfrey M
75995b2109 fix checkout to location email 2025-08-26 15:34:38 -07:00
snipe
d1eefc3fea Merge pull request #17692 from grokability/#17387-make-saml-key-size-an-env
Fixed #17386 - Added SAML key size to env - possible alternative to #17387
2025-08-26 16:28:27 +01:00
Brady Wetherington
16795382fc Many cleanups to default-mode of LDAP troubleshooter 2025-08-26 15:53:18 +01:00
snipe
eb17974adc Merge pull request #17722 from grokability/#17704-retain-linebreaks
Fixed #17704 -  retain linebreaks on custom field clipboard copy
2025-08-26 15:44:38 +01:00
snipe
22852c27f8 Use generic length for asterisks
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:38:56 +01:00
snipe
f4a94d975d Fixes #17704 - retain linebreaks in clipboard for multi-line custom field copying
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:33:19 +01:00
snipe
7a36bbbd1e Merge pull request #17721 from grokability/small-ldap-preview-display-tweaks
Improved LDAP field sync preview
2025-08-26 15:26:09 +01:00
snipe
2b401b965b Fixed casing
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:22:00 +01:00
snipe
314bc5b44f Added manager
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:14:29 +01:00
snipe
76374f0d5a Updated text
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:14:22 +01:00
snipe
264efb015e Fixed jobtitle field mapping
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 15:05:05 +01:00
Brady Wetherington
e74460aefc Merge branch 'develop' into improve_ldap_certifcate_ignoring 2025-08-26 15:01:11 +01:00
Brady Wetherington
55a5a12b30 Formalize the 'double-barrel' method of setting TLS cert ignores 2025-08-26 15:00:33 +01:00
snipe
58944a38eb Make screen and table wider
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 14:59:11 +01:00
snipe
469e3bd475 Nicer ldap preview layout, show all mapped fields
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 14:51:34 +01:00
snipe
17650c5735 Changed field title
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 14:03:11 +01:00
Brady Wetherington
15e64155b5 Add version checking to LDAP troubleshooter, clean up ldap model 2025-08-26 13:57:25 +01:00
snipe
39955ac760 Add @akaspeh1 as a contributor 2025-08-26 12:42:20 +01:00
snipe
855a176ca9 Add @nickwest as a contributor 2025-08-26 12:42:15 +01:00
snipe
47b2b30455 Merge pull request #17710 from akaspeh1/develop
Adds support for label sheets Avery L4736 & L6009
2025-08-26 12:42:02 +01:00
snipe
b702e3e2de Merge pull request #17492 from ischooluw/17448-feature-notes-api-endpoints
Fixes #17448: feat(api) - API endpoint for Adding Ad-Hoc Notes to Assets
2025-08-26 12:40:52 +01:00
snipe
a6b74d56c6 Merge pull request #17709 from grokability/add-display-name-to-users-fixed
Added display name to users for LDAP/SCIM, added new sync fields (replaced #17650)
2025-08-26 12:39:25 +01:00
snipe
a4222bcaef Merge pull request #17711 from grokability/dependabot/github_actions/develop/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-26 12:10:24 +01:00
snipe
ecf24511cd Fixed tests for real this time tho
Signed-off-by: snipe <snipe@snipe.net>
2025-08-26 12:09:55 +01:00
snipe
abb097a391 Merge pull request #17714 from Godmartinz/Audit_null_fix
Added null checks to MS Teams Audit notification
2025-08-26 10:44:51 +01:00
Godfrey M
dd742a2e4a add a check for audit notification variables in MS Teams and a translation 2025-08-25 15:10:41 -07:00
dependabot[bot]
73ac00bc51 Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 16:25:39 +00:00
snipe
3524e23e38 Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-25 17:17:45 +01:00
Jakub Aqaba Štarman
be0f3910bb Fixed: Old computation 2025-08-25 16:57:32 +02:00
snipe
07dbc6842c Are you KIDDING ME, Github??
This reverts commit c8e79aa5ca, reversing
changes made to e60f2b2332.

Signed-off-by: snipe <snipe@snipe.net>
2025-08-25 15:56:28 +01:00
Jakub Aqaba Štarman
5a16b59462 Adds support for label sheets Avery L4736 & L6009 2025-08-25 16:47:52 +02:00
Brady Wetherington
13cd7071b8 WIP improving some LDAP stuff 2025-08-25 15:41:01 +01:00
snipe
40108b196c Trying to fix import tests :(
Signed-off-by: snipe <snipe@snipe.net>
2025-08-25 15:28:43 +01:00
snipe
c8e79aa5ca Merge branch 'develop' into add-display-name-to-users-fixed 2025-08-25 15:28:20 +01:00
snipe
e60f2b2332 Tightened up accessor code for better inheritence
Signed-off-by: snipe <snipe@snipe.net>
2025-08-25 15:00:10 +01:00
snipe
b6d397bcca Updated ->present()->fullName() to ->display_name
Signed-off-by: snipe <snipe@snipe.net>
2025-08-25 14:57:34 +01:00
snipe
6503f9c667 Revert "Merge pull request #17650 from grokability/add-displayName-to-users"
This reverts commit 4770e469b4, reversing
changes made to 29a18c7c8b.

Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 20:23:47 +01:00
snipe
4770e469b4 Merge pull request #17650 from grokability/add-displayName-to-users
Add display name to users for LDAP/SCIM, added new sync fields
2025-08-21 18:22:34 +01:00
snipe
29a18c7c8b Merge pull request #17696 from uberbrady/add_created_at_index_to_models
Fixed [FD-49550] - added a 'created_at' index to the models table
2025-08-21 14:54:20 +01:00
Brady Wetherington
6db0003e3f Adds a 'created_at' index to the models table 2025-08-21 13:44:14 +01:00
snipe
c538c460fa Merge pull request #17695 from grokability/#17482-better-localization-indates-on-asset-view
Use nicer local for purchase date
2025-08-21 13:13:26 +01:00
snipe
822339fe42 Moved warning
Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 13:13:11 +01:00
snipe
b84d9282ca Use normal locale for warranty
Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 13:05:01 +01:00
snipe
952b6f33bb Add @strobelm as a contributor 2025-08-21 11:51:37 +01:00
snipe
c57c4b8ff2 Merge pull request #17691 from qay21/fix-components-url
Fix components presenting wrong URLs
2025-08-21 11:37:27 +01:00
snipe
39e6223ff2 POssible alternative to #17386 - adding SAML key size to env
Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 11:27:50 +01:00
qay
d8dd274c08 Fix components presenting wrong URLs 2025-08-21 12:26:13 +02:00
snipe
15f97b6cb9 Merge pull request #17591 from Godmartinz/add-serial-to-expiring-asset-report
Adds #17440 serial number column to Expiring Assets Report
2025-08-21 11:14:45 +01:00
snipe
fc091c1174 Added comments
Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 09:29:12 +01:00
snipe
c07ef4d87f A few small tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-08-21 09:25:42 +01:00
snipe
11eee833bb Fixed #17667 - Switch to hyphens for windows
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 15:56:10 +01:00
snipe
fec9d716ee Merge pull request #17679 from grokability/#17674-add-ods-and-odt
Fixed #17674: added .ods, .odp, and .odt as acceptable upload types
2025-08-20 14:17:08 +01:00
snipe
da5b1afd19 Removed logging
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 14:11:42 +01:00
snipe
618106c103 Fixed #17674 - added odp, ods, odt to accepted files
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 14:11:20 +01:00
snipe
312be98132 Add @FlorestanII as a contributor 2025-08-20 12:43:43 +01:00
snipe
e0bb77a6d6 Merge pull request #17664 from FlorestanII/feature/support-for-dymo-11354-labels
Support for Dymo 11354 Labels.
2025-08-20 12:43:29 +01:00
snipe
855922c21a Account for null in tetss (vs 0)
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 11:32:16 +01:00
snipe
bc645d2621 Use email formatter in licensed_to_email display
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 11:24:16 +01:00
snipe
9c06ff3899 Check for numeric
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 11:00:18 +01:00
snipe
2a37aa3b49 Fixed tooltip
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 10:34:05 +01:00
snipe
bf591320af Fixed #17665 - delete custom report modal
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 09:58:30 +01:00
snipe
56e687bed2 Retuen the display name in the API call
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 09:33:00 +01:00
snipe
07b25fe376 Add display name to summary
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 20:52:18 +01:00
snipe
c2ecd20b7d Updated field text
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 20:47:48 +01:00
snipe
1b42abcc98 Fixed mapping
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 19:54:32 +01:00
snipe
9efb49d510 Merge pull request #17663 from Godmartinz/sub-out-translation
Fixes #17653 changes translation to administrator
2025-08-19 19:43:47 +01:00
snipe
2d6270c697 Updated validation, switch to string() as db field type
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 19:19:29 +01:00
snipe
0823c23a6e Fixed placeholder text
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 18:51:56 +01:00
snipe
b3f0ce4b2a Use fieldsets for LDAP settings
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 18:38:47 +01:00
snipe
8b83584b67 Added mapping fields to LDAP
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 18:31:58 +01:00
Godfrey M
9eb686fe08 changes translation to administrator 2025-08-19 10:23:15 -07:00
Johannes Pollitt
765051ce88 Added LabelWriter for 11354 format labels.
Printable for example with the Dymo LabelWriter 450.
2025-08-19 19:21:48 +02:00
Godfrey M
ed402e0122 adds serial underneath name 2025-08-19 10:10:20 -07:00
snipe
1488271a83 Added #8522 - depreciation info on Asset API
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 14:48:48 +01:00
snipe
48bbf8d005 Merge pull request #17655 from uberbrady/add_category_indexes
Add new indexes to category_id and deleted_at
2025-08-19 14:26:38 +01:00
Brady Wetherington
e97b969d66 Add new indexes to category_id and deleted_at 2025-08-19 14:20:36 +01:00
snipe
cdd12df81a Fixed #17627 - jquery UI fix for draggable/sortable
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 14:12:06 +01:00
snipe
050a3afc74 Fixed #17649 - nicer layout on new location modal
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 13:56:21 +01:00
snipe
270401c693 Added display name to user create modal
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 13:12:57 +01:00
snipe
551822ce7d Merge pull request #17648 from grokability/possible-fix-for-#17641-map-mobile-via-scim
Fixed #17641: map mobile number via SCIM
2025-08-19 13:09:07 +01:00
snipe
4b8c371097 Updated true to false
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 12:59:28 +01:00
snipe
90fbf6da46 Modify the presenter to see if they have a display_name set
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 12:56:44 +01:00
snipe
0c3103e3d2 Modify the getter
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 12:56:30 +01:00
snipe
6a8e1566fe Added display_name to a few more places
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 12:56:11 +01:00
snipe
ced30082a6 Added display_name as user field
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 12:10:28 +01:00
snipe
f6c64abc1a Fixed #17641 - map mobile number via SCIM
Signed-off-by: snipe <snipe@snipe.net>
2025-08-19 11:41:02 +01:00
snipe
7f9939a896 Merge pull request #17638 from Godmartinz/asset-tag-added-to-subject-line
Adds asset tag to subject line of check in check out
2025-08-19 09:36:25 +01:00
Godfrey M
1c99f2dfdd readd doesntorequireacceptance() to test 2025-08-18 10:52:35 -07:00
Godfrey M
1974fccac3 add tag to other notification test 2025-08-18 10:48:39 -07:00
Godfrey M
911552035e fix other test 2025-08-18 10:39:10 -07:00
Godfrey M
ff25d275ee fix tests 2025-08-18 10:31:03 -07:00
Godfrey M
1fcf5e03e7 adds asset tag to subject line of checkin/out 2025-08-18 10:16:47 -07:00
Godfrey M
ba85af11aa adds serial to expiring assets report email 2025-08-12 14:59:20 -07:00
Nicky West
c94a8c42f4 Changed NotesController::getList() to NotesController::index() & reordered methods for consistency 2025-07-28 16:57:46 -07:00
Nicky West
16fdb16a56 Changed over to route model binding and simplified logic & gates 2025-07-28 16:55:11 -07:00
Nicky West
822f9a6f28 Fixed deviations from code standards 2025-07-28 16:37:08 -07:00
Nicky West
b264bbf69f feat(api): Add API endpoints for managing asset history notes
- Add POST endpoint to create a history note attached to an asset
- Add GET endpoint to retrieve history notes for an asset
- Add ActionLog factory state for manual notes
- Implement controller methods with authorization checks
- Add feature tests for note creation, retrieval, and access control
- Register new API routes for these endpoints

Supports automation by enabling programmatic asset history note management.
2025-07-28 15:55:37 -07:00
1204 changed files with 36222 additions and 5677 deletions

View File

@@ -4198,6 +4198,42 @@
"contributions": [
"code"
]
},
{
"login": "FlorestanII",
"name": "Johannes Pollitt",
"avatar_url": "https://avatars.githubusercontent.com/u/15015119?v=4",
"profile": "https://github.com/FlorestanII",
"contributions": [
"code"
]
},
{
"login": "strobelm",
"name": "Michael Strobel",
"avatar_url": "https://avatars.githubusercontent.com/u/14185442?v=4",
"profile": "https://strobelm.de",
"contributions": [
"code"
]
},
{
"login": "nickwest",
"name": "Nicky West",
"avatar_url": "https://avatars.githubusercontent.com/u/634790?v=4",
"profile": "http://nickwest.me",
"contributions": [
"code"
]
},
{
"login": "akaspeh1",
"name": "akaspeh1",
"avatar_url": "https://avatars.githubusercontent.com/u/1347327?v=4",
"profile": "https://github.com/akaspeh1",
"contributions": [
"code"
]
}
]
}

View File

@@ -193,11 +193,17 @@ LDAP_TIME_LIM=600
IMPORT_TIME_LIMIT=600
IMPORT_MEMORY_LIMIT=500M
REPORT_TIME_LIMIT=12000
REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
LIVEWIRE_URL_PREFIX=null
# --------------------------------------------
# OPTIONAL: SAML SETTINGS
# --------------------------------------------
REQUIRE_SAML=false
SAML_KEY_SIZE=2048
# --------------------------------------------
# OPTIONAL: HASHING
# --------------------------------------------

View File

@@ -26,7 +26,7 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -32,7 +32,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Crowdin push
uses: crowdin/github-action@v2

View File

@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
uses: actions/checkout@v5
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
uses: actions/checkout@v5
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx

View File

@@ -11,7 +11,7 @@ jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Docker Hub Description
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4

View File

@@ -37,7 +37,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Get Composer Cache Directory
id: composer-cache

View File

@@ -34,7 +34,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Get Composer Cache Directory
id: composer-cache

View File

@@ -25,7 +25,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Get Composer Cache Directory
id: composer-cache

View File

@@ -68,7 +68,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -55,6 +55,8 @@ class LdapSync extends Command
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
// Map the LDAP attributes to the Snipe-IT user fields.
$ldap_map = [
"username" => Setting::getSettings()->ldap_username_field,
"last_name" => Setting::getSettings()->ldap_lname_field,
@@ -63,11 +65,17 @@ class LdapSync extends Command
"emp_num" => Setting::getSettings()->ldap_emp_num,
"email" => Setting::getSettings()->ldap_email,
"phone" => Setting::getSettings()->ldap_phone_field,
"mobile" => Setting::getSettings()->ldap_mobile,
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
"address" => Setting::getSettings()->ldap_address,
"city" => Setting::getSettings()->ldap_city,
"state" => Setting::getSettings()->ldap_state,
"zip" => Setting::getSettings()->ldap_zip,
"country" => Setting::getSettings()->ldap_country,
"location" => Setting::getSettings()->ldap_location,
"dept" => Setting::getSettings()->ldap_dept,
"manager" => Setting::getSettings()->ldap_manager,
"display_name" => Setting::getSettings()->ldap_display_name,
];
$ldap_default_group = Setting::getSettings()->ldap_default_group;
@@ -234,9 +242,11 @@ class LdapSync extends Command
}
// Assign the mapped LDAP attributes for each user to the Snipe-IT user fields
for ($i = 0; $i < $results['count']; $i++) {
$item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
@@ -244,8 +254,13 @@ class LdapSync extends Command
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? '';
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? '';
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? '';
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? '';
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
@@ -278,6 +293,9 @@ class LdapSync extends Command
if($ldap_map["username"] != null){
$user->username = $item['username'];
}
if($ldap_map["display_name"] != null){
$user->display_name = $item['display_name'];
}
if($ldap_map["last_name"] != null){
$user->last_name = $item['lastname'];
}
@@ -293,6 +311,9 @@ class LdapSync extends Command
if($ldap_map["phone"] != null){
$user->phone = $item['telephone'];
}
if($ldap_map["mobile"] != null){
$user->mobile = $item['mobile'];
}
if($ldap_map["jobtitle"] != null){
$user->jobtitle = $item['jobtitle'];
}

View File

@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
use App\Models\Setting;
use Exception;
use Illuminate\Support\Facades\Crypt;
use App\Models\Ldap;
/**
* Check if a given ip is in a network
@@ -160,7 +161,15 @@ class LdapTroubleshooter extends Command
$output[] = "-x";
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
$output[] = "-w ".escapeshellarg(Crypt::Decrypt($settings->ldap_pword));
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
exit(0);
}
$output[] = "-w ". escapeshellarg($w);
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
if($settings->ldap_tls) {
$this->line("# adding STARTTLS option");
@@ -171,6 +180,23 @@ class LdapTroubleshooter extends Command
$this->line(implode(" \\\n",$output));
exit(0);
}
//PHP Version check for warning
$php_version = phpversion();
list($major, $minor, $patch) = explode('.', $php_version);
if (
$major < 8 ||
($major == 8 && $minor < 3) ||
($major == 8 && $minor == 3 && $patch < 21) ||
($major == 8 && $minor == 4 && $patch < 7)
) {
$this->warn("PHP Version: $php_version WARNING - Versions before 8.3.21 or 8.4.7 will return INCONSISTENT results!");
if (!$this->confirm("Are you sure you wish to continue?")) {
$this->warn("ABORTING");
exit(-1);
}
}
if(!$this->option('force')) {
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
if(!$confirmation) {
@@ -179,7 +205,7 @@ class LdapTroubleshooter extends Command
}
}
//$this->line(print_r($settings,true));
$this->info("STAGE 1: Checking settings");
$this->line("STAGE 1: Checking settings");
if(!$settings->ldap_enabled) {
$this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)");
}
@@ -210,32 +236,40 @@ class LdapTroubleshooter extends Command
$this->info("Determined LDAP hostname to be: ".$parsed['host']);
}
$this->info("Performing DNS lookup of: ".$parsed['host']);
$ips = dns_get_record($parsed['host']);
$raw_ips = [];
//$this->info("Host IP is: ".print_r($ips,true));
if (inet_pton($parsed['host']) !== false) {
$this->line($parsed['host'] . " already looks like an address; skipping DNS lookup");
$raw_ips[] = $parsed['host'];
} else {
$this->line("Performing DNS lookup of: " . $parsed['host']);
$ips = dns_get_record($parsed['host']);
if(!$ips || count($ips) == 0) {
$this->error("ERROR: DNS lookup of host: ".$parsed['host']." has failed. ABORTING.");
exit(-1);
}
$this->debugout("IP's? ".print_r($ips,true));
foreach($ips as $ip) {
if(!isset($ip['ip'])) {
continue;
//$this->info("Host IP is: ".print_r($ips,true));
if (!$ips || count($ips) == 0) {
$this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING.");
exit(-1);
}
$raw_ips[]=$ip['ip'];
if($ip['ip'] == "127.0.0.1") {
$this->debugout("IP's? " . print_r($ips, true));
foreach ($ips as $ip) {
if (!isset($ip['ip'])) {
continue;
}
$raw_ips[] = $ip['ip'];
}
}
foreach ($raw_ips as $ip) {
if ($ip == "127.0.0.1") {
$this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong");
}
if(ip_in_range($ip['ip'],'10.0.0.0/8') || ip_in_range($ip['ip'],'192.168.0.0/16') || ip_in_range($ip['ip'], '172.16.0.0/12')) {
if (ip_in_range($ip, '10.0.0.0/8') || ip_in_range($ip, '192.168.0.0/16') || ip_in_range($ip, '172.16.0.0/12')) {
$this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network");
}
}
$this->info("STAGE 2: Checking basic network connectivity");
$ports = [389,636];
$this->line("STAGE 2: Checking basic network connectivity");
$ports = [636, 389];
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
$ports[] = $parsed['port'];
}
@@ -246,7 +280,7 @@ class LdapTroubleshooter extends Command
$errstr = '';
$timeout = 30.0;
$result = '';
$this->info("Attempting to connect to port: ".$port." - may take up to $timeout seconds");
$this->line("Attempting to connect to port: " . $port . " - may take up to $timeout seconds");
try {
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
} catch(Exception $e) {
@@ -265,9 +299,9 @@ class LdapTroubleshooter extends Command
exit(-1);
}
$this->info("STAGE 3: Determine encryption algorithm, if any");
$this->line("STAGE 3: Determine encryption algorithm, if any");
$ldap_urls = [];
$ldap_urls = []; // [url, cert-check?, start_tls?]
$pretty_ldap_urls = [];
foreach($open_ports as $port) {
$this->line("Trying TLS first for port $port");
@@ -275,35 +309,46 @@ class LdapTroubleshooter extends Command
if($this->test_anonymous_bind($ldap_url)) {
$this->info("Anonymous bind succesful to $ldap_url!");
$ldap_urls[] = [ $ldap_url, true, false ];
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
$pretty_ldap_urls[] = [$ldap_url, "enabled", "n/a (no)"];
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
} else {
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
}
if($this->test_anonymous_bind($ldap_url, false)) {
$this->info("Anonymous bind succesful to $ldap_url with certifcate-checks disabled");
$ldap_urls[] = [ $ldap_url, false, false ];
$pretty_ldap_urls[] = [ $ldap_url, "no", "no" ];
$this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled");
$ldap_urls[] = [$ldap_url, false, false];
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
}
// now switching to ldap:// URL's from ldaps://
$ldap_url = "ldap://".$parsed['host'].":$port";
if($this->test_anonymous_bind($ldap_url, true, true)) {
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
$ldap_urls[] = [ $ldap_url, true, true ];
$pretty_ldap_urls[] = [ $ldap_url, "YES", "YES" ];
$pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without STARTTLS");
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without certificate checks.");
}
if ($this->test_anonymous_bind($ldap_url, false, true)) {
$this->info("Plain connection to $ldap_url with STARTTLS and cert checks *disabled* successful!");
$ldap_urls[] = [$ldap_url, false, true];
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "STARTTLS ENABLED"];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled, and cert checks disabled. Trying without STARTTLS");
}
if($this->test_anonymous_bind($ldap_url)) {
$this->info("Plain connection to $ldap_url succesful!");
$ldap_urls[] = [ $ldap_url, true, false ];
$pretty_ldap_urls[] = [ $ldap_url, "YES", "no" ];
$pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
@@ -313,23 +358,29 @@ class LdapTroubleshooter extends Command
$this->debugout(print_r($ldap_urls,true));
if(count($ldap_urls) > 0 ) {
$this->info("Found working LDAP URL's: ");
$this->debugout("Found working LDAP URL's: ");
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
$this->info("LDAP URL: ".$ldap_url[0]);
$this->info($ldap_url[0]. ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled"). ($ldap_url[2] ? " STARTTLS Enabled ": " STARTTLS Disabled"));
$this->debugout("LDAP URL: " . $ldap_url[0]);
$this->debugout($ldap_url[0] . ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled") . ($ldap_url[2] ? " STARTTLS Enabled " : " STARTTLS Disabled"));
}
$this->table(["URL", "Cert Checks Enabled?", "STARTTLS Enabled?"],$pretty_ldap_urls);
$this->table(["URL", "Cert Checks?", "STARTTLS?"], $pretty_ldap_urls);
} else {
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
exit(1);
}
$this->info("STAGE 4: Test Administrative Bind for LDAP Sync");
$this->line("STAGE 4: Test Administrative Bind for LDAP Sync");
foreach($ldap_urls AS $ldap_url) {
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, Crypt::decrypt($settings->ldap_pword));
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
exit(0);
}
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w);
}
$this->info("STAGE 5: Test BaseDN");
$this->line("STAGE 5: Test BaseDN");
//grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
$all_defined_constants = get_defined_constants();
$ldap_constants = [];
@@ -341,16 +392,23 @@ class LdapTroubleshooter extends Command
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
foreach($ldap_urls AS $ldap_url) {
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,Crypt::decrypt($settings->ldap_pword),$settings)) {
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
exit(0);
}
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,$w,$settings)) {
$this->info("Success getting informational bind!");
} else {
$this->error("Unable to get information from bind.");
}
}
$this->info("STAGE 6: Test LDAP Login to Snipe-IT");
$this->line("STAGE 6: Test LDAP Login to Snipe-IT");
foreach($ldap_urls AS $ldap_url) {
$this->info("Starting auth to ".$ldap_url[0]);
$this->line("Starting auth to " . $ldap_url[0]);
while(true) {
$with_tls = $ldap_url[1] ? "with": "without";
$with_startssl = $ldap_url[2] ? "using": "not using";
@@ -359,7 +417,12 @@ class LdapTroubleshooter extends Command
}
$username = $this->ask("Username");
$password = $this->secret("Password");
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
$results = $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
if ($results) {
$this->info("Success authenticating with " . $username);
} else {
$this->error("Unable to authenticate with " . $username);
}
}
}
@@ -368,14 +431,17 @@ class LdapTroubleshooter extends Command
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
{
if ($check_cert) {
$this->line("we *ARE* checking certs");
Ldap::ignoreCertificates(false);
} else {
$this->line("we are IGNORING certs");
Ldap::ignoreCertificates(true);
}
$lconn = ldap_connect($ldap_url);
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3?
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
if(!$check_cert) {
putenv('LDAPTLS_REQCERT=never'); // This is horrible; is this *really* the only way to do it?
} else {
putenv('LDAPTLS_REQCERT'); // have to very explicitly and manually *UN* set the env var here to ensure it works
}
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
// client-side TLS certificate support for LDAP (Google Secure LDAP)
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
@@ -404,9 +470,10 @@ class LdapTroubleshooter extends Command
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
try {
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
$this->info("gonna try to bind now, this can take a while if we mess it up");
$this->line("Attempting to bind now, this can take a while if we mess it up");
$bind_results = ldap_bind($lconn);
$this->info("Bind results are: ".$bind_results." which translate into boolean: ".(bool)$bind_results);
$this->line("Bind results are: " . $bind_results . " which translate into boolean: " . (bool)$bind_results);
ldap_close($lconn);
return (bool)$bind_results;
} catch (Exception $e) {
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
@@ -421,6 +488,7 @@ class LdapTroubleshooter extends Command
try {
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
$bind_results = ldap_bind($lconn, $username, $password);
ldap_close($lconn);
if(!$bind_results) {
$this->error("WARNING: Failed to bind to $ldap_url as $username");
return false;
@@ -446,22 +514,62 @@ class LdapTroubleshooter extends Command
return false;
}
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
$results = ldap_get_entries($conn, $result);
$cleaned_results = $this->ldap_results_cleaner($results);
$this->line(print_r($cleaned_results,true));
//okay, great - now how do we display those results? I have no idea.
$cleaned_results = [];
try {
// This _may_ only work for Active Directory?
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
$results = ldap_get_entries($conn, $result);
$cleaned_results = $this->ldap_results_cleaner($results);
//$this->line(print_r($cleaned_results,true));
$default_naming_contexts = $cleaned_results[0]['namingcontexts'];
$this->info("Default Naming Contexts:");
$this->info(implode(", ", $default_naming_contexts));
//okay, great - now how do we display those results? I have no idea.
} catch (\Exception $e) {
$this->error("Unable to get base naming contexts - here's what we *did* get:");
$this->line(print_r($cleaned_results, true));
}
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
$this->comment("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
$this->debugout("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
$entries = ldap_get_entries($conn, $search_results);
$this->info("Printing first 10 results: ");
for($i=0;$i<10;$i++) {
$this->info($search_results[$i]);
$pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10);
//print_r($data);
$headers = [];
foreach ($pretty_data as $row) {
//populate headers
foreach ($row as $key => $value) {
//skip objectsid and objectguid because it junks up output
if ($key == "objectsid" || $key == "objectguid") {
continue;
}
if (!in_array($key, $headers)) {
$headers[] = $key;
}
}
}
$table = [];
//repeat again to populate table
foreach ($pretty_data as $row) {
$newrow = [];
foreach ($headers as $header) {
if (is_array(@$row[$header])) {
$newrow[] = "[" . implode(", ", $row[$header]) . "]";
} else {
$newrow[] = @$row[$header];
}
}
$table[] = $newrow;
}
$this->table($headers, $table);
} catch (\Exception $e) {
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
return false;
} finally {
ldap_close($conn);
}
});
}
@@ -477,7 +585,7 @@ class LdapTroubleshooter extends Command
{
if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
// POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout)
$this->info('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
$this->line('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
return $function();
} else {
$parent_pid = posix_getpid();
@@ -514,4 +622,6 @@ class LdapTroubleshooter extends Command
}
}
}

View File

@@ -77,7 +77,7 @@ class SendAcceptanceReminder extends Command
if(!$email){
$no_email_list[] = [
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->present()->fullName(),
'name' => $acceptance->assignedTo?->display_name,
];
} else {
$count++;

View File

@@ -1197,19 +1197,30 @@ class Helper
'webp' => 'far fa-image',
'avif' => 'far fa-image',
'svg' => 'fas fa-vector-square',
// word
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
// Excel
'xls' => 'far fa-file-excel',
'xlsx' => 'far fa-file-excel',
'ods' => 'far fa-file-excel',
// Presentation
'ppt' => 'far fa-file-powerpoint',
'odp' => 'far fa-file-powerpoint',
// archive
'zip' => 'fas fa-file-archive',
'rar' => 'fas fa-file-archive',
//Text
'odt' => 'far fa-file-alt',
'txt' => 'far fa-file-alt',
'rtf' => 'far fa-file-alt',
'xml' => 'fas fa-code',
// Misc
'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save',

View File

@@ -29,7 +29,7 @@ class StorageHelper
public static function getMediaType($file_with_path) {
// The file exists and is allowed to be displayed inline
// Get the file extension and determine the media type
if (Storage::exists($file_with_path)) {
$fileinfo = pathinfo($file_with_path);
$extension = strtolower($fileinfo['extension']);
@@ -51,6 +51,15 @@ class StorageHelper
case 'webm':
case 'mov':
return 'video';
case 'doc':
case 'docx':
return 'document';
case 'txt':
return 'text';
case 'xls':
case 'xlsx':
case 'ods':
return 'spreadsheet';
default:
return $extension; // Default for unknown types
}

View File

@@ -146,6 +146,7 @@ class AssetsController extends Controller
'model.category',
'model.manufacturer',
'model.fieldset',
'model.depreciation',
'supplier'
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
@@ -609,7 +610,7 @@ class AssetsController extends Controller
$asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute();
$asset->use_text .= ' → ' . $asset->assigned->display_name;
}

View File

@@ -230,13 +230,13 @@ class ConsumablesController extends Controller
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
'user' => ($consumable_assignment->user) ? [
'id' => (int) $consumable_assignment->user->id,
'name'=> e($consumable_assignment->user->present()->fullName()),
'name'=> e($consumable_assignment->user->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
'created_by' => ($consumable_assignment->adminuser) ? [
'id' => (int) $consumable_assignment->adminuser->id,
'name'=> e($consumable_assignment->adminuser->present()->fullName()),
'name'=> e($consumable_assignment->adminuser->display_name),
] : null,
];
}

View File

@@ -195,7 +195,7 @@ class ImportController extends Controller
// Run a backup immediately before processing
if ($request->get('run-backup')) {
Log::debug('Backup manually requested via importer');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]);
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H-i-s')]);
} else {
Log::debug('NO BACKUP requested via importer');
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
/**
* This class controls all API actions related to notes for
* the Snipe-IT Asset Management application.
*/
class NotesController extends Controller
{
/**
* Retrieve a list of manual notes (action logs) for a given asset.
*
* Checks authorization to view assets, attempts to find the asset by ID,
* and fetches related action log entries of type 'note added', including
* user information for each note. Returns a JSON response with the notes or errors.
*
* @param \Illuminate\Http\Request $request The incoming HTTP request.
* @param Asset $asset The ID of the asset whose notes to retrieve.
* @return \Illuminate\Http\JsonResponse
*/
public function index(Asset $asset): JsonResponse
{
$this->authorize('view', $asset);
// Get the manual notes for the asset
$notes = ActionLog::with('user:id,username')
->where('item_type', Asset::class)
->where('item_id', $asset->id)
->where('action_type', 'note added')
->orderBy('created_at', 'desc')
->get(['id', 'created_at', 'note', 'created_by', 'item_id', 'item_type', 'action_type', 'target_id', 'target_type']);
$notesArray = $notes->map(function ($note) {
return [
'id' => $note->id,
'created_at' => $note->created_at,
'note' => $note->note,
'created_by' => $note->created_by,
'username' => $note->user?->username, // adding the username
'item_id' => $note->item_id,
'item_type' => $note->item_type,
'action_type' => $note->action_type,
];
});
// Return a success response
return response()->json(Helper::formatStandardApiResponse('success', ['notes' => $notesArray, 'asset_id' => $asset->id]));
}
/**
* Store a manual note on a specified asset and log the action.
*
* Checks authorization for updating assets, validates the presence of the 'note',
* attempts to find the asset by ID, and creates a new ActionLog entry if successful.
* Returns JSON responses indicating success or failure with appropriate HTTP status codes.
*
* @param \Illuminate\Http\Request $request The incoming HTTP request containing the 'note'.
* @param Asset $asset The ID of the asset to attach the note to.
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request, Asset $asset): JsonResponse
{
$this->authorize('update', $asset);
if ($request->input('note', '') == '') {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.required', ['attribute' => 'note'])), 422);
}
// Create the note
$logaction = new ActionLog();
$logaction->item_type = get_class($asset);
$logaction->created_by = Auth::id();
$logaction->item_id = $asset->id;
$logaction->note = $request->input('note', '');
if ($logaction->logaction('note added')) {
// Return a success response
return response()->json(Helper::formatStandardApiResponse('success', ['note' => $logaction->note, 'item_id' => $asset->id], trans('general.note_added')));
}
// Return an error response if something went wrong
return response()->json(Helper::formatStandardApiResponse('error', null, 'Something went wrong'), 500);
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Transformers\DatatablesTransformer;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
@@ -51,10 +50,22 @@ class SettingsController extends Controller
})->slice(0, 10)->map(function ($item) use ($settings) {
return (object) [
'username' => $item[$settings['ldap_username_field']][0] ?? null,
'display_name' => $item[$settings['ldap_display_name']][0] ?? null,
'employee_number' => $item[$settings['ldap_emp_num']][0] ?? null,
'lastname' => $item[$settings['ldap_lname_field']][0] ?? null,
'firstname' => $item[$settings['ldap_fname_field']][0] ?? null,
'email' => $item[$settings['ldap_email']][0] ?? null,
'phone' => $item[$settings['ldap_phone_field']][0] ?? null,
'mobile' => $item[$settings['ldap_mobile']][0] ?? null,
'jobtitle' => $item[$settings['ldap_jobtitle']][0] ?? null,
'department' => $item[$settings['ldap_department']][0] ?? null,
'manager' => $item[$settings['ldap_manager']][0] ?? null,
'address' => $item[$settings['ldap_address']][0] ?? null,
'city' => $item[$settings['ldap_city']][0] ?? null,
'state' => $item[$settings['ldap_state']][0] ?? null,
'zip' => $item[$settings['ldap_zip']][0] ?? null,
'country' => $item[$settings['ldap_country']][0] ?? null,
'location' => $item[$settings['ldap_location']][0] ?? null,
];
});
if ($users->count() > 0) {
@@ -78,7 +89,7 @@ class SettingsController extends Controller
}
} catch (\Exception $e) {
Log::debug('Connection failed but we cannot debug it any further on our end.');
return response()->json(['message' => $e->getMessage()], 500);
return response()->json(['message' => $e->getMessage()], 400);
}

View File

@@ -64,6 +64,7 @@ class UsersController extends Controller
'users.jobtitle',
'users.last_login',
'users.last_name',
'users.display_name',
'users.locale',
'users.location_id',
'users.manager_id',
@@ -154,6 +155,10 @@ class UsersController extends Controller
$users = $users->where('users.last_name', '=', $request->input('last_name'));
}
if ($request->filled('display_name')) {
$users = $users->where('users.display_name', '=', $request->input('display_name'));
}
if ($request->filled('employee_num')) {
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
}
@@ -284,6 +289,7 @@ class UsersController extends Controller
[
'last_name',
'first_name',
'display_name',
'email',
'jobtitle',
'username',
@@ -355,6 +361,7 @@ class UsersController extends Controller
'users.employee_num',
'users.first_name',
'users.last_name',
'users.display_name',
'users.gravatar',
'users.avatar',
'users.email',
@@ -365,20 +372,17 @@ class UsersController extends Controller
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
});
}
$users = $users->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
$users = $users->orderBy('display_name', 'asc')->orderBy('last_name', 'asc')->orderBy('first_name', 'asc');
$users = $users->paginate(50);
foreach ($users as $user) {
$name_str = '';
if ($user->last_name != '') {
$name_str .= $user->last_name.', ';
}
$name_str .= $user->first_name;
$name_str = $user->display_name;
if ($user->username != '') {
$name_str .= ' ('.$user->username.')';
@@ -511,6 +515,10 @@ class UsersController extends Controller
$user->username = $request->input('username');
}
if ($request->filled('display_name')) {
$user->display_name = $request->input('display_name');
}
if ($request->filled('email')) {
$user->email = $request->input('email');
}

View File

@@ -797,7 +797,7 @@ class AssetsController extends Controller
'item_id' => $asset->id,
'item_type' => Asset::class,
'created_by' => auth()->id(),
'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer',
'note' => 'Checkout imported by '.auth()->user()->display_name.' from history importer',
'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
'target_type' => User::class,
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
@@ -825,7 +825,7 @@ class AssetsController extends Controller
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
'item_type' => Asset::class,
'created_by' => auth()->id(),
'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer',
'note' => 'Checkin imported by '.auth()->user()->display_name.' from history importer',
'target_id' => null,
'created_at' => $checkin_date,
'action_type' => 'checkin',

View File

@@ -364,7 +364,7 @@ class LicensesController extends Controller
$license->order_number,
$license->free_seat_count,
$license->seats,
($license->adminuser ? $license->adminuser->present()->fullName() : trans('admin/reports/general.deleted_user')),
($license->adminuser ? $license->adminuser->display_name : trans('admin/reports/general.deleted_user')),
$license->depreciation ? $license->depreciation->name: '',
$license->updated_at,
$license->deleted_at,

View File

@@ -275,7 +275,7 @@ class ReportsController extends Controller
if ($actionlog->target) {
if ($actionlog->targetType() == 'user') {
$target_name = $actionlog->target->getFullNameAttribute();
$target_name = $actionlog->target->display_name;
} else {
$target_name = $actionlog->target->getDisplayNameAttribute();
}
@@ -289,7 +289,7 @@ class ReportsController extends Controller
$row = [
$actionlog->created_at,
($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '',
($actionlog->adminuser) ? e($actionlog->adminuser->display_name) : '',
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
@@ -856,7 +856,7 @@ class ReportsController extends Controller
}
if ($request->filled('assigned_to')) {
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? $asset->assigned->getFullNameAttribute() : ($asset->assigned ? $asset->assigned->display_name : '');
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ?? $asset->assigned->display_name;
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : $asset->assignedType();
}

View File

@@ -873,6 +873,7 @@ class SettingsController extends Controller
$setting->ldap_default_group = $request->input('ldap_default_group');
$setting->ldap_filter = $request->input('ldap_filter');
$setting->ldap_username_field = $request->input('ldap_username_field');
$setting->ldap_display_name = $request->input('ldap_display_name');
$setting->ldap_lname_field = $request->input('ldap_lname_field');
$setting->ldap_fname_field = $request->input('ldap_fname_field');
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
@@ -889,7 +890,12 @@ class SettingsController extends Controller
$setting->ldap_pw_sync = $request->input('ldap_pw_sync', '0');
$setting->custom_forgot_pass_url = $request->input('custom_forgot_pass_url');
$setting->ldap_phone_field = $request->input('ldap_phone');
$setting->ldap_mobile = $request->input('ldap_mobile');
$setting->ldap_jobtitle = $request->input('ldap_jobtitle');
$setting->ldap_address = $request->input('ldap_address');
$setting->ldap_city = $request->input('ldap_city');
$setting->ldap_state = $request->input('ldap_state');
$setting->ldap_zip = $request->input('ldap_zip');
$setting->ldap_country = $request->input('ldap_country');
$setting->ldap_location = $request->input('ldap_location');
$setting->ldap_dept = $request->input('ldap_dept');

View File

@@ -88,6 +88,7 @@ class UsersController extends Controller
//Username, email, and password need to be handled specially because the need to respect config values on an edit.
$user->email = trim($request->input('email'));
$user->username = trim($request->input('username'));
$user->display_name = $request->input('display_name');
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
@@ -240,6 +241,7 @@ class UsersController extends Controller
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->display_name = $request->input('display_name');
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
$user->locale = $request->input('locale');
$user->employee_num = $request->input('employee_num');
@@ -562,10 +564,10 @@ class UsersController extends Controller
$user->employee_num,
$user->first_name,
$user->last_name,
$user->present()->fullName(),
$user->display_name,
$user->username,
$user->email,
($user->manager) ? $user->manager->present()->fullName() : '',
($user->manager) ? $user->manager->display_name : '',
($user->userloc) ? $user->userloc->name : '',
($user->department) ? $user->department->name : '',
$user->assets->count(),

View File

@@ -185,7 +185,7 @@ class ViewAssetsController extends Controller
$logaction->target_type = User::class;
$data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1;
$data['requested_by'] = $user->present()->fullName();
$data['requested_by'] = $user->display_name;
$data['item'] = $item;
$data['item_type'] = $itemType;
$data['target'] = auth()->user();

View File

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

View File

@@ -44,7 +44,7 @@ class AccessoriesTransformer
'checkouts_count' => $accessory->checkouts_count,
'created_by' => ($accessory->adminuser) ? [
'id' => (int) $accessory->adminuser->id,
'name'=> e($accessory->adminuser->present()->fullName()),
'name'=> e($accessory->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'),

View File

@@ -150,7 +150,7 @@ class ActionlogsTransformer
'item' => ($actionlog->item) ? [
'id' => (int) $actionlog->item->id,
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
'name' => e($actionlog->item->display_name) ?? null,
'type' => e($actionlog->itemType()),
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
] : null,
@@ -165,19 +165,19 @@ class ActionlogsTransformer
'action_type' => $actionlog->present()->actionType(),
'admin' => ($actionlog->adminuser) ? [
'id' => (int) $actionlog->adminuser->id,
'name' => e($actionlog->adminuser->getFullNameAttribute()),
'name' => e($actionlog->adminuser->display_name),
'first_name'=> e($actionlog->adminuser->first_name),
'last_name'=> e($actionlog->adminuser->last_name)
] : null,
'created_by' => ($actionlog->adminuser) ? [
'id' => (int) $actionlog->adminuser->id,
'name' => e($actionlog->adminuser->getFullNameAttribute()),
'name' => e($actionlog->adminuser->display_name),
'first_name'=> e($actionlog->adminuser->first_name),
'last_name'=> e($actionlog->adminuser->last_name)
] : null,
'target' => ($actionlog->target) ? [
'id' => (int) $actionlog->target->id,
'name' => ($actionlog->targetType()=='user') ? e($actionlog->target->getFullNameAttribute()) : e($actionlog->target->getDisplayNameAttribute()),
'name' => ($actionlog->target->display_name) ?? null,
'type' => e($actionlog->targetType()),
] : null,

View File

@@ -68,7 +68,7 @@ class AssetModelsTransformer
'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes),
'created_by' => ($assetmodel->adminuser) ? [
'id' => (int) $assetmodel->adminuser->id,
'name'=> e($assetmodel->adminuser->present()->fullName()),
'name'=> e($assetmodel->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'),

View File

@@ -58,6 +58,13 @@ class AssetsTransformer
'id' => (int) $asset->model->manufacturer->id,
'name'=> e($asset->model->manufacturer->name),
] : null,
'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? [
'id' => (int) $asset->model->depreciation->id,
'name'=> e($asset->model->depreciation->name),
'months'=> (int) $asset->model->depreciation->months,
'type'=> e($asset->model->depreciation->depreciation_type),
'minimum'=> ($asset->model->depreciation->depreciation_min) ? (int) $asset->model->depreciation->depreciation_min : null,
] : null,
'supplier' => ($asset->supplier) ? [
'id' => (int) $asset->supplier->id,
'name'=> e($asset->supplier->name),
@@ -84,7 +91,7 @@ class AssetsTransformer
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
'created_by' => ($asset->adminuser) ? [
'id' => (int) $asset->adminuser->id,
'name'=> e($asset->adminuser->present()->fullName()),
'name'=> e($asset->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'),
@@ -280,7 +287,7 @@ class AssetsTransformer
'id' => (int) $asset->id,
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
'type' => 'asset',
'name' => e($asset->present()->fullName()),
'name' => e($asset->display_name),
'model' => ($asset->model) ? e($asset->model->name) : null,
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'asset_tag' => e($asset->asset_tag),

View File

@@ -64,7 +64,7 @@ class CategoriesTransformer
'licenses_count' => (int) $category->licenses_count,
'created_by' => ($category->adminuser) ? [
'id' => (int) $category->adminuser->id,
'name'=> e($category->adminuser->present()->fullName()),
'name'=> e($category->adminuser->display_name),
] : null,
'notes' => Helper::parseEscapedMarkedownInline($category->notes),
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),

View File

@@ -38,7 +38,7 @@ class CompaniesTransformer
'users_count' => (int) $company->users_count,
'created_by' => ($company->adminuser) ? [
'id' => (int) $company->adminuser->id,
'name'=> e($company->adminuser->present()->fullName()),
'name'=> e($company->adminuser->display_name),
] : null,
'notes' => Helper::parseEscapedMarkedownInline($company->notes),
'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'),

View File

@@ -51,7 +51,7 @@ class ComponentsTransformer
'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null,
'created_by' => ($component->adminuser) ? [
'id' => (int) $component->adminuser->id,
'name'=> e($component->adminuser->present()->fullName()),
'name'=> e($component->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'),

View File

@@ -42,7 +42,7 @@ class ConsumablesTransformer
'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null,
'created_by' => ($consumable->adminuser) ? [
'id' => (int) $consumable->adminuser->id,
'name'=> e($consumable->adminuser->present()->fullName()),
'name'=> e($consumable->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'),

View File

@@ -35,7 +35,7 @@ class DepartmentsTransformer
] : null,
'manager' => ($department->manager) ? [
'id' => (int) $department->manager->id,
'name' => e($department->manager->getFullNameAttribute()),
'name' => e($department->manager->display_name),
'first_name'=> e($department->manager->first_name),
'last_name'=> e($department->manager->last_name),
] : null,

View File

@@ -33,7 +33,7 @@ class DepreciationsTransformer
'licenses_count' => ($depreciation->licenses_count > 0) ? (int) $depreciation->licenses_count : 0,
'created_by' => ($depreciation->adminuser) ? [
'id' => (int) $depreciation->adminuser->id,
'name'=> e($depreciation->adminuser->present()->fullName()),
'name'=> e($depreciation->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime')

View File

@@ -29,7 +29,7 @@ class GroupsTransformer
'notes' => Helper::parseEscapedMarkedownInline($group->notes),
'created_by' => ($group->adminuser) ? [
'id' => (int) $group->adminuser->id,
'name'=> e($group->adminuser->present()->fullName()),
'name'=> e($group->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'),

View File

@@ -48,7 +48,7 @@ class LicensesTransformer
'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null,
'created_by' => ($license->adminuser) ? [
'id' => (int) $license->adminuser->id,
'name'=> e($license->adminuser->present()->fullName()),
'name'=> e($license->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),

View File

@@ -73,11 +73,11 @@ class MaintenancesTransformer
'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'),
'user_id' => ($assetmaintenance->adminuser) ? [
'id' => $assetmaintenance->adminuser->id,
'name'=> e($assetmaintenance->adminuser->present()->fullName())
'name'=> e($assetmaintenance->adminuser->display_name)
] : null, // legacy to not change the shape of the API
'created_by' => ($assetmaintenance->adminuser) ? [
'id' => (int) $assetmaintenance->adminuser->id,
'name'=> e($assetmaintenance->adminuser->present()->fullName()),
'name'=> e($assetmaintenance->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'),

View File

@@ -40,7 +40,7 @@ class ManufacturersTransformer
'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes),
'created_by' => ($manufacturer->adminuser) ? [
'id' => (int) $manufacturer->adminuser->id,
'name'=> e($manufacturer->adminuser->present()->fullName()),
'name'=> e($manufacturer->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'),

View File

@@ -34,7 +34,7 @@ class PredefinedKitsTransformer
'name' => e($kit->name),
'created_by' => ($kit->adminuser) ? [
'id' => (int) $kit->adminuser->id,
'name'=> e($kit->adminuser->present()->fullName()),
'name'=> e($kit->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'),

View File

@@ -26,7 +26,7 @@ class ProfileTransformer
'id' => (int) $file->id,
'icon' => Helper::filetype_icon($file->filename),
'item' => ($file->item) ? [
'name' => ($file->itemType()=='user') ? e($file->item->getFullNameAttribute()) : e($file->item->getDisplayNameAttribute()),
'name' => ($file->itemType()=='user') ? e($file->item->display_name) : e($file->item->getDisplayNameAttribute()),
'type' => e($file->itemType()),
] : null,
'filename' => e($file->filename),

View File

@@ -32,7 +32,7 @@ class StatuslabelsTransformer
'notes' => e($statuslabel->notes),
'created_by' => ($statuslabel->adminuser) ? [
'id' => (int) $statuslabel->adminuser->id,
'name'=> e($statuslabel->adminuser->present()->fullName()),
'name'=> e($statuslabel->adminuser->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'),

View File

@@ -45,7 +45,7 @@ class UploadedFilesTransformer
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()),
'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()) ?? false,
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
];

View File

@@ -31,16 +31,17 @@ class UsersTransformer
$array = [
'id' => (int) $user->id,
'avatar' => e($user->present()->gravatar) ?? null,
'name' => e($user->getFullNameAttribute()),
'first_name' => e($user->first_name),
'last_name' => e($user->last_name),
'username' => e($user->username),
'name' => e($user->getFullNameAttribute()) ?? null,
'first_name' => e($user->first_name) ?? null,
'last_name' => e($user->last_name) ?? null,
'display_name' => e($user->getRawOriginal('display_name')) ?? null,
'username' => e($user->username) ?? null,
'remote' => ($user->remote == '1') ? true : false,
'locale' => ($user->locale) ? e($user->locale) : null,
'employee_num' => ($user->employee_num) ? e($user->employee_num) : null,
'manager' => ($user->manager) ? [
'id' => (int) $user->manager->id,
'name'=> e($user->manager->first_name).' '.e($user->manager->last_name),
'name'=> e($user->manager->display_name),
] : null,
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
'vip' => ($user->vip == '1') ? true : false,
@@ -59,7 +60,7 @@ class UsersTransformer
] : null,
'department_manager' => ($user->department?->manager) ? [
'id' => (int) $user->department->manager->id,
'name'=> e($user->department->manager->full_name),
'name'=> e($user->department->manager->display_name),
] : null,
'location' => ($user->userloc) ? [
'id' => (int) $user->userloc->id,
@@ -82,7 +83,7 @@ class UsersTransformer
'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null,
'created_by' => ($user->createdBy) ? [
'id' => (int) $user->createdBy->id,
'name'=> e($user->createdBy->present()->fullName),
'name'=> e($user->createdBy->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($user->updated_at, 'datetime'),
@@ -138,6 +139,7 @@ class UsersTransformer
'first_name' => e($user->first_name),
'last_name' => e($user->last_name),
'username' => e($user->username),
'display_name' => e($user->display_name),
'created_by' => $user->adminuser ? [
'id' => (int) $user->adminuser->id,
'name'=> e($user->adminuser->present()->fullName),

View File

@@ -72,6 +72,7 @@ abstract class Importer
'termination_date' => 'termination date',
'warranty_months' => 'warranty',
'full_name' => 'full name',
'display_name' => 'display name',
'email' => 'email',
'username' => 'username',
'address' => 'address',
@@ -299,6 +300,7 @@ abstract class Importer
'full_name' => $this->findCsvMatch($row, 'full_name'),
'first_name' => $this->findCsvMatch($row, 'first_name'),
'last_name' => $this->findCsvMatch($row, 'last_name'),
'display_name' => $this->findCsvMatch($row, 'display_name'),
'email' => $this->findCsvMatch($row, 'email'),
'manager_id'=> '',
'department_id' => '',
@@ -369,6 +371,7 @@ abstract class Importer
$user->first_name = $user_array['first_name'];
$user->last_name = $user_array['last_name'];
$user->username = $user_array['username'];
$user->display_name = $user_array['display_name'] ?? null;
$user->email = $user_array['email'];
$user->manager_id = $user_array['manager_id'] ?? null;
$user->department_id = $user_array['department_id'] ?? null;

View File

@@ -47,6 +47,7 @@ class UserImporter extends ItemImporter
// Pull the records from the CSV to determine their values
$this->item['id'] = trim($this->findCsvMatch($row, 'id'));
$this->item['username'] = trim($this->findCsvMatch($row, 'username'));
$this->item['display_name'] = trim($this->findCsvMatch($row, 'display_name'));
$this->item['first_name'] = trim($this->findCsvMatch($row, 'first_name'));
$this->item['last_name'] = trim($this->findCsvMatch($row, 'last_name'));
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));

View File

@@ -339,6 +339,7 @@ class Importer extends Component
'start_date' => trans('general.start_date'),
'state' => trans('general.state'),
'username' => trans('admin/users/table.username'),
'display_name' => trans('admin/users/table.display_name'),
'vip' => trans('general.importer.vip'),
'website' => trans('general.website'),
'zip' => trans('general.zip'),
@@ -485,6 +486,13 @@ class Importer extends Component
'username',
trans('general.importer.checked_out_to_username'),
],
'display_name' =>
[
'display name',
'displayName',
'display',
trans('admin/users/table.display_name'),
],
'first_name' =>
[
'first name',

View File

@@ -47,7 +47,7 @@ class CheckinAssetMail extends Mailable
return new Envelope(
from: $from,
subject: trans('mail.Asset_Checkin_Notification'),
subject: trans('mail.Asset_Checkin_Notification', ['tag' => $this->item->asset_tag]),
);
}

View File

@@ -3,6 +3,8 @@
namespace App\Mail;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
@@ -41,7 +43,7 @@ class CheckoutAccessoryMail extends Mailable
return new Envelope(
from: $from,
subject: (trans('mail.Accessory_Checkout_Notification')),
subject: trans('mail.Accessory_Checkout_Notification'),
);
}
@@ -54,6 +56,17 @@ class CheckoutAccessoryMail extends Mailable
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
$name = null;
if($this->target instanceof User){
$name = $this->target->display_name;
}
else if($this->target instanceof Asset){
$name = $this->target->assignedto?->display_name;
}
else if($this->target instanceof Location){
$name = $this->target->manager->name;
}
return new Content(
markdown: 'mail.markdown.checkout-accessory',
@@ -61,14 +74,35 @@ class CheckoutAccessoryMail extends Mailable
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
'target' => $name,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => $accept_url,
'checkout_qty' => $this->checkout_qty,
'introduction_line' => $this->introductionLine(),
],
);
}
private function introductionLine(): string
{
if ($this->target instanceof Location) {
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
}
if ($this->requiresAcceptance()) {
return trans('mail.new_item_checked_with_acceptance');
}
if (!$this->requiresAcceptance()) {
return trans('mail.new_item_checked');
}
// we shouldn't get here but let's send a default message just in case
return trans('new_item_checked');
}
private function requiresAcceptance(): int|bool
{
return method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
}
/**
* Get the attachments for the message.

View File

@@ -4,6 +4,7 @@ namespace App\Mail;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Bus\Queueable;
@@ -36,14 +37,6 @@ class CheckoutAssetMail extends Mailable
$this->settings = Setting::getSettings();
$this->target = $checkedOutTo;
// Location is a target option, but there are no emails currently associated with locations.
if($this->target instanceof User){
$this->target = $this->target->present()?->fullName();
}
else if($this->target instanceof Asset){
$this->target = $this->target->assignedto?->present()?->fullName();
}
$this->last_checkout = '';
$this->expected_checkin = '';
@@ -85,6 +78,17 @@ class CheckoutAssetMail extends Mailable
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
$req_accept = $this->requiresAcceptance();
$fields = [];
$name = null;
if($this->target instanceof User){
$name = $this->target->display_name;
}
else if($this->target instanceof Asset){
$name = $this->target->assignedto?->display_name;
}
else if($this->target instanceof Location){
$name = $this->target->manager->name;
}
// Check if the item has custom fields associated with it
if (($this->item->model) && ($this->item->model->fieldset)) {
@@ -100,7 +104,7 @@ class CheckoutAssetMail extends Mailable
'admin' => $this->admin,
'status' => $this->item->assetstatus?->name,
'note' => $this->note,
'target' => $this->target,
'target' => $name,
'fields' => $fields,
'eula' => $eula,
'req_accept' => $req_accept,
@@ -125,7 +129,7 @@ class CheckoutAssetMail extends Mailable
private function getSubject(): string
{
if ($this->firstTimeSending) {
return trans('mail.Asset_Checkout_Notification');
return trans('mail.Asset_Checkout_Notification', ['tag' => $this->item->asset_tag]);
}
return trans('mail.unaccepted_asset_reminder');
@@ -133,6 +137,9 @@ class CheckoutAssetMail extends Mailable
private function introductionLine(): string
{
if ($this->firstTimeSending && $this->target instanceof Location) {
return trans('mail.new_item_checked_location', ['location' => $this->target->name ]);
}
if ($this->firstTimeSending && $this->requiresAcceptance()) {
return trans('mail.new_item_checked_with_acceptance');
}

View File

@@ -31,10 +31,10 @@ class CheckoutLicenseMail extends Mailable
$this->target = $checkedOutTo;
if($this->target instanceof User){
$this->target = $this->target->present()?->fullName();
$this->target = $this->target->display_name;
}
elseif($this->target instanceof Asset){
$this->target = $this->target->assignedto?->present()?->fullName();
$this->target = $this->target->display_name;
}
}

View File

@@ -30,10 +30,10 @@ class FieldOption
if ($asset->relationLoaded('assignedTo')) {
// If the "assignedTo" relationship was eager loaded then the way to get the
// relationship changes from $asset->assignedTo to $asset->assigned.
return $asset->assigned ? $asset->assigned->present()->fullName() : null;
return $asset->assigned ? $asset->assigned->display_name : null;
}
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
return $asset->assignedTo ? $asset->assignedTo->display_name : null;
}
// Handle Laravel's stupid Carbon datetime casting

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
use App\Helpers\Helper;
use App\Models\Labels\RectangleSheet;
abstract class L6009 extends RectangleSheet
{
private const PAPER_FORMAT = 'A4';
private const PAPER_ORIENTATION = 'P';
/* Data in pt from Word Template */
private const COLUMN1_X = 31.70;
private const COLUMN2_X = 167.92;
private const ROW1_Y = 53.00;
private const ROW2_Y = 112.8;
private const LABEL_W = 122.24;
private const LABEL_H = 66.5;
private float $pageWidth;
private float $pageHeight;
private float $pageMarginLeft;
private float $pageMarginTop;
private float $columnSpacing;
private float $rowSpacing;
private float $labelWidth;
private float $labelHeight;
public function __construct()
{
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
$this->pageWidth = $paperSize->width;
$this->pageHeight = $paperSize->height;
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
}
public function getPageWidth()
{
return $this->pageWidth;
}
public function getPageHeight()
{
return $this->pageHeight;
}
public function getPageMarginTop()
{
return $this->pageMarginTop;
}
public function getPageMarginBottom()
{
return $this->pageMarginTop;
}
public function getPageMarginLeft()
{
return $this->pageMarginLeft;
}
public function getPageMarginRight()
{
return $this->pageMarginLeft;
}
public function getColumns()
{
return 4;
}
public function getRows()
{
return 12;
}
public function getLabelColumnSpacing()
{
return $this->columnSpacing;
}
public function getLabelRowSpacing()
{
return $this->rowSpacing;
}
public function getLabelWidth()
{
return $this->labelWidth;
}
public function getLabelHeight()
{
return $this->labelHeight;
}
public function getLabelBorder()
{
return 0;
}
}
?>

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Models\Labels\Sheets\Avery;
class L6009_A extends L6009
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 4.80;
private const TITLE_SIZE = 3.00;
private const TITLE_MARGIN = 1.80;
private const LABEL_SIZE = 2.8;
private const LABEL_MARGIN = - 0.45;
private const FIELD_SIZE = 3.80;
private const FIELD_MARGIN = 0.20;
public function getUnit()
{
return 'mm';
}
public function getLabelMarginTop()
{
return 0.06;
}
public function getLabelMarginBottom()
{
return 0.06;
}
public function getLabelMarginLeft()
{
return 0.06;
}
public function getLabelMarginRight()
{
return 0.06;
}
public function getSupportAssetTag()
{
return true;
}
public function getSupport1DBarcode()
{
return false;
}
public function getSupport2DBarcode()
{
return true;
}
public function getSupportFields()
{
return 4;
}
public function getSupportLogo()
{
return false;
}
public function getSupportTitle()
{
return true;
}
public function preparePDF($pdf)
{
}
public function write($pdf, $record)
{
$pa = $this->getLabelPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$pa->x1, $pa->y1,
'freesans', '', self::TITLE_SIZE, 'C',
$pa->w, self::TITLE_SIZE, true, 0
);
}
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
$usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
$barcodeSize = $usableHeight;
if ($record->has('barcode2d')) {
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', self::LABEL_SIZE, 'L',
$usableWidth, self::LABEL_SIZE, true, 0
);
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.01
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
}
}
?>

View File

@@ -0,0 +1,116 @@
<?php
namespace App\Models\Labels\Tapes\Dymo;
class LabelWriter_11354 extends LabelWriter
{
private const BARCODE1D_HEIGHT = 3.00;
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 2.80;
private const TITLE_SIZE = 2.80;
private const TITLE_MARGIN = 0.50;
private const FIELD_SIZE = 2.80;
private const FIELD_MARGIN = 0.15;
public function getUnit()
{
return 'mm';
}
public function getWidth()
{
return 57;
}
public function getHeight()
{
return 32;
}
public function getSupportAssetTag()
{
return true;
}
public function getSupport1DBarcode()
{
return true;
}
public function getSupport2DBarcode()
{
return true;
}
public function getSupportFields()
{
return 5;
}
public function getSupportLogo()
{
return false;
}
public function getSupportTitle()
{
return true;
}
public function preparePDF($pdf)
{
}
public function write($pdf, $record)
{
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$usableHeight = $pa->h;
// Wide 1D barcode on top
if ($record->has('barcode1d')) {
static::write1DBarcode(
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
$currentX, $currentY, $usableWidth, self::BARCODE1D_HEIGHT
);
$currentY += self::BARCODE1D_HEIGHT + self::BARCODE_MARGIN;
$usableHeight -= self::BARCODE1D_HEIGHT + self::BARCODE_MARGIN;
}
// 2D Barcode in left column
if ($record->has('barcode2d')) {
$barcodeSize = $usableHeight - self::TAG_SIZE;
static::writeText(
$pdf, $record->get('tag'),
$currentX, $pa->y2 - self::TAG_SIZE,
'freesans', 'b', self::TAG_SIZE, 'C',
$barcodeSize, self::TAG_SIZE, true, 0
);
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
}
// Right column
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$currentX, $currentY,
'freesans', 'b', self::TITLE_SIZE, 'L',
$usableWidth, self::TITLE_SIZE, true, 0
);
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
$currentX, $currentY,
'freesans', '', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
}
}

View File

@@ -27,6 +27,35 @@ use Illuminate\Support\Facades\Crypt;
class Ldap extends Model
{
public static function ignoreCertificates(bool $ignore_cert = true)
{
if (defined('LDAP_OPT_X_TLS_REQUIRE_CERT') && defined('LDAP_OPT_X_TLS_NEVER')) {
// TODO - we are currently, as a 'safety', doing *both* the following 'new-style' ldap_set_option calls,
// as well as "falling-through" to the 'old-style' putenv() calls.
//
// I *suspect* we can eventually remove the putenv() calls, but I'm just a little nervous about that.
// According to the PHP docs, the LDAP_OPT_X_TLS_REQUIRE_CERT constant has been available since PHP 7.0.
// We're currently using PHP versions way, way later than that (v8.2-v8.4 as of this writing). So it's
// unlikely that these constants wouldn't be defined - unless you didn't have LDAP support in the first
// place. But if that were to happen, I would hope we would've detected that long, long ago, rather than at
// this point.
if ($ignore_cert) {
if (ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER)) {
//return true;
}
} else {
if (ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) {
//return true;
}
}
}
if ($ignore_cert) {
return putenv('LDAPTLS_REQCERT=never');
} else {
return putenv('LDAPTLS_REQCERT');
}
}
/**
* Makes a connection to LDAP using the settings in Admin > Settings.
*
@@ -43,15 +72,12 @@ class Ldap extends Model
// If we are ignoring the SSL cert we need to setup the environment variable
// before we create the connection
if ($ldap_server_cert_ignore == '1') {
putenv('LDAPTLS_REQCERT=never');
}
self::ignoreCertificates((bool)$ldap_server_cert_ignore);
// If the user specifies where CA Certs are, make sure to use them
if (env('LDAPTLS_CACERT')) {
putenv('LDAPTLS_CACERT='.env('LDAPTLS_CACERT'));
}
$connection = @ldap_connect($ldap_host);
if (! $connection) {

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Helpers\Helper;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class SnipeModel extends Model
@@ -21,7 +22,7 @@ class SnipeModel extends Model
*/
public function setPurchaseCostAttribute($value)
{
if (is_float($value)) {
if (is_numeric($value)) {
//value is *already* a floating-point number. Just assign it directly
$this->attributes['purchase_cost'] = $value;
return;
@@ -155,9 +156,13 @@ class SnipeModel extends Model
$this->attributes['status_id'] = $value;
}
//
public function getDisplayNameAttribute()
protected function displayName(): Attribute
{
return $this->name;
return Attribute:: make(
get: fn(mixed $value) => $this->name,
);
}
}

View File

@@ -34,6 +34,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
'validations' => [
$user_prefix . 'userName' => 'required',
$user_prefix . 'displayName' => 'nullable|string',
$user_prefix . 'name.givenName' => 'required',
$user_prefix . 'name.familyName' => 'nullable|string',
$user_prefix . 'externalId' => 'nullable|string',
@@ -121,7 +122,7 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
'honorificSuffix' => null
],
'displayName' => null,
'displayName' => AttributeMapping::eloquent("display_name"),
'nickName' => null,
'profileUrl' => null,
'title' => AttributeMapping::eloquent('jobtitle'),

View File

@@ -64,6 +64,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'first_name',
'jobtitle',
'last_name',
'display_name',
'ldap_import',
'locale',
'location_id',
@@ -103,6 +104,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
protected $rules = [
'first_name' => 'required|string|min:1|max:191',
'last_name' => 'nullable|string|max:191',
'display_name' => 'nullable|string|max:191',
'username' => 'required|string|min:1|unique_undeleted|max:191',
'email' => 'email|nullable|max:191',
'password' => 'required|min:8',
@@ -113,9 +116,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'start_date' => 'nullable|date_format:Y-m-d',
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
'autoassign_licenses' => 'boolean',
'address' => 'max:191|nullable',
'city' => 'max:191|nullable',
'state' => 'min:2|max:191|nullable',
'address' => 'nullable|string|max:191',
'city' => 'nullable|string|max:191',
'state' => 'nullable|string|max:191',
'country' => 'min:2|max:191|nullable',
'zip' => 'max:10|nullable',
'vip' => 'boolean',
@@ -132,15 +135,16 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'address',
'city',
'country',
'display_name',
'email',
'employee_num',
'first_name',
'jobtitle',
'last_name',
'locale',
'mobile',
'notes',
'phone',
'mobile',
'state',
'username',
'website',
@@ -157,7 +161,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'department' => ['name'],
'groups' => ['name'],
'company' => ['name'],
'manager' => ['first_name', 'last_name', 'username'],
'manager' => ['first_name', 'last_name', 'username', 'display_name'],
];
@@ -196,8 +200,20 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
});
}
/**
* This overrides the SnipeModel displayName accessor to return the full name if display_name is not set
* @see SnipeModel::displayName()
* @return Attribute
*/
public function isAvatarExternal()
protected function displayName(): Attribute
{
return Attribute:: make(
get: fn(mixed $value) => $value ?? $this->getFullNameAttribute(),
);
}
public function isAvatarExternal() : bool
{
// Check if it's a google avatar or some external avatar
if (Str::startsWith($this->avatar, ['http://', 'https://'])) {
@@ -859,6 +875,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
return $query->where('first_name', 'LIKE', '%' . $search . '%')
->orWhere('last_name', 'LIKE', '%' . $search . '%')
->orWhere('display_name', 'LIKE', '%' . $search . '%')
->orWhereMultipleColumns(
[
'users.first_name',
@@ -1068,6 +1085,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
->orWhere('users.jobtitle', 'LIKE', '%' . $search . '%')
->orWhere('users.employee_num', 'LIKE', '%' . $search . '%')
->orWhere('users.username', 'LIKE', '%' . $search . '%')
->orWhere('users.display_name', 'LIKE', '%' . $search . '%')
->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\'');
}

View File

@@ -57,14 +57,14 @@ use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
return (new SlackMessage)
->success()
->content(class_basename(get_class($this->params['item'])).' Audited')
->content(class_basename(get_class($this->params['item'])).' '.trans('general.audited'))
->from(($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot')
->to($channel)
->attachment(function ($attachment) {
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$fields = [
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>',
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->display_name.'>',
];
array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note'];
array_key_exists('location', $this->params) && $fields['Location'] = $this->params['location'];
@@ -76,22 +76,22 @@ use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
public static function toMicrosoftTeams($params)
{
$item = $params['item'];
$admin_user = $params['admin'];
$note = $params['note'];
$location = $params['location'];
$item = $params['item'] ?? null;
$admin_user = $params['admin'] ?? null;
$note = $params['note'] ?? '';
$location = $params['location'] ?? '';
$setting = Setting::getSettings();
if(!Str::contains($setting->webhook_endpoint, 'workflows')) {
return MicrosoftTeamsMessage::create()
->to($setting->webhook_endpoint)
->type('success')
->title(class_basename(get_class($params['item'])) . ' Audited')
->title(class_basename(get_class($params['item'])) .' '.trans('general.audited'))
->addStartGroupToSection('activityText')
->fact(trans('mail.asset'), $item)
->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->present()->fullName());
->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->display_name);
}
$message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->present()->fullName();
$message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->display_name;
$details = [
trans('mail.asset') => htmlspecialchars_decode($item->present()->name),
trans('mail.notes') => $note ?: '',

View File

@@ -73,8 +73,8 @@ class CheckinAccessoryNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -109,7 +109,7 @@ class CheckinAccessoryNotification extends Notification
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '')
->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName())
->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
@@ -118,7 +118,7 @@ class CheckinAccessoryNotification extends Notification
$details = [
trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name),
trans('mail.checked_into') => $item->location->name ? $item->location->name : '',
trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->present()->fullName(),
trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->display_name,
trans('admin/consumables/general.remaining')=> $item->numRemaining(),
trans('mail.notes') => $note ?: '',
];

View File

@@ -78,7 +78,7 @@ class CheckinAssetNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
trans('general.status') => $item->assetstatus?->name,
trans('general.location') => ($item->location) ? $item->location->name : '',
];
@@ -116,7 +116,7 @@ class CheckinAssetNotification extends Notification
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
->fact(trans('mail.checked_into'), ($item->location) ? $item->location->name : '')
->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->present()->fullName())
->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->display_name)
->fact(trans('admin/hardware/form.status'), $item->assetstatus?->name)
->fact(trans('mail.notes'), $note ?: '');
}
@@ -126,7 +126,7 @@ class CheckinAssetNotification extends Notification
$details = [
trans('mail.asset') => htmlspecialchars_decode($item->present()->name),
trans('mail.checked_into') => ($item->location) ? $item->location->name : '',
trans('mail.Asset_Checkin_Notification')." by " => $admin->present()->fullName(),
trans('mail.Asset_Checkin_Notification')." by " => $admin->display_name,
trans('admin/hardware/form.status') => $item->assetstatus?->name,
trans('mail.notes') => $note ?: '',
];

View File

@@ -76,8 +76,8 @@ class CheckinComponentNotification extends Notification
if ($admin) {
$fields = [
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -90,7 +90,7 @@ class CheckinComponentNotification extends Notification
} else {
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'To' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
'By' => 'CLI tool',
];
}
@@ -119,16 +119,16 @@ class CheckinComponentNotification extends Notification
->title(trans('mail.Component_checkin_notification'))
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'header')
->fact(trans('mail.Component_checkin_notification')." by ", $admin->present()->fullName() ?: 'CLI tool')
->fact(trans('mail.checkedin_from'), $target->present()->fullName())
->fact(trans('mail.Component_checkin_notification')." by ", $admin->display_name ?: 'CLI tool')
->fact(trans('mail.checkedin_from'), $target->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
$message = trans('mail.Component_checkin_notification');
$details = [
trans('mail.checkedin_from')=> $target->present()->fullName(),
trans('mail.Component_checkin_notification')." by " => $admin->present()->fullName() ?: 'CLI tool',
trans('mail.checkedin_from')=> $target->display_name,
trans('mail.Component_checkin_notification')." by " => $admin->display_name ?: 'CLI tool',
trans('admin/consumables/general.remaining') => $item->numRemaining(),
trans('mail.notes') => $note ?: '',
];
@@ -153,7 +153,7 @@ class CheckinComponentNotification extends Notification
Section::create(
KeyValue::create(
trans('mail.checkedin_from') ?: '',
$target->present()->fullName() ?: '',
$target->display_name ?: '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
)
->onClick(route('components.show', $item->id))

View File

@@ -77,8 +77,8 @@ class CheckinLicenseSeatNotification extends Notification
if ($admin) {
$fields = [
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -91,7 +91,7 @@ class CheckinLicenseSeatNotification extends Notification
} else {
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'To' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
'By' => 'CLI tool',
];
}
@@ -120,17 +120,17 @@ class CheckinLicenseSeatNotification extends Notification
->title(trans('mail.License_Checkin_Notification'))
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'header')
->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool')
->fact(trans('mail.checkedin_from'), $target->present()->fullName())
->fact(trans('mail.License_Checkin_Notification')." by ", $admin->display_name ?: 'CLI tool')
->fact(trans('mail.checkedin_from'), $target->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
$message = trans('mail.License_Checkin_Notification');
$details = [
trans('mail.checkedin_from')=> $target->present()->fullName(),
trans('mail.checkedin_from')=> $target->display_name,
trans('mail.license_for') => htmlspecialchars_decode($item->present()->name),
trans('mail.License_Checkin_Notification')." by " => $admin->present()->fullName() ?: 'CLI tool',
trans('mail.License_Checkin_Notification')." by " => $admin->display_name ?: 'CLI tool',
trans('admin/consumables/general.remaining') => $item->availCount()->count(),
trans('mail.notes') => $note ?: '',
];
@@ -155,7 +155,7 @@ class CheckinLicenseSeatNotification extends Notification
Section::create(
KeyValue::create(
trans('mail.checkedin_from') ?: '',
$target->present()->fullName() ?: '',
$target->display_name ?: '',
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
)
->onClick(route('licenses.show', $item->id))

View File

@@ -100,8 +100,8 @@ class CheckoutAccessoryNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -140,7 +140,7 @@ class CheckoutAccessoryNotification extends Notification
->fact(trans('mail.assigned_to'), $target->present()->name)
->fact(trans('general.qty'), $this->checkout_qty)
->fact(trans('mail.checkedout_from'), $item->location->name ? $item->location->name : '')
->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName())
->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
@@ -151,7 +151,7 @@ class CheckoutAccessoryNotification extends Notification
trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name),
trans('general.qty') => $this->checkout_qty,
trans('mail.checkedout_from') => $item->location->name ? $item->location->name : '',
trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->present()->fullName(),
trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->display_name,
trans('admin/consumables/general.remaining')=> $item->numRemaining(),
trans('mail.notes') => $note ?: '',
];

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->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -135,7 +135,7 @@ class CheckoutAssetNotification extends Notification
->addStartGroupToSection('activityText')
->fact(trans('mail.assigned_to'), $target->present()->name)
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText')
->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName())
->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->display_name)
->fact(trans('mail.notes'), $note ?: '');
}
@@ -143,7 +143,7 @@ class CheckoutAssetNotification extends Notification
$details = [
trans('mail.assigned_to') => $target->present()->name,
trans('mail.asset') => htmlspecialchars_decode($item->present()->name),
trans('mail.Asset_Checkout_Notification'). ' by' => $admin->present()->fullName(),
trans('mail.Asset_Checkout_Notification'). ' by' => $admin->display_name,
trans('mail.notes') => $note ?: '',
];
return array($message, $details);

View File

@@ -80,8 +80,8 @@ class CheckoutComponentNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -117,17 +117,17 @@ class CheckoutComponentNotification extends Notification
->title(trans('mail.Component_checkout_notification'))
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
->fact(trans('mail.Component_checkout_notification')." by ", $admin->present()->fullName())
->fact(trans('mail.assigned_to'), $target->present()->fullName())
->fact(trans('mail.Component_checkout_notification')." by ", $admin->display_name)
->fact(trans('mail.assigned_to'), $target->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
$message = trans('mail.Component_checkout_notification');
$details = [
trans('mail.assigned_to') => $target->present()->fullName(),
trans('mail.assigned_to') => $target->display_name,
trans('mail.item') => htmlspecialchars_decode($item->present()->name),
trans('mail.Component_checkout_notification').' by' => $admin->present()->fullName(),
trans('mail.Component_checkout_notification').' by' => $admin->display_name,
trans('admin/consumables/general.remaining') => $item->numRemaining(),
trans('mail.notes') => $note ?: '',
];
@@ -152,7 +152,7 @@ class CheckoutComponentNotification extends Notification
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->fullName() ?: '',
$target->display_name ?: '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
)
->onClick(route('api.assets.show', $target->id))

View File

@@ -80,8 +80,8 @@ class CheckoutConsumableNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -117,17 +117,17 @@ class CheckoutConsumableNotification extends Notification
->title(trans('mail.Consumable_checkout_notification'))
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName())
->fact(trans('mail.assigned_to'), $target->present()->fullName())
->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->display_name)
->fact(trans('mail.assigned_to'), $target->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
$message = trans('mail.Consumable_checkout_notification');
$details = [
trans('mail.assigned_to') => $target->present()->fullName(),
trans('mail.assigned_to') => $target->display_name,
trans('mail.item') => htmlspecialchars_decode($item->present()->name),
trans('mail.Consumable_checkout_notification').' by' => $admin->present()->fullName(),
trans('mail.Consumable_checkout_notification').' by' => $admin->display_name,
trans('admin/consumables/general.remaining') => $item->numRemaining(),
trans('mail.notes') => $note ?: '',
];
@@ -152,7 +152,7 @@ class CheckoutConsumableNotification extends Notification
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->fullName() ?: '',
$target->display_name ?: '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
)
->onClick(route('users.show', $target->id))

View File

@@ -78,8 +78,8 @@ class CheckoutLicenseSeatNotification extends Notification
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
$fields = [
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->display_name.'>',
];
if ($item->location) {
@@ -115,17 +115,17 @@ class CheckoutLicenseSeatNotification extends Notification
->title(trans('mail.License_Checkout_Notification'))
->addStartGroupToSection('activityText')
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName())
->fact(trans('mail.assigned_to'), $target->present()->fullName())
->fact(trans('mail.License_Checkout_Notification')." by ", $admin->display_name)
->fact(trans('mail.assigned_to'), $target->display_name)
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
$message = trans('mail.License_Checkout_Notification');
$details = [
trans('mail.assigned_to') => $target->present()->fullName(),
trans('mail.assigned_to') => $target->display_name,
trans('mail.license_for') => htmlspecialchars_decode($item->present()->name),
trans('mail.License_Checkout_Notification').' by' => $admin->present()->fullName(),
trans('mail.License_Checkout_Notification').' by' => $admin->display_name,
trans('admin/consumables/general.remaining') => $item->availCount()->count(),
trans('mail.notes') => $note ?: '',
];

View File

@@ -79,7 +79,7 @@ class RequestAssetCancelation extends Notification
$fields = [
'QTY' => $qty,
'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
];
if (($this->expected_checkin) && ($this->expected_checkin != '')) {

View File

@@ -78,7 +78,7 @@ class RequestAssetNotification extends Notification
$fields = [
'QTY' => $qty,
'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->display_name.'>',
];
return (new SlackMessage)

View File

@@ -53,6 +53,7 @@ class LicensePresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('admin/licenses/form.to_email'),
'formatter' => 'emailFormatter',
], [
'field' => 'license_name',
'searchable' => true,

View File

@@ -3,6 +3,7 @@
namespace App\Presenters;
use App\Models\SnipeModel;
use Illuminate\Database\Eloquent\Casts\Attribute;
abstract class Presenter
{
@@ -69,10 +70,30 @@ abstract class Presenter
return '';
}
public function name()
{
return $this->model->name;
}
// public function name()
// {
// return $this->model->name;
// }
//
// public function display_name()
// {
// return $this->model->display_name;
// }
// protected function displayName(): Attribute
// {
// // This override should only kick in if the model has a display_name prope
// if ($this->getRawOriginal('display_name')) {
// return Attribute:: make (
// get: fn(mixed $value) => 'Poop:'.$this->display_name
// );
// }
//
// return Attribute:: make(
// get: fn(mixed $value) => 'Fart: '.$this->name,
// );
// }
public function __get($property)
{
@@ -80,7 +101,7 @@ abstract class Presenter
return $this->{$property}();
}
return e($this->model->{$property});
return $this->model->{$property};
}
public function __call($method, $args)

View File

@@ -79,6 +79,14 @@ class UserPresenter extends Presenter
'visible' => false,
'formatter' => 'usersLinkFormatter',
],
[
'field' => 'display_name',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/users/table.display_name'),
'visible' => true,
],
[
'field' => 'jobtitle',
'searchable' => true,
@@ -191,6 +199,7 @@ class UserPresenter extends Presenter
'visible' => true,
'formatter' => 'usernameRoleLinkFormatter',
],
[
'field' => 'employee_num',
'searchable' => true,
@@ -447,20 +456,23 @@ class UserPresenter extends Presenter
*
* @return string
*/
public function fullName()
{
return html_entity_decode($this->first_name.' '.$this->last_name, ENT_QUOTES | ENT_XML1, 'UTF-8');
}
// public function fullName()
// {
// if ($this->display_name) {
// return 'kjdfh'.html_entity_decode($this->display_name, ENT_QUOTES | ENT_XML1, 'UTF-8');
// }
// return 'roieuoe'.html_entity_decode($this->first_name.' '.$this->last_name, ENT_QUOTES | ENT_XML1, 'UTF-8');
// }
/**
* Standard accessor.
* @TODO Remove presenter::fullName() entirely?
* @return string
*/
public function name()
{
return $this->fullName();
}
// /**
// * Standard accessor.
// * @TODO Remove presenter::fullName() entirely?
// * @return string
// */
// public function name()
// {
// return $this->fullName();
// }

View File

@@ -74,12 +74,12 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('hardware.show', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push($asset->present()->fullName(), route('hardware.show', $asset))
->push($asset->display_name, route('hardware.show', $asset))
);
Breadcrumbs::for('hardware.edit', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push($asset->present()->fullName(), route('hardware.show', $asset))
->push($asset->display_name, route('hardware.show', $asset))
->push(trans('admin/hardware/general.edit'))
);
@@ -579,7 +579,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('users.show', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->getFullNameAttribute() ?? 'Missing Username!', route('users.show', $user))
->push($user->display_name ?? 'Missing Username!', route('users.show', $user))
);
Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) =>

View File

@@ -207,7 +207,7 @@ return [
/*
|--------------------------------------------------------------------------
| Require SAML Login
| Require SAML Login
|--------------------------------------------------------------------------
|
| Disable the ability to login via form login, and disables the 'nosaml'
@@ -220,6 +220,23 @@ return [
'require_saml' => env('REQUIRE_SAML', false),
/*
|--------------------------------------------------------------------------
| SAML KEYS
|--------------------------------------------------------------------------
|
| This is the size of the keys used by openssl_pkey_new for SAML authentication.
| The default is 2048 bits, but this can be changed to 3072 or 4096 bits
| for higher security. Note that this will increase the time it takes to
| generate the keys, so it is not recommended to set this to a very high value
| unless you have a specific need for it.
|
| The European Commission now requires at least 3072-bit keys for new SAML certificates
| @link https://github.com/grokability/snipe-it/issues/17386
*/
'saml_key_size' => env('SAML_KEY_SIZE', 2048),
/*
|--------------------------------------------------------------------------

View File

@@ -123,6 +123,9 @@ $config['allowed_upload_extensions_array'] = [
'mov',
'mp3',
'mp4',
'odp',
'ods',
'odt',
'ogg',
'pdf',
'png',
@@ -140,12 +143,15 @@ $config['allowed_upload_extensions_array'] = [
];
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
$config['allowed_upload_mimetypes_array'] = [
'application/json',
'application/msword',
'application/pdf',
'application/vnd.ms-excel',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.text',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/x-rar-compressed',

View File

@@ -84,6 +84,28 @@ class ActionlogFactory extends Factory
});
}
/**
* This sets up an ActionLog representing a manually added note tied to an Asset,
* with an optional User as the creator. If no User is provided, one is generated.
*
* @param User|null $user Optional user to associate as the creator of the note.
* @return \Illuminate\Database\Eloquent\Factories\Factory<ActionLog>
*/
public function assetNote(?User $user=null)
{
return $this
->state(function () use ($user) {
return [
'action_type' => 'note added',
'item_type' => Asset::class,
'target_type' => 'asset',
'note' => 'Factory-generated manual note',
'created_by' => $user?->id ?? User::factory(),
];
})
->for($user ?? User::factory(), 'user');
}
public function licenseCheckoutToUser()
{
return $this->state(function () {

View File

@@ -28,8 +28,9 @@ class UserFactory extends Factory
'email' => $this->faker->safeEmail(),
'employee_num' => $this->faker->numberBetween(3500, 35050),
'first_name' => $this->faker->firstName(),
'jobtitle' => $this->faker->jobTitle(),
'last_name' => $this->faker->lastName(),
'display_name' => null,
'jobtitle' => $this->faker->jobTitle(),
'locale' => 'en-US',
'notes' => 'Created by DB seeder',
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password

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('users', function (Blueprint $table) {
if (!Schema::hasColumn('users', 'display_name')) {
$table->text('display_name')->after('last_name')->nullable()->default(null);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'display_name')) {
$table->dropColumn('display_name');
}
});
}
};

View File

@@ -0,0 +1,59 @@
<?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('categories', function (Blueprint $table) {
$table->index(['deleted_at']);
});
Schema::table('accessories', function (Blueprint $table) {
$table->index(['deleted_at','category_id']);
});
Schema::table('consumables', function (Blueprint $table) {
$table->index(['deleted_at','category_id']);
});
Schema::table('components', function (Blueprint $table) {
$table->index(['deleted_at','category_id']);
});
Schema::table('licenses', function (Blueprint $table) {
$table->index(['deleted_at','category_id']);
});
Schema::table('models', function (Blueprint $table) {
$table->index(['deleted_at','category_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('categories', function (Blueprint $table) {
$table->dropIndex(['deleted_at']);
});
Schema::table('accessories', function (Blueprint $table) {
$table->dropIndex(['deleted_at','category_id']);
});
Schema::table('consumables', function (Blueprint $table) {
$table->dropIndex(['deleted_at','category_id']);
});
Schema::table('components', function (Blueprint $table) {
$table->dropIndex(['deleted_at','category_id']);
});
Schema::table('licenses', function (Blueprint $table) {
$table->dropIndex(['deleted_at','category_id']);
});
Schema::table('models', function (Blueprint $table) {
$table->dropIndex(['deleted_at','category_id']);
});
}
};

View File

@@ -0,0 +1,82 @@
<?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('settings', function (Blueprint $table) {
if (!Schema::hasColumn('settings', 'ldap_display_name')) {
$table->string('ldap_display_name', 191)->after('ldap_fname_field')->nullable()->default(null);
}
if (!Schema::hasColumn('settings', 'ldap_zip')) {
$table->string('ldap_zip', 191)->after('ldap_manager')->nullable()->default(null);
}
if (!Schema::hasColumn('settings', 'ldap_state')) {
$table->string('ldap_state', 191)->after('ldap_manager')->nullable()->default(null);
}
if (!Schema::hasColumn('settings', 'ldap_city')) {
$table->string('ldap_city', 191)->after('ldap_manager')->nullable()->default(null);
}
if (!Schema::hasColumn('settings', 'ldap_address')) {
$table->string('ldap_address', 191)->after('ldap_manager')->nullable()->default(null);
}
if (!Schema::hasColumn('settings', 'ldap_mobile')) {
$table->string('ldap_mobile', 191)->after('ldap_phone_field')->nullable()->default(null);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_display_name')) {
$table->dropColumn('ldap_display_name');
}
});
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_zip')) {
$table->dropColumn('ldap_zip');
}
});
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_address')) {
$table->dropColumn('ldap_address');
}
});
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_city')) {
$table->dropColumn('ldap_city');
}
});
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_state')) {
$table->dropColumn('ldap_state');
}
});
Schema::table('settings', function (Blueprint $table) {
if (Schema::hasColumn('settings', 'ldap_mobile')) {
$table->dropColumn('ldap_mobile');
}
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('models', function (Blueprint $table) {
$table->index(['created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('models', function (Blueprint $table) {
$table->dropIndex(['created_at']);
});
}
};

View File

@@ -1487,6 +1487,15 @@ caption.tableCaption {
.popover.help-popover .popover-header {
color: #000;
}
.visually-hidden {
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: preserve;
display: inline-block;
}
/*# sourceMappingURL=app.css.map*/

File diff suppressed because one or more lines are too long

View File

@@ -1108,6 +1108,15 @@ caption.tableCaption {
.popover.help-popover .popover-header {
color: #000;
}
.visually-hidden {
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: preserve;
display: inline-block;
}
/*# sourceMappingURL=overrides.css.map*/

File diff suppressed because one or more lines are too long

View File

@@ -22823,6 +22823,15 @@ caption.tableCaption {
.popover.help-popover .popover-header {
color: #000;
}
.visually-hidden {
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: preserve;
display: inline-block;
}
/*# sourceMappingURL=app.css.map*/
@@ -24418,6 +24427,15 @@ caption.tableCaption {
.popover.help-popover .popover-header {
color: #000;
}
.visually-hidden {
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: preserve;
display: inline-block;
}
/*# sourceMappingURL=overrides.css.map*/

18819
public/js/dist/all.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
{
"/js/dist/all.js": "/js/dist/all.js?id=b4f9d0394ed0703585d495cf5995a169",
"/js/dist/all.js": "/js/dist/all.js?id=76d88f0f91b852f7eecbce357ab5858b",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=42f97cd5b9ee7521b04a448e7fc16ac9",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=d81a7ed323f68a7c5e3e9115f7fb5404",
"/css/build/overrides.css": "/css/build/overrides.css?id=81e3e83b8d669c69d443a21241184f2e",
"/css/build/app.css": "/css/build/app.css?id=b34e2d41a3a0d0c949d813eabdc0204b",
"/css/build/overrides.css": "/css/build/overrides.css?id=257e65d85ce9cf5a413065df1b131c03",
"/css/build/app.css": "/css/build/app.css?id=a1136819126f6c7c3cd37861d69abb84",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=ee0ed88465dd878588ed044eefb67723",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=3d8a3d2035ea28aaad4a703c2646f515",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=3979929a3423ff35b96b1fc84299fdf3",
@@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=b2cd9f59d7e8587939ce27b2d3363d82",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=7277edd636cf46aa7786a4449ce0ead7",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=cbd06cc1d58197ccc81d4376bbaf0d28",
"/css/dist/all.css": "/css/dist/all.css?id=ddd108d2798f64c3a47dc2e61760af12",
"/css/dist/all.css": "/css/dist/all.css?id=4130f13eb82937aa7147bd89f93aaca5",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",

View File

@@ -11,7 +11,7 @@ window.$ = jQuery
itself
*****************************************/
require('jquery-ui'); //should we export this to the window?
require("jquery-ui/dist/jquery-ui")
jQuery.fn.uitooltip = jQuery.fn.tooltip;
require('bootstrap-less');
require('select2');

View File

@@ -1243,3 +1243,12 @@ caption.tableCaption {
color: #000;
}
.visually-hidden {
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: preserve;
display: inline-block;
}

View File

@@ -0,0 +1,15 @@
<?php
return [
'select_type' => 'crwdns13392:0crwdne13392:0',
'asset_maintenance_type' => 'crwdns11333:0crwdne11333:0',
'title' => 'crwdns1354:0crwdne1354:0',
'start_date' => 'crwdns11335:0crwdne11335:0',
'completion_date' => 'crwdns11337:0crwdne11337:0',
'cost' => 'crwdns1357:0crwdne1357:0',
'is_warranty' => 'crwdns1358:0crwdne1358:0',
'asset_maintenance_time' => 'crwdns11339:0crwdne11339:0',
'notes' => 'crwdns1360:0crwdne1360:0',
'update' => 'crwdns11341:0crwdne11341:0',
'create' => 'crwdns11343:0crwdne11343:0'
];

View File

@@ -0,0 +1,16 @@
<?php
return [
'asset_maintenances' => 'crwdns1363:0crwdne1363:0',
'edit' => 'crwdns1364:0crwdne1364:0',
'delete' => 'crwdns1365:0crwdne1365:0',
'view' => 'crwdns1366:0crwdne1366:0',
'repair' => 'crwdns1367:0crwdne1367:0',
'maintenance' => 'crwdns1368:0crwdne1368:0',
'upgrade' => 'crwdns1369:0crwdne1369:0',
'calibration' => 'crwdns5812:0crwdne5812:0',
'software_support' => 'crwdns5814:0crwdne5814:0',
'hardware_support' => 'crwdns5816:0crwdne5816:0',
'configuration_change' => 'crwdns10530:0crwdne10530:0',
'pat_test' => 'crwdns10532:0crwdne10532:0',
];

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