Compare commits

...

316 Commits

Author SHA1 Message Date
snipe 35b358d336 Check for $user to handle tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 14:47:58 +01:00
snipe ae109be631 Small tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 14:43:52 +01:00
snipe 3f7ed73395 Added laravel telescope for dev environment
Signed-off-by: snipe <snipe@snipe.net>
2025-08-20 14:26:28 +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 9efb49d510 Merge pull request #17663 from Godmartinz/sub-out-translation
Fixes #17653 changes translation to administrator
2025-08-19 19:43:47 +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
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 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 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
snipe 9b4101855f Undo double-float
Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 15:24:15 +01:00
snipe 9253d894d3 Removed XSS-Protection header
@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-XSS-Protection#security_considerations

Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 13:30:53 +01:00
snipe ebd79f22c7 Merge pull request #17636 from grokability/#17627-custom-fields-sorting
Fixed #17627: custom fields not sorting correctly
2025-08-18 12:47:03 +01:00
snipe c1b139fb9a Fixed #17627: custom fields not sorting correctly
Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 12:31:03 +01:00
snipe a88bcea8ca Merge pull request #17635 from grokability/#17367-fixed-padlock-icon
Fixed #17367: Small adjustment to css-padlock
2025-08-18 11:25:55 +01:00
snipe 21566560a7 Fixed #17367: Small adjustment to css-padlock
Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 11:24:05 +01:00
snipe e3ca43bf40 Remove use of formatCurrencyOutput for input display
Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 11:00:19 +01:00
snipe 61abb8d5cb Fixed hardware.bulkedit redirect
Signed-off-by: snipe <snipe@snipe.net>
2025-08-18 09:45:02 +01:00
snipe ecad656551 Merge pull request #17626 from grokability/#17606-s3-url-for-models-on-requestable-view
Fixed #17606 - use `getImageUrl()` to determine if local or S3
2025-08-17 14:54:13 +01:00
snipe 615e6d6e4f Fixes #17606 - use getImageUrl() to determine if local or S3
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 14:51:52 +01:00
snipe 6dceefb96e Merge pull request #17625 from grokability/#17620-delete-method-custom-fields
Fixed #17620 - delete method custom fields causing method not allowed error
2025-08-17 14:11:17 +01:00
snipe 69eff394fd Removed use statement
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 14:06:56 +01:00
snipe a9da3aca81 Combine fields and fieldset exception
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 14:06:49 +01:00
snipe 91f3556375 Added delete test
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:33:53 +01:00
snipe aab7c3a840 Updated custom fields and fieldset pages to use standard delete modal
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:33:47 +01:00
snipe 9c823119e3 Added new factories for user custom field permissions
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:31:14 +01:00
snipe f5128833f6 Updated comments
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:30:52 +01:00
snipe 2bc144354a Use translations and more standard error bag
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:30:43 +01:00
snipe e6fec6ec34 Trim model name for display
Signed-off-by: snipe <snipe@snipe.net>
2025-08-17 13:30:28 +01:00
snipe 53389875bf Merge pull request #17611 from grokability/#9965-fallback-to-category-image-for-consumables
Fixed #9965 - fallback to category images (f there are any) when no c…
2025-08-15 15:07:13 +02:00
snipe 3b243b38c8 Fixed #9965 - fallback to category images (f there are any) when no consumable image is present
Signed-off-by: snipe <snipe@snipe.net>
2025-08-15 15:03:09 +02:00
snipe 3d9580808b Merge pull request #17524 from Godmartinz/add-category-and-model-to-checkout-emial
Adds #17507 Category and Model No. to accessory checkout markdown
2025-08-15 14:39:58 +02:00
snipe 2141ee71d4 Merge pull request #17544 from marcusmoore/fixes/custom-field-filter
Fixed invalid custom fields being used for filtering
2025-08-15 14:39:09 +02:00
snipe 01dd07083e Merge pull request #17584 from spencerrlongg/bug/17312-custom-field-checkbox-will-not-clear-if-no-checkboxes-should-be-selected
Fixed #17312 - Fix Nulling Checkboxes
2025-08-15 14:35:37 +02:00
snipe 42a28ea06b Merge pull request #17593 from Godmartinz/add-admin-to-acceptance-emails
FIXED #17380 Adds Admin name to acceptance emails
2025-08-15 14:33:02 +02:00
snipe 180cb6ba8e Merge pull request #17610 from grokability/#17600-add-checkout-date-to-accessory-list
Fixed #17600 - adds checkout date to accessories tab in user view
2025-08-15 14:31:38 +02:00
snipe a78762e40b Fixed #17600 - adds checkout date to accessories tab in user view
Signed-off-by: snipe <snipe@snipe.net>
2025-08-15 14:29:55 +02:00
snipe 9797bb19e2 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-08-15 14:23:22 +02:00
snipe 08a9554b3c Merge pull request #17607 from Godmartinz/color-corrections-pt9000
Fixes #17488 more info text colors
2025-08-14 20:39:26 +02:00
Godfrey M d79bd825ee fix popover text color 2025-08-14 10:51:31 -07:00
Godfrey M fe3d225cfa fix tests 2025-08-14 09:15:19 -07:00
snipe 376e0db66e Merge pull request #17601 from ubc-cpsc/bugfix/CVE-2025-55166
Fixes CVE-2025-55166
2025-08-13 20:49:41 +02:00
Joël Pittet 5fdabc1a62 Fixes CVE-2025-55166 2025-08-13 11:42:14 -07:00
Godfrey M dfe2a75d72 adds user that checked out item to acceptance emails 2025-08-12 15:34:46 -07:00
Godfrey M db58b80d27 Merge branch 'develop' into add-category-and-model-to-checkout-emial
# Conflicts:
#	app/Mail/CheckoutLicenseMail.php
2025-08-12 14:20:08 -07:00
Godfrey M 5cb8aae383 add ternaries 2025-08-12 14:16:46 -07:00
spencerrlongg 817530429b added condition to make sure the request has checkbox 2025-08-12 14:52:52 -05:00
Marcus Moore 4a7b7183d2 Add custom_fields. prefix so custom fields can be filtered against 2025-08-11 14:58:41 -07:00
snipe 94bd39cf23 Merge pull request #17570 from grokability/#10038-add-active-flag-filter
Added sidenav to filter on activated vs inactive users
2025-08-11 20:45:22 +01:00
snipe 4038a22093 Added sidenav to filter on activated vs inactive users
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 20:41:55 +01:00
snipe 682baec0c9 Merge pull request #17569 from grokability/#10284-add-mobile-number
Fixed #10284: Added mobile phone to users
2025-08-11 18:49:49 +01:00
snipe ff91be491d Added mobile to tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 18:43:37 +01:00
snipe ef35a0f2f1 Fixed #10284: Added mobile phone to users
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 18:38:22 +01:00
snipe f12a3bb08b Fixed #10306 - cast purchase cost to a float
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 18:12:37 +01:00
snipe c8a5065ffa Merge pull request #17567 from grokability/#11754-nicer-menu-alignment
Fixed #11754: nicer menu alignment for dropdowns
2025-08-11 14:57:59 +01:00
snipe 23da5573f3 Fixed #11754 - nicer top menu dropdown alignment
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 14:56:43 +01:00
snipe b08f985776 Merge pull request #17566 from grokability/partial-fix-for-#17565-standard-layout
Show all icons on location table, even if no results
2025-08-11 14:17:59 +01:00
snipe 9b968baaa7 Show all icons, even if no results
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 14:14:15 +01:00
snipe 07edbe6f1c Add @mckaygerhard as a contributor 2025-08-11 13:08:54 +01:00
snipe 1f55a8b6e3 Added icon and tooltip
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 13:06:37 +01:00
snipe f6b9e11810 Merge pull request #17538 from mckaygerhard/mail-log-improvements
Mail log for #17491 and some improvements on log errors
2025-08-11 13:05:56 +01:00
snipe c18a3e4266 Fixed #17562 - bootstrap table formater undefined
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 11:18:20 +01:00
snipe 5840ef1c6f Fixed #17560
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 06:26:15 +01:00
snipe 7974baddf5 Merge pull request #17551 from grokability/move-file-uploads-paths-to-base-controller
Move the object type mapping and such to the base controller to de-dupe
2025-08-11 05:44:39 +01:00
snipe 4bf569758f Cleans up a few rmore outes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-11 05:05:00 +01:00
snipe f56fd9bb0b Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 21:04:33 +01:00
snipe 357ee5fc45 Copy over the old dirs just in case
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 21:02:37 +01:00
snipe c6dea085b2 Missed a few
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 21:01:23 +01:00
snipe 8782c3ecec Merge pull request #17554 from grokability/#13997-add-ldap-sync-via-api
Adds #13997 - API endpoint to sync users via LDAP
2025-08-10 20:30:44 +01:00
snipe b636cf2ef0 Merge pull request #17555 from grokability/#17490-use-numeric-for-purchase-cost
Fixed #17490: use numeric for purchase cost
2025-08-10 20:30:15 +01:00
snipe 6dee2b8601 Fixed 17490 - currency symbol breaks purchase_cost
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 19:04:52 +01:00
snipe bcf301ac17 Adds #13997 - API endpoint to sync users via LDAP
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 18:48:01 +01:00
snipe bf2120fb31 Use newer file path 2025-08-10 18:26:41 +01:00
snipe de56b74f3e Merge branch 'develop' into move-file-uploads-paths-to-base-controller 2025-08-10 18:25:47 +01:00
snipe 2f146abe91 Let people upload images on the demo
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 18:20:35 +01:00
snipe 543d41b6ff Merge pull request #17553 from grokability/#17547-asset-model-images-not-loading
Fixed #17547: asset model images not loading
2025-08-10 18:15:57 +01:00
snipe 8da0dd7563 Use strtolower
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 18:11:39 +01:00
snipe a2217d7dbc Specify the public disk for creating directories
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 18:08:15 +01:00
snipe ea84728a3f Rename models uploads dir
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 17:58:11 +01:00
snipe b2d10f7ccf Rename directory
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 17:56:59 +01:00
snipe b6af25ce99 Fixed #17548 - treeview menu class on people menu
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 17:20:49 +01:00
snipe 7a9d2454d4 Move the object type mapping and such to the base controller to de-dupe
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 16:30:32 +01:00
snipe a9254cff02 Merge pull request #17550 from grokability/addded-observer-for-maintenances
Added basic logging for maintenances
2025-08-10 16:00:49 +01:00
snipe d14b34141c Updated comment
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 15:53:53 +01:00
snipe 14bc2cc1ba Added basic logging for maintenances
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 15:51:48 +01:00
snipe a91b54b97a Added buttons to maintenances table
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 14:55:34 +01:00
snipe ead655e1db Fixed translation
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 14:52:40 +01:00
snipe c5f28748f7 Merge pull request #17549 from grokability/rename_title_to_name_for_maintenances
Renamed table from `asset_maintenances` to `maintenances` and `title` to `name` for maintenances
2025-08-10 14:28:51 +01:00
snipe ee4831cb30 Removed followsRedirects so we can check the status
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 14:23:41 +01:00
snipe deb1afd28b Few more replcamenents
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 14:14:21 +01:00
snipe 9e8eead71e Renamed routes and method names
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:29:48 +01:00
snipe 3f96f7cbd7 Updated file paths for uploads
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:24:45 +01:00
snipe dde2e88332 Renamed variables in purge
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:24:32 +01:00
snipe ff25015595 Renamed more files
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:24:14 +01:00
snipe 7d0c695808 Renamed language directories
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:23:52 +01:00
snipe 906385def9 Renamed directories
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:23:16 +01:00
snipe a6c6c7eae9 Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 13:11:50 +01:00
snipe 205725c767 Renamed model
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:30:50 +01:00
snipe c207efbb35 Rename model in breadcrumbs
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:30:42 +01:00
snipe c0211e59b3 Renames maintenances presenter
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:30:23 +01:00
snipe dd2678cbb9 Rename maintenances path
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:30:09 +01:00
snipe e2c87b664e Rename factory
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:28:58 +01:00
snipe 29d4b4dd53 Updated API routes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:28:28 +01:00
snipe 3fba307e55 Updated routes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:28:18 +01:00
snipe 7171fa36d8 Added migrations
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:27:59 +01:00
snipe c570f656bf Renamed test
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 12:27:48 +01:00
snipe a5e37519f5 Merge pull request #17539 from grokability/add-file-uploads-to-maintenances
WIP: Add file uploads to maintenances
2025-08-10 11:13:19 +01:00
snipe 0f88d6eec3 Removed error logging
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 11:09:29 +01:00
snipe 651c51bb01 Remove unused statements
Signed-off-by: snipe <snipe@snipe.net>
2025-08-10 10:41:46 +01:00
mckaygerhard 0fdbdfd5c2 Improve log error handling regarding notification sending for issue #17491
* when an error is generated when denying checkouts, there are not enough logs
to determine the problem from the email provider
* missing handling of log test mail config, there is none of logs cos there
is no log handling on test email, becouse all the results are just sent to
the http response and no log were writen.
2025-08-08 12:18:04 -04:00
snipe 31056ff858 Added new dirs to restore tool
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:56:07 +01:00
snipe 8d2643696b Deleted unused user file controller
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:55:59 +01:00
snipe e7488d19e9 Fixed name for uploaded files controller
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:55:48 +01:00
snipe 2bb3b6d64c Fixed prefixes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:55:24 +01:00
snipe 5744e48ae8 Added getDisplayNameAttribute() to maintenances
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:54:36 +01:00
snipe 82d0a21440 Added to actionlog model
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:54:09 +01:00
snipe 58133cffac Updated maintenance model
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:37:03 +01:00
snipe bfd8c2f310 Added fles UI on maintenance page
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:36:51 +01:00
snipe 30d447c023 Updated urls/routes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:36:35 +01:00
snipe 9a0846b8a6 Added directory
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 12:36:16 +01:00
snipe 3667fcddd7 Mark flappy API rate limiting tests as skipped :(
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 11:37:36 +01:00
snipe 906741d662 Bumped debug to warning
Signed-off-by: snipe <snipe@snipe.net>
2025-08-08 11:32:04 +01:00
snipe 12be088c4f Merge pull request #17523 from Godmartinz/fix-create-new-rediret
Fixes #17457 Previous Page redirect option
2025-08-08 09:50:40 +01:00
snipe 6737ba80cd Merge pull request #17489 from grokability/fixes/#17485-handle-alert-menu-better-if-no-alerts
Fixed #17485: nicer alert menu if no items are below qty
2025-08-08 09:50:14 +01:00
snipe 862a3d938e Merge pull request #17543 from Godmartinz/salutation-target-fix
Salutation target fix
2025-08-08 09:49:24 +01:00
snipe 09e82377a5 Merge pull request #17520 from marcusmoore/missing-user-redirect-fix
Fixed potential failure in license checkin due to redirect option
2025-08-08 09:48:43 +01:00
snipe 59470864e7 Merge pull request #17542 from akemidx/assetpolicyclassimport
AssetPolicy class import
2025-08-08 09:40:23 +01:00
Marcus Moore c95aeb3730 Filter out unallowed columns (custom fields) 2025-08-07 17:25:20 -07:00
Godfrey M 5c55c90d68 add null checks to target 2025-08-07 15:27:50 -07:00
Godfrey M e47972731b fixed target name for checkouts with licenses and assets 2025-08-07 15:12:23 -07:00
Godfrey M 5851cc9e41 fixed target name for checkouts with licenses and assets 2025-08-07 15:09:38 -07:00
akemidx 6f615230e9 class import 2025-08-07 17:00:28 -04:00
snipe d91598a25e Merge pull request #17540 from marcusmoore/fixes/asset-serial-validation
Fixed 500 when sending non-string for serial property
2025-08-07 20:53:07 +01:00
snipe 9e416778d9 Merge pull request #17541 from marcusmoore/remove-dump-in-test
Removed debugging dump() in test
2025-08-07 20:52:07 +01:00
Marcus Moore 860a117567 Remove dump in test 2025-08-07 12:50:02 -07:00
Marcus Moore b8fe3b18d4 Add "string" to serial rules for asset 2025-08-07 12:27:48 -07:00
snipe 40269a724b Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:42:59 +01:00
snipe ec828318d8 Merge pull request #17417 from marcusmoore/snipe-it-17073-asset-requests-are-not-deleted-when-asset-is-deleted
Fixed #17073 - delete old checkout requests
2025-08-07 18:32:13 +01:00
snipe d31e7ed534 Use new route
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:24:02 +01:00
snipe 5c2dbe438b Added maintenances
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:23:57 +01:00
snipe 10857635ac Removed unused use statement
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:23:44 +01:00
snipe df2545ef80 Updated routes
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:23:17 +01:00
snipe f6ff729316 Added new generic files upload controller
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:22:57 +01:00
snipe 38678803eb Removed unused controllers
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 18:22:45 +01:00
snipe 67c931f196 Merge pull request #17080 from marcusmoore/allow-id-on-location-select
Allowed setting `id` on location-select component
2025-08-07 18:16:58 +01:00
snipe 1c23092d0e Merge pull request #17537 from grokability/add-maintenance-images-and-files
Fixed #10357: Add maintenance image upload
2025-08-07 17:02:34 +01:00
snipe a90ff21cbf Cleaned up a few more tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 16:58:44 +01:00
snipe 0ce0cee81f Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 16:53:18 +01:00
Герхард PICCORO Lenz McKAY f4be5ffb5d Fix workaround for #17491 log error on failed response for mail sending
* Part of bunch of fixes, this fix #17491 where admins at test install cannot see the log of errors for UI test mail button, we can just see that this is the correct form cos other parts of the code manage the exception inside the catch using log interface class
2025-08-07 11:42:17 -04:00
snipe 19958748bf Use image upload request
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:39:12 +01:00
snipe d6ca8468e3 Use snake case for naming paths
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:39:01 +01:00
snipe 7bccb7718b Added partial and enctype="multipart/form-data for upload
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:38:22 +01:00
snipe f6b63b5e44 Added image to view
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:38:04 +01:00
snipe 9a2c5ff195 Updated/added tests
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:37:57 +01:00
snipe 3597f759da Updated transformers and presenters
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:37:45 +01:00
snipe 3ed3b21286 Added maintenance file singleton
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:37:32 +01:00
snipe b89b636474 Added migration
Signed-off-by: snipe <snipe@snipe.net>
2025-08-07 15:37:16 +01:00
snipe 2afc595452 Don’t show license key formatter if no value
Signed-off-by: snipe <snipe@snipe.net>
2025-08-06 16:47:47 +01:00
snipe c7262f2885 Merge pull request #17532 from grokability/add-available-licenses-back-for-now
[FD-50162] Put remaining seats back on license view for now
2025-08-06 16:35:34 +01:00
snipe 8662aa2277 Put remaining seats back on license view for now
Signed-off-by: snipe <snipe@snipe.net>
2025-08-06 16:33:02 +01:00
snipe 8095e0ab72 Normalize consumables user response
Signed-off-by: snipe <snipe@snipe.net>
2025-08-06 16:25:51 +01:00
snipe be3c8ddd5c Hotfix for FD-50160
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 23:19:27 +01:00
Godfrey M ec5b9ce903 adds category and model no to accessory checkout markdown 2025-08-05 12:44:07 -07:00
Godfrey M bd2acefecc rethought, keeping previous page as an option 2025-08-05 12:29:59 -07:00
Godfrey M 18e49e9067 only redirect to previous page if not creating 2025-08-05 12:05:22 -07:00
snipe a0d65520a3 Use count() instead of ->count() for user count in print view
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 19:34:59 +01:00
snipe a35731d9d5 Fixed #17513 - updated language string
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 19:06:08 +01:00
snipe 9d3623cca6 Merge pull request #17521 from grokability/#17518-add-break-after-sigs
Fixed #17518: Adds printer line break after signatures
2025-08-05 19:02:24 +01:00
snipe 2fe08a721f Do not break the page if it’s the last entry
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 19:00:57 +01:00
Marcus Moore 7abc3a7d7d Only push to session if user exists 2025-08-05 10:57:07 -07:00
snipe d4a34f1a3c Adds printer line break after signatures
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 18:50:47 +01:00
snipe ddda4848d3 Added avif to inline
Signed-off-by: snipe <snipe@snipe.net>
2025-08-05 18:13:17 +01:00
snipe 8516856d37 Merge pull request #17456 from spencerrlongg/9511-validation-always-fails-on-encrypted-custom-fields
Fixed #9511 - Validation For Encrypted Custom Fields
2025-08-05 17:45:38 +01:00
snipe 132327594b Merge pull request #17515 from grokability/add-submenu-to-users
Added dropdown menu for users
2025-08-04 22:26:59 +01:00
snipe d2a2c63070 Added dropdown menu for users
Signed-off-by: snipe <snipe@snipe.net>
2025-08-04 22:25:23 +01:00
snipe 170a5158fa Merge pull request #17514 from grokability/images-on-cloning
Added ability to copy images on cloning
2025-08-04 21:04:56 +01:00
snipe 1d8493d388 Improved messaging for cloning/editing assets that inherit images
Signed-off-by: snipe <snipe@snipe.net>
2025-08-04 20:51:24 +01:00
Marcus Moore ff39e8bd2c Merge branch 'develop' into snipe-it-17073-asset-requests-are-not-deleted-when-asset-is-deleted 2025-08-04 12:43:03 -07:00
snipe c3442033da Removed debugging
Signed-off-by: snipe <snipe@snipe.net>
2025-08-04 18:49:07 +01:00
snipe f1dd84edba Added option to clone original images
Signed-off-by: snipe <snipe@snipe.net>
2025-08-04 18:47:26 +01:00
snipe 06b040a337 Nicer padding
Signed-off-by: snipe <snipe@snipe.net>
2025-08-02 18:41:26 +01:00
snipe fa546ddc5b Merge pull request #17510 from grokability/fixes-#17498-add-serial-to-acceptance
Fixed #17498 - added serial to user acceptance
2025-08-02 14:46:46 +01:00
snipe f811352c79 Cleaned up HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-08-02 14:46:34 +01:00
snipe 7ed8963b9f Fixed #17498 - added serial to user acceptance
Signed-off-by: snipe <snipe@snipe.net>
2025-08-02 14:38:57 +01:00
snipe a9fc8b79fd Merge pull request #17508 from grokability/add-table-buttons
Add table buttons and admin filter
2025-08-01 23:12:04 +01:00
snipe afd794b4c7 Fixed HTML
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 22:20:17 +01:00
snipe c4a28f0ec4 Use consistent icon for adding people
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 22:18:02 +01:00
snipe db343bf795 Tweaked bootstrap admin indicators
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 22:15:13 +01:00
snipe 0157043dc5 Added table buttons to user view
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 21:58:48 +01:00
snipe a947f9bd32 Fixed delete modal
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 21:30:18 +01:00
snipe 2a4181c7c3 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 21:19:18 +01:00
snipe 30192f5b14 Removed extra modal code
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 21:11:54 +01:00
snipe c41b5e8844 Fixed license delete check
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 21:11:40 +01:00
snipe b27928807b Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 20:44:44 +01:00
snipe 16f1b5e23e Added a few more buttons
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 19:31:25 +01:00
snipe ed651b6869 Use translations
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:55:15 +01:00
snipe b9d925c7aa Carry admin/superadmin into the API request
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:49:58 +01:00
snipe 3650a29381 Added superadmin/admin formatter
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:49:37 +01:00
snipe de84ee3693 Cleaned up asset view table
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:47:51 +01:00
snipe 42ba31591d New formatter for icon
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:46:20 +01:00
snipe a78a243e20 Added admin/superadmin filter to API
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:46:10 +01:00
snipe 38924ced4a Provide the role so we can use it in the javascript
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:45:23 +01:00
snipe 5e8cc66f5c Added scope for admins and superadmins
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 18:45:07 +01:00
snipe 1353837584 More buttons
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 15:58:15 +01:00
snipe 7cb5a89523 Added access keys
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 15:58:07 +01:00
snipe 1db09a7953 Allow category_id in license export by category
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 15:21:12 +01:00
snipe bc6aa12dd0 Added buttons to table
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 15:20:55 +01:00
snipe c3bea88979 Added table button JS
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 15:20:39 +01:00
snipe 6e85e466b0 Merge pull request #17493 from grokability/gallery-view-for-file-uploads
Use the file uploads API for file listing tables, adds gallery view for file uploads
2025-08-01 13:27:27 +01:00
snipe 3327cc70c9 Revert pageSize
Signed-off-by: snipe <snipe@snipe.net>
2025-08-01 12:01:57 +01:00
snipe c9eac66a93 Tweaked button layout
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:21:58 +01:00
snipe 53e9bd6e48 Use updated formatter
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:21:49 +01:00
snipe eaa18e1efb Use existing actionlog methods instead of inline
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:21:40 +01:00
snipe afa3dacc31 Check if it’s an accepted/declined file
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:21:22 +01:00
snipe c803c4a57a Use new formatters
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:20:35 +01:00
snipe 2d3a53e449 Made existing formatters more flexible, removed unused
Signed-off-by: snipe <snipe@snipe.net>
2025-07-31 13:20:24 +01:00
snipe 5e076754ce Merge pull request #17501 from uberbrady/fix_manufacturer_seeder_button
Fixed #17500 [FD-50045] - Make Manufacturer Seeder button work
2025-07-31 04:09:38 +01:00
Brady Wetherington 927e217961 Fix Manufacturer Seeder button 2025-07-30 09:04:04 -06:00
snipe 80b48101aa Added formatter back
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:19:10 +01:00
snipe 08530e6133 Added icon data-dash to formatters
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:11:35 +01:00
snipe 97130ef6c1 Updated IDs to be less generic
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:11:12 +01:00
snipe da37feae6d Removed comment
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:10:42 +01:00
snipe f96172e61f Updated manifest
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:10:33 +01:00
snipe e35477b8db Made modal control more flexible
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:10:26 +01:00
snipe cea5560a67 Removed duplicated code for modal handling
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 15:07:35 +01:00
snipe 311bd5e67e Use placeholder for delete button
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 03:31:39 +01:00
snipe 1cfddf2a4c Restore old limit code
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 03:31:29 +01:00
snipe abe58117fe Moved code closer to actions
Signed-off-by: snipe <snipe@snipe.net>
2025-07-30 03:31:01 +01:00
snipe ee5f89f70d Fixed pagination
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 22:58:00 +01:00
snipe 4f545ed101 Layout tweaks to template
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 22:57:49 +01:00
snipe 136de4208e Added string
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 21:44:45 +01:00
snipe 7650a2c2a7 Sort by created_by desc by default
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 21:44:23 +01:00
snipe c3d1987fac Switch to panel
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 21:44:06 +01:00
snipe 12ef78bb1c Added PDF embed
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 21:43:58 +01:00
snipe 16c4241a6e WHY does this work? It’s not in the docs
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 05:42:35 +01:00
snipe 4992c77818 Updated template
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 05:41:43 +01:00
snipe 3a0b1de136 Changed table name
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 05:41:36 +01:00
snipe 1c3ef02c7b FIX THIS!!!
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 05:41:15 +01:00
snipe f268fe9e80 Added gallery card
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 02:03:12 +01:00
snipe 2ed98c17d4 Added print icon
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 02:03:02 +01:00
snipe 571ae4fbfd Use CSS for nowrap
Signed-off-by: snipe <snipe@snipe.net>
2025-07-29 01:20:20 +01:00
snipe 6e61e94e02 New manifest
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:36:31 +01:00
snipe 6a7972c5a1 Added new formatters
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:36:18 +01:00
snipe db4fbe315a Added helper to get media type so we know what kind of lightbox to give it
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:36:11 +01:00
snipe f3613d7103 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:35:45 +01:00
snipe cbbed36428 Added multi-file upload for users (bug)
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:35:35 +01:00
snipe e86e9697b3 Use plural for item type
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:33:25 +01:00
snipe fd6b2d5715 Simpler blade component calls
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:33:08 +01:00
snipe fbb36d1665 Fixed file routes
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:32:45 +01:00
snipe 07be1b8192 Added sorting, updated formatters
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:32:25 +01:00
snipe 33880393ac Added string
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:32:00 +01:00
snipe 5123fe7838 Use server side endpoint for filetable blade component
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:31:51 +01:00
snipe cbe26a365d Made route signature more consistent
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:31:14 +01:00
snipe f1bb72b2a6 Added custom view extension
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 23:30:51 +01:00
snipe 2c33654395 Fixed #17485 - nicer alert menu if no items are below qty
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 17:50:26 +01:00
snipe dd86de017e Dev assets one more time just for good luck
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 17:38:27 +01:00
snipe 3eabde9630 Dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 17:36:22 +01:00
snipe 640c51af31 Merge pull request #17487 from uberbrady/improve_javascript_3
Optimize javascript for smaller files and faster builds (Rebase of #15175)
2025-07-28 17:34:59 +01:00
Brady Wetherington 7167b17d25 Rebased and brought up to current from the original 2025-07-28 09:57:20 -06:00
snipe 8a35948678 Import DB facade
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 16:17:11 +01:00
snipe 0fe63d3fb9 Re-added jquery-ui
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 14:03:12 +01:00
snipe e4302c3e88 Fixed comment
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 09:13:32 +01:00
snipe a7df6fb465 Added DB_SOCKET to example env
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 09:11:00 +01:00
snipe 133e7598e0 Merge pull request #17478 from grokability/library-upgrades
Library upgrades
2025-07-28 09:00:02 +01:00
snipe c1a52ffa75 Bumped jspdf-autotable from ^3.8.4 to ^5.0.2
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:52:52 +01:00
snipe 4f46313388 Bumped tableexport to ^1.33.0
https://www.npmjs.com/package/tableexport.jquery.plugin
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:50:47 +01:00
snipe 03b2cc9cd2 Dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:43:22 +01:00
snipe 1a2bf8dc95 Bumped boostrap table from 1.24.1 to 1.24.2
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:43:17 +01:00
snipe dd63fbeb84 Moved webpack to dev dependencies
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:38:26 +01:00
snipe 59e435c418 Bumped additional libraries
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:36:13 +01:00
snipe f89f0a19b5 Updated axios
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:33:07 +01:00
snipe cbc6ef95cb Removed babel-preset
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:26:52 +01:00
snipe 0ceecc9e1d Removed jquery UI
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:24:16 +01:00
snipe c816902025 Updated postcss
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:19:56 +01:00
snipe cfb03cdca0 Updated imagemin JS
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:07:34 +01:00
snipe 266f77b08c Update svg-sanitize
Signed-off-by: snipe <snipe@snipe.net>
2025-07-28 08:02:54 +01:00
spencerrlongg d0593c6b8d remove some commented things 2025-07-23 16:19:32 -05:00
spencerrlongg 8a40d7e35c tests added, regex validation working 2025-07-23 16:12:19 -05:00
spencerrlongg e9948f0718 fixes booleans, adds note, changes name 2025-07-21 15:34:08 -05:00
spencerrlongg 2f74a8afe1 mac address rule working 2025-07-21 12:02:45 -05:00
Marcus Moore 0102599708 Implement tests 2025-07-16 17:20:28 -07:00
Marcus Moore 960edd4adf Improve clarity 2025-07-16 17:11:00 -07:00
Marcus Moore 3547fa723c Delete requests when asset model is deleted 2025-07-16 17:04:14 -07:00
Marcus Moore 7a456185c6 Add explicit state for assets 2025-07-16 16:57:03 -07:00
Marcus Moore dd79c3f2d6 Scaffold tests 2025-07-16 16:47:28 -07:00
Marcus Moore 35682d11f0 Add command to clean checkout requests 2025-07-16 14:49:45 -07:00
Marcus Moore d04b3f0907 Enable test 2025-07-16 13:15:06 -07:00
Marcus Moore c926358e04 Delete requests when user is deleted 2025-07-16 13:11:59 -07:00
Marcus Moore 856ba52f36 Delete requests when asset is deleted 2025-07-16 12:43:56 -07:00
Marcus Moore a5bea31154 Scaffold tests 2025-07-16 12:38:08 -07:00
Marcus Moore 2afcc1e384 Add basic tests around asset request index 2025-07-16 12:25:37 -07:00
spencerrlongg 826521f053 added rules, still needs a little more... 2025-07-15 15:21:10 -05:00
spencerrlongg f9b05bc8de more encryption rules extenting laravel's own 2025-07-15 15:03:51 -05:00
spencerrlongg b8239e8ed9 use laravel validation methods, email works 2025-07-15 14:17:49 -05:00
Marcus Moore 69b9b0bbc0 Allow setting id within location-select 2025-06-02 15:53:25 -07:00
Marcus Moore 3c1088f030 Improve variable name 2025-06-02 15:49:16 -07:00
1079 changed files with 31592 additions and 298891 deletions
+18
View File
@@ -4189,6 +4189,24 @@
"contributions": [
"code"
]
},
{
"login": "mckaygerhard",
"name": "Герхард PICCORO Lenz McKAY ",
"avatar_url": "https://avatars.githubusercontent.com/u/1571724?v=4",
"profile": "https://github-readme-stats.vercel.app/api?username=mckaygerhard",
"contributions": [
"code"
]
},
{
"login": "FlorestanII",
"name": "Johannes Pollitt",
"avatar_url": "https://avatars.githubusercontent.com/u/15015119?v=4",
"profile": "https://github.com/FlorestanII",
"contributions": [
"code"
]
}
]
}
+1
View File
@@ -28,6 +28,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=db
DB_SOCKET=null
DB_PORT='3306'
DB_DATABASE=snipeit
DB_USERNAME=snipeit
+1
View File
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
# --------------------------------------------
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_SOCKET=null
DB_PORT=3306
DB_DATABASE=null
DB_USERNAME=null
+1
View File
@@ -68,6 +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/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") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use App\Models\CheckoutRequest;
use Illuminate\Console\Command;
class CleanOldCheckoutRequests extends Command
{
private int $deletions = 0;
private int $skips = 0;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:clean-old-checkout-requests';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes checkout requests that reference deleted assets or users.';
/**
* Execute the console command.
*/
public function handle()
{
$requests = CheckoutRequest::with([
'user' => function ($query) {
$query->withTrashed();
},
'requestedItem' => function ($query) {
$query->withTrashed();
},
])->get();
$this->info("Processing {$requests->count()} checkout requests");
$this->withProgressBar($requests, function ($request) {
if ($this->shouldForceDelete($request)) {
$request->forceDelete();
$this->deletions++;
return;
}
if ($this->shouldSoftDelete($request)) {
$request->delete();
$this->deletions++;
return;
}
$this->skips++;
});
$this->info("Final deletion count: $this->deletions, and skip count: $this->skips");
return 0;
}
private function shouldForceDelete(CheckoutRequest $request)
{
// check if the requestable or user relationship is null
return !$request->requestable || !$request->user;
}
private function shouldSoftDelete(CheckoutRequest $request)
{
return $request->requestable->trashed() || $request->user->trashed();
}
}
@@ -96,7 +96,7 @@ class MoveUploadsToNewDisk extends Command
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
$private_uploads['assetmodels'] = glob('storage/private_uploads/assetmodels'."/*.*");
$private_uploads['assetmodels'] = glob('storage/private_uploads/models'."/*.*");
$private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*");
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
+4 -4
View File
@@ -62,19 +62,19 @@ class Purge extends Command
$assetcount = $assets->count();
$this->info($assets->count().' assets purged.');
$asset_assoc = 0;
$asset_maintenances = 0;
$maintenances = 0;
foreach ($assets as $asset) {
$this->info('- Asset "'.$asset->present()->name().'" deleted.');
$asset_assoc += $asset->assetlog()->count();
$asset->assetlog()->forceDelete();
$asset_maintenances += $asset->assetmaintenances()->count();
$asset->assetmaintenances()->forceDelete();
$maintenances += $asset->maintenances()->count();
$asset->maintenances()->forceDelete();
$asset->forceDelete();
}
$this->info($asset_assoc.' corresponding log records purged.');
$this->info($asset_maintenances.' corresponding maintenance records purged.');
$this->info($maintenances.' corresponding maintenance records purged.');
$locations = Location::whereNotNull('deleted_at')->withTrashed()->get();
$this->info($locations->count().' locations purged.');
+4 -1
View File
@@ -243,6 +243,8 @@ class RestoreFromBackup extends Command
$private_dirs = [
'storage/private_uploads/accessories',
'storage/private_uploads/assetmodels',
'storage/private_uploads/maintenances',
'storage/private_uploads/models',
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
'storage/private_uploads/audits',
'storage/private_uploads/components',
@@ -260,9 +262,10 @@ class RestoreFromBackup extends Command
];
$public_dirs = [
'public/uploads/accessories',
'public/uploads/assetmodels',
'public/uploads/maintenances',
'public/uploads/assets', // these are asset _pictures_, not asset files
'public/uploads/avatars',
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
'public/uploads/categories',
'public/uploads/companies',
'public/uploads/components',
+4 -6
View File
@@ -138,13 +138,13 @@ class Handler extends ExceptionHandler
if (in_array('bulkedit', $ids, true)) {
$error_array = session()->get('bulk_asset_errors');
return redirect()
->route('hardware.bulkedit')
->route('hardware.index')
->withErrors($error_array, 'bulk_asset_errors')
->withInput();
}
// This gets the MVC model name from the exception and formats in a way that's less fugly
$model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))));
// This gets the MVC model name from the exception and formats in a way that's less fugly
$model_name = trim(strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))));
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
// Sigh.
@@ -160,9 +160,7 @@ class Handler extends ExceptionHandler
$route = 'maintenances.index';
} elseif ($route === 'licenseseats.index') {
$route = 'licenses.index';
} elseif ($route === 'customfields.index') {
$route = 'fields.index';
} elseif ($route === 'customfieldsets.index') {
} elseif (($route === 'customfieldsets.index') || ($route === 'customfields.index')) {
$route = 'fields.index';
}
+11 -5
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',
@@ -1543,11 +1554,6 @@ class Helper
// return to previous page
if ($redirect_option === 'back') {
if ($backUrl === route('home')) {
return redirect()->to($backUrl)
->with('warning', trans('general.page_error'));
}
return redirect()->to($backUrl);
}
+3
View File
@@ -43,6 +43,8 @@ class IconHelper
return 'fa-regular fa-envelope';
case 'phone':
return 'fa-solid fa-phone';
case 'mobile':
return 'fas fa-mobile-screen-button';
case 'long-arrow-right':
return 'fas fa-long-arrow-alt-right';
case 'download':
@@ -151,6 +153,7 @@ class IconHelper
case 'location':
return 'fas fa-map-marker-alt';
case 'superadmin':
case 'admin':
return 'fas fa-crown';
case 'print':
return 'fa-solid fa-print';
+39 -1
View File
@@ -27,6 +27,45 @@ class StorageHelper
}
}
public static function getMediaType($file_with_path) {
// Get the file extension and determine the media type
if (Storage::exists($file_with_path)) {
$fileinfo = pathinfo($file_with_path);
$extension = strtolower($fileinfo['extension']);
switch ($extension) {
case 'avif':
case 'jpg':
case 'png':
case 'gif':
case 'svg':
case 'webp':
return 'image';
case 'pdf':
return 'pdf';
case 'mp3':
case 'wav':
case 'ogg':
return 'audio';
case 'mp4':
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
}
}
return null;
}
/**
* This determines the file types that should be allowed inline and checks their fileinfo extension
@@ -52,7 +91,6 @@ class StorageHelper
'pdf',
'png',
'svg',
'svg',
'wav',
'webm',
'webp',
@@ -77,9 +77,25 @@ class AccessoriesController extends Controller
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory = $request->handleImages($accessory);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'accessories/'.$new_image_name;
Storage::disk('public')->copy('accessories/'.$cloned_model_img->image, $new_image);
$accessory->image = $new_image_name;
}
} else {
$accessory = $request->handleImages($accessory);
}
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created?
if ($accessory->save()) {
// Redirect to the new accessory page
@@ -114,11 +130,12 @@ class AccessoriesController extends Controller
$this->authorize('create', Accessory::class);
$cloned = clone $accessory;
$accessory_to_clone = $accessory;
$cloned->id = null;
$cloned->deleted_at = '';
$cloned->location_id = null;
return view('accessories/edit')
->with('cloned_model', $accessory_to_clone)
->with('item', $cloned);
}
@@ -1,132 +0,0 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
* @param UploadFileRequest $request
* @param int $accessoryId
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse
{
if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
}
$accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) {
$this->authorize('accessories.files', $accessory);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/accessories')) {
Storage::makeDirectory('private_uploads/accessories', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Deletes the selected accessory file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $accessoryId
* @param int $fileId
*/
public function destroy($accessoryId = null, $fileId = null) : RedirectResponse
{
if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('update', $accessory);
if ($log = Actionlog::find($fileId)) {
if (Storage::exists('private_uploads/accessories/'.$log->filename)) {
try {
Storage::delete('private_uploads/accessories/' . $log->filename);
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
} catch (\Exception $e) {
Log::debug($e);
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
}
}
}
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $accessoryId
* @param int $fileId
*/
public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{
// the accessory is valid
if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
$file = 'private_uploads/accessories/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
}
@@ -232,6 +232,7 @@ class AcceptanceController extends Controller
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'logo' => $path_logo,
'date_settings' => $branding_settings->date_display_format,
'admin' => auth()->user()->present()?->fullName,
];
if ($pdf_view_route!='') {
@@ -347,6 +348,7 @@ class AcceptanceController extends Controller
$acceptance->decline($sig_filename, $request->input('note'));
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
Log::debug('New event acceptance.');
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
}
@@ -356,13 +358,16 @@ class AcceptanceController extends Controller
$recipient = User::find($acceptance->alert_on_response_id);
if ($recipient) {
Log::debug('Attempting to send email acceptance.');
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
$acceptance,
$recipient,
$request->input('asset_acceptance') === 'accepted',
));
Log::debug('Send email notification sucess on checkout acceptance response.');
}
} catch (Exception $e) {
Log::error($e->getMessage());
Log::warning($e);
}
}
+10 -4
View File
@@ -117,15 +117,20 @@ class AssetsController extends Controller
'jobtitle',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
}
$filter = [];
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
}
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
}
$assets = Asset::select('assets.*')
@@ -141,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.
@@ -228,11 +228,16 @@ class ConsumablesController extends Controller
foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'user' => ($consumable_assignment->user) ? [
'id' => (int) $consumable_assignment->user->id,
'name'=> e($consumable_assignment->user->present()->fullName()),
] : null,
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response
'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null,
'created_by' => ($consumable_assignment->adminuser) ? [
'id' => (int) $consumable_assignment->adminuser->id,
'name'=> e($consumable_assignment->adminuser->present()->fullName()),
] : null,
];
}
@@ -4,11 +4,11 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetMaintenancesTransformer;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\MaintenancesTransformer;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Maintenance;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
@@ -18,13 +18,13 @@ use Illuminate\Http\JsonResponse;
*
* @version v2.0
*/
class AssetMaintenancesController extends Controller
class MaintenancesController extends Controller
{
/**
* Generates the JSON response for asset maintenances listing view.
*
* @see AssetMaintenancesController::getIndex() method that generates view
* @see MaintenancesController::getIndex() method that generates view
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
@@ -33,7 +33,7 @@ class AssetMaintenancesController extends Controller
{
$this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*')
$maintenances = Maintenance::select('maintenances.*')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
if ($request->filled('search')) {
@@ -45,11 +45,11 @@ class AssetMaintenancesController extends Controller
}
if ($request->filled('supplier_id')) {
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
$maintenances->where('maintenances.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('created_by')) {
$maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by'));
$maintenances->where('maintenances.created_by', '=', $request->input('created_by'));
}
if ($request->filled('asset_maintenance_type')) {
@@ -63,7 +63,7 @@ class AssetMaintenancesController extends Controller
$allowed_columns = [
'id',
'title',
'name',
'asset_maintenance_time',
'asset_maintenance_type',
'cost',
@@ -112,7 +112,7 @@ class AssetMaintenancesController extends Controller
$total = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get();
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total);
return (new MaintenancesTransformer())->transformMaintenances($maintenances, $total);
}
@@ -121,22 +121,23 @@ class AssetMaintenancesController extends Controller
/**
* Validates and stores the new asset maintenance
*
* @see AssetMaintenancesController::getCreate() method for the form
* @see MaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
*/
public function store(Request $request) : JsonResponse | array
public function store(ImageUploadRequest $request) : JsonResponse | array
{
$this->authorize('update', Asset::class);
// create a new model instance
$maintenance = new AssetMaintenance();
$maintenance = new Maintenance();
$maintenance->fill($request->all());
$maintenance->created_by = auth()->id();
$maintenance = $request->handleImages($maintenance);
// Was the asset maintenance created?
if ($maintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.create.success')));
}
@@ -157,11 +158,11 @@ class AssetMaintenancesController extends Controller
{
$this->authorize('update', Asset::class);
if ($maintenance = AssetMaintenance::with('asset')->find($id)) {
if ($maintenance = Maintenance::with('asset')->find($id)) {
// Can this user manage this asset?
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.action_permission_denied', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id, 'action' => trans('general.edit')])));
}
// The asset this miantenance is attached to is not valid or has been deleted
@@ -172,13 +173,13 @@ class AssetMaintenancesController extends Controller
$maintenance->fill($request->all());
if ($maintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/asset_maintenances/message.edit.success')));
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.edit.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $maintenance->getErrors()));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/asset_maintenances/general.maintenance'), 'id' => $id])));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('admin/maintenances/general.maintenance'), 'id' => $id])));
}
@@ -186,20 +187,20 @@ class AssetMaintenancesController extends Controller
* Delete an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $assetMaintenanceId
* @param int $maintenanceId
* @version v1.0
* @since [v4.0]
*/
public function destroy($assetMaintenanceId) : JsonResponse | array
public function destroy($maintenanceId) : JsonResponse | array
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
$maintenance = Maintenance::findOrFail($maintenanceId);
$assetMaintenance->delete();
$maintenance->delete();
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success')));
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.delete.success')));
}
@@ -208,19 +209,19 @@ class AssetMaintenancesController extends Controller
* View an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $assetMaintenanceId
* @param int $maintenanceId
* @version v1.0
* @since [v4.0]
*/
public function show($assetMaintenanceId) : JsonResponse | array
public function show($maintenanceId) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
$maintenance = Maintenance::findOrFail($maintenanceId);
if (! Company::isCurrentUserHasAccess($maintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
}
return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance);
return (new MaintenancesTransformer())->transformMaintenance($maintenance);
}
}
@@ -150,8 +150,11 @@ class SettingsController extends Controller
if (!config('app.lock_passwords')) {
try {
Notification::send(Setting::first(), new MailTest());
Log::debug('Attempting to sending to '.config('mail.reply_to.address'));
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
} catch (\Exception $e) {
Log::error('Mail sent error using '.config('mail.reply_to.address') .': '. $e->getMessage());
Log::debug($e);
return response()->json(['message' => $e->getMessage()], 500);
}
}
@@ -315,4 +318,4 @@ class SettingsController extends Controller
}
}
}
@@ -194,7 +194,7 @@ class SuppliersController extends Controller
public function destroy($id) : JsonResponse
{
$this->authorize('delete', Supplier::class);
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
$supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
$this->authorize('delete', $supplier);
@@ -202,8 +202,8 @@ class SuppliersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count])));
}
if ($supplier->asset_maintenances_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count])));
if ($supplier->maintenances_count > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count])));
}
if ($supplier->licenses_count > 0) {
@@ -7,18 +7,9 @@ use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\UploadedFilesTransformer;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -28,45 +19,6 @@ class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/assetmodels/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
/**
* List files for an object
*
@@ -98,15 +50,22 @@ class UploadedFilesController extends Controller
'created_at',
];
$uploads = $object->uploads();
$offset = ($request->input('offset') > $object->count()) ? $object->count() : abs($request->input('offset'));
$uploads = Actionlog::select('action_logs.*')
->whereNotNull('filename')
->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)
->where('action_type', '=', 'uploaded')
->with('adminuser');
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'action_logs.created_at';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
// Text search on action_logs fields
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searcghing across all relations
// And we generally won't need that here
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searching across all relations
// and we generally won't need that here
if ($request->filled('search')) {
$uploads->where(
@@ -117,8 +76,10 @@ class UploadedFilesController extends Controller
);
}
$total = $uploads->count();
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
return (new UploadedFilesTransformer())->transformFiles($uploads, $total);
}
@@ -20,6 +20,7 @@ use App\Models\Consumable;
use App\Models\License;
use App\Models\User;
use App\Notifications\CurrentInventory;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
@@ -69,6 +70,7 @@ class UsersController extends Controller
'users.notes',
'users.permissions',
'users.phone',
'users.mobile',
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
@@ -108,10 +110,26 @@ class UsersController extends Controller
$users = $users->where('users.activated', '=', $request->input('activated'));
}
if ($request->input('admins') == 'true') {
$users = $users->OnlyAdminsAndSuperAdmins();
}
if ($request->input('superadmins') == 'true') {
$users = $users->OnlySuperAdmins();
}
if ($request->filled('company_id')) {
$users = $users->where('users.company_id', '=', $request->input('company_id'));
}
if ($request->filled('phone')) {
$users = $users->where('users.phone', '=', $request->input('phone'));
}
if ($request->filled('mobile')) {
$users = $users->where('users.mobile', '=', $request->input('mobile'));
}
if ($request->filled('location_id')) {
$users = $users->where('users.location_id', '=', $request->input('location_id'));
}
@@ -284,6 +302,7 @@ class UsersController extends Controller
'manages_users_count',
'manages_locations_count',
'phone',
'mobile',
'address',
'city',
'state',
@@ -814,4 +833,37 @@ class UsersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')), 200);
}
/**
* Run the LDAP sync command to import users from LDAP via API.
*
* @author A. Gianotto <snipe@snipe.net>
* @since 8.2.2
*
* @return \Illuminate\Http\JsonResponse
*/
public function syncLdapUsers(Request $request)
{
$this->authorize('update', User::class);
// Call Artisan LDAP import command.
Artisan::call('snipeit:ldap-sync', ['--location_id' => $request->input('location_id'), '--json_summary' => true]);
// Collect and parse JSON summary.
$ldap_results_json = Artisan::output();
$ldap_results = json_decode($ldap_results_json, true);
if (!$ldap_results) {
return response()->json(Helper::formatStandardApiResponse('error', null,trans('general.no_results')), 200);
}
// Direct user to appropriate status page.
if ($ldap_results['error']) {
return response()->json(Helper::formatStandardApiResponse('error', null, $ldap_results['error_message']), 200);
}
return response()->json(Helper::formatStandardApiResponse('success', null, $ldap_results['summary']), 200);
}
}
+15 -2
View File
@@ -87,7 +87,20 @@ class AssetModelsController extends Controller
$model->fieldset_id = $request->input('fieldset_id');
}
$model = $request->handleImages($model);
if ($request->has('use_cloned_image')) {
$cloned_model_img = AssetModel::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'models/'.$new_image_name;
Storage::disk('public')->copy('models/'.$cloned_model_img->image, $new_image);
$model->image = $new_image_name;
}
} else {
$model = $request->handleImages($model);
}
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
@@ -271,7 +284,7 @@ class AssetModelsController extends Controller
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
->with('model_id', $model->id)
->with('clone_model', $cloned_model);
->with('cloned_model', $cloned_model);
}
@@ -1,115 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetModelsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('update', $model);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
$model->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$this->authorize('view', $model);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that model/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/assetmodels/'.$log->filename;
if (! Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(AssetModel $model, $fileId = null) : RedirectResponse
{
$rel_path = 'private_uploads/assetmodels';
$this->authorize('update', $model);
$log = Actionlog::find($fileId);
if ($log) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}
@@ -1,108 +0,0 @@
<?php
namespace App\Http\Controllers\Assets;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use \Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $assetId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse
{
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success'));
}
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
*/
public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
$this->authorize('view', $asset);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(Asset $asset, $fileId = null) : RedirectResponse
{
$this->authorize('update', $asset);
$rel_path = 'private_uploads/assets';
if ($log = Actionlog::find($fileId)) {
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found'));
}
}
@@ -157,8 +157,16 @@ class AssetsController extends Controller
$asset->location_id = $request->input('rtd_location_id', null);
}
// Create the image (if one was chosen.)
if ($request->has('image')) {
if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'assets/'.$new_image_name;
Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
}
} else {
$asset = $request->handleImages($asset);
}
@@ -226,9 +234,13 @@ class AssetsController extends Controller
$failures[] = join(",", $asset->getErrors()->all());
}
}
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
session()->put(['redirect_option' => $request->get('redirect_option'),
'checkout_to_type' => $request->get('checkout_to_type'),
session()->put(['checkout_to_type' => $request->get('checkout_to_type'),
'other_redirect' => 'model' ]);
@@ -412,6 +424,9 @@ class AssetsController extends Controller
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->element == 'checkbox' && !$request->has($field->db_column)) {
$asset->{$field->db_column} = null;
}
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
@@ -644,8 +659,9 @@ class AssetsController extends Controller
*/
public function getClone(Asset $asset)
{
$this->authorize('create', $asset);
$this->authorize('create', Asset::class);
$cloned = clone $asset;
$cloned_model = $asset;
$cloned->id = null;
$cloned->asset_tag = '';
$cloned->serial = '';
@@ -655,6 +671,7 @@ class AssetsController extends Controller
return view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList())
->with('cloned_model', $cloned_model)
->with('item', $cloned);
}
@@ -544,7 +544,7 @@ class BulkAssetsController extends Controller
session()->put('bulk_asset_errors',$error_array);
return redirect()
->route('hardware.bulkedit')
->route('hardware.index')
->with('bulk_asset_errors', $error_array)
->withInput();
}
@@ -88,7 +88,12 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
if ($component->save()) {
return Helper::getRedirectOption($request, $component->id, 'Components')
@@ -1,138 +0,0 @@
<?php
namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
* @param UploadFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('components.show', ['component'=>$componentId])->with('error', trans('general.feature_disabled'));
}
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('update', $component);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/components')) {
Storage::makeDirectory('private_uploads/components', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('components.show', $component->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('components.show', $component->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('components.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected component file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $componentId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($componentId = null, $fileId = null)
{
$component = Component::find($componentId);
// the asset is valid
if (isset($component->id)) {
$this->authorize('update', $component);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('components/'.$log->filename)) {
try {
Storage::delete('components/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $componentId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null, $fileId = null)
{
Log::debug('Private filesystem is: '.config('filesystems.default'));
// the component is valid
if ($component = Component::find($componentId)) {
$this->authorize('view', $component);
$this->authorize('components.files', $component);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
$file = 'private_uploads/components/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}
@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Consumable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
@@ -81,13 +81,29 @@ class ConsumablesController extends Controller
$consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = $request->input('qty');
$consumable->created_by = auth()->id();
$consumable->created_by = auth()->id();
$consumable->notes = $request->input('notes');
$consumable = $request->handleImages($consumable);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Consumable::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'consumables/'.$new_image_name;
Storage::disk('public')->copy('consumables/'.$cloned_model_img->image, $new_image);
$consumable->image = $new_image_name;
}
} else {
$consumable = $request->handleImages($consumable);
}
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) {
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
@@ -213,9 +229,10 @@ class ConsumablesController extends Controller
$consumable_to_close = $consumable;
$consumable = clone $consumable_to_close;
$consumable->id = null;
$consumable->image = null;
$consumable->created_by = null;
return view('consumables/edit')->with('item', $consumable);
return view('consumables/edit')
->with('cloned_model', $consumable_to_close)
->with('item', $consumable);
}
}
@@ -1,134 +0,0 @@
<?php
namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
* @param UploadFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
}
$consumable = Consumable::find($consumableId);
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/consumables')) {
Storage::makeDirectory('private_uploads/consumables', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('consumables.show', $consumable->id)->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->route('consumables.show', $consumable->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('consumables.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected consumable file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $consumableId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
// the asset is valid
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('consumables/'.$log->filename)) {
try {
Storage::delete('consumables/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()->withFragment('files')
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $consumableId
* @param int $fileId
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
// the consumable is valid
if (isset($consumable->id)) {
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
$file = 'private_uploads/consumables/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}
+48
View File
@@ -22,6 +22,15 @@
namespace App\Http\Controllers;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\Maintenance;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
@@ -32,6 +41,45 @@ abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
static $map_object_type = [
'accessories' => Accessory::class,
'maintenances' => Maintenance::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'hardware' => Asset::class,
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'maintenances' => 'private_uploads/maintenances/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'hardware' => 'private_uploads/assets/',
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/models/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'maintenances' => 'maintenance',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'hardware' => 'asset',
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
public function __construct()
{
view()->share('signedIn', Auth::check());
+13 -18
View File
@@ -144,10 +144,9 @@ class CustomFieldsController extends Controller
*/
public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::find($field_id);
$this->authorize('update', $field);
// Check that the field exists - this is mostly related to the demo, where we
// rewrite the data every x minutes, so it's possible someone might be disassociating
// a field from a fieldset just as we're wiping the database
@@ -157,11 +156,12 @@ class CustomFieldsController extends Controller
return redirect()->route('fieldsets.show', ['fieldset' => $fieldset_id])
->with('success', trans('admin/custom_fields/message.field.delete.success'));
} else {
return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]);
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error'))
->withInput();
}
}
return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]);
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.error'));
}
@@ -172,20 +172,16 @@ class CustomFieldsController extends Controller
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8]
*/
public function destroy($field_id) : RedirectResponse
public function destroy(CustomField $field) : RedirectResponse
{
if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field);
$this->authorize('delete', CustomField::class);
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
}
$field->delete();
return redirect()->route("fields.index")
->with("success", trans('admin/custom_fields/message.field.delete.success'));
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
return redirect()->back()->with('error', trans('admin/custom_fields/message.field.delete.in_use'));
}
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
$field->delete();
return redirect()->route("fields.index")
->with("success", trans('admin/custom_fields/message.field.delete.success'));
}
@@ -198,7 +194,7 @@ class CustomFieldsController extends Controller
*/
public function edit(Request $request, CustomField $field) : View | RedirectResponse
{
$this->authorize('update', $field);
$this->authorize('update', CustomField::class);
$fieldsets = CustomFieldset::get();
$customFormat = '';
if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) {
@@ -228,7 +224,7 @@ class CustomFieldsController extends Controller
*/
public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
{
$this->authorize('update', $field);
$this->authorize('update', CustomField::class);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
@@ -265,7 +261,6 @@ class CustomFieldsController extends Controller
if ($field->save()) {
// Sync fields with fieldsets
$fieldset_array = $request->input('associate_fieldsets');
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
@@ -87,7 +87,9 @@ class LicenseCheckinController extends Controller
if($licenseSeat->assigned_to != null){
$return_to = User::withTrashed()->find($licenseSeat->assigned_to);
session()->put('checkedInFrom', $return_to->id);
if ($return_to) {
session()->put('checkedInFrom', $return_to->id);
}
} else {
$return_to = Asset::find($licenseSeat->asset_id);
}
@@ -1,132 +0,0 @@
<?php
namespace App\Http\Controllers\Licenses;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
* @param UploadFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(UploadFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
if (isset($license->id)) {
$this->authorize('update', $license);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/licenses')) {
Storage::makeDirectory('private_uploads/licenses', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles'));
}
// Prepare the error message
return redirect()->route('licenses.index')
->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Deletes the selected license file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $licenseId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($licenseId = null, $fileId = null)
{
if ($license = License::find($licenseId)) {
$this->authorize('update', $license);
if ($log = Actionlog::find($fileId)) {
// Remove the file if one exists
if (Storage::exists('licenses/'.$log->filename)) {
try {
Storage::delete('licenses/'.$log->filename);
} catch (\Exception $e) {
Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->route('licenses.index')->with('error', trans('general.log_does_not_exist'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $licenseId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
// the license is valid
if (isset($license->id)) {
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
$file = 'private_uploads/licenses/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));
}
}
@@ -102,7 +102,11 @@ class LicensesController extends Controller
$license->created_by = auth()->id();
$license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
if ($license->save()) {
return Helper::getRedirectOption($request, $license->id, 'Licenses')
@@ -304,13 +308,16 @@ class LicensesController extends Controller
$response = new StreamedResponse(function () {
// Open output stream
$handle = fopen('php://output', 'w');
$licenses= License::with('company',
$licenses = License::with('company',
'manufacturer',
'category',
'supplier',
'adminuser',
'assignedusers')
->orderBy('created_at', 'DESC');
'assignedusers');
if (request()->filled('category_id')) {
$licenses = $licenses->where('category_id', request()->input('category_id'));
}
$licenses = $licenses->orderBy('created_at', 'DESC');
Company::scopeCompanyables($licenses)
->chunk(500, function ($licenses) use ($handle) {
$headers = [
+13 -2
View File
@@ -96,7 +96,18 @@ class LocationsController extends Controller
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
if ($request->has('use_cloned_image')) {
$cloned_model_img = Location::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'locations/'.$new_image_name;
Storage::disk('public')->copy('locations/'.$cloned_model_img->image, $new_image);
$location->image = $new_image_name;
}
} else {
$location = $request->handleImages($location);
}
if ($location->save()) {
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.create.success'));
@@ -275,9 +286,9 @@ class LocationsController extends Controller
// unset these values
$location->id = null;
$location->image = null;
return view('locations/edit')
->with('cloned_model', $location_to_clone)
->with('item', $location);
}
@@ -1,111 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Location;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class LocationsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @param UploadFileRequest $request
* @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, Location $location) : RedirectResponse
{
$this->authorize('update', $location);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/locations')) {
Storage::makeDirectory('private_uploads/locations', 775);
}
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file);
$location->logUpload($file_name, $request->get('notes'));
}
return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success'));
}
return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**
* Check for permissions and display the file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse
{
$this->authorize('view', $location);
if (! $log = Actionlog::find($fileId)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file record');
}
$file = 'private_uploads/locations/'.$log->filename;
if (! Storage::exists($file)) {
return redirect()->back()->withFragment('files')->with('error', 'No matching file on server');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
/**
* Delete the associated file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $modelId
* @param int $fileId
* @since [v1.0]
*/
public function destroy(Location $location, $fileId = null) : RedirectResponse
{
$rel_path = 'private_uploads/locations';
$this->authorize('update', $location);
$log = Actionlog::find($fileId);
if ($log) {
// This should be moved to purge
// if (Storage::exists($rel_path.'/'.$log->filename)) {
// Storage::delete($rel_path.'/'.$log->filename);
// }
$log->delete();
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success'));
}
}
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Maintenance;
use App\Models\Company;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
@@ -17,29 +18,23 @@ use \Illuminate\Http\RedirectResponse;
*
* @version v2.0
*/
class AssetMaintenancesController extends Controller
class MaintenancesController extends Controller
{
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the asset maintenances listing, which is generated in getDatatable.
*
* @todo This should be replaced with middleware and/or policies
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* the content for the asset maintenances listing.
*/
public function index() : View
{
$this->authorize('view', Asset::class);
return view('asset_maintenances/index');
return view('maintenances.index');
}
/**
* Returns a form view to create a new asset maintenance.
*
* @see AssetMaintenancesController::postCreate() method that stores the data
* @see MaintenancesController::postCreate() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
@@ -55,21 +50,21 @@ class AssetMaintenancesController extends Controller
$asset->asset_id = $asset->id;
}
return view('asset_maintenances/edit')
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
return view('maintenances/edit')
->with('maintenanceType', Maintenance::getImprovementOptions())
->with('asset', $asset)
->with('item', new AssetMaintenance);
->with('item', new Maintenance);
}
/**
* Validates and stores the new asset maintenance
*
* @see AssetMaintenancesController::getCreate() method for the form
* @see MaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
*/
public function store(Request $request) : RedirectResponse
public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
@@ -78,72 +73,73 @@ class AssetMaintenancesController extends Controller
// Loop through the selected assets
foreach ($assets as $asset) {
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->notes = $request->input('notes');
$maintenance = new Maintenance();
$maintenance->supplier_id = $request->input('supplier_id');
$maintenance->is_warranty = $request->input('is_warranty');
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
// Save the asset maintenance data
$assetMaintenance->asset_id = $asset->id;
$assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id();
$maintenance->asset_id = $asset->id;
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
$maintenance->created_by = auth()->id();
if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '')
&& ($assetMaintenance->start_date !== '0000-00-00')
if (($maintenance->completion_date !== null)
&& ($maintenance->start_date !== '')
&& ($maintenance->start_date !== '0000-00-00')
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
$startDate = Carbon::parse($maintenance->start_date);
$completionDate = Carbon::parse($maintenance->completion_date);
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
$maintenance = $request->handleImages($maintenance);
// Was the asset maintenance created?
if (!$assetMaintenance->save()) {
return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors());
if (!$maintenance->save()) {
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
}
}
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.create.success'));
->with('success', trans('admin/maintenances/message.create.success'));
}
/**
* Returns a form view to edit a selected asset maintenance.
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @see MaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
*/
public function edit(AssetMaintenance $maintenance) : View | RedirectResponse
public function edit(Maintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
return view('asset_maintenances/edit')
return view('maintenances/edit')
->with('selected_assets', $maintenance->asset->pluck('id')->toArray())
->with('asset_ids', request()->input('asset_ids', []))
->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions())
->with('maintenanceType', Maintenance::getImprovementOptions())
->with('item', $maintenance);
}
/**
* Validates and stores an update to an asset maintenance
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @see MaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param Request $request
* @param int $assetMaintenanceId
* @param int $maintenanceId
* @version v1.0
* @since [v1.8]
*/
public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse
public function update(ImageUploadRequest $request, Maintenance $maintenance) : View | RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
@@ -153,7 +149,7 @@ class AssetMaintenancesController extends Controller
$maintenance->cost = $request->input('cost');
$maintenance->notes = $request->input('notes');
$maintenance->asset_maintenance_type = $request->input('asset_maintenance_type');
$maintenance->title = $request->input('title');
$maintenance->name = $request->input('name');
$maintenance->start_date = $request->input('start_date');
$maintenance->completion_date = $request->input('completion_date');
@@ -176,10 +172,11 @@ class AssetMaintenancesController extends Controller
$completionDate = Carbon::parse($maintenance->completion_date);
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
$maintenance = $request->handleImages($maintenance);
if ($maintenance->save()) {
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.edit.success'));
->with('success', trans('admin/maintenances/message.edit.success'));
}
return redirect()->back()->withInput()->withErrors($maintenance->getErrors());
@@ -189,11 +186,11 @@ class AssetMaintenancesController extends Controller
* Delete an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @param int $maintenanceId
* @version v1.0
* @since [v1.8]
*/
public function destroy(AssetMaintenance $maintenance) : RedirectResponse
public function destroy(Maintenance $maintenance) : RedirectResponse
{
$this->authorize('update', Asset::class);
$this->authorize('update', $maintenance->asset);
@@ -201,19 +198,19 @@ class AssetMaintenancesController extends Controller
$maintenance->delete();
// Redirect to the asset_maintenance management page
return redirect()->route('maintenances.index')
->with('success', trans('admin/asset_maintenances/message.delete.success'));
->with('success', trans('admin/maintenances/message.delete.success'));
}
/**
* View an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @param int $maintenanceId
* @version v1.0
* @since [v1.8]
*/
public function show(AssetMaintenance $maintenance) : View | RedirectResponse
public function show(Maintenance $maintenance) : View | RedirectResponse
{
return view('asset_maintenances/view')->with('assetMaintenance', $maintenance);
return view('maintenances.view')->with('maintenance', $maintenance);
}
}
@@ -51,7 +51,7 @@ class ManufacturersController extends Controller
$manufacturers_count = Manufacturer::withTrashed()->count();
if ($manufacturers_count == 0) {
Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']);
Artisan::call('db:seed', ['--class' => 'Database\\Seeders\\ManufacturerSeeder', '--force' => true]);
return redirect()->route('manufacturers.index')->with('success', trans('general.seeding.manufacturers.success'));
}
+24 -26
View File
@@ -9,7 +9,7 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\AssetMaintenance;
use App\Models\Maintenance;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\CustomField;
@@ -17,13 +17,11 @@ use App\Models\Depreciation;
use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -1038,11 +1036,11 @@ class ReportsController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
public function getAssetMaintenancesReport() : View
public function getMaintenancesReport() : View
{
$this->authorize('reports.view');
return view('reports.asset_maintenances');
return view('reports.maintenances');
}
/**
@@ -1051,11 +1049,11 @@ class ReportsController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
public function exportAssetMaintenancesReport() : Response
public function exportMaintenancesReport() : Response
{
$this->authorize('reports.view');
// Grab all the improvements
$assetMaintenances = AssetMaintenance::with('asset', 'supplier')
$Maintenances = Maintenance::with('asset', 'supplier')
->orderBy('created_at', 'DESC')
->get();
@@ -1063,36 +1061,36 @@ class ReportsController extends Controller
$header = [
trans('admin/hardware/table.asset_tag'),
trans('admin/asset_maintenances/table.asset_name'),
trans('admin/maintenances/table.asset_name'),
trans('general.supplier'),
trans('admin/asset_maintenances/form.asset_maintenance_type'),
trans('admin/asset_maintenances/form.title'),
trans('admin/asset_maintenances/form.start_date'),
trans('admin/asset_maintenances/form.completion_date'),
trans('admin/asset_maintenances/form.asset_maintenance_time'),
trans('admin/asset_maintenances/form.cost'),
trans('admin/maintenances/form.asset_maintenance_type'),
trans('admin/maintenances/form.title'),
trans('admin/maintenances/form.start_date'),
trans('admin/maintenances/form.completion_date'),
trans('admin/maintenances/form.asset_maintenance_time'),
trans('admin/maintenances/form.cost'),
];
$header = array_map('trim', $header);
$rows[] = implode(',', $header);
foreach ($assetMaintenances as $assetMaintenance) {
foreach ($Maintenances as $maintenance) {
$row = [];
$row[] = str_replace(',', '', e($assetMaintenance->asset->asset_tag));
$row[] = str_replace(',', '', e($assetMaintenance->asset->name));
$row[] = str_replace(',', '', e($assetMaintenance->supplier->name));
$row[] = e($assetMaintenance->improvement_type);
$row[] = e($assetMaintenance->title);
$row[] = e($assetMaintenance->start_date);
$row[] = e($assetMaintenance->completion_date);
if (is_null($assetMaintenance->asset_maintenance_time)) {
$row[] = str_replace(',', '', e($maintenance->asset->asset_tag));
$row[] = str_replace(',', '', e($maintenance->asset->name));
$row[] = str_replace(',', '', e($maintenance->supplier->name));
$row[] = e($maintenance->improvement_type);
$row[] = e($maintenance->name);
$row[] = e($maintenance->start_date);
$row[] = e($maintenance->completion_date);
if (is_null($maintenance->asset_maintenance_time)) {
$improvementTime = (int) Carbon::now()
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
->diffInDays(Carbon::parse($maintenance->start_date), true);
} else {
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
$improvementTime = (int) $maintenance->asset_maintenance_time;
}
$row[] = $improvementTime;
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($maintenance->cost);
$rows[] = implode(',', $row);
}
+6 -2
View File
@@ -1084,6 +1084,7 @@ class SettingsController extends Controller
if (! config('app.lock_passwords')) {
if (Storage::exists($path.'/'.$filename)) {
Log::warning('User '.auth()->user()->username.' is attempting to download backup file: '.$filename);
return StorageHelper::downloader($path.'/'.$filename);
} else {
// Redirect to the backup page
@@ -1111,6 +1112,7 @@ class SettingsController extends Controller
if (Storage::exists($path . '/' . $filename)) {
try {
Log::warning('User '.auth()->user()->username.' is attempting to delete backup file: '.$filename);
Storage::delete($path . '/' . $filename);
return redirect()->route('settings.backups.index')->with('success', trans('admin/settings/message.backup.file_deleted'));
} catch (\Exception $e) {
@@ -1190,7 +1192,7 @@ class SettingsController extends Controller
'--force' => true,
]);
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
Log::warning('User '.auth()->user()->username.' is attempting to restore from: '. storage_path($path).'/'.$filename);
$restore_params = [
'--force' => true,
@@ -1339,9 +1341,11 @@ class SettingsController extends Controller
'name' => config('mail.from.name'),
'email' => config('mail.from.address'),
])->notify(new MailTest());
Log::debug('Attempting to send mail to '.config('mail.from.address'));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent')));
} catch (\Exception $e) {
Log::error('Mail sent from '.config('mail.from.address') .' with errors '. $e->getMessage());
Log::debug($e);
return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage()));
}
}
+3 -4
View File
@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Supplier;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
@@ -122,7 +121,7 @@ class SuppliersController extends Controller
public function destroy($supplierId) : RedirectResponse
{
$this->authorize('delete', Supplier::class);
if (is_null($supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
if (is_null($supplier = Supplier::with('maintenances', 'assets', 'licenses')->withCount('maintenances as maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.not_found'));
}
@@ -130,8 +129,8 @@ class SuppliersController extends Controller
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_assets', ['asset_count' => (int) $supplier->assets_count]));
}
if ($supplier->asset_maintenances_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['asset_maintenances_count' => $supplier->asset_maintenances_count]));
if ($supplier->maintenances_count > 0) {
return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.delete.assoc_maintenances', ['maintenances_count' => $supplier->maintenances_count]));
}
if ($supplier->licenses_count > 0) {
@@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* This controller provide the health route for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*
* @return \Illuminate\Http\JsonResponse
*/
class UploadedFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to store so we can check permisisons
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function store(UploadFileRequest $request, $object_type, $id) : RedirectResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', $object);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// If the file storage directory doesn't exist, create it
if (! Storage::exists(self::$map_storage_path[$object_type])) {
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
}
if ($request->hasFile('file')) {
// Loop over the attached files and add them to the object
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.upload.success', count($files)));
}
// No files were submitted
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.nofiles'));
}
/**
* Check for permissions and display the file.
* This isn't currently used, but is here for future use.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to show from the action_logs table
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function show($object_type, $id, $file_id) : RedirectResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// Check that the file being requested exists for the object
if (! $log = Actionlog::whereNotNull('filename')->where('item_type', self::$map_object_type[$object_type])->where('item_id', $object->id)->find($file_id))
{
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.invalid_id'));
}
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename))
{
return redirect()->back()->withFragment('files')->with('error', trans('general.file_upload_status.file_not_found'));
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
}
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
}
/**
* Delete the associated file
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param string $object_type the type of object to upload the file to
* @param int $id the ID of the object to delete from so we can check permisisons
* @param $file_id the ID of the file to delete from the action_logs table
* @since [v8.2.2]
* @author [A. Gianotto <snipe@snipe.net>]
*/
public function destroy($object_type, $id, $file_id) : RedirectResponse
{
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
}
// Check for the file
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)->first();
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
}
// Delete the record of the file
if ($log->delete()) {
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.success', 1));
}
}
// The file doesn't seem to really exist, so report an error
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.error', 1));
}
}
@@ -1,129 +0,0 @@
<?php
namespace App\Http\Controllers\Users;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\User;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller
{
/**
* Return JSON response with a list of user details for the getIndex() view.
*
* @param UploadFileRequest $request
* @param int $userId
* @return string JSON
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
*/
public function store(UploadFileRequest $request, User $user)
{
$this->authorize('update', $user);
$files = $request->file('file');
if (is_null($files)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
$logAction->item_id = $user->id;
$logAction->item_type = User::class;
$logAction->created_by = auth()->id();
$logAction->note = $request->input('notes');
$logAction->target_id = null;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->filename = $file_name;
$logAction->action_type = 'uploaded';
if (! $logAction->save()) {
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
}
/**
* Delete file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param int $userId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($userId = null, $fileId = null)
{
if ($user = User::find($userId)) {
$this->authorize('delete', $user);
$rel_path = 'private_uploads/users';
if ($log = Actionlog::find($fileId)) {
$filename = $log->filename;
$log->delete();
if (Storage::exists($rel_path.'/'.$filename)) {
Storage::delete($rel_path.'/'.$filename);
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.deletefile.success'));
}
}
// The log record doesn't exist somehow
return redirect()->back()->with('success', trans('admin/users/message.deletefile.success'));
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
}
/**
* Display/download the uploaded file
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param int $userId
* @param int $fileId
* @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show(User $user, $fileId = null)
{
if (empty($fileId)) {
return redirect()->route('users.show')->with('error', 'Invalid file request');
}
$this->authorize('view', $user);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
}
}
+12 -3
View File
@@ -98,6 +98,7 @@ class UsersController extends Controller
$user->activated = $request->input('activated', 0);
$user->jobtitle = $request->input('jobtitle');
$user->phone = $request->input('phone');
$user->mobile = $request->input('mobile');
$user->location_id = $request->input('location_id', null);
$user->department_id = $request->input('department_id', null);
$user->company_id = Company::getIdForUser($request->input('company_id', null));
@@ -126,7 +127,12 @@ class UsersController extends Controller
// we have to invoke the form request here to handle image uploads
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if($request->get('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
}
if ($user->save()) {
if ($request->filled('groups')) {
@@ -239,6 +245,7 @@ class UsersController extends Controller
$user->employee_num = $request->input('employee_num');
$user->jobtitle = $request->input('jobtitle', null);
$user->phone = $request->input('phone');
$user->mobile = $request->input('mobile');
$user->location_id = $request->input('location_id', null);
$user->company_id = Company::getIdForUser($request->input('company_id', null));
$user->manager_id = $request->input('manager_id', null);
@@ -432,7 +439,7 @@ class UsersController extends Controller
app('request')->request->set('permissions', $permissions);
$user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
$user_to_clone = User::with('userloc')->withTrashed()->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user_to_clone);
@@ -447,6 +454,8 @@ class UsersController extends Controller
$user->last_name = '';
$user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0);
$user->id = null;
$user->username = null;
$user->avatar = null;
// Get this user's groups
$userGroups = $user_to_clone->groups()->pluck('name', 'id');
@@ -462,7 +471,7 @@ class UsersController extends Controller
->with('user', $user)
->with('groups', Group::pluck('name', 'id'))
->with('userGroups', $userGroups)
->with('clone_user', $user_to_clone)
->with('cloned_model', $user_to_clone)
->with('item', $user);
}
-1
View File
@@ -26,7 +26,6 @@ class SecurityHeaders
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
// Ugh. Feature-Policy is dumb and clumsy and mostly irrelevant for Snipe-IT,
// since we don't provide any way to IFRAME anything in in the first place.
+12 -6
View File
@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exception\NotReadableException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class ImageUploadRequest extends Request
{
@@ -70,19 +71,25 @@ class ImageUploadRequest extends Request
public function handleImages($item, $w = 600, $form_fieldname = 'image', $path = null, $db_fieldname = 'image')
{
$type = strtolower(class_basename(get_class($item)));
$type = class_basename(get_class($item));
if (is_null($path)) {
$path = str_plural($type);
$path = strtolower(str_plural($type));
if ($type == 'assetmodel') {
if ($type == 'AssetModel') {
$path = 'models';
}
if ($type == 'user') {
$path = 'avatars';
}
}
if (!Storage::disk('public')->exists($path)) {
Storage::disk('public')->makeDirectory($path);
}
if ($this->offsetGet($form_fieldname) instanceof UploadedFile) {
@@ -93,10 +100,9 @@ class ImageUploadRequest extends Request
if (isset($image)) {
if (!config('app.lock_passwords')) {
$ext = $image->guessExtension();
$file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext;
$file_name = $type.'-'.$form_fieldname.($item->id ?? '-'.$item->id).'-'.str_random(10).'.'.$ext;
if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) {
// If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing
@@ -138,7 +144,7 @@ class ImageUploadRequest extends Request
// Remove Current image if exists
$item = $this->deleteExistingImage($item, $path, $db_fieldname);
$item->{$db_fieldname} = $file_name;
}
// If the user isn't uploading anything new but wants to delete their old image, do so
@@ -2,6 +2,7 @@
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CustomField;
@@ -16,6 +17,7 @@ use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class ActionlogsTransformer
{
@@ -133,24 +135,6 @@ class ActionlogsTransformer
$clean_meta= $this->changedInfo($clean_meta);
}
$file_url = '';
if($actionlog->filename!='') {
if ($actionlog->action_type == 'accepted') {
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
} else {
if ($actionlog->item) {
if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'accessory') {
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') {
$file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]);
}
}
}
}
$array = [
'id' => (int) $actionlog->id,
@@ -158,8 +142,10 @@ class ActionlogsTransformer
'file' => ($actionlog->filename!='')
?
[
'url' => $file_url,
'url' => $actionlog->uploads_file_url(),
'filename' => $actionlog->filename,
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
] : null,
'item' => ($actionlog->item) ? [
@@ -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),
@@ -25,7 +25,7 @@ class ConsumablesTransformer
$array = [
'id' => (int) $consumable->id,
'name' => e($consumable->name),
'image' => ($consumable->image) ? Storage::disk('public')->url('consumables/'.e($consumable->image)) : null,
'image' => ($consumable->getImageUrl()) ? ($consumable->getImageUrl()) : null,
'category' => ($consumable->category) ? ['id' => $consumable->category->id, 'name' => e($consumable->category->name)] : null,
'company' => ($consumable->company) ? ['id' => (int) $consumable->company->id, 'name' => e($consumable->company->name)] : null,
'item_no' => e($consumable->item_no),
@@ -62,7 +62,7 @@ class LicensesTransformer
'checkin' => Gate::allows('checkin', License::class),
'clone' => Gate::allows('create', License::class),
'update' => Gate::allows('update', License::class),
'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count > 0)) ? true : false,
'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count == $license->seats)) ? true : false,
];
$array += $permissions_array;
@@ -4,23 +4,24 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Maintenance;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
class AssetMaintenancesTransformer
class MaintenancesTransformer
{
public function transformAssetMaintenances(Collection $assetmaintenances, $total)
public function transformMaintenances(Collection $maintenances, $total)
{
$array = [];
foreach ($assetmaintenances as $assetmaintenance) {
$array[] = self::transformAssetMaintenance($assetmaintenance);
foreach ($maintenances as $assetmaintenance) {
$array[] = self::transformMaintenance($assetmaintenance);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformAssetMaintenance(AssetMaintenance $assetmaintenance)
public function transformMaintenance(Maintenance $assetmaintenance)
{
$array = [
'id' => (int) $assetmaintenance->id,
@@ -33,6 +34,7 @@ class AssetMaintenancesTransformer
'created_at' => Helper::getFormattedDateObject($assetmaintenance->asset->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($assetmaintenance->asset->updated_at, 'datetime'),
] : null,
'image' => ($assetmaintenance->image != '') ? Storage::disk('public')->url('maintenances/'.e($assetmaintenance->image)) : null,
'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [
'id' => (int) $assetmaintenance->asset->model->id,
'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null,
@@ -48,7 +50,8 @@ class AssetMaintenancesTransformer
'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null,
] : null,
'title' => ($assetmaintenance->title) ? e($assetmaintenance->title) : null,
'name' => ($assetmaintenance->name) ? e($assetmaintenance->name) : null,
'title' => ($assetmaintenance->name) ? e($assetmaintenance->name) : null, // legacy to not change the shape of the API
'location' => (($assetmaintenance->asset) && ($assetmaintenance->asset->location)) ? [
'id' => (int) $assetmaintenance->asset->location->id,
'name'=> e($assetmaintenance->asset->location->name),
@@ -32,10 +32,11 @@ class UploadedFilesTransformer
'name' => e($file->filename),
'item' => ($file->item_type) ? [
'id' => (int) $file->item_id,
'type' => strtolower(class_basename($file->item_type)),
'type' => str_plural(strtolower(class_basename($file->item_type))),
] : null,
'filename' => e($file->filename),
'filetype' => StorageHelper::getFiletype($file->uploads_file_path()),
'mediatype' => StorageHelper::getMediaType($file->uploads_file_path()),
'url' => $file->uploads_file_url(),
'note' => ($file->note) ? e($file->note) : null,
'created_by' => ($file->adminuser) ? [
@@ -44,7 +45,7 @@ class UploadedFilesTransformer
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
'inline' => 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),
];
@@ -22,6 +22,12 @@ class UsersTransformer
public function transformUser(User $user)
{
$role = '';
if ($user->isSuperUser()) {
$role = 'superadmin';
} elseif ($user->isAdmin()) {
$role = 'admin';
}
$array = [
'id' => (int) $user->id,
'avatar' => e($user->present()->gravatar) ?? null,
@@ -39,6 +45,7 @@ class UsersTransformer
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
'vip' => ($user->vip == '1') ? true : false,
'phone' => ($user->phone) ? e($user->phone) : null,
'mobile' => ($user->mobile) ? e($user->mobile) : null,
'website' => ($user->website) ? e($user->website) : null,
'address' => ($user->address) ? e($user->address) : null,
'city' => ($user->city) ? e($user->city) : null,
@@ -59,6 +66,7 @@ class UsersTransformer
'name'=> e($user->userloc->name),
] : null,
'notes'=> Helper::parseEscapedMarkedownInline($user->notes),
'role' => $role,
'permissions' => $user->decodePermissions(),
'activated' => ($user->activated == '1') ? true : false,
'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false,
+1
View File
@@ -52,6 +52,7 @@ class UserImporter extends ItemImporter
$this->item['email'] = trim($this->findCsvMatch($row, 'email'));
$this->item['gravatar'] = trim($this->findCsvMatch($row, 'gravatar'));
$this->item['phone'] = trim($this->findCsvMatch($row, 'phone_number'));
$this->item['mobile'] = trim($this->findCsvMatch($row, 'mobile_number'));
$this->item['website'] = trim($this->findCsvMatch($row, 'website'));
$this->item['jobtitle'] = trim($this->findCsvMatch($row, 'jobtitle'));
$this->item['address'] = trim($this->findCsvMatch($row, 'address'));
+8
View File
@@ -334,6 +334,7 @@ class Importer extends Component
'manager_username' => trans('general.importer.manager_username'),
'notes' => trans('general.notes'),
'phone_number' => trans('admin/users/table.phone'),
'mobile_number' => trans('admin/users/table.mobile'),
'remote' => trans('admin/users/general.remote'),
'start_date' => trans('general.start_date'),
'state' => trans('general.state'),
@@ -510,6 +511,13 @@ class Importer extends Component
'telephone',
'tel.',
],
'mobile_number' =>
[
'mobile',
'mobile number',
'cell',
'cellphone',
],
'serial' =>
[
+1 -1
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]),
);
}
+11 -2
View File
@@ -24,16 +24,25 @@ class CheckoutAssetMail extends Mailable
/**
* Create a new message instance.
* @throws \Exception
*/
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note, bool $firstTimeSending = true)
{
$this->item = $asset;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$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 = '';
@@ -116,7 +125,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');
+9 -1
View File
@@ -2,6 +2,7 @@
namespace App\Mail;
use App\Models\Asset;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\User;
@@ -25,9 +26,16 @@ class CheckoutLicenseMail extends Mailable
$this->item = $licenseSeat;
$this->admin = $checkedOutBy;
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->settings = Setting::getSettings();
$this->target = $checkedOutTo;
if($this->target instanceof User){
$this->target = $this->target->present()?->fullName();
}
elseif($this->target instanceof Asset){
$this->target = $this->target->assignedto?->present()?->fullName();
}
}
/**
+25 -25
View File
@@ -7,6 +7,7 @@ use App\Presenters\Presentable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
/**
* Model for the Actionlog (the table that keeps a historical log of
@@ -457,38 +458,40 @@ class Actionlog extends SnipeModel
public function uploads_file_url()
{
switch ($this->item_type) {
case Accessory::class:
return route('show.accessoryfile', [$this->item_id, $this->id]);
case Asset::class:
return route('show/assetfile', [$this->item_id, $this->id]);
case AssetModel::class:
return route('show/modelfile', [$this->item_id, $this->id]);
case Consumable::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case Component::class:
return route('show.componentfile', [$this->item_id, $this->id]);
case License::class:
return route('show.licensefile', [$this->item_id, $this->id]);
case Location::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case User::class:
return route('show/userfile', [$this->item_id, $this->id]);
default:
return null;
if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) {
return route('log.storedeula.download', ['filename' => $this->filename]);
}
$object = Str::snake(str_plural(str_replace("App\Models\\", '', $this->item_type)));
if ($object == 'asset_models') {
$object = 'models';
}
return route('ui.files.show', [
'object_type' => $object,
'id' => $this->item_id,
'file_id' => $this->id,
]);
}
public function uploads_file_path()
{
if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) {
return 'private_uploads/eula-pdfs/'.$this->filename;
}
switch ($this->item_type) {
case Accessory::class:
return 'private_uploads/accessories/'.$this->filename;
case Asset::class:
return 'private_uploads/assets/'.$this->filename;
case AssetModel::class:
return 'private_uploads/assetmodels/'.$this->filename;
return 'private_uploads/models/'.$this->filename;
case Consumable::class:
return 'private_uploads/consumables/'.$this->filename;
case Component::class:
@@ -497,6 +500,8 @@ class Actionlog extends SnipeModel
return 'private_uploads/licenses/'.$this->filename;
case Location::class:
return 'private_uploads/locations/'.$this->filename;
case Maintenance::class:
return 'private_uploads/maintenances/'.$this->filename;
case User::class:
return 'private_uploads/users/'.$this->filename;
default:
@@ -505,11 +510,6 @@ class Actionlog extends SnipeModel
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{
+19 -4
View File
@@ -112,7 +112,7 @@ class Asset extends Depreciable
'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
'serial' => ['nullable', 'unique_undeleted:assets,serial'],
'serial' => ['nullable', 'string', 'unique_undeleted:assets,serial'],
'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'],
'supplier_id' => ['nullable', 'exists:suppliers,id'],
'asset_eol_date' => ['nullable', 'date'],
@@ -206,6 +206,17 @@ class Asset extends Depreciable
'model.manufacturer' => ['name'],
];
protected static function booted(): void
{
static::forceDeleted(function (Asset $asset) {
$asset->requests()->forceDelete();
});
static::softDeleted(function (Asset $asset) {
$asset->requests()->delete();
});
}
// To properly set the expected checkin as Y-m-d
public function setExpectedCheckinAttribute($value)
{
@@ -226,7 +237,11 @@ class Asset extends Depreciable
foreach ($this->model->fieldset->fields as $field) {
if ($field->format == 'BOOLEAN') {
// this just casts booleans that may come through as strings to an actual boolean type
// adding !$field->field_encrypted because when the encrypted value comes through it
// screws things up for the encrypted validation rules (and the encrypted string
// is not a valid boolean type)
if ($field->format == 'BOOLEAN' && !$field->field_encrypted) {
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
@@ -756,9 +771,9 @@ class Asset extends Depreciable
* @since 1.0
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assetmaintenances()
public function maintenances()
{
return $this->hasMany(\App\Models\AssetMaintenance::class, 'asset_id')
return $this->hasMany(\App\Models\Maintenance::class, 'asset_id')
->orderBy('created_at', 'desc');
}
+9 -1
View File
@@ -98,8 +98,16 @@ class AssetModel extends SnipeModel
'manufacturer' => ['name'],
];
protected static function booted(): void
{
static::forceDeleted(function (AssetModel $assetModel) {
$assetModel->requests()->forceDelete();
});
static::softDeleted(function (AssetModel $assetModel) {
$assetModel->requests()->delete();
});
}
/**
* Establishes the model -> assets relationship
+2
View File
@@ -2,11 +2,13 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CheckoutRequest extends Model
{
use HasFactory;
use SoftDeletes;
protected $fillable = ['user_id'];
protected $table = 'checkout_requests';
+7 -2
View File
@@ -230,11 +230,16 @@ class Consumable extends SnipeModel
*/
public function getImageUrl()
{
// If there is a consumable image, use that
if ($this->image) {
return Storage::disk('public')->url(app('consumables_upload_path').$this->image);
}
return false;
// Otherwise check for a category image
} elseif (($this->category) && ($this->category->image)) {
return Storage::disk('public')->url(app('categories_upload_path').e($this->category->image));
}
return false;
}
/**
+1
View File
@@ -16,6 +16,7 @@ class CustomField extends Model
UniqueUndeletedTrait;
/**
*
* Custom field predfined formats
*
* @var array
+66 -12
View File
@@ -3,12 +3,19 @@
namespace App\Models;
use App\Rules\AlphaEncrypted;
use App\Rules\BooleanEncrypted;
use App\Rules\DateEncrypted;
use App\Rules\EmailEncrypted;
use App\Rules\IPEncrypted;
use App\Rules\IPv4Encrypted;
use App\Rules\IPv6Encrypted;
use App\Rules\MacEncrypted;
use App\Rules\NumericEncrypted;
use App\Rules\RegexEncrypted;
use App\Rules\UrlEncrypted;
use Gate;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Watson\Validating\ValidatingTrait;
class CustomFieldset extends Model
@@ -99,7 +106,7 @@ class CustomFieldset extends Model
* @since [v3.0]
* @return array
*/
public function validation_rules()
public function validation_rules(): array
{
$rules = [];
foreach ($this->fields as $field) {
@@ -121,18 +128,65 @@ class CustomFieldset extends Model
$rules[$field->db_column_name()] = $rule;
// these are to replace the standard 'numeric' and 'alpha' rules if the custom field is also encrypted.
// the values need to be decrypted first, because encrypted strings are alphanumeric
if ($field->format === 'NUMERIC' && $field->field_encrypted) {
// this is to switch the rules to rules specially made for encrypted custom fields that decrypt the value before validating
if ($field->field_encrypted) {
$numericKey = array_search('numeric', $rules[$field->db_column_name()]);
$rules[$field->db_column_name()][$numericKey] = new NumericEncrypted;
$alphaKey = array_search('alpha', $rules[$field->db_column_name()]);
$emailKey = array_search('email', $rules[$field->db_column_name()]);
$dateKey = array_search('date', $rules[$field->db_column_name()]);
$urlKey = array_search('url', $rules[$field->db_column_name()]);
$ipKey = array_search('ip', $rules[$field->db_column_name()]);
$ipv4Key = array_search('ipv4', $rules[$field->db_column_name()]);
$ipv6Key = array_search('ipv6', $rules[$field->db_column_name()]);
$macKey = array_search('regex:/^[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}$/', $rules[$field->db_column_name()]);
$booleanKey = array_search('boolean', $rules[$field->db_column_name()]);
// find objects in array that start with "regex:"
// collect because i couldn't figure how to do this
// with array filter and get keys out of it
$regexCollect = collect($rules[$field->db_column_name()]);
$regexKeys = $regexCollect->filter(function ($value, $key) {
return starts_with($value, 'regex:');
})->keys()->values()->toArray();
switch ($field->format) {
case 'NUMERIC':
$rules[$field->db_column_name()][$numericKey] = new NumericEncrypted;
break;
case 'ALPHA':
$rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted;
break;
case 'EMAIL':
$rules[$field->db_column_name()][$emailKey] = new EmailEncrypted;
break;
case 'DATE':
$rules[$field->db_column_name()][$dateKey] = new DateEncrypted;
break;
case 'URL':
$rules[$field->db_column_name()][$urlKey] = new UrlEncrypted;
break;
case 'IP':
$rules[$field->db_column_name()][$ipKey] = new IPEncrypted;
break;
case 'IPV4':
$rules[$field->db_column_name()][$ipv4Key] = new IPv4Encrypted;
break;
case 'IPV6':
$rules[$field->db_column_name()][$ipv6Key] = new IPv6Encrypted;
break;
case 'MAC':
$rules[$field->db_column_name()][$macKey] = new MacEncrypted;
break;
case 'BOOLEAN':
$rules[$field->db_column_name()][$booleanKey] = new BooleanEncrypted;
break;
case starts_with($field->format, 'regex'):
foreach ($regexKeys as $regexKey) {
$rules[$field->db_column_name()][$regexKey] = new RegexEncrypted;
}
break;
}
}
if ($field->format === 'ALPHA' && $field->field_encrypted) {
$alphaKey = array_search('alpha', $rules[$field->db_column_name()]);
$rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted;
}
// add not_array to rules for all fields but checkboxes
if ($field->element != 'checkbox') {
@@ -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;
}
}
}
@@ -4,31 +4,34 @@ namespace App\Models;
use App\Helpers\Helper;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;
use App\Models\Traits\HasUploads;
/**
* Model for Asset Maintenances.
*
* @version v1.0
*/
class AssetMaintenance extends Model implements ICompanyableChild
class Maintenance extends SnipeModel implements ICompanyableChild
{
use HasFactory;
use HasUploads;
use SoftDeletes;
use CompanyableChildTrait;
use ValidatingTrait;
use Loggable, Presentable;
protected $table = 'asset_maintenances';
protected $table = 'maintenances';
protected $rules = [
'asset_id' => 'required|integer',
'supplier_id' => 'nullable|integer',
'asset_maintenance_type' => 'required',
'title' => 'required|max:100',
'name' => 'required|max:100',
'is_warranty' => 'boolean',
'start_date' => 'required|date_format:Y-m-d',
'completion_date' => 'date_format:Y-m-d|nullable|after_or_equal:start_date',
@@ -43,7 +46,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
* @var array
*/
protected $fillable = [
'title',
'name',
'asset_id',
'supplier_id',
'asset_maintenance_type',
@@ -64,7 +67,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
protected $searchableAttributes =
[
'title',
'name',
'notes',
'asset_maintenance_type',
'cost',
@@ -100,14 +103,14 @@ class AssetMaintenance extends Model implements ICompanyableChild
public static function getImprovementOptions()
{
return [
trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'),
trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'),
trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
trans('admin/asset_maintenances/general.pat_test') => trans('admin/asset_maintenances/general.pat_test'),
trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'),
trans('admin/asset_maintenances/general.software_support') => trans('admin/asset_maintenances/general.software_support'),
trans('admin/asset_maintenances/general.hardware_support') => trans('admin/asset_maintenances/general.hardware_support'),
trans('admin/asset_maintenances/general.configuration_change') => trans('admin/asset_maintenances/general.configuration_change'),
trans('admin/maintenances/general.maintenance') => trans('admin/maintenances/general.maintenance'),
trans('admin/maintenances/general.repair') => trans('admin/maintenances/general.repair'),
trans('admin/maintenances/general.upgrade') => trans('admin/maintenances/general.upgrade'),
trans('admin/maintenances/general.pat_test') => trans('admin/maintenances/general.pat_test'),
trans('admin/maintenances/general.calibration') => trans('admin/maintenances/general.calibration'),
trans('admin/maintenances/general.software_support') => trans('admin/maintenances/general.software_support'),
trans('admin/maintenances/general.hardware_support') => trans('admin/maintenances/general.hardware_support'),
trans('admin/maintenances/general.configuration_change') => trans('admin/maintenances/general.configuration_change'),
];
}
@@ -166,6 +169,21 @@ class AssetMaintenance extends Model implements ICompanyableChild
return $this->belongsTo(\App\Models\Asset::class, 'asset_id')
->withTrashed();
}
/**
* Get the maintenance logs
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v8.2.2]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assetlog()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
@@ -187,6 +205,11 @@ class AssetMaintenance extends Model implements ICompanyableChild
->withTrashed();
}
public function getDisplayNameAttribute()
{
return $this->name;
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
@@ -203,7 +226,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderBySupplier($query, $order)
{
return $query->leftJoin('suppliers as suppliers_maintenances', 'asset_maintenances.supplier_id', '=', 'suppliers_maintenances.id')
return $query->leftJoin('suppliers as suppliers_maintenances', 'maintenances.supplier_id', '=', 'suppliers_maintenances.id')
->orderBy('suppliers_maintenances.name', $order);
}
@@ -219,7 +242,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderByTag($query, $order)
{
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.asset_tag', $order);
}
@@ -233,7 +256,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderByAssetName($query, $order)
{
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.name', $order);
}
@@ -247,7 +270,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderByAssetSerial($query, $order)
{
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
return $query->leftJoin('assets', 'maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.serial', $order);
}
@@ -261,7 +284,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderStatusName($query, $order)
{
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
return $query->join('assets as maintained_asset', 'maintenances.asset_id', '=', 'maintained_asset.id')
->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id')
->orderBy('maintained_asset_status.name', $order);
}
@@ -276,7 +299,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderLocationName($query, $order)
{
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
return $query->join('assets as maintained_asset', 'maintenances.asset_id', '=', 'maintained_asset.id')
->leftjoin('locations as maintained_asset_location', 'maintained_asset_location.id', '=', 'maintained_asset.location_id')
->orderBy('maintained_asset_location.name', $order);
}
@@ -286,6 +309,6 @@ class AssetMaintenance extends Model implements ICompanyableChild
*/
public function scopeOrderByCreatedBy($query, $order)
{
return $query->leftJoin('users as admin_sort', 'asset_maintenances.created_by', '=', 'admin_sort.id')->select('asset_maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
return $query->leftJoin('users as admin_sort', 'maintenances.created_by', '=', 'admin_sort.id')->select('maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
}
+1 -1
View File
@@ -21,7 +21,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;
+15 -6
View File
@@ -153,12 +153,21 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
'phoneNumbers' => [[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite()
]],
// Mobile and work phone numbers
'phoneNumbers' => [
[
"value" => AttributeMapping::eloquent("phone"),
"display" => null,
"type" => AttributeMapping::constant("work")->ignoreWrite(),
"primary" => AttributeMapping::constant(true)->ignoreWrite(),
],
[
"value" => AttributeMapping::eloquent("mobile"),
"display" => null,
"type" => AttributeMapping::constant("mobile")->ignoreWrite(),
"primary" => AttributeMapping::constant(false)->ignoreWrite()
]
],
'ims' => [[
"value" => null,
+4 -4
View File
@@ -7,7 +7,7 @@ use App\Models\Traits\Searchable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;
use \Illuminate\Database\Eloquent\Relations\Relation;
class Supplier extends SnipeModel
{
use HasFactory;
@@ -133,7 +133,7 @@ class Supplier extends SnipeModel
* Establishes the supplier -> admin user relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @return Relation
*/
public function adminuser()
{
@@ -147,9 +147,9 @@ class Supplier extends SnipeModel
* @since [v1.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function asset_maintenances()
public function maintenances(): Relation
{
return $this->hasMany(\App\Models\AssetMaintenance::class, 'supplier_id');
return $this->hasMany(\App\Models\Maintenance::class, 'supplier_id');
}
/**
+58 -7
View File
@@ -70,6 +70,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'manager_id',
'password',
'phone',
'mobile',
'notes',
'state',
'username',
@@ -139,6 +140,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'locale',
'notes',
'phone',
'mobile',
'state',
'username',
'website',
@@ -183,6 +185,17 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
);
}
protected static function booted(): void
{
static::forceDeleted(function (User $user) {
CheckoutRequest::where(['user_id' => $user->id])->forceDelete();
});
static::softDeleted(function (User $user) {
CheckoutRequest::where(['user_id' => $user->id])->delete();
});
}
public function isAvatarExternal()
{
@@ -398,9 +411,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
* @since [v4.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function assetmaintenances()
public function maintenances()
{
return $this->hasMany(\App\Models\AssetMaintenance::class, 'user_id')->withTrashed();
return $this->hasMany(\App\Models\Maintenance::class, 'user_id')->withTrashed();
}
/**
@@ -891,6 +904,49 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
);
}
/**
* Return only admins and superusers
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*/
public function scopeOnlySuperAdmins($query)
{
return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%')
->orWhere('users.permissions', 'LIKE', '%"superuser":1%')
->orWhereHas(
'groups', function ($query) {
$query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%')
->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%');
}
);
}
/**
* Return only admins and superusers
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*/
public function scopeOnlyAdminsAndSuperAdmins($query)
{
return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%')
->orWhere('users.permissions', 'LIKE', '%"superuser":1%')
->orWhere('users.permissions', 'LIKE', '%"admin":1%')
->orWhere('users.permissions', 'LIKE', '%"admin":"1"%')
->orWhereHas(
'groups', function ($query) {
$query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%')
->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%')
->orWhere('permission_groups.permissions', 'LIKE', '%"admin":1%')
->orWhere('permission_groups.permissions', 'LIKE', '%"admin":"1"%');
}
);
}
/**
* Query builder scope to order on manager
@@ -964,7 +1020,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
/**
* Get the preferred locale for the user.
*
@@ -1003,7 +1058,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
public function scopeUserLocation($query, $location, $search)
{
return $query->where('location_id', '=', $location)
->where('users.first_name', 'LIKE', '%' . $search . '%')
->orWhere('users.email', 'LIKE', '%' . $search . '%')
@@ -1016,9 +1070,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
->orWhere('users.username', 'LIKE', '%' . $search . '%')
->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\'');
}
/**
@@ -29,6 +29,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
$this->assigned_to = $params['assigned_to'];
$this->note = $params['note'];
$this->company_name = $params['company_name'];
$this->admin = $params['admin'] ?? null;
$this->settings = Setting::getSettings();
}
@@ -72,6 +73,7 @@ class AcceptanceAssetAcceptedNotification extends Notification
'assigned_to' => $this->assigned_to,
'company_name' => $this->company_name,
'intro_text' => trans('mail.acceptance_asset_accepted'),
'admin' => $this->admin,
])
->subject(trans('mail.acceptance_asset_accepted'));
+6
View File
@@ -20,7 +20,11 @@ class ComponentObserver
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
if($component->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('update');
}
@@ -37,6 +41,7 @@ class ComponentObserver
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
if($component->imported) {
$logAction->setActionSource('importer');
@@ -56,6 +61,7 @@ class ComponentObserver
$logAction->item_type = Component::class;
$logAction->item_id = $component->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
$logAction->logaction('delete');
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Observers;
use App\Models\Actionlog;
use App\Models\Maintenance;
use App\Models\Asset;
class MaintenanceObserver
{
/**
* Listen to the User created event.
*
* @param Maintenance $maintenance
* @return void
*/
public function updated(Maintenance $maintenance)
{
$logAction = new Actionlog();
$logAction->item_type = Maintenance::class;
$logAction->item_id = $maintenance->id;
$logAction->target_type = Asset::class;
$logAction->target_id = $maintenance->asset_id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
if($maintenance->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('update');
}
/**
* Listen to the Component created event when
* a new component is created.
*
* @param Maintenance $maintenance
* @return void
*/
public function created(Maintenance $maintenance)
{
$logAction = new Actionlog();
$logAction->item_type = Maintenance::class;
$logAction->item_id = $maintenance->id;
$logAction->target_type = Asset::class;
$logAction->target_id = $maintenance->asset_id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
if($maintenance->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
/**
* Listen to the Component deleting event.
*
* @param Maintenance $maintenance
* @return void
*/
public function deleting(Maintenance $maintenance)
{
$logAction = new Actionlog();
$logAction->item_type = Maintenance::class;
$logAction->item_id = $maintenance->id;
$logAction->target_type = Asset::class;
$logAction->target_id = $maintenance->asset_id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->action_date = date('Y-m-d H:i:s');
$logAction->created_by = auth()->id();
$logAction->logaction('delete');
}
}
+1
View File
@@ -3,6 +3,7 @@
namespace App\Policies;
use App\Models\User;
use App\Models\Asset;
class AssetPolicy extends CheckoutablePermissionsPolicy
{
+6 -6
View File
@@ -85,7 +85,7 @@ abstract class SnipePermissionsPolicy
}
/**
* Determine whether the user can view the accessory.
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @return mixed
@@ -101,7 +101,7 @@ abstract class SnipePermissionsPolicy
}
/**
* Determine whether the user can create accessories.
* Determine whether the user can create model.
*
* @param \App\Models\User $user
* @return mixed
@@ -112,7 +112,7 @@ abstract class SnipePermissionsPolicy
}
/**
* Determine whether the user can update the accessory.
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @return mixed
@@ -124,7 +124,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can update the accessory.
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @return mixed
@@ -135,7 +135,7 @@ abstract class SnipePermissionsPolicy
}
/**
* Determine whether the user can delete the accessory.
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @return mixed
@@ -151,7 +151,7 @@ abstract class SnipePermissionsPolicy
}
/**
* Determine whether the user can manage the accessory.
* Determine whether the user can manage the model.
*
* @param \App\Models\User $user
* @return mixed
+1 -1
View File
@@ -328,7 +328,7 @@ class AssetPresenter extends Presenter
// name can break the listings page. - snipe
foreach ($fields as $field) {
$layout[] = [
'field' => 'custom_fields.'.$field->db_column,
'field' => $field->db_column,
'searchable' => true,
'sortable' => true,
'switchable' => true,
+2 -2
View File
@@ -106,7 +106,7 @@ class HistoryPresenter extends Presenter
'switchable' => true,
'title' => trans('general.file_name'),
'visible' => true,
'formatter' => 'fileUploadNameFormatter',
'formatter' => 'fileNameFormatter',
],
[
'field' => 'file_download',
@@ -115,7 +115,7 @@ class HistoryPresenter extends Presenter
'switchable' => true,
'title' => trans('general.download'),
'visible' => true,
'formatter' => 'fileUploadFormatter',
'formatter' => 'fileDownloadButtonsFormatter',
],
[
'field' => 'note',
+1
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,
+2
View File
@@ -67,7 +67,9 @@ class LocationPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'title' => trans('admin/locations/message.current_location'),
'titleTooltip' => trans('admin/locations/message.current_location'),
'visible' => true,
'class' => 'css-house-laptop',
], [
'field' => 'rtd_assets_count',
'searchable' => false,
@@ -5,7 +5,7 @@ namespace App\Presenters;
/**
* Class AssetModelPresenter
*/
class AssetMaintenancesPresenter extends Presenter
class MaintenancesPresenter extends Presenter
{
/**
* Json Column Layout for bootstrap table
@@ -22,7 +22,7 @@ class AssetMaintenancesPresenter extends Presenter
'title' => trans('general.id'),
'visible' => false,
], [
'field' => 'title',
'field' => 'name',
'searchable' => true,
'sortable' => true,
'switchable' => true,
@@ -30,6 +30,15 @@ class AssetMaintenancesPresenter extends Presenter
'visible' => true,
'formatter' => 'maintenancesLinkFormatter',
],
[
'field' => 'image',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.image'),
'visible' => true,
'formatter' => 'imageFormatter',
],
[
'field' => 'company',
'searchable' => true,
@@ -42,7 +51,7 @@ class AssetMaintenancesPresenter extends Presenter
'field' => 'asset_name',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/table.asset_name'),
'title' => trans('admin/maintenances/table.asset_name'),
'formatter' => 'assetNameLinkFormatter',
], [
'field' => 'asset_tag',
@@ -89,35 +98,35 @@ class AssetMaintenancesPresenter extends Presenter
'field' => 'asset_maintenance_type',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.asset_maintenance_type'),
'title' => trans('admin/maintenances/form.asset_maintenance_type'),
], [
'field' => 'start_date',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.start_date'),
'title' => trans('admin/maintenances/form.start_date'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'completion_date',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.completion_date'),
'title' => trans('admin/maintenances/form.completion_date'),
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'notes',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.notes'),
'title' => trans('admin/maintenances/form.notes'),
], [
'field' => 'is_warranty',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/table.is_warranty'),
'title' => trans('admin/maintenances/table.is_warranty'),
'formatter' => 'trueFalseFormatter'
], [
'field' => 'cost',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.cost'),
'title' => trans('admin/maintenances/form.cost'),
'class' => 'text-right',
], [
'field' => 'created_by',
+12 -9
View File
@@ -18,7 +18,7 @@ class UploadedFilesPresenter extends Presenter
$layout = [
[
'field' => 'id',
'searchable' => false,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.id'),
@@ -30,6 +30,7 @@ class UploadedFilesPresenter extends Presenter
'sortable' => false,
'switchable' => false,
'title' => trans('general.type'),
'visible' => true,
'formatter' => 'iconFormatter',
],
[
@@ -38,16 +39,17 @@ class UploadedFilesPresenter extends Presenter
'sortable' => false,
'switchable' => true,
'title' => trans('general.image'),
'formatter' => 'inlineImageFormatter',
'visible' => true,
'formatter' => 'filePreviewFormatter',
],
[
'field' => 'filename',
'searchable' => false,
'sortable' => false,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.file_name'),
'visible' => true,
'formatter' => 'fileUploadNameFormatter',
'formatter' => 'fileNameFormatter',
],
[
'field' => 'download',
@@ -56,7 +58,7 @@ class UploadedFilesPresenter extends Presenter
'switchable' => true,
'title' => trans('general.download'),
'visible' => true,
'formatter' => 'downloadOrOpenInNewWindowFormatter',
'formatter' => 'fileDownloadButtonsFormatter',
],
[
'field' => 'note',
@@ -68,10 +70,10 @@ class UploadedFilesPresenter extends Presenter
],
[
'field' => 'created_by',
'searchable' => false,
'searchable' => true,
'sortable' => true,
'title' => trans('general.created_by'),
'visible' => false,
'visible' => true,
'formatter' => 'usersLinkObjFormatter',
],
[
@@ -80,7 +82,7 @@ class UploadedFilesPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'title' => trans('general.created_at'),
'visible' => false,
'visible' => true,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'available_actions',
@@ -88,6 +90,7 @@ class UploadedFilesPresenter extends Presenter
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'visible' => true,
'formatter' => 'deleteUploadFormatter',
],
];
+10 -1
View File
@@ -124,6 +124,15 @@ class UserPresenter extends Presenter
'visible' => true,
'formatter' => 'phoneFormatter',
],
[
'field' => 'mobile',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/users/table.mobile'),
'visible' => false,
'formatter' => 'mobileFormatter',
],
[
'field' => 'website',
'searchable' => true,
@@ -180,7 +189,7 @@ class UserPresenter extends Presenter
'switchable' => false,
'title' => trans('admin/users/table.username'),
'visible' => true,
'formatter' => 'usersLinkFormatter',
'formatter' => 'usernameRoleLinkFormatter',
],
[
'field' => 'employee_num',
+9
View File
@@ -7,6 +7,7 @@ use App\Models\Asset;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Maintenance;
use App\Models\User;
use App\Models\Setting;
use App\Models\SnipeSCIMConfig;
@@ -17,6 +18,7 @@ use App\Observers\ComponentObserver;
use App\Observers\ConsumableObserver;
use App\Observers\LicenseObserver;
use App\Observers\SettingObserver;
use App\Observers\MaintenanceObserver;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
@@ -67,6 +69,7 @@ class AppServiceProvider extends ServiceProvider
Schema::defaultStringLength(191);
Asset::observe(AssetObserver::class);
Maintenance::observe(MaintenanceObserver::class);
User::observe(UserObserver::class);
Accessory::observe(AccessoryObserver::class);
Component::observe(ComponentObserver::class);
@@ -82,6 +85,12 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
if ($this->app->environment('local')) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
$this->app->register(TelescopeServiceProvider::class);
}
// Only load rollbar if there is a rollbar key and the app is in production
if (($this->app->environment('production')) && (config('logging.channels.rollbar.access_token'))) {
$this->app->register(\Rollbar\Laravel\RollbarServiceProvider::class);
+25 -5
View File
@@ -2,7 +2,7 @@
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetMaintenance;
use App\Models\Maintenance;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
@@ -413,14 +413,14 @@ class BreadcrumbsServiceProvider extends ServiceProvider
->push(trans('general.create'), route('maintenances.create'))
);
Breadcrumbs::for('maintenances.show', fn (Trail $trail, AssetMaintenance $maintenance) =>
Breadcrumbs::for('maintenances.show', fn (Trail $trail, Maintenance $maintenance) =>
$trail->parent('maintenances.index', route('maintenances.index'))
->push($maintenance->title, route('maintenances.show', $maintenance))
->push($maintenance->name, route('maintenances.show', $maintenance))
);
Breadcrumbs::for('maintenances.edit', fn (Trail $trail, AssetMaintenance $maintenance) =>
Breadcrumbs::for('maintenances.edit', fn (Trail $trail, Maintenance $maintenance) =>
$trail->parent('maintenances.index', route('maintenances.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $maintenance->title]), route('maintenances.edit', $maintenance))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $maintenance->name]), route('maintenances.edit', $maintenance))
);
@@ -539,6 +539,26 @@ class BreadcrumbsServiceProvider extends ServiceProvider
->push(trans('general.users'), route('users.index'))
->push(trans('general.deleted_users'), route('users.index'))
);
} elseif ((request()->is('users*')) && (request()->admins=='true')) {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
->push(trans('general.show_admins'), route('users.index'))
);
} elseif ((request()->is('users*')) && (request()->superadmins=='true')) {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
->push(trans('general.show_superadmins'), route('users.index'))
);
} elseif ((request()->is('users*')) && (request()->activated=='0')) {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
->push(trans('general.login_disabled'), route('users.index'))
);
} elseif ((request()->is('users*')) && (request()->activated=='1')) {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
->push(trans('general.login_enabled'), route('users.index'))
);
} else {
Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home'))
->push(trans('general.users'), route('users.index'))
+32 -28
View File
@@ -31,7 +31,7 @@ class SettingsServiceProvider extends ServiceProvider
// Make sure the limit is actually set, is an integer and does not exceed system limits
\App::singleton('api_limit_value', function () {
app()->singleton('api_limit_value', function () {
$limit = config('app.max_results');
$int_limit = intval(request('limit'));
@@ -43,7 +43,7 @@ class SettingsServiceProvider extends ServiceProvider
});
// Make sure the offset is actually set and is an integer
\App::singleton('api_offset_value', function () {
app()->singleton('api_offset_value', function () {
$offset = intval(request('offset'));
return $offset;
});
@@ -57,117 +57,121 @@ class SettingsServiceProvider extends ServiceProvider
// Model paths and URLs
\App::singleton('eula_pdf_path', function () {
app()->singleton('eula_pdf_path', function () {
return 'eula_pdf_path/';
});
\App::singleton('assets_upload_path', function () {
app()->singleton('assets_upload_path', function () {
return 'assets/';
});
\App::singleton('audits_upload_path', function () {
app()->singleton('maintenances_path', function () {
return 'maintenances/';
});
app()->singleton('audits_upload_path', function () {
return 'audits/';
});
\App::singleton('accessories_upload_path', function () {
app()->singleton('accessories_upload_path', function () {
return 'public/uploads/accessories/';
});
\App::singleton('models_upload_path', function () {
app()->singleton('models_upload_path', function () {
return 'models/';
});
\App::singleton('models_upload_url', function () {
app()->singleton('models_upload_url', function () {
return 'models/';
});
// Categories
\App::singleton('categories_upload_path', function () {
app()->singleton('categories_upload_path', function () {
return 'categories/';
});
\App::singleton('categories_upload_url', function () {
app()->singleton('categories_upload_url', function () {
return 'categories/';
});
// Locations
\App::singleton('locations_upload_path', function () {
app()->singleton('locations_upload_path', function () {
return 'locations/';
});
\App::singleton('locations_upload_url', function () {
app()->singleton('locations_upload_url', function () {
return 'locations/';
});
// Users
\App::singleton('users_upload_path', function () {
app()->singleton('users_upload_path', function () {
return 'avatars/';
});
\App::singleton('users_upload_url', function () {
app()->singleton('users_upload_url', function () {
return 'users/';
});
// Manufacturers
\App::singleton('manufacturers_upload_path', function () {
app()->singleton('manufacturers_upload_path', function () {
return 'manufacturers/';
});
\App::singleton('manufacturers_upload_url', function () {
app()->singleton('manufacturers_upload_url', function () {
return 'manufacturers/';
});
// Suppliers
\App::singleton('suppliers_upload_path', function () {
app()->singleton('suppliers_upload_path', function () {
return 'suppliers/';
});
\App::singleton('suppliers_upload_url', function () {
app()->singleton('suppliers_upload_url', function () {
return 'suppliers/';
});
// Departments
\App::singleton('departments_upload_path', function () {
app()->singleton('departments_upload_path', function () {
return 'departments/';
});
\App::singleton('departments_upload_url', function () {
app()->singleton('departments_upload_url', function () {
return 'departments/';
});
// Company paths and URLs
\App::singleton('companies_upload_path', function () {
app()->singleton('companies_upload_path', function () {
return 'companies/';
});
\App::singleton('companies_upload_url', function () {
app()->singleton('companies_upload_url', function () {
return 'companies/';
});
// Accessories paths and URLs
\App::singleton('accessories_upload_path', function () {
app()->singleton('accessories_upload_path', function () {
return 'accessories/';
});
\App::singleton('accessories_upload_url', function () {
app()->singleton('accessories_upload_url', function () {
return 'accessories/';
});
// Consumables paths and URLs
\App::singleton('consumables_upload_path', function () {
app()->singleton('consumables_upload_path', function () {
return 'consumables/';
});
\App::singleton('consumables_upload_url', function () {
app()->singleton('consumables_upload_url', function () {
return 'consumables/';
});
// Components paths and URLs
\App::singleton('components_upload_path', function () {
app()->singleton('components_upload_path', function () {
return 'components/';
});
\App::singleton('components_upload_url', function () {
app()->singleton('components_upload_url', function () {
return 'components/';
});
@@ -0,0 +1,66 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->hasMonitoredTag();
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in NON-LOCAL environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
if ($user->isSuperUser()) {
return true;
}
return false;
});
}
}
+3 -1
View File
@@ -5,9 +5,11 @@ namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class AlphaEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
@@ -18,7 +20,7 @@ class AlphaEncrypted implements ValidationRule
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!ctype_alpha($decrypted) && !is_null($decrypted)) {
if (!$this->validateAlpha($attributeName, $decrypted, 'ascii') && !is_null($decrypted)) {
$fail(trans('validation.alpha', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class BooleanEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateBoolean($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.ipv6', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class DateEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateDate($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.date', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class EmailEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateEmail($attribute, $decrypted, []) && !is_null($decrypted)) {
$fail(trans('validation.email', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class IPEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateIp($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.ip', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class IPv4Encrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateIpv4($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.ipv4', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class IPv6Encrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateIpv6($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.ipv6', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Rules;
use App\Models\CustomField;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class MacEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateRegex($attributeName, $decrypted, ['/^[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}$/']) && !is_null($decrypted)) {
$fail(trans('validation.mac_address', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}
+5 -2
View File
@@ -3,12 +3,16 @@
namespace App\Rules;
use Closure;
use Egulias\EmailValidator\Validation\RFCValidation;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class NumericEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
@@ -16,11 +20,10 @@ class NumericEncrypted implements ValidationRule
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!is_numeric($decrypted) && !is_null($decrypted)) {
if (!$this->validateNumeric($attributeName, $decrypted) && !is_null($decrypted)) {
$fail(trans('validation.numeric', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Rules;
use App\Models\CustomField;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class RegexEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$field = CustomField::where('db_column', $attribute)->first();
$regex = $field->format;
$regex = str_replace('regex:', '', $regex);
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateRegex($attributeName, $decrypted, [$regex]) && !is_null($decrypted)) {
$fail(trans('validation.regex', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e->getMessage());
$fail(trans('general.something_went_wrong'));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Validation\Concerns\ValidatesAttributes;
class UrlEncrypted implements ValidationRule
{
use ValidatesAttributes;
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$attributeName = trim(preg_replace('/_+|snipeit|\d+/', ' ', $attribute));
$decrypted = Crypt::decrypt($value);
if (!$this->validateUrl($attributeName, $decrypted, []) && !is_null($decrypted)) {
$fail(trans('validation.url', ['attribute' => $attributeName]));
}
} catch (\Exception $e) {
report($e);
$fail(trans('general.something_went_wrong'));
}
}
}

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