Compare commits

...

214 Commits

Author SHA1 Message Date
snipe e20338ff9e Merge branch 'develop' 2018-07-09 22:38:04 -07:00
snipe 78530ae123 Fix tests 2018-07-09 21:57:45 -07:00
snipe 52d605d13e Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-09 19:21:23 -07:00
snipe 0182615e7e Bumped version 2018-07-09 19:18:38 -07:00
snipe fad84d4437 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-07-09 19:07:49 -07:00
snipe c162e9a4de Bumped hash 2018-07-09 19:06:56 -07:00
snipe bf1e742df6 Merge branch 'develop' 2018-07-09 19:05:19 -07:00
snipe 0e88a6b268 Fixed bug in branding image upload size text 2018-07-09 19:04:18 -07:00
Till Deeke c1e870528e Fixes the label association (#5510) (#5790) 2018-07-09 14:51:17 -07:00
snipe 35fc001c58 Fixed #5742 - create_function() is deprecated 2018-07-05 20:49:01 -07:00
snipe 339263a295 Fixed #5751 - added option for unique constraint on serial 2018-07-05 19:30:36 -07:00
snipe a44bd9abe0 Disallow deleting category if there are licenses 2018-07-05 18:02:25 -07:00
snipe b850d47282 Merge branch 'develop' 2018-07-05 15:40:07 -07:00
snipe 4099c06b27 fix middleware priority: handle trusted proxies prior setup check
From @plexorama
2018-07-05 15:36:59 -07:00
snipe e559879f91 Add @plexorama as a contributor 2018-07-05 15:35:37 -07:00
snipe abb95e7872 Tweaked custom field default value layout
This still needs work. It’s ugly.
2018-07-05 15:31:27 -07:00
snipe 869de3d251 Fixed broken pagination on status labels API 2018-07-05 14:42:39 -07:00
snipe f3526eccb9 Merge branch 'features/textarea-custom-field' into develop
# Conflicts:
#	public/js/build/all.js
#	public/js/build/vue.js
#	public/js/build/vue.js.map
#	public/js/dist/all.js
#	public/mix-manifest.json
2018-07-05 12:37:07 -07:00
Daniel Meltzer 880faa83a6 Importer2 checkout (#5771)
* Importer: checkout to location, backend changes+tests.

* Import location checkout. Frontend changes.

* Allow importing of item number/model number for consumables.
2018-07-05 12:22:24 -07:00
Juan Font 311f9fcefb Implemented method to get info on the current user of the API (#5722)
* Implemented method to get info on the current user of the API

* Move userinfo method to UsersController

* Added missing files
2018-07-02 20:35:10 -07:00
snipe 8732f299e6 Added logo class for logo override in custom CSS 2018-07-02 18:47:30 -07:00
Arunas Skirius b30aac536a fixed the alignment of a couple navbar icons (#5764) 2018-07-02 18:10:25 -07:00
snipe d7dc4ae0c0 Added manager to custom report 2018-06-27 00:45:09 -07:00
snipe 5bb4c85ccb Update twitter handle 2018-06-27 00:44:54 -07:00
snipe 80dda198c5 Parse line breaks in the detail view 2018-06-21 09:44:10 -07:00
snipe 9442736518 Adds textarea as a custom field type 2018-06-21 09:35:04 -07:00
snipe 2fbad52c71 Merge branch 'develop' 2018-06-21 07:54:01 -07:00
snipe 5975c9fac7 Add @ParadoxGuitarist as a contributor 2018-06-21 07:52:24 -07:00
snipe 7b0e392ecd Add @thelamer as a contributor 2018-06-21 07:52:04 -07:00
snipe b51a10b46b Add @RichardRay as a contributor 2018-06-21 07:51:39 -07:00
snipe 6c58f59d72 Add @EarlRamirez as a contributor 2018-06-21 07:51:24 -07:00
snipe cd9caa24ad Add @SjamonDaal as a contributor 2018-06-21 07:51:13 -07:00
snipe cbb4b4d846 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-06-21 07:46:27 -07:00
snipe ea4cdadc6e Bumped version 2018-06-21 07:44:58 -07:00
snipe 6638d64d68 Merge remote-tracking branch 'origin/master' into develop 2018-06-21 07:43:32 -07:00
snipe eb412c2bcb Missed one 2018-06-21 07:43:12 -07:00
snipe fde4a59510 Bumped version 2018-06-21 07:40:37 -07:00
snipe 1ee394aa69 Added Select2 to class for dropdowns 2018-06-21 07:40:30 -07:00
snipe 707f90573c Merge branch 'thelamer-docker-fixes' into develop 2018-06-21 07:31:55 -07:00
snipe f8429ad357 Merge branch 'docker-fixes' of https://github.com/thelamer/snipe-it into thelamer-docker-fixes 2018-06-21 07:31:15 -07:00
snipe aa5003d297 Merge branch 'develop' 2018-06-21 07:21:24 -07:00
snipe e9901f5e58 Set composer timeout to 3000 2018-06-21 07:17:51 -07:00
Djamon Staal f0d04a4a57 End help text with a period consistently. (#5731) 2018-06-21 07:13:29 -07:00
Djamon Staal 32e3f748d8 Make version footer configurable. (#5730) 2018-06-21 07:12:16 -07:00
Earl Ramirez fa465a84df Added cron to list of packages (#5729) 2018-06-20 23:28:35 -07:00
Earl Ramirez 82cf1a4467 Updated SELinux label (#5728) 2018-06-20 23:28:20 -07:00
tiagom62 3bbd49dbad Don't run composer as root (#5689)
* dont run composer as root

* better naming
2018-06-20 19:59:44 -07:00
Daniel Meltzer ad21857cae Update my email address across files. (#5716) 2018-06-20 01:59:59 -07:00
Daniel Meltzer 2d18b73138 Fix #5408. (#5715)
The temporary password cannot be added to the users data until after do
any update-related logic, otherwise their password will be overwritten.
2018-06-20 01:59:04 -07:00
Richard Ray Thomas e7bc18dad4 Fixed inconsistent or incorrect comment labels (#5691)
Accessories table was labeled 'Checked out License table' likely just a duplicate of the above comment for the actual licenses table. Very minor.
2018-06-11 20:11:58 -07:00
snipe 62e4eabab0 Fixed #5693 - don’t truncate license key 2018-06-11 18:47:02 -07:00
tiagom62 d204eebab9 Ubuntu Bionic Beaver 18.04 support (#5687)
* bionic box

* bionic beaver support
2018-06-09 11:21:28 -07:00
thelamer ed5823151b in order to avoid manual user intervention of running "php artisan migrate" before installation on a clean database pulling in migration scripts on startup if needed, also ownership of keys needs to be the docker user 2018-06-07 22:29:00 -07:00
snipe 968d7d1f11 Fixed #5354 - adds dd/mm/yyyy to localization 2018-06-05 15:30:50 -07:00
snipe 086683319a Fixed #5172 - autosum on assets detail view for components tab 2018-06-02 06:21:51 -07:00
snipe e5c1f4847d Added emoji to exempt list to match current labels 2018-06-01 13:55:39 -07:00
snipe 087e114d34 Merge branch 'develop' 2018-05-31 16:34:25 -07:00
snipe 5a6b8bb856 Make default custom field value null 2018-05-31 14:21:18 -07:00
snipe 102f567cb5 Fixed typo 2018-05-31 10:55:48 -07:00
snipe 1a64879b65 Only allow remote user settings to be saved if the app is not in demo mode 2018-05-31 10:55:48 -07:00
Brady Wetherington 4a0e5e4b88 Possible fix for oauth issue in Docker 2018-05-31 01:36:51 -07:00
snipe 01857fb056 Added created_at to file upload UI, added header/footer to files modal, fixed string for actions 2018-05-29 16:38:23 -07:00
snipe 3afe4938f9 Fixed #5616 - removed duplicate call to ekkoLightbox on asset view
It’s already in the default blade
2018-05-29 16:36:42 -07:00
snipe 9e0544e735 Fixed #5617 - incorrect url to fieldset editing 2018-05-29 10:41:46 -07:00
snipe 3993c6ad6b Use custom header color in emails 2018-05-29 10:23:39 -07:00
Brian Monroe 649563457d Added notes field to acessories and consumable checkout pages. Resolves request #5607. (#5608) 2018-05-28 14:30:18 -07:00
snipe 6ec75714f0 Force default srtorage engine 2018-05-22 14:57:05 -07:00
snipe 15916e6668 Allow checkout to non-user objects even if the object requires checkout 2018-05-21 17:35:04 -07:00
snipe 76d0562716 Added last name to expected checkin notification
For non-US countries like Germany where it’s considered rude not to include last name
2018-05-21 17:34:27 -07:00
snipe 2ecc4aead7 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 15:43:48 -07:00
snipe d8210847a4 Bumped version 2018-05-21 15:43:04 -07:00
snipe ece916e12f Merge branch 'develop' 2018-05-21 15:41:40 -07:00
snipe 1a29d4f60f Check for > 0 expected assets 2018-05-21 15:41:19 -07:00
snipe 11832daf5c Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 15:17:40 -07:00
snipe 79555f5647 Bumped version 2018-05-21 15:16:56 -07:00
snipe 1868013704 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-21 14:59:08 -07:00
snipe 39cc13f155 Bumped version 2018-05-21 14:58:16 -07:00
snipe e636875797 Merge branch 'develop' 2018-05-21 14:56:57 -07:00
snipe 260749337e Updated languages 2018-05-21 14:54:36 -07:00
snipe 20a3b556bb Removed log 2018-05-21 14:33:27 -07:00
Tim Bishop fa0c58e42a Don't install require-dev packages. (#5549)
This is already set for a local composer install, but not a global
one. They should be consistent.
2018-05-18 16:06:05 -07:00
snipe abbb94239d Add @doekman as a contributor 2018-05-18 16:05:12 -07:00
snipe d89ef43834 Make category counters ints 2018-05-18 16:05:12 -07:00
Doeke Zanstra f84ab2beda Fixed bug #5540 (#5550)
Error message with button "Test LDAP" is empty #5540
2018-05-18 16:04:34 -07:00
snipe 86d398abda Fixed category test 2018-05-18 14:58:06 -07:00
snipe 883c65981b More test fixes 2018-05-16 20:09:18 -07:00
snipe 8eb96efa13 Merge branch 'develop' 2018-05-16 19:38:23 -07:00
snipe e9973670ea Could should not be equal to 1 2018-05-16 19:38:02 -07:00
snipe ef8d2d06df Fixes #5519 - count() for php 7.2 2018-05-16 19:35:14 -07:00
snipe 0b5bb520a7 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-16 19:24:31 -07:00
lea-mink 233fb23cb8 Create asset maintenance - Added orange bar for required asset to edit view (#5520)
* Added orange bar for required asset to edit view

* disable redirection to asset maintenances view

* Update - disable redirection to asset maintenances view
2018-05-16 19:23:23 -07:00
snipe fa89f45cb8 Bumped hash 2018-05-16 19:22:59 -07:00
snipe 4c656c0321 De-normalize new counters from 4.3.0 (#5547)
* Added de-norm counter migration for assets

* Renaming counter columns, since Eloquent has a magical *_count helper

* Added artisan command to sync counters (one-off)

* Update API to use de-normed fields

* Increment counters for checkin;/checkout

* Derp.

* Added request increment/decrementer

* Move increment for checkout to the Asset::checkout method

* Added “could take a while” message
2018-05-16 19:20:43 -07:00
snipe 87c6ee2035 Importer test fixes 2018-05-16 19:05:00 -07:00
snipe aab190423f Partial fix for license+category tests 2018-05-16 18:35:11 -07:00
snipe 05e3e6bda6 Added indexes for speed 2018-05-14 20:27:56 -07:00
Tim Bishop 1f299ed73e Fix padding on next page with Chrome. (#5509)
It looks like Chrome (and probably other browsers) is optimising the
next-padding div out since it has no content. This means the margin
doesn't apply. Adding the nbsp, and making sure it takes up no space
itself, is enough to make the margin do the right thing.

I also tried using padding instead of margin, but this results in an
extra page at the end of the output (there'd need to be a way to stop
page-break and next-padding appearing at the end of the document). I also
tried setting a min-height on next-padding, but this had the same issue.
2018-05-09 17:00:36 -07:00
snipe 4ba9792fbe Merge branch 'develop' 2018-05-09 15:29:59 -07:00
snipe f405511b6b Fixed #5501 - regression disallowing license files to be downloaded 2018-05-08 14:24:51 -07:00
snipe 8ad5eb3e59 Fixed #5500 - present() on correct location value 2018-05-08 09:21:43 -07:00
snipe 3df8fa99f0 Merge branch 'develop' 2018-05-08 07:37:44 -07:00
snipe cca97341e9 More flexible date range in datepicker for expected checkin filter on custom reports 2018-05-08 07:37:18 -07:00
snipe 13195d06fd Fixed #5491 - added default location filter for custom report 2018-05-08 07:34:14 -07:00
snipe 9b6e86b55c Merge branch 'develop' 2018-05-08 05:59:54 -07:00
snipe 65cf7527b0 Added model name to expected checkin reminders 2018-05-08 05:59:34 -07:00
snipe f74d50439c Merge branch 'develop' 2018-05-08 05:39:28 -07:00
snipe 28e7ca5a84 Formatting tweaks to email notifications 2018-05-08 05:39:11 -07:00
snipe 8f64da5bc7 Added admin alert on expiring notifications 2018-05-08 05:27:03 -07:00
snipe 25f537e730 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-08 03:49:28 -07:00
snipe 6d8af3d9c0 Bumped hash 2018-05-08 03:48:35 -07:00
snipe e56a46882d Include EULA/acceptance in license interfaces 2018-05-08 03:47:28 -07:00
snipe 0476ffecdb Removed debugging comments 2018-05-08 03:46:48 -07:00
snipe 47a0400a72 Fixed comment typo 2018-05-08 03:14:25 -07:00
snipe de3417d557 Added link to SnipeitPS powershell wrapper 2018-05-08 01:34:53 -07:00
snipe 83b546c1c5 Disable privacy policy link in footer if in demo mode 2018-05-08 01:07:15 -07:00
snipe 04709dc1df Fixed #5477 - added GDPR privacy policy link in email and webpage 2018-05-08 00:50:13 -07:00
snipe f48171dcab Add category to licenses 2018-05-08 00:14:38 -07:00
snipe 7b8362b64c Added license categories 2018-05-04 21:01:38 -07:00
snipe 188538651a Fixed slack notification error if location is not set on checkin 2018-05-04 21:01:25 -07:00
snipe a9fc7e04e9 Fixed php7.2 count issue 2018-05-04 21:00:58 -07:00
snipe 4812285512 Fixed #5482 - typo in custom fields info 2018-05-04 14:52:19 -07:00
snipe ec1fa8e90a Merge branch 'develop' 2018-05-03 08:06:58 -07:00
snipe 3a1b432234 Fixed #5472 - show_in_email for custom fields missing in edit field UI 2018-05-03 08:06:28 -07:00
snipe 98f853128a Merge branch 'develop' 2018-05-03 05:43:49 -07:00
snipe 276d2bc866 Fixed advanced search on model number
(RB:347)
2018-05-03 05:43:25 -07:00
snipe 0472e3a3e5 Merge branch 'develop' 2018-05-02 14:41:10 -07:00
lea-mink a0afa9f2e8 Modified the affectation of the value of the password in credential mail sent for the first user sign up (#5446)
* Modified the affectation of the value of the password

* Remove e()
2018-05-02 14:40:41 -07:00
snipe 0116fa9b95 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-05-02 14:35:16 -07:00
snipe 42f0eebf8f Bumped version 2018-05-02 14:33:01 -07:00
snipe 8194660d16 Updated languages 2018-05-02 14:32:50 -07:00
snipe 532a9ef9fe Additional languages in language selector 2018-05-02 14:20:42 -07:00
snipe 0be69f57ac Improved files display 2018-05-02 14:13:06 -07:00
snipe 97f748d58e Removed old reports methods and routes
We only use the custom asset report now
2018-05-02 03:44:31 -07:00
snipe 3662a58ad8 Removed unusued parameters in BS table formmatters 2018-05-01 21:35:07 -07:00
snipe f5bf6a0beb Added larger icon size 2018-05-01 21:34:31 -07:00
snipe 0fc0aa39b1 Use flexible language string for confirm delete 2018-05-01 21:33:51 -07:00
snipe c8cf46f62b Fixed #5431 - category widget error on dashboard 2018-05-01 21:33:03 -07:00
tiagom62 28a1960fda PHP 7.1 for all (#5456)
* PHP 7.1 for all

* fedora support. strict versioning.

* Add fedora 26 and 27
2018-05-01 21:02:14 -07:00
Brady Wetherington 44bb79c90e Fix to lockfile, update to package.json to non-vuln versions (#5460) 2018-05-01 21:01:45 -07:00
snipe 2f01bb2e63 Update issue templates 2018-05-01 21:01:27 -07:00
snipe 71708e349c PHP7.2 count fixes (#5427)
* PHP 7.2 count() fixes

* Re-enable php travis 7.2
2018-04-29 06:10:49 -07:00
tiagom62 6ad3d40216 installer automation? (#5433)
* cli

* less spaces!

* rename functions

* move progress spinner variables into progress ()

* $hosts was used in one place

* missed this one

* make testing snipeit.sh easier
2018-04-29 06:09:46 -07:00
snipe d6d498bc8f Fixes incorrect gate for “new” button for status labels on asset create/edit form 2018-04-26 16:35:08 -07:00
snipe 6df7f6d6ec Fixes wrong users index route name 2018-04-26 16:31:02 -07:00
snipe 5365182c86 Fixed advanced search on supplier, count for PHP7.2 2018-04-25 20:25:03 -07:00
snipe e5121b33e6 Shorter syntax for demo disabled 2018-04-25 19:23:03 -07:00
snipe 5fb4eacf5b Disable history importer on the demo 2018-04-25 19:18:05 -07:00
Hannah Tinkler c4c520c1a3 Fixes #4445: prevents assigned assets from being checked out in bulk checkout (#5421)
* Fixes #4445: prevents assigned assets from being checked out in bulk checkout

* Updates data attribute to more versatile 'data-asset-status-type'

* Fixes broken unit test
2018-04-25 02:39:23 -07:00
snipe 9eab9ad40d Merge branch 'develop' 2018-04-24 16:25:42 -07:00
snipe a2fef11016 Use Bootstrap Tables on custom fields screens for column selector 2018-04-24 16:25:10 -07:00
snipe 088eb3da14 Merge branch 'develop' 2018-04-24 13:24:24 -07:00
snipe a0706b8780 Added gitignore 2018-04-24 13:24:15 -07:00
snipe 0e1dfcf408 Changed directory for audits image dir 2018-04-24 13:20:15 -07:00
snipe 3ca9f5f389 Merge branch 'develop' 2018-04-24 12:49:28 -07:00
snipe 5acd225f0f Fixed #5423 - removed required text on preflight 2018-04-24 12:48:58 -07:00
snipe 1708bb5cdf Fixes #5422 - remove extension ending from uploaded file name 2018-04-24 12:47:09 -07:00
snipe 8127484081 Better error checking for private file display method 2018-04-24 03:12:30 -07:00
snipe 103c75e78c Removed max cap in image validation 2018-04-24 03:12:17 -07:00
snipe d886dcc7c3 Reset skin for demo 2018-04-24 03:00:56 -07:00
snipe ea54d73911 Merge branch 'develop' 2018-04-24 02:59:19 -07:00
snipe 1ef4cc9fc2 Fixed #4301 - added image upload to audit 2018-04-24 02:54:54 -07:00
snipe 4785db4471 Update factory format for custom fields 2018-04-23 21:24:59 -07:00
lea-mink c8cbc55b59 Bulk Checkout to Assets and Location (#5385) 2018-04-23 21:24:49 -07:00
Hannah Tinkler 8d501e1c24 Feature/custom fields default values (#5389)
* Fixes CustomFieldsetsController::fields() which I think is not used anywhere else and don't think ever worked as you can't call get() on a Collection.
Have tested extensively and doesn't seem to affect anywhere else?

* Adds default value functionality

* Adds built assets

* Fixes assignment to asset_model_id which should have been evaluation and alters route so it sits more in line with existing work

* Updates built assets

* Remove silly docker.env file; fix Dockerfile to preserve Oauth keys (#5377)

* Added department to custom asset export
Updates build assets

* Adds translation support for 'add default values' checkbox label
2018-04-23 21:16:55 -07:00
snipe 132a5d424d Check for valid accessory category 2018-04-23 16:04:01 -07:00
snipe 4c5f20fde4 Merge branch 'develop'
# Conflicts:
#	app/Importer/Importer.php
#	config/version.php
2018-04-23 13:33:34 -07:00
snipe 35a20fb197 Fixes #5410 - missing subject string for license delivery 2018-04-23 13:30:32 -07:00
snipe 63848e8fc2 Additional rules for non-green skins 2018-04-23 13:30:32 -07:00
lea-mink 16a7409ce1 Matched Asset currency and Asset Maintenance currency in Asset Maintenance view (#5386)
* Matched Asset currency and Asset Maintenance currency in Asset Maintenance editing view

* Cleaning code & add condition on currency of an asset location
2018-04-20 14:03:31 -07:00
Stephen c23955d0b5 Allow setting of "ldap_import" through the API (#5218)
* Allow setting of "ldap_import" through the API, this will allow cusom scripts to be made to import data from Active directory using the API, this would allow any field to be filled such as the manager (based on the ID), department etc.

* Password fix for LDAP through API
2018-04-20 14:02:52 -07:00
Tim F 18ef355d2a Updated rules (#5325)
Added .modal-content, as I either forgot it previously or an update started calling it for popups on the New Asset pages.

Brought .main-header, .navbar, .main-header, and .logo into the fold with the variables -- good addition there :)
2018-04-20 13:56:45 -07:00
snipe def1eb0b5f Bumped hash 2018-04-20 13:52:32 -07:00
snipe bdbc189a4f Bumped hash 2018-04-20 13:51:54 -07:00
snipe 6efe9efab8 Fixes #5393 - added notes to suppliers API (#5400) 2018-04-19 18:28:22 -07:00
Daniel Meltzer 7b72dde222 Another importer fix. (#5383)
* Fix condition where matching user fails when providing a username but no full name.  Also shortcircuit username matching if a user exists.

* Simplify Logic

If the user provided is numeric, but doesn't exist in the database, assume that the user's name is a number and go through all relevant generation.  of email/first+last names.  Alternatively we may want to abort or remove the is_numeric bits.. it seems a little counterintuitive
2018-04-18 07:58:26 -07:00
snipe 5948a0b235 Added department to custom asset export 2018-04-16 20:10:38 -07:00
Brady Wetherington a6fc7ba07a Remove silly docker.env file; fix Dockerfile to preserve Oauth keys (#5377) 2018-04-16 16:29:42 -07:00
snipe a326adc863 Add @hannahtinkler as a contributor 2018-04-13 14:55:11 -07:00
snipe ab31c633d0 Possible fix for #5211 2018-04-13 14:55:11 -07:00
Hannah Tinkler 48254a93f0 Fixes #5338 - mark required setup fields during setup (#5359) 2018-04-13 14:52:32 -07:00
snipe 365c8c18d7 Fixed #5319 - signature pad too small on mobile 2018-04-06 19:19:31 -07:00
snipe bbc0695a8f Added count of checkins, checkouts, requests (#5314)
* Added count of checkins, checkouts, requests

* Removed old commented items

* Use actionlog instead of redefining the relationship
2018-04-06 16:23:39 -07:00
snipe 1d0f8f01f2 Fixed #5266 - small scroll window for EULA accept screen 2018-04-06 15:50:45 -07:00
snipe 2253439940 Added default location/address to custom report 2018-04-05 17:33:25 -07:00
snipe c1838a60df Bumped hash 2018-04-04 17:36:47 -07:00
snipe 8a6713d5c0 WIP - Improved requested assets (#5289)
* WIP - beginning of improved requested assets

- Use Ajax tables for faster loading
- Use new notifications for requesting an asset

TODO:
- Use ajax tables for requestable asset models
- Use new notifications for canceling an asset request
- Expire requests once the asset has been checked out to the requesting user

* Only show asset name in email if it has one

* Refactor requested method to only include non-canceled requests

* Refactored requestable assets to log request and cancelation

* Added softdeletes on checkout requests

* Differentiate between canceling and deleting requests

* Added asset request cancelation notification

* Added timestamps and corrected unique key on requests table

* Improved requests view

* Re-use blade for cancel/request email

* Refactored BS table formatter for requested assets

* Location name min reduced to 2

* Added PAT test as maintenance option

This needs to be refactored into database-driven options with a UI

* Better slack message

* Added getImageUrl method for assets

* Include qty in request notifications

TODO:
- Try to pull requested info from original request for cancelation, otherwise it will default to 1

* Removed old asset request/cancel emails

* Added user profile asset request routes

* Added profile controller requested assets method

* Added blade link to requested assets for profile view

* Sort user history desc

* Added requested assets blade

* Added canceled at to checkoutRequest method

* Include qty in request

* Fixed comment, removed allowed_columns

* Removed Queable methods, since we don’t use a queue

* Fixed return type in method doc

* Fixed version number

* Changed id to user_id for clarity
2018-04-04 17:33:02 -07:00
snipe 201efecafa Fixed #5293 - component category drilldown 2018-04-02 16:12:19 -07:00
Daniel Meltzer 79f061be93 Move first_name and last_name to only be displayed for user importer. Also sort items alphabetically regardless of their source. (#5292) 2018-03-30 19:32:43 -07:00
snipe 4786c1c59f Check for custom fields in Importer 2018-03-30 18:50:09 -07:00
snipe 116cad88a0 Fixed #5279 - [regression] edit button not appearing in asset view 2018-03-29 11:28:37 -07:00
snipe 7d1200c434 Add @lea-mink as a contributor 2018-03-29 05:32:52 -07:00
lea-mink 99a9707a34 Add title field in Asset Maintenances list/filter/export (#5287) 2018-03-29 05:32:09 -07:00
Daniel Meltzer 787f2390fb Add location_id to fillable (#5286)
Should fix #5268
2018-03-29 05:11:07 -07:00
snipe a510ac4052 Fixed #5272 - make city min length 2 instead of 3 2018-03-29 04:36:18 -07:00
snipe 9f414baa99 Remove notifiable from asset 2018-03-28 19:08:08 -07:00
snipe 086711e467 Fixed checkout to checkin in loggable checkin trait 2018-03-28 19:01:51 -07:00
Daniel Meltzer bf2d6dd0fa Avoid populating db manually. (#5255)
* Avoid populating db manually.  Instead rely on a seeded database existing and use api/fucntional tests based on that.

* Seed the Setting object with default values.

* Update Setting seeder to match web default.  Also only generate one Setting instance.
2018-03-28 19:01:51 -07:00
Daniel Meltzer 4c43226b99 Fix #4798. (#5271)
Filter down the custom fields we are iterating over to only custom fields in the header row.
This prevents nulling out fields when performing an import-update that doesn't include
all custom fields as well as properly nulls out values between items being imported.
2018-03-28 19:01:51 -07:00
snipe dfe8aac10b Fixed checkout to checkin in loggable checkin trait 2018-03-28 19:01:10 -07:00
snipe 983786c29f Fixed component checkout bug 2018-03-28 18:53:18 -07:00
snipe edb81425cf Fixed component checkout bug (#5282) 2018-03-28 18:53:03 -07:00
Daniel Meltzer 69478aea58 Avoid populating db manually. (#5255)
* Avoid populating db manually.  Instead rely on a seeded database existing and use api/fucntional tests based on that.

* Seed the Setting object with default values.

* Update Setting seeder to match web default.  Also only generate one Setting instance.
2018-03-27 17:43:28 -07:00
Daniel Meltzer e9d9c0c42d Fix #4798. (#5271)
Filter down the custom fields we are iterating over to only custom fields in the header row.
This prevents nulling out fields when performing an import-update that doesn't include
all custom fields as well as properly nulls out values between items being imported.
2018-03-27 16:52:44 -07:00
snipe b41adc2eee Merge branch 'develop' 2018-03-26 16:05:50 -07:00
snipe 115d6e29df Added location address to custom asset report export 2018-03-26 15:59:09 -07:00
snipe 83781f3a70 Merge branch 'develop'
# Conflicts:
#	config/version.php
2018-03-26 15:20:40 -07:00
snipe 5f1ec550ec Fixed hash 2018-03-26 15:16:35 -07:00
snipe 95f8dccc14 Bumped hash 2018-03-26 15:15:43 -07:00
snipe 0134ec7b04 Added asset presenter, fixed asset maintenances button in asset view 2018-03-26 14:49:49 -07:00
snipe 590938fa33 Added asset maintenances presenter, fixed broken action buttons on asset view 2018-03-26 14:46:37 -07:00
snipe 46f5f21368 Notification improvements (#5254)
* Added “show fields in email” to custom fields

* Added “show images in email” to settings

* Added nicer HTML emails

* Break notifications out into their own, instead of trying to mash them all together

* Remove old notification for accessory checkout

* Janky fix for #5076 - “The asset you have attempted to accept was not checked out to you”

* Add method for image url for accessories

* Added accessory checkout email blade

* Make accessory email notification on checkout screen consistent with assets

* Added native consumables notifications

* Fixes for asset notification

* Updated notification blades with correct-er fields

* Updated notifications

* License checkin notification - does not work yet

Need to figure out whether the license seat is assigned to a person or an asset before we can pass the target

* Added alternate “cc” email for admins

* Only try to trigger notifications if the target is a user

* Fix tests

* Fixed consumable URL

* Removed unused notification

* Pass target type in params

* Show slack status

* Pass additional parameters

There is a logic bug in this :( Will send to slack twice, since the admin CC and the user are both using the same notification. Fuckity fuck fuck fuck.

* Pass a variable to the notification to supress the duplicate slack message

* Slack is broken :( Trying to fix

Will try a git bisect

* Put preview back into checkout

* Pulled old archaic mail

* Removed debugging

* Fixed wrong email title

* Fixed slack endpoint not firing

* Poobot, we hardly knew ye.

* Removed old, manual mail from API

* Typo :-/

* Code cleanup

* Use defined formatted date in JSON

* Use static properties for checkin/checkout notifiers for cleaner code

* Removed debugging

* Use date formatter

* Fixed target_type

* Fixed language in consumable email
2018-03-25 13:46:57 -07:00
snipe b6bf3800c7 Merge branch 'develop' 2018-03-23 16:20:09 -07:00
snipe 6043d37b05 Added the backup env flag to the example env 2018-03-23 16:19:26 -07:00
snipe 34919b0396 Added API calls to look up assets by tag and serial 2018-03-23 14:50:11 -07:00
814 changed files with 13995 additions and 7109 deletions
+81
View File
@@ -893,6 +893,87 @@
"bug",
"doc"
]
},
{
"login": "lea-mink",
"name": "lea-mink",
"avatar_url": "https://avatars2.githubusercontent.com/u/37537300?v=4",
"profile": "https://github.com/lea-mink",
"contributions": [
"code"
]
},
{
"login": "hannahtinkler",
"name": "Hannah Tinkler",
"avatar_url": "https://avatars0.githubusercontent.com/u/7140719?v=4",
"profile": "https://github.com/hannahtinkler",
"contributions": [
"code"
]
},
{
"login": "doekman",
"name": "Doeke Zanstra",
"avatar_url": "https://avatars1.githubusercontent.com/u/1086388?v=4",
"profile": "https://github.com/doekman",
"contributions": [
"code"
]
},
{
"login": "SjamonDaal",
"name": "Djamon Staal",
"avatar_url": "https://avatars1.githubusercontent.com/u/4325936?v=4",
"profile": "https://www.sdhd.nl/",
"contributions": [
"code"
]
},
{
"login": "EarlRamirez",
"name": "Earl Ramirez",
"avatar_url": "https://avatars3.githubusercontent.com/u/12306859?v=4",
"profile": "https://github.com/EarlRamirez",
"contributions": [
"code"
]
},
{
"login": "RichardRay",
"name": "Richard Ray Thomas",
"avatar_url": "https://avatars2.githubusercontent.com/u/8671456?v=4",
"profile": "https://github.com/RichardRay",
"contributions": [
"code"
]
},
{
"login": "thelamer",
"name": "Ryan Kuba",
"avatar_url": "https://avatars3.githubusercontent.com/u/1852688?v=4",
"profile": "https://www.taisun.io/",
"contributions": [
"code"
]
},
{
"login": "ParadoxGuitarist",
"name": "Brian Monroe",
"avatar_url": "https://avatars1.githubusercontent.com/u/6751928?v=4",
"profile": "https://github.com/ParadoxGuitarist",
"contributions": [
"code"
]
},
{
"login": "plexorama",
"name": "plexorama",
"avatar_url": "https://avatars1.githubusercontent.com/u/605167?v=4",
"profile": "https://github.com/plexorama",
"contributions": [
"code"
]
}
]
}
+1
View File
@@ -7,6 +7,7 @@ APP_KEY=ChangeMe
APP_URL=null
APP_TIMEZONE='UTC'
APP_LOCALE=en
BACKUP_ENV=false
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
+61
View File
@@ -0,0 +1,61 @@
---
name: Bug report
about: Create a report to help us improve
---
#### Please confirm you have done the following before posting your bug report:
- [ ] I have enabled debug mode
- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Error Messages**
- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
- If a stacktrace is provided in the error, include that too.
- Any errors that appear in your browser's error console.
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `storage/logs` and your webserver's logs.
**Additional context**
- Is this a fresh install or an upgrade?
- What OS and web server you're running Snipe-IT on
- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
- Include what you've done so far in the installation, and if you got any error messages along the way.
- Indicate whether or not you've manually edited any data directly in the database
Add any other context about the problem here.
Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.
+23
View File
@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+4
View File
@@ -9,6 +9,10 @@ exemptLabels:
- :woman_technologist: ready for dev
- :moneybag: bounty
- :hand: bug
- "🔐 security"
- "👩‍💻 ready for dev"
- "💰 bounty"
- "✋ bug"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
+1 -1
View File
@@ -16,7 +16,7 @@ services:
php:
- 5.6
- 7.0
# - 7.2 DISABLE Temporarily until we fix the count(null) bugs
- 7.2
- 7.1.4
# execute any number of scripts before the test run, custom env's are available as variables
+5 -1
View File
@@ -18,6 +18,7 @@ patch \
curl \
vim \
git \
cron \
mysql-client \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -64,7 +65,10 @@ RUN chown -R docker /var/www/html
RUN \
rm -r "/var/www/html/storage/private_uploads" && ln -fs "/var/lib/snipeit/data/private_uploads" "/var/www/html/storage/private_uploads" \
&& rm -rf "/var/www/html/public/uploads" && ln -fs "/var/lib/snipeit/data/uploads" "/var/www/html/public/uploads" \
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups"
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
&& mkdir "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
&& chown docker "/var/lib/snipeit/keys/"
############## DEPENDENCIES via COMPOSER ###################
+5 -3
View File
@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeyhead.svg?style=social)](https://twitter.com/snipeyhead) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-96-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-105-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
## Snipe-IT - Open Source Asset Management System
@@ -57,6 +57,7 @@ Since the release of the JSON REST API, several third-party developers have been
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
@@ -81,7 +82,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars2.githubusercontent.com/u/16108587?v=3" width="110px;"/><br /><sub>Jason Spriggs</sub>](http://jasonspriggs.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [<img src="https://avatars2.githubusercontent.com/u/1134568?v=3" width="110px;"/><br /><sub>Nate Felton</sub>](http://n8felton.wordpress.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [<img src="https://avatars2.githubusercontent.com/u/14036694?v=3" width="110px;"/><br /><sub>Manasses Ferreira</sub>](http://homepages.dcc.ufmg.br/~manassesferreira)<br />[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [<img src="https://avatars0.githubusercontent.com/u/15913949?v=3" width="110px;"/><br /><sub>Steve</sub>](https://github.com/steveelwood)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [<img src="https://avatars1.githubusercontent.com/u/3361683?v=3" width="110px;"/><br /><sub>matc</sub>](http://twitter.com/matc)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [<img src="https://avatars3.githubusercontent.com/u/7405702?v=3" width="110px;"/><br /><sub>Cole R. Davis</sub>](http://www.davisracingteam.com)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [<img src="https://avatars2.githubusercontent.com/u/10167681?v=3" width="110px;"/><br /><sub>gibsonjoshua55</sub>](https://github.com/gibsonjoshua55)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/2809241?v=4" width="110px;"/><br /><sub>Robin Temme</sub>](https://github.com/zwerch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [<img src="https://avatars0.githubusercontent.com/u/6961695?v=4" width="110px;"/><br /><sub>Iman</sub>](https://github.com/imanghafoori1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") |
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/37537300?v=4" width="110px;"/><br /><sub>lea-mink</sub>](https://github.com/lea-mink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [<img src="https://avatars0.githubusercontent.com/u/7140719?v=4" width="110px;"/><br /><sub>Hannah Tinkler</sub>](https://github.com/hannahtinkler)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1086388?v=4" width="110px;"/><br /><sub>Doeke Zanstra</sub>](https://github.com/doekman)<br />[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [<img src="https://avatars1.githubusercontent.com/u/4325936?v=4" width="110px;"/><br /><sub>Djamon Staal</sub>](https://www.sdhd.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [<img src="https://avatars3.githubusercontent.com/u/12306859?v=4" width="110px;"/><br /><sub>Earl Ramirez</sub>](https://github.com/EarlRamirez)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [<img src="https://avatars2.githubusercontent.com/u/8671456?v=4" width="110px;"/><br /><sub>Richard Ray Thomas</sub>](https://github.com/RichardRay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [<img src="https://avatars3.githubusercontent.com/u/1852688?v=4" width="110px;"/><br /><sub>Ryan Kuba</sub>](https://www.taisun.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [<img src="https://avatars1.githubusercontent.com/u/6751928?v=4" width="110px;"/><br /><sub>Brian Monroe</sub>](https://github.com/ParadoxGuitarist)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [<img src="https://avatars1.githubusercontent.com/u/605167?v=4" width="110px;"/><br /><sub>plexorama</sub>](https://github.com/plexorama)<br />[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
Vendored
+84
View File
@@ -0,0 +1,84 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
Vagrant.configure("2") do |config|
config.vm.define "bionic" do |bionic|
bionic.vm.box = "ubuntu/bionic64"
bionic.vm.hostname = 'bionic'
bionic.vm.network "public_network", bridge: NETWORK_BRIDGE
bionic.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
bionic.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "xenial" do |xenial|
xenial.vm.box = "ubuntu/xenial64"
xenial.vm.hostname = 'xenial'
xenial.vm.network "public_network", bridge: NETWORK_BRIDGE
xenial.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
xenial.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "trusty" do |trusty|
trusty.vm.box = "ubuntu/trusty32"
trusty.vm.hostname = 'trusty'
trusty.vm.network "public_network", bridge: NETWORK_BRIDGE
trusty.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
trusty.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "centos7" do |centos7|
centos7.vm.box = "centos/7"
centos7.vm.hostname = 'centos7'
centos7.vm.network "public_network", bridge: NETWORK_BRIDGE
centos7.vm.provision :shell, :inline => "sudo yum -y update"
centos7.vm.provision :shell, :inline => "yum install -y wget"
centos7.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
centos7.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "centos6" do |centos6|
centos6.vm.box = "centos/6"
centos6.vm.hostname = 'centos6'
centos6.vm.network "public_network", bridge: NETWORK_BRIDGE
centos6.vm.provision :shell, :inline => "sudo yum -y update"
centos6.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
centos6.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "jessie" do |jessie|
jessie.vm.box = "debian/jessie64"
jessie.vm.hostname = 'debian8'
jessie.vm.network "public_network", bridge: NETWORK_BRIDGE
jessie.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
jessie.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "stretch" do |stretch|
stretch.vm.box = "debian/stretch64"
stretch.vm.hostname = 'debian9'
stretch.vm.network "public_network", bridge: NETWORK_BRIDGE
stretch.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
stretch.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "fedora27" do |fedora27|
fedora27.vm.box = "fedora/27-cloud-base"
fedora27.vm.hostname = 'fedora27'
fedora27.vm.network "public_network", bridge: NETWORK_BRIDGE
fedora27.vm.provision :shell, :inline => "dnf -y install wget"
fedora27.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
fedora27.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
config.vm.define "fedora26" do |fedora26|
fedora26.vm.box = "fedora/26-cloud-base"
fedora26.vm.hostname = 'fedora26'
fedora26.vm.network "public_network", bridge: NETWORK_BRIDGE
fedora26.vm.provision :shell, :inline => "dnf -y install wget"
fedora26.vm.provision :shell, :inline => "wget #{SNIPEIT_SH_URL}"
fedora26.vm.provision :shell, :inline => "chmod 755 snipeit.sh"
end
end
@@ -55,6 +55,7 @@ class ResetDemoSettings extends Command
$settings->ldap_enabled = 0;
$settings->full_multiple_companies_support = 1;
$settings->alt_barcode = 'C128';
$settings->skin = '';
$settings->email_domain = 'snipeitapp.com';
$settings->email_format = 'filastname';
$settings->username_format = 'filastname';
@@ -4,8 +4,10 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Console\Command;
use App\Notifications\ExpectedCheckinNotification;
use App\Notifications\ExpectedCheckinAdminNotification;
use Carbon\Carbon;
class SendExpectedCheckinAlerts extends Command
@@ -42,21 +44,28 @@ class SendExpectedCheckinAlerts extends Command
*/
public function fire()
{
$settings = Setting::getSettings();
$whenNotify = Carbon::now()->addDays(7);
$assets = Asset::with('assignedTo')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$assets = Asset::with('assignedTo')->whereNotNull('assigned_to')->whereNotNull('expected_checkin')->where('expected_checkin', '<=', $whenNotify)->get();
$this->info($whenNotify.' is deadline');
$this->info($assets->count().' assets');
foreach ($assets as $asset) {
if ($asset->assignedTo && $asset->checkoutOutToUser()) {
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
//$this->info($asset);
if ($asset->assigned && $asset->checkedOutToUser()) {
$asset->assigned->notify((new ExpectedCheckinNotification($asset)));
}
}
// Send a rollup to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AlertRecipient();
if (($assets) && ($assets->count() > 0) && ($settings->alert_email!='')) {
$recipient->notify(new ExpectedCheckinAdminNotification($assets));
}
@@ -77,7 +77,7 @@ class SendExpirationAlerts extends Command
$this->info(count($expiring_licenses).' expiring licenses');
$license_data['count'] = count($expiring_licenses);
$license_data['count'] = $expiring_licenses->count();
$license_data['email_content'] = '';
foreach ($expiring_licenses as $license) {
@@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Asset;
class SyncAssetCounters extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:counter-sync';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Syncs checkedout, checked in, and requested counters for assets';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start = microtime(true);
$assets = Asset::withCount('checkins', 'checkouts', 'userRequests')
->withTrashed()->get();
if ($assets) {
if ($assets->count() > 0) {
$bar = $this->output->createProgressBar($assets->count());
foreach ($assets as $asset) {
$asset->checkin_counter = (int) $asset->checkins_count;
$asset->checkout_counter = (int) $asset->checkouts_count;
$asset->requests_counter = (int) $asset->user_requests_count;
$asset->unsetEventDispatcher();
$asset->save();
$output['info'][] = 'Asset: ' . $asset->id . ' has ' . $asset->checkin_counter . ' checkins, ' . $asset->checkout_counter . ' checkouts, and ' . $asset->requests_counter . ' requests';
$bar->advance();
}
$bar->finish();
foreach ($output['info'] as $key => $output_text) {
$this->info($output_text);
}
$time_elapsed_secs = microtime(true) - $start;
$this->info('Sync executed in ' . $time_elapsed_secs . ' seconds');
} else {
$this->info('No assets to sync');
}
}
}
}
+1
View File
@@ -29,6 +29,7 @@ class Kernel extends ConsoleKernel
Commands\ResetDemoSettings::class,
Commands\SyncAssetLocations::class,
Commands\RegenerateAssetTags::class,
Commands\SyncAssetCounters::class,
];
/**
+71 -2
View File
@@ -330,7 +330,14 @@ class Helper
*/
public static function categoryTypeList()
{
$category_types = array('' => '','accessory' => 'Accessory', 'asset' => 'Asset', 'consumable' => 'Consumable','component' => 'Component');
$category_types = array(
'' => '',
'accessory' => 'Accessory',
'asset' => 'Asset',
'consumable' => 'Consumable',
'component' => 'Component',
'license' => 'License'
);
return $category_types;
}
@@ -406,7 +413,7 @@ class Helper
{
$keys = array_keys(CustomField::$PredefinedFormats);
$stuff = array_combine($keys, $keys);
return $stuff+["" => trans('admin/custom_fields/general.custom_format')];
return $stuff;
}
/**
@@ -738,6 +745,7 @@ class Helper
static $max_size = -1;
if ($max_size < 0) {
// Start with post_max_size.
$post_max_size = Helper::parse_size(ini_get('post_max_size'));
if ($post_max_size > 0) {
@@ -751,6 +759,7 @@ class Helper
$max_size = $upload_max;
}
}
return $max_size;
}
@@ -787,6 +796,66 @@ class Helper
}
public static function filetype_icon($filename) {
$extension = substr(strrchr($filename,'.'),1);
if ($extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
case 'gif':
case 'png':
return "fa fa-file-image-o";
break;
case 'doc':
case 'docx':
return "fa fa-file-word-o";
break;
case 'xls':
case 'xlsx':
return "fa fa-file-excel-o";
break;
case 'zip':
case 'rar':
return "fa fa-file-archive-o";
break;
case 'pdf':
return "fa fa-file-pdf-o";
break;
case 'txt':
return "fa fa-file-text-o";
break;
case 'lic':
return "fa fa-floppy-o";
break;
default:
return "fa fa-file-o";
}
}
return "fa fa-file-o";
}
public static function show_file_inline($filename) {
$extension = substr(strrchr($filename,'.'),1);
if ($extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
case 'gif':
case 'png':
return true;
break;
default:
return false;
}
}
return false;
}
}
+10 -23
View File
@@ -12,7 +12,6 @@ use DB;
use Gate;
use Input;
use Lang;
use Mail;
use Redirect;
use Illuminate\Http\Request;
use Slack;
@@ -259,10 +258,17 @@ class AccessoriesController extends Controller
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize('checkout', $accessory);
if ($accessory->category) {
$this->authorize('checkout', $accessory);
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'));
}
return redirect()->back()->with('error', 'The category type for this accessory is not valid. Edit the accessory and select a valid accessory category.');
// Get the dropdown of users and then pass it to the checkout view
return view('accessories/checkout', compact('accessory'));
}
@@ -313,16 +319,6 @@ class AccessoriesController extends Controller
$data['expected_checkin'] = '';
$data['note'] = $logaction->note;
$data['require_acceptance'] = $accessory->requireAcceptance();
// TODO: Port this to new mail notifications
if ((($accessory->requireAcceptance()=='1') || ($accessory->getEula())) && ($user->email!='')) {
Mail::send('emails.accept-accessory', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_accessory_delivery'));
});
}
// Redirect to the new accessory page
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
@@ -393,15 +389,6 @@ class AccessoriesController extends Controller
$data['item_tag'] = '';
$data['note'] = e($logaction->note);
if ((($accessory->checkin_email()=='1')) && ($user->email!='')) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_Accessory_Checkin'));
});
}
if ($backto=='user') {
return redirect()->route("users.show", $return_to)->with('success', trans('admin/accessories/message.checkin.success'));
}
+101 -32
View File
@@ -77,12 +77,15 @@ class AssetsController extends Controller
'last_audit_date',
'next_audit_date',
'warranty_months',
'checkout_counter',
'checkin_counter',
'requests_counter',
];
$filter = array();
if ($request->has('filter')) {
$filter = json_decode($request->input('filter'));
$filter = json_decode($request->input('filter'), true);
}
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
@@ -95,10 +98,6 @@ class AssetsController extends Controller
'model.category', 'model.manufacturer', 'model.fieldset','supplier');
// These are used by the API to query against specific ID numbers.
// They are also used by the individual searches on detail pages like
// locations, etc.
@@ -106,6 +105,10 @@ class AssetsController extends Controller
$assets->where('assets.status_id', '=', $request->input('status_id'));
}
if ($request->input('requestable')=='true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->has('model_id')) {
$assets->InModelList([$request->input('model_id')]);
}
@@ -116,7 +119,6 @@ class AssetsController extends Controller
if ($request->has('location_id')) {
$assets->where('assets.location_id', '=', $request->input('location_id'));
// dd($assets->toSql());
}
if ($request->has('supplier_id')) {
@@ -215,7 +217,8 @@ class AssetsController extends Controller
}
if (count($filter) > 0) {
if ((!is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->has('search')) {
$assets->TextSearch($request->input('search'));
@@ -274,6 +277,44 @@ class AssetsController extends Controller
}
/**
* Returns JSON with information about an asset (by tag) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $tag
* @since [v4.2.1]
* @return JsonResponse
*/
public function showByTag($tag)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->where('asset_tag',$tag)->first()) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 404);
}
/**
* Returns JSON with information about an asset (by serial) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $serial
* @since [v4.2.1]
* @return JsonResponse
*/
public function showBySerial($serial)
{
if ($assets = Asset::with('assetstatus')->with('assignedTo')
->withTrashed()->where('serial',$serial)->get()) {
$this->authorize('view', $assets);
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 404);
}
/**
* Returns JSON with information about an asset for detail view.
*
@@ -284,11 +325,12 @@ class AssetsController extends Controller
*/
public function show($id)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->findOrFail($id)) {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()->withCount('checkins', 'checkouts', 'userRequests')->findOrFail($id)) {
$this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset);
}
}
@@ -313,6 +355,9 @@ class AssetsController extends Controller
'assets.status_id'
])->with('model', 'assetstatus', 'assignedTo')->NotArchived());
if ($request->has('assetStatusType') && $request->input('assetStatusType') === 'RTD') {
$assets = $assets->RTD();
}
if ($request->has('search')) {
$assets = $assets->AssignedSearch($request->input('search'));
@@ -629,7 +674,7 @@ class AssetsController extends Controller
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = e(Input::get('name'));
$asset->name = Input::get('name');
$asset->location_id = $asset->rtd_location_id;
if ($request->has('location_id')) {
@@ -637,32 +682,11 @@ class AssetsController extends Controller
}
if (Input::has('status_id')) {
$asset->status_id = e(Input::get('status_id'));
$asset->status_id = Input::get('status_id');
}
// Was the asset updated?
if ($asset->save()) {
$logaction = $asset->logCheckin($target, e(request('note')));
$data['log_id'] = $logaction->id;
$data['first_name'] = get_class($target) == User::class ? $target->first_name : '';
$data['item_name'] = $asset->present()->name();
$data['checkin_date'] = $logaction->created_at;
$data['item_tag'] = $asset->asset_tag;
$data['item_serial'] = $asset->serial;
$data['note'] = $logaction->note;
$data['manufacturer_name'] = $asset->model->manufacturer->name;
$data['model_name'] = $asset->model->name;
$data['model_number'] = $asset->model->model_number;
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!config('app.lock_passwords'))) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_Asset_Checkin'));
});
}
$asset->logCheckin($target, e(request('note')));
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
}
@@ -718,5 +742,50 @@ class AssetsController extends Controller
}
/**
* Returns JSON listing of all requestable assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
* @return JsonResponse
*/
public function requestable(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select('assets.*'),"company_id","assets")
->with('location', 'assetstatus', 'assetlog', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier')->where('assets.requestable', '=', '1');
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$assets->TextSearch($request->input('search'));
switch ($request->input('sort')) {
case 'model':
$assets->OrderModels($order);
break;
case 'model_number':
$assets->OrderModelNumber($order);
break;
case 'category':
$assets->OrderCategory($order);
break;
case 'manufacturer':
$assets->OrderManufacturer($order);
break;
default:
$assets->orderBy('assets.created_at', $order);
break;
}
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
}
}
@@ -21,10 +21,10 @@ class CategoriesController extends Controller
public function index(Request $request)
{
$this->authorize('view', Category::class);
$allowed_columns = ['id', 'name','category_type', 'category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count', 'image'];
$allowed_columns = ['id', 'name','category_type', 'category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email', 'assets_count', 'accessories_count', 'consumables_count', 'components_count','licenses_count', 'image'];
$categories = Category::select(['id', 'created_at', 'updated_at', 'name','category_type','use_default_eula','eula_text', 'require_acceptance','checkin_email','image'])
->withCount('assets', 'accessories', 'consumables', 'components');
->withCount('assets', 'accessories', 'consumables', 'components','licenses');
if ($request->has('search')) {
$categories = $categories->TextSearch($request->input('search'));
@@ -35,6 +35,10 @@ class ComponentsController extends Controller
$components->where('company_id','=',$request->input('company_id'));
}
if ($request->has('category_id')) {
$components->where('category_id','=',$request->input('category_id'));
}
$offset = request('offset', 0);
$limit = request('limit', 50);
@@ -179,7 +179,7 @@ class ConsumablesController extends Controller
foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'created_at' => ($consumable_assignment->created_at->format('Y-m-d H:i:s')=='-0001-11-30 00:00:00') ? '' : $consumable_assignment->created_at->format('Y-m-d H:i:s'),
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : '',
];
}
@@ -26,9 +26,7 @@ class CustomFieldsController extends Controller
{
$this->authorize('index', CustomFields::class);
$fields = CustomField::get();
$total = count($fields);
return (new CustomFieldsTransformer)->transformCustomFields($fields, $total);
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
}
/**
@@ -44,9 +44,7 @@ class CustomFieldsetsController extends Controller
{
$this->authorize('index', CustomFieldset::class);
$fieldsets = CustomFieldset::withCount(['fields', 'models'])->get();
$total = count($fieldsets);
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $total);
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
}
@@ -156,8 +154,26 @@ class CustomFieldsetsController extends Controller
{
$this->authorize('view', CustomFieldset::class);
$set = CustomFieldset::findOrFail($id);
$fields = $set->fields->get();
$fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFields($fields, $fields->count());
}
/**
* Return JSON containing a list of fields belonging to a fieldset with the
* default values for a given model
*
* @param $modelId
* @param $fieldsetId
* @return string JSON
*/
public function fieldsWithDefaultValues($fieldsetId, $modelId)
{
$this->authorize('view', CustomFieldset::class);
$set = CustomFieldset::findOrFail($fieldsetId);
$fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
}
}
@@ -25,7 +25,7 @@ class LicensesController extends Controller
public function index(Request $request)
{
$this->authorize('view', License::class);
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier')->withCount('freeSeats'));
$licenses = Company::scopeCompanyables(License::with('company', 'manufacturer', 'freeSeats', 'supplier','category')->withCount('freeSeats'));
if ($request->has('company_id')) {
@@ -64,6 +64,10 @@ class LicensesController extends Controller
$licenses->where('supplier_id','=',$request->input('supplier_id'));
}
if ($request->has('category_i')) {
$licenses->where('category_i','=',$request->input('category_i'));
}
if ($request->has('depreciation_id')) {
$licenses->where('depreciation_id','=',$request->input('depreciation_id'));
}
@@ -90,11 +94,14 @@ class LicensesController extends Controller
case 'supplier':
$licenses = $licenses->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
break;
case 'category':
$licenses = $licenses->leftJoin('categories', 'licenses.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
break;
case 'company':
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break;
default:
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','license_name','license_email','free_seats_count','seats'];
$allowed_columns = ['id','name','purchase_cost','expiration_date','purchase_order','order_number','notes','purchase_date','serial','company','category','license_name','license_email','free_seats_count','seats'];
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$licenses = $licenses->orderBy($sort, $order);
break;
@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\CheckoutRequest;
use App\Http\Controllers\Controller;
use Auth;
use App\Helpers\Helper;
class ProfileController extends Controller
{
/**
* Display a listing of requested assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.3.0]
*
* @return Array
*/
public function requestedAssets()
{
$checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
$results = [];
$results['total'] = $checkoutRequests->count();
foreach ($checkoutRequests as $checkoutRequest) {
$results['rows'][] = [
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
'name' => $checkoutRequest->itemRequested()->present()->name(),
'type' => $checkoutRequest->itemType(),
'qty' => $checkoutRequest->quantity,
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
}
return $results;
}
}
@@ -28,7 +28,6 @@ class ReportsController extends Controller
if (($request->has('target_type')) && ($request->has('target_id'))) {
$actionlogs = $actionlogs->where('target_id','=',$request->input('target_id'))
->where('target_type','=',"App\\Models\\".ucwords($request->input('target_type')));
}
if (($request->has('item_type')) && ($request->has('item_id'))) {
@@ -40,6 +39,10 @@ class ReportsController extends Controller
$actionlogs = $actionlogs->where('action_type','=',$request->input('action_type'))->orderBy('created_at', 'desc');
}
if ($request->has('uploads')) {
$actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc');
}
$allowed_columns = [
'id',
'created_at',
@@ -134,12 +134,6 @@ class SettingsController extends Controller
if (!config('app.lock_passwords')) {
try {
Notification::send(Setting::first(), new MailTest());
/*Mail::send('emails.test', [], function ($m) {
$m->to(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.test_email'));
});*/
return response()->json(['message' => 'Mail sent to '.config('mail.reply_to.address')], 200);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 500);
@@ -25,7 +25,7 @@ class SuppliersController extends Controller
$allowed_columns = ['id','name','address','phone','contact','fax','email','image','assets_count','licenses_count', 'accessories_count'];
$suppliers = Supplier::select(
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image')
array('id','name','address','address2','city','state','country','fax', 'phone','email','contact','created_at','updated_at','deleted_at','image','notes')
)->withCount('assets')->withCount('licenses')->withCount('accessories')->whereNull('deleted_at');
+16 -1
View File
@@ -194,7 +194,9 @@ class UsersController extends Controller
$this->authorize('view', User::class);
$user = new User;
$user->fill($request->all());
$user->password = bcrypt($request->input('password'));
$tmp_pass = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 20);
$user->password = bcrypt($request->get('password', $tmp_pass));
if ($user->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.create')));
@@ -318,4 +320,17 @@ class UsersController extends Controller
return response()->json(['message' => 'No ID provided'], 500);
}
/**
* Get info on the current user.
*
* @author [Juan Font] [<juanfontalonso@gmail.com>]
* @since [v4.4.2]
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function getCurrentUserInfo(Request $request)
{
return response()->json($request->user());
}
}
@@ -113,7 +113,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(e($request->input('asset_id')));
if (!Company::isCurrentUserHasAccess($asset)) {
if ((!Company::isCurrentUserHasAccess($asset)) && ($asset!=null)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -110,6 +110,10 @@ class AssetModelsController extends Controller
// Was it created?
if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) {
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
}
// Redirect to the new model page
return redirect()->route("models.index")->with('success', trans('admin/models/message.create.success'));
}
@@ -206,10 +210,16 @@ class AssetModelsController extends Controller
$model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0');
$this->removeCustomFieldsDefaultValues($model);
if ($request->input('custom_fieldset')=='') {
$model->fieldset_id = null;
} else {
$model->fieldset_id = $request->input('custom_fieldset');
if ($this->shouldAddDefaultValues($request->input())) {
$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'));
}
}
$old_image = $model->image;
@@ -531,4 +541,43 @@ class AssetModelsController extends Controller
}
/**
* Returns true if a fieldset is set, 'add default values' is ticked and if
* any default values were entered into the form.
*
* @param array $input
* @return boolean
*/
private function shouldAddDefaultValues(array $input)
{
return !empty($input['add_default_values'])
&& !empty($input['default_values'])
&& !empty($input['custom_fieldset']);
}
/**
* Adds default values to a model (as long as they are truthy)
*
* @param AssetModel $model
* @param array $defaultValues
* @return void
*/
private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues)
{
foreach ($defaultValues as $customFieldId => $defaultValue) {
if ($defaultValue) {
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
}
}
}
/**
* Removes all default values
*
* @return void
*/
private function removeCustomFieldsDefaultValues(AssetModel $model)
{
$model->defaultValues()->detach();
}
}
+78 -45
View File
@@ -302,8 +302,13 @@ class AssetsController extends Controller
if ($request->has('image_delete')) {
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
try {
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::error($e);
}
}
@@ -591,14 +596,6 @@ class AssetsController extends Controller
$data['model_name'] = $asset->model->name;
$data['model_number'] = $asset->model->model_number;
if ((($asset->checkin_email()=='1')) && (isset($user)) && (!empty($user->email)) && (!config('app.lock_passwords'))) {
Mail::send('emails.checkin-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_Asset_Checkin'));
});
}
if ($backto=='user') {
return redirect()->route("users.show", $user->id)->with('success', trans('admin/hardware/message.checkin.success'));
}
@@ -956,8 +953,7 @@ class AssetsController extends Controller
if ($request->hasFile('assetfile')) {
foreach ($request->file('assetfile') as $file) {
$extension = $file->getClientOriginalExtension();
$filename = 'hardware-'.$asset->id.'-'.str_random(8);
$filename .= '-'.str_slug($file->getClientOriginalName()).'.'.$extension;
$filename = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$file->move($destinationPath, $filename);
$asset->logUpload($filename, e(Input::get('notes')));
}
@@ -1008,28 +1004,43 @@ class AssetsController extends Controller
* @since [v1.0]
* @return View
*/
public function displayFile($assetId = null, $fileId = null)
public function displayFile($assetId = null, $fileId = null, $download = true)
{
$asset = Asset::find($assetId);
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
$log = Actionlog::find($fileId);
$file = $log->get_src('assets');
$filetype = Helper::checkUploadIsImage($file);
if (!$log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
if ($filetype) {
$contents = file_get_contents($file);
return Response::make($contents)->header('Content-Type', $filetype);
$file = $log->get_src('assets');
if ($log->action_type =='audit') {
$file = $log->get_src('audits');
}
if (!file_exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents($file)) {
return Response::make($contents)->header('Content-Type', $filetype);
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Response::download($file);
}
// Prepare the error message
$error = trans('admin/hardware/message.does_not_exist', compact('id'));
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', $error);
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist', compact('id')));
}
/**
@@ -1206,23 +1217,25 @@ class AssetsController extends Controller
public function postBulkCheckout(Request $request)
{
$this->validate($request, [
"assigned_to" => 'required'
]);
$user = User::find(e(Input::get('assigned_to')));
$admin = Auth::user();
if (!$user) {
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.user_does_not_exist'));
// Find checkout to type
if (request('checkout_to_type')=='location') {
$target = Location::find(request('assigned_location'));
} elseif (request('checkout_to_type')=='asset') {
$target = Asset::find(request('assigned_asset'));
} elseif (request('checkout_to_type')=='user') {
$target = User::find(request('assigned_user'));
}
if (!is_array(Input::get('selected_assets'))) {
return redirect()->route('hardware/bulkcheckout')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
}
$asset_ids = array_filter(Input::get('selected_assets'));
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
return redirect()->back()->with('error', 'You cannot check an asset out to itself.');
}
}
if ((Input::has('checkout_at')) && (Input::get('checkout_at')!= date("Y-m-d"))) {
$checkout_at = e(Input::get('checkout_at'));
} else {
@@ -1237,21 +1250,19 @@ class AssetsController extends Controller
$errors = [];
DB::transaction(function () use ($user, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids) {
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids) {
foreach ($asset_ids as $asset_id) {
$asset = Asset::find($asset_id);
$this->authorize('checkout', $asset);
$error = $asset->checkOut($user, $admin, $checkout_at, $expected_checkin, e(Input::get('note')), null);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e(Input::get('note')), null);
if ($user->location_id!='') {
$asset->location_id = $user->location_id;
if ($target->location_id!='') {
$asset->location_id = $target->location_id;
$asset->unsetEventDispatcher();
$asset->save();
}
if ($error) {
array_merge_recursive($errors, $asset->getErrors()->toArray());
}
@@ -1286,7 +1297,7 @@ class AssetsController extends Controller
}
public function auditStore(Request $request, $id)
public function auditStore(AssetFileRequest $request, $id)
{
$this->authorize('audit', Asset::class);
@@ -1296,11 +1307,13 @@ class AssetsController extends Controller
);
$validator = \Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
}
$asset = Asset::findOrFail($id);
// We don't want to log this as a normal update, so let's bypass that
$asset->unsetEventDispatcher();
@@ -1308,17 +1321,37 @@ class AssetsController extends Controller
$asset->last_audit_date = date('Y-m-d h:i:s');
if ($asset->save()) {
$asset->logAudit(request('note'), request('location_id'));
$filename = '';
if ($request->hasFile('image')) {
$file = $request->file('image');
try {
$destinationPath = config('app.private_uploads').'/audits';
$extension = $file->getClientOriginalExtension();
$filename = 'audit-'.$asset->id.'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$file->move($destinationPath, $filename);
} catch (\Exception $e) {
\Log::error($e);
}
}
$asset->logAudit($request->input('note'), $request->input('location_id'), $filename);
return redirect()->to("hardware")->with('success', trans('admin/hardware/message.audit.success'));
}
}
public function getRequestedIndex($id = null)
public function getRequestedIndex($user_id = null)
{
if ($id) {
$requestedItems = CheckoutRequest::where('user_id', $id)->with('user', 'requestedItem')->get();
$requestedItems = CheckoutRequest::with('user', 'requestedItem')->whereNull('canceled_at')->with('user', 'requestedItem');
if ($user_id) {
$requestedItems->where('user_id', $user_id)->get();
}
$requestedItems = CheckoutRequest::with('user', 'requestedItem')->get();
$requestedItems = $requestedItems->orderBy('created_at', 'desc')->get();
return view('hardware/requested', compact('requestedItems'));
}
@@ -7,13 +7,11 @@ use App\Models\Company;
use App\Models\Consumable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutNotification;
use Auth;
use Config;
use DB;
use Input;
use Lang;
use Mail;
use Redirect;
use Slack;
use Str;
@@ -279,14 +277,6 @@ class ConsumablesController extends Controller
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
if ((($consumable->requireAcceptance()=='1') || ($consumable->getEula())) && $user->email!='') {
Mail::send('emails.accept-asset', $data, function ($m) use ($user) {
$m->to($user->email, $user->first_name . ' ' . $user->last_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Confirm_consumable_delivery'));
});
}
// Redirect to the new consumable page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
@@ -78,6 +78,7 @@ class CustomFieldsController extends Controller
"help_text" => $request->get("help_text"),
"field_values" => $request->get("field_values"),
"field_encrypted" => $request->get("field_encrypted", 0),
"show_in_email" => $request->get("show_in_email", 0),
"user_id" => Auth::user()->id
]);
@@ -165,13 +166,13 @@ class CustomFieldsController extends Controller
public function update(CustomFieldRequest $request, $id)
{
$field = CustomField::find($id);
$field->name = e($request->get("name"));
$field->element = e($request->get("element"));
$field->field_values = e($request->get("field_values"));
$field->field_encrypted = e($request->get("field_encrypted", 0));
$field->user_id = Auth::user()->id;
$field->help_text = $request->get("help_text");
$field->show_in_email = $request->get("show_in_email", 0);
if (!in_array(Input::get('format'), array_keys(CustomField::$PredefinedFormats))) {
$field->format = e($request->get("custom_format"));
@@ -179,7 +180,6 @@ class CustomFieldsController extends Controller
$field->format = e($request->get("format"));
}
if ($field->save()) {
return redirect()->route("fields.index")->with("success", trans('admin/custom_fields/message.field.update.success'));
}
+29 -4
View File
@@ -107,6 +107,7 @@ class LicensesController extends Controller
$license->seats = $request->input('seats');
$license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
@@ -182,6 +183,7 @@ class LicensesController extends Controller
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
@@ -419,6 +421,7 @@ class LicensesController extends Controller
if (!$return_to) {
$return_to = Asset::find($licenseSeat->asset_id);
}
// Update the asset data
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
@@ -506,7 +509,7 @@ class LicensesController extends Controller
foreach (Input::file('licensefile') as $file) {
$rules = array(
'licensefile' => 'required|mimes:png,gif,jpg,jpeg,doc,docx,pdf,txt,zip,rar,rtf,xml,lic|max:2000'
'licensefile' => 'required|mimes:png,gif,jpg,jpeg,doc,docx,pdf,txt,zip,rar,rtf,xml,lic'
);
$validator = Validator::make(array('licensefile'=> $file), $rules);
@@ -514,8 +517,7 @@ class LicensesController extends Controller
return redirect()->back()->with('error', trans('admin/licenses/message.upload.invalidfiles'));
}
$extension = $file->getClientOriginalExtension();
$filename = 'license-'.$license->id.'-'.str_random(8);
$filename .= '-'.str_slug($file->getClientOriginalName()).'.'.$extension;
$filename = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$upload_success = $file->move($destinationPath, $filename);
//Log the upload to the log
@@ -581,7 +583,7 @@ class LicensesController extends Controller
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function displayFile($licenseId = null, $fileId = null)
public function displayFile($licenseId = null, $fileId = null, $download = true)
{
$license = License::find($licenseId);
@@ -591,8 +593,31 @@ class LicensesController extends Controller
$this->authorize('view', $license);
$log = Actionlog::find($fileId);
$file = $log->get_src('licenses');
if ($file =='') {
return response('File not found on server', 404)
->header('Content-Type', 'text/plain');
}
$mimetype = \File::mimeType($file);
if (!file_exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents($file)) {
return Response::make($contents)->header('Content-Type', $mimetype);
}
return JsonResponse::create(["error" => "Failed validation: "], 500);
}
return Response::download($file);
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', compact('id')));
}
+79 -130
View File
@@ -82,136 +82,6 @@ class ReportsController extends Controller
return $response;
}
/**
* Display asset report view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
public function getAssetsReport()
{
$settings = \App\Models\Setting::first();
return view('reports/asset', compact('assets'))->with('settings', $settings);
}
/**
* Exports the assets to CSV
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function exportAssetReport(Request $request)
{
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
// Open output stream
$handle = fopen('php://output', 'w');
$assets = Asset::with('assignedTo', 'location','defaultLoc','assignedTo','model','supplier','assetstatus','model.manufacturer');
// This is used by the sidenav, mostly
switch ($request->input('status')) {
case 'Deleted':
$assets->withTrashed()->Deleted();
break;
case 'Pending':
$assets->Pending();
break;
case 'RTD':
$assets->RTD();
break;
case 'Undeployable':
$assets->Undeployable();
break;
case 'Archived':
$assets->Archived();
break;
case 'Requestable':
$assets->RequestableAssets();
break;
case 'Deployed':
$assets->Deployed();
break;
}
$headers=[
trans('general.company'),
trans('admin/hardware/table.asset_tag'),
trans('admin/hardware/form.manufacturer'),
trans('general.category'),
trans('admin/hardware/form.model'),
trans('general.model_no'),
trans('general.name'),
trans('admin/hardware/table.serial'),
trans('general.status'),
trans('admin/hardware/table.purchase_date'),
trans('admin/hardware/table.purchase_cost'),
trans('admin/hardware/form.order'),
trans('general.supplier'),
trans('admin/hardware/table.checkoutto'),
trans('general.type'),
trans('admin/hardware/table.checkout_date'),
trans('admin/hardware/table.location'),
trans('general.notes'),
];
foreach ($customfields as $field) {
$headers[]=$field->name;
}
fputcsv($handle, $headers);
$assets->orderBy('created_at', 'DESC')->chunk(500, function($assets) use($handle, $customfields) {
foreach ($assets as $asset) {
// Add a new row with data
$values=[
($asset->company) ? $asset->company->name : '',
$asset->asset_tag,
($asset->model->manufacturer) ? $asset->model->manufacturer->name : '',
($asset->model->category) ? $asset->model->category->name : '',
($asset->model) ? $asset->model->name : '',
($asset->model->model_number) ? $asset->model->model_number : '',
($asset->name) ? $asset->name : '',
($asset->serial) ? $asset->serial : '',
($asset->assetstatus) ? e($asset->present()->statusText) : '',
($asset->purchase_date) ? e($asset->purchase_date) : '',
($asset->purchase_cost > 0) ? Helper::formatCurrencyOutput($asset->purchase_cost) : '',
($asset->order_number) ? e($asset->order_number) : '',
($asset->supplier) ? e($asset->supplier->name) : '',
($asset->checkedOutToUser() && $asset->assigned) ? e($asset->assigned->getFullNameAttribute()) : ($asset->assigned ? e($asset->assigned->display_name) : ''),
($asset->checkedOutToUser() && $asset->assigned) ? 'user' : e($asset->assignedType()),
($asset->last_checkout!='') ? e($asset->last_checkout) : '',
($asset->location) ? e($asset->location->name) : '',
($asset->notes) ? e($asset->notes) : '',
];
foreach ($customfields as $field) {
$values[]=$asset->{$field->db_column_name()};
}
fputcsv($handle, $values);
}
});
// Close the output stream
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition'
=> 'attachment; filename="'.(($request->has('status')) ? trim($request->input('status')) : 'all').'-assets-'.date('Y-m-d-his').'.csv"',
]);
return $response;
}
/**
* Show depreciation report for assets.
*
@@ -494,6 +364,28 @@ class ReportsController extends Controller
if ($request->has('location')) {
$header[] = trans('admin/hardware/table.location');
}
if ($request->has('location_address')) {
$header[] = trans('general.address');
$header[] = trans('general.address');
$header[] = trans('general.city');
$header[] = trans('general.state');
$header[] = trans('general.country');
$header[] = trans('general.zip');
}
if ($request->has('rtd_location')) {
$header[] = trans('admin/hardware/form.default_location');
}
if ($request->has('rtd_location_address')) {
$header[] = trans('general.address');
$header[] = trans('general.address');
$header[] = trans('general.city');
$header[] = trans('general.state');
$header[] = trans('general.country');
$header[] = trans('general.zip');
}
if ($request->has('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto');
@@ -508,6 +400,14 @@ class ReportsController extends Controller
$header[] = 'Employee No.';
}
if ($request->has('manager')) {
$header[] = trans('admin/users/table.manager');
}
if ($request->has('department')) {
$header[] = trans('general.department');
}
if ($request->has('status')) {
$header[] = trans('general.status');
}
@@ -567,6 +467,11 @@ class ReportsController extends Controller
$assets->where('assets.location_id', $request->input('by_location_id'));
}
if ($request->has('by_rtd_location_id')) {
\Log::debug('RTD location should match: '.$request->input('by_rtd_location_id'));
$assets->where('assets.rtd_location_id', $request->input('by_rtd_location_id'));
}
if ($request->has('by_supplier_id')) {
$assets->where('assets.supplier_id', $request->input('by_supplier_id'));
}
@@ -602,6 +507,10 @@ class ReportsController extends Controller
if (($request->has('created_start')) && ($request->has('created_end'))) {
$assets->whereBetween('assets.created_at', [$request->input('created_start'), $request->input('created_end')]);
}
if (($request->has('expected_checkin_start')) && ($request->has('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
$assets->orderBy('assets.created_at', 'ASC')->chunk(500, function($assets) use($handle, $customfields, $request) {
@@ -662,6 +571,29 @@ class ReportsController extends Controller
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
}
if ($request->has('location_address')) {
$row[] = ($asset->location) ? $asset->location->address : '';
$row[] = ($asset->location) ? $asset->location->address2 : '';
$row[] = ($asset->location) ? $asset->location->city : '';
$row[] = ($asset->location) ? $asset->location->state : '';
$row[] = ($asset->location) ? $asset->location->country : '';
$row[] = ($asset->location) ? $asset->location->zip : '';
}
if ($request->has('rtd_location')) {
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->present()->name() : '';
}
if ($request->has('rtd_location_address')) {
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->address : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->address2 : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->city : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->state : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->country : '';
$row[] = ($asset->defaultLoc) ? $asset->defaultLoc->zip : '';
}
if ($request->has('assigned_to')) {
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? e($asset->assigned->getFullNameAttribute()) : ($asset->assigned ? e($asset->assigned->display_name) : '');
$row[] = ($asset->checkedOutToUser() && $asset->assigned) ? 'user' : e($asset->assignedType());
@@ -685,6 +617,23 @@ class ReportsController extends Controller
}
}
if ($request->has('manager')) {
if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedto) && ($asset->assignedto->manager)) ? $asset->assignedto->manager->present()->fullName : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->has('department')) {
if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedto) && ($asset->assignedto->department)) ? $asset->assignedto->department->name : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->has('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
+13 -6
View File
@@ -192,8 +192,7 @@ class SettingsController extends Controller
$data['username'] = $user->username;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['password'] = $user->password;
$data['password'] = $request->input('password');
$user->notify(new FirstAdminNotification($data));
/*Mail::send(['text' => 'emails.firstadmin'], $data, function ($m) use ($data) {
@@ -335,6 +334,8 @@ class SettingsController extends Controller
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->load_remote = $request->input('load_remote', '0');
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
$setting->show_archived_in_list = $request->input('show_archived_in_list', '0');
$setting->dashboard_message = $request->input('dashboard_message');
$setting->email_domain = $request->input('email_domain');
@@ -347,6 +348,7 @@ class SettingsController extends Controller
$setting->default_eula_text = $request->input('default_eula_text');
$setting->thumbnail_max_h = $request->input('thumbnail_max_h');
$setting->privacy_policy_link = $request->input('privacy_policy_link');
if (Input::get('per_page')!='') {
$setting->per_page = $request->input('per_page');
@@ -395,6 +397,7 @@ class SettingsController extends Controller
$setting->brand = $request->input('brand', '1');
$setting->header_color = $request->input('header_color');
$setting->support_footer = $request->input('support_footer');
$setting->version_footer = $request->input('version_footer');
$setting->footer_text = $request->input('footer_text');
$setting->skin = $request->input('skin');
$setting->show_url_in_emails = $request->input('show_url_in_emails', '0');
@@ -477,6 +480,11 @@ class SettingsController extends Controller
$setting->two_factor_enabled = null;
} else {
$setting->two_factor_enabled = $request->input('two_factor_enabled');
# remote user login
$setting->login_remote_user_enabled = (int)$request->input('login_remote_user_enabled');
$setting->login_common_disabled= (int)$request->input('login_common_disabled');
$setting->login_remote_user_custom_logout_url = $request->input('login_remote_user_custom_logout_url');
}
}
@@ -485,10 +493,6 @@ class SettingsController extends Controller
$setting->pwd_secure_min = (int) $request->input('pwd_secure_min');
$setting->pwd_secure_complexity = '';
# remote user login
$setting->login_remote_user_enabled = (int)$request->input('login_remote_user_enabled');
$setting->login_common_disabled= (int)$request->input('login_common_disabled');
$setting->login_remote_user_custom_logout_url = $request->input('login_remote_user_custom_logout_url');
if ($request->has('pwd_secure_complexity')) {
$setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity'));
@@ -577,8 +581,11 @@ class SettingsController extends Controller
$alert_email = rtrim($request->input('alert_email'), ',');
$alert_email = trim($alert_email);
$admin_cc_email = rtrim($request->input('admin_cc_email'), ',');
$admin_cc_email = trim($admin_cc_email);
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');
+24 -27
View File
@@ -392,9 +392,9 @@ class UsersController extends Controller
return redirect()->route('users.index')->with('error', 'This user still has ' . $user->assets()->count() . ' assets associated with them.');
}
if (count($user->assets) > 0) {
if ($user->assets->count() > 0) {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', 'This user still has ' . count($user->assets) . ' assets associated with them.');
return redirect()->route('users.index')->with('error', 'This user still has ' . count($user->assets->count()) . ' assets associated with them.');
}
if ($user->licenses()->count() > 0) {
@@ -438,23 +438,19 @@ class UsersController extends Controller
public function postBulkEdit(Request $request)
{
$this->authorize('update', User::class);
if ((!Input::has('ids')) || (count(Input::input('ids')) == 0)) {
return redirect()->back()->with('error', 'No users selected');
} else {
if (($request->has('ids')) && (count($request->input('ids')) > 0)) {
$statuslabel_list = Helper::statusLabelList();
$user_raw_array = array_keys(Input::get('ids'));
$licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get();
$users = User::whereIn('id', $user_raw_array)->with('groups', 'assets', 'licenses', 'accessories')->get();
if ($request->input('bulk_actions')=='edit') {
if ($request->input('bulk_actions') == 'edit') {
return view('users/bulk-edit', compact('users'))
->with('groups', Group::pluck('name', 'id'));
}
return view('users/confirm-bulk-delete', compact('users', 'statuslabel_list'));
}
return redirect()->back()->with('error', 'No users selected');
}
@@ -468,15 +464,13 @@ class UsersController extends Controller
public function postBulkEditSave(Request $request)
{
$this->authorize('update', User::class);
if ((!Input::has('ids')) || (count(Input::input('ids')) == 0)) {
return redirect()->back()->with('error', 'No users selected');
} else {
$user_raw_array = Input::get('ids');
if (($request->has('ids')) && (count($request->input('ids')) > 0)) {
$user_raw_array = $request->input('ids');
$update_array = array();
$manager_conflict = false;
$users = User::whereIn('id', $user_raw_array)->where('id','!=',Auth::user()->id)->get();
$users = User::whereIn('id', $user_raw_array)->where('id', '!=', Auth::user()->id)->get();
if ($request->has('location_id')) {
$update_array['location_id'] = $request->input('location_id');
@@ -492,14 +486,12 @@ class UsersController extends Controller
}
if ($request->has('manager_id')) {
// Do not allow a manager update if the selected manager is one of the users being
// edited.
if (!array_key_exists($request->input('manager_id'), $user_raw_array)) {
$update_array['manager_id'] = $request->input('manager_id');
} else {
$manager_conflict = true;
}
@@ -509,8 +501,9 @@ class UsersController extends Controller
$update_array['activated'] = $request->input('activated');
}
// Save the updated info
if (count($update_array) > 0) {
User::whereIn('id', $user_raw_array)->where('id','!=',Auth::user()->id)->update($update_array);
User::whereIn('id', $user_raw_array)->where('id', '!=', Auth::user()->id)->update($update_array);
}
// Only sync groups if groups were selected
@@ -520,13 +513,17 @@ class UsersController extends Controller
}
}
}
if ($manager_conflict) {
if ($manager_conflict) {
return redirect()->route('users.index')
->with('warning', trans('admin/users/message.bulk_manager_warn'));
}
return redirect()->route('users.index')
->with('warning', trans('admin/users/message.bulk_manager_warn'));
->with('success', trans('admin/users/message.success.update_bulk'));
}
return redirect()->route('users.index')
->with('success', trans('admin/users/message.success.update_bulk'));
return redirect()->back()->with('error', 'No users selected');
}
@@ -538,13 +535,13 @@ class UsersController extends Controller
* @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/
public function postBulkSave()
public function postBulkSave(Request $request)
{
$this->authorize('update', User::class);
if ((!Input::has('ids')) || (count(Input::has('ids')) == 0)) {
if ((!$request->has('ids')) || (count($request->input('ids')) == 0)) {
return redirect()->back()->with('error', 'No users selected');
} elseif ((!Input::has('status_id')) || (count(Input::has('status_id')) == 0)) {
} elseif ((!$request->has('status_id')) || ($request->input('status_id')=='')) {
return redirect()->route('users.index')->with('error', 'No status selected');
} else {
+64 -133
View File
@@ -12,6 +12,8 @@ use App\Models\Consumable;
use App\Models\License;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetNotification;
use App\Notifications\RequestAssetCancelationNotification;
use Auth;
use Config;
use DB;
@@ -59,7 +61,7 @@ class ViewAssetsController extends Controller
$error = trans('admin/users/message.user_not_found', compact('id'));
// Redirect to the user management page
return redirect()->route('users')->with('error', $error);
return redirect()->route('users.index')->with('error', $error);
}
}
@@ -80,116 +82,71 @@ class ViewAssetsController extends Controller
{
$item = null;
$fullItemType = 'App\\Models\\' . studly_case($itemType);
if ($itemType == "asset_model") {
$itemType = "model";
}
$item = call_user_func(array($fullItemType, 'find'), $itemId);
$user = Auth::user();
$quantity = $data['item_quantity'] = Input::has('request-quantity') ? e(Input::get('request-quantity')) : 1;
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $item->id;
$logaction->item_type = $fullItemType;
$logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s");
if ($user->location_id) {
$logaction->location_id = $user->location_id;
}
$logaction->target_id = $data['user_id'] = Auth::user()->id;
$logaction->target_type = User::class;
$data['item_quantity'] = Input::has('request-quantity') ? e(Input::get('request-quantity')) : 1;
$data['requested_by'] = $user->present()->fullName();
$data['item_name'] = $item->name;
$data['item'] = $item;
$data['item_type'] = $itemType;
$data['target'] = Auth::user();
if ($fullItemType == Asset::class) {
$data['item_url'] = route('hardware.show', $item->id);
$slackMessage = ' Asset <'.url('/').'/hardware/'.$item->id.'/view'.'|'.$item->present()->name().'> requested by <'.url('/').'/users/'.$item->user_id.'/view'.'|'.$user->present()->fullName().'>.';
} else {
$data['item_url'] = route("view/${itemType}", $item->id);
$slackMessage = $quantity. ' ' . class_basename(strtoupper($logaction->item_type)).' <'.$data['item_url'].'|'.$item->name.'> requested by <'.url('/').'/user/'.$item->id.'/view'.'|'.$user->present()->fullName().'>.';
}
$settings = Setting::getSettings();
if ($settings->slack_endpoint) {
$slack_settings = [
'username' => $settings->botname,
'channel' => $settings->slack_channel,
'link_names' => true
];
$slackClient = new \Maknz\Slack\Client($settings->slack_endpoint, $slack_settings);
}
if ($item->isRequestedBy($user)) {
$item->cancelRequest();
$log = $logaction->logaction('request_canceled');
if ($item_request = $item->isRequestedBy($user)) {
$item->cancelRequest();
$data['item_quantity'] = $item_request->qty;
$logaction->logaction('request_canceled');
if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) {
Mail::send('emails.asset-canceled', $data, function ($m) use ($user, $settings) {
$m->to(explode(',', $settings->alert_email), $settings->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Item_Request_Canceled'));
});
}
if ($settings->slack_endpoint) {
try {
$slackClient->attach([
'color' => 'good',
'fields' => [
[
'title' => 'CANCELED:',
'value' => $slackMessage
]
]
])->send('Item Request Canceled');
} catch (Exception $e) {
}
$settings->notify(new RequestAssetCancelationNotification($data));
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} else {
$item->request();
$log = $logaction->logaction('requested');
if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) {
Mail::send('emails.asset-requested', $data, function ($m) use ($user, $settings) {
$m->to(explode(',', $settings->alert_email), $settings->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.Item_Requested'));
});
$logaction->logaction('requested');
$settings->notify(new RequestAssetNotification($data));
}
if ($settings->slack_endpoint) {
try {
$slackClient->attach([
'color' => 'good',
'fields' => [
[
'title' => 'REQUESTED:',
'value' => $slackMessage
]
]
])->send('Item Requested');
} catch (Exception $e) {
}
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
}
}
public function getRequestAsset($assetId = null)
{
@@ -197,74 +154,47 @@ class ViewAssetsController extends Controller
// Check if the asset exists and is requestable
if (is_null($asset = Asset::RequestableAssets()->find($assetId))) {
// Redirect to the asset management page
return redirect()->route('requestable-assets')->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable'));
return redirect()->route('requestable-assets')
->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable'));
} elseif (!Company::isCurrentUserHasAccess($asset)) {
return redirect()->route('requestable-assets')->with('error', trans('general.insufficient_permissions'));
return redirect()->route('requestable-assets')
->with('error', trans('general.insufficient_permissions'));
}
// If it's requested, cancel the request.
$data['item'] = $asset;
$data['target'] = Auth::user();
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s");
if ($user->location_id) {
$logaction->location_id = $user->location_id;
}
$logaction->target_id = $data['user_id'] = Auth::user()->id;
$logaction->target_type = User::class;
// If it's already requested, cancel the request.
if ($asset->isRequestedBy(Auth::user())) {
$asset->cancelRequest();
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
$asset->decrement('requests_counter', 1);
$logaction->logaction('request canceled');
$settings->notify(new RequestAssetCancelationNotification($data));
return redirect()->route('requestable-assets')
->with('success')->with('success', trans('admin/hardware/message.requests.cancel-success'));
} else {
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = Asset::class;
$logaction->created_at = $data['requested_date'] = date("Y-m-d H:i:s");
$data['asset_type'] = 'hardware';
if ($user->location_id) {
$logaction->location_id = $user->location_id;
}
$logaction->target_id = $data['user_id'] = Auth::user()->id;
$logaction->target_type = User::class;
$log = $logaction->logaction('requested');
$data['requested_by'] = $user->present()->fullName();
$data['asset_name'] = $asset->present()->name();
$settings = Setting::getSettings();
if (($settings->alert_email!='') && ($settings->alerts_enabled=='1') && (!config('app.lock_passwords'))) {
Mail::send('emails.asset-requested', $data, function ($m) use ($user, $settings) {
$m->to(explode(',', $settings->alert_email), $settings->site_name);
$m->replyTo(config('mail.reply_to.address'), config('mail.reply_to.name'));
$m->subject(trans('mail.asset_requested'));
});
}
$logaction->logaction('requested');
$asset->request();
$asset->increment('requests_counter', 1);
$settings->notify(new RequestAssetNotification($data));
if ($settings->slack_endpoint) {
$slack_settings = [
'username' => $settings->botname,
'channel' => $settings->slack_channel,
'link_names' => true
];
$client = new \Maknz\Slack\Client($settings->slack_endpoint, $slack_settings);
try {
$client->attach([
'color' => 'good',
'fields' => [
[
'title' => 'REQUESTED:',
'value' => class_basename(strtoupper($logaction->item_type)).' asset <'.url('/').'/hardware/'.$asset->id.'/view'.'|'.$asset->present()->name().'> requested by <'.url('/').'/hardware/'.$asset->id.'/view'.'|'.Auth::user()->present()->fullName().'>.'
]
]
])->send('Asset Requested');
} catch (Exception $e) {
}
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
}
@@ -273,13 +203,10 @@ class ViewAssetsController extends Controller
public function getRequestedAssets()
{
$checkoutrequests = CheckoutRequest::all();
return view('account/requested-items', compact($checkoutrequests));
return view('account/requested');
}
// Get the acceptance screen
public function getAcceptAsset($logID = null)
{
@@ -297,10 +224,12 @@ class ViewAssetsController extends Controller
$user = Auth::user();
if ($user->id != $findlog->item->assigned_to) {
// TODO - Fix this for non-assets
if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) {
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
$item = $findlog->item;
// Check if the asset exists
@@ -336,7 +265,7 @@ class ViewAssetsController extends Controller
$user = Auth::user();
if ($user->id != $findlog->item->assigned_to) {
if (($findlog->item_type==Asset::class) && ($user->id != $findlog->item->assigned_to)) {
return redirect()->to('account/view-assets')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
}
@@ -388,9 +317,11 @@ class ViewAssetsController extends Controller
->where('id', $findlog->id)
->update(array('accepted_id' => $logaction->id));
if (($findlog->item_id!='') && ($findlog->item_type==Asset::class)) {
$affected_asset = $logaction->item;
$affected_asset->accepted = $accepted;
$affected_asset->save();
}
if ($update_checkout) {
return redirect()->to('account/view-assets')->with('success', $return_msg);
+1 -1
View File
@@ -22,8 +22,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\ReferrerPolicyHeader::class,
\App\Http\Middleware\ContentSecurityPolicyHeader::class,
\App\Http\Middleware\NosniffGuard::class,
\App\Http\Middleware\CheckForSetup::class,
\Fideloper\Proxy\TrustProxies::class,
\App\Http\Middleware\CheckForSetup::class,
\App\Http\Middleware\CheckForDebug::class,
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
+2 -1
View File
@@ -23,8 +23,9 @@ class AssetFileRequest extends Request
*/
public function rules()
{
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:png,gif,jpg,jpeg,doc,docx,pdf,txt,zip,rar|max:2000'
'file.*' => 'required|mimes:png,gif,jpg,jpeg,doc,docx,pdf,txt,zip,rar|max:'.$max_file_size,
];
}
+2 -2
View File
@@ -24,8 +24,8 @@ class ImageUploadRequest extends Request
public function rules()
{
return [
'image' => 'mimes:png,gif,jpg,jpeg,svg|max:2000',
'avatar' => 'mimes:png,gif,jpg,jpeg,svg|max:2000',
'image' => 'mimes:png,gif,jpg,jpeg,svg',
'avatar' => 'mimes:png,gif,jpg,jpeg,svg',
];
}
+4 -1
View File
@@ -35,7 +35,10 @@ class SaveUserRequest extends Request
{
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('store');
if ($this->request->get('ldap_import') == false)
{
$rules['password'] = Setting::passwordComplexityRulesSaving('store');
}
break;
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Traits;
trait UniqueSerialTrait
{
/**
* Prepare a unique_ids rule, adding a model identifier if required.
*
* @param array $parameters
* @param string $field
* @return string
*/
protected function prepareUniqueSerialRule($parameters, $field)
{
if ($settings = \App\Models\Setting::first()) {
if ($settings->unique_serial=='1') {
return 'unique_undeleted:'.$this->table.','. $this->getKey();
}
}
}
}
@@ -22,10 +22,20 @@ class ActionlogsTransformer
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$icon = $actionlog->present()->icon();
if ($actionlog->filename!='') {
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
}
$array = [
'id' => (int) $actionlog->id,
'icon' => $actionlog->present()->icon(),
'image' => (method_exists($actionlog->item, 'getImageUrl')) ? $actionlog->item->getImageUrl() : null,
'icon' => $icon,
'file' => ($actionlog->filename!='') ?
[
'url' => route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]),
'filename' => $actionlog->filename,
'inlineable' => (bool) \App\Helpers\Helper::show_file_inline($actionlog->filename),
] : null,
'item' => ($actionlog->item) ? [
'id' => (int) $actionlog->item->id,
'name' => e($actionlog->item->getDisplayNameAttribute()),
@@ -59,12 +69,11 @@ class ActionlogsTransformer
];
return $array;
}
public function transformCheckedoutActionlog (Collection $accessories_users, $total)
{
+39 -1
View File
@@ -77,11 +77,14 @@ class AssetsTransformer
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
'checkin_counter' => (int) $asset->checkin_counter,
'checkout_counter' => (int) $asset->checkout_counter,
'requests_counter' => (int) $asset->requests_counter,
'user_can_checkout' => (bool) $asset->availableForCheckout(),
];
if (($asset->model) && ($asset->model->fieldset) && (count($asset->model->fieldset->fields)> 0)) {
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = array();
foreach ($asset->model->fieldset->fields as $field) {
@@ -160,4 +163,39 @@ class AssetsTransformer
'type' => $asset->assignedType()
] : null;
}
public function transformRequestedAssets(Collection $assets, $total)
{
$array = array();
foreach ($assets as $asset) {
$array[] = self::transformRequestedAsset($asset);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformRequestedAsset(Asset $asset) {
$array = [
'id' => (int) $asset->id,
'name' => e($asset->name),
'asset_tag' => e($asset->asset_tag),
'serial' => e($asset->serial),
'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null,
'model' => ($asset->model) ? e($asset->model->name) : null,
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
'location' => ($asset->location) ? e($asset->location->name) : null,
'status'=> ($asset->assetstatus) ? $asset->present()->statusMeta : null,
];
$permissions_array['available_actions'] = [
'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false,
'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true,
];
$array += $permissions_array;
return $array;
}
}
@@ -30,17 +30,18 @@ class CategoriesTransformer
'eula' => ($category->getEula()) ? true : false,
'checkin_email' => ($category->checkin_email =='1') ? true : false,
'require_acceptance' => ($category->require_acceptance =='1') ? true : false,
'assets_count' => $category->assets_count,
'accessories_count' => $category->accessories_count,
'consumables_count' => $category->consumables_count,
'components_count' => $category->components_count,
'assets_count' => (int) $category->assets_count,
'accessories_count' => (int) $category->accessories_count,
'consumables_count' => (int) $category->consumables_count,
'components_count' => (int) $category->components_count,
'licenses_count' => (int) $category->licenses_count,
'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($category->updated_at, 'datetime'),
];
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Category::class) ? true : false,
'delete' => (Gate::allows('delete', Category::class) && ($category->assets_count == 0) && ($category->accessories_count == 0) && ($category->consumables_count == 0) && ($category->components_count == 0)) ? true : false,
'delete' => (Gate::allows('delete', Category::class) && ($category->assets_count == 0) && ($category->accessories_count == 0) && ($category->consumables_count == 0) && ($category->components_count == 0) && ($category->licenses_count == 0)) ? true : false,
];
$array += $permissions_array;
@@ -18,15 +18,34 @@ class CustomFieldsTransformer
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
/**
* Builds up an array of formatted custom fields
* @param Collection $fields
* @param int $modelId
* @param int $total
* @return array
*/
public function transformCustomFieldsWithDefaultValues (Collection $fields, $modelId, $total)
{
$array = [];
foreach ($fields as $field) {
$array[] = self::transformCustomFieldWithDefaultValue($field, $modelId);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformCustomField (CustomField $field)
{
$array = [
'id' => $field->id,
'name' => e($field->name),
'db_column_name' => e($field->db_column_name()),
'format' => e($field->format),
'field_values' => ($field->field_values) ? e($field->field_values) : null,
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
'type' => e($field->element),
'required' => $field->pivot ? $field->pivot->required : false,
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
@@ -34,5 +53,22 @@ class CustomFieldsTransformer
return $array;
}
/**
* Returns the core data for a field, including the default value it has
* when attributed to a certain model
*
* @param CustomField $field
* @param int $modelId
* @return array
*/
public function transformCustomFieldWithDefaultValue (CustomField $field, $modelId)
{
return [
'id' => $field->id,
'name' => e($field->name),
'type' => e($field->element),
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
'default_value' => $field->defaultValue($modelId),
];
}
}
@@ -38,6 +38,7 @@ class LicensesTransformer
'license_email' => e($license->license_email),
'maintained' => ($license->maintained == 1) ? true : false,
'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id,'name'=> e($license->supplier->name)] : null,
'category' => ($license->category) ? ['id' => (int) $license->category->id,'name'=> e($license->category->name)] : null,
'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'),
'user_can_checkout' => (bool) ($license->free_seats_count > 0),
@@ -9,13 +9,13 @@ use App\Helpers\Helper;
class StatuslabelsTransformer
{
public function transformStatuslabels (Collection $statuslabels)
public function transformStatuslabels (Collection $statuslabels, $total)
{
$array = array();
foreach ($statuslabels as $statuslabel) {
$array[] = self::transformStatuslabel($statuslabel);
}
return (new DatatablesTransformer)->transformDatatables($array);
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformStatuslabel (Statuslabel $statuslabel)
+1 -1
View File
@@ -73,7 +73,7 @@ class UsersTransformer
$array += $permissions_array;
$numGroups = count($user->groups);
$numGroups = $user->groups->count();
if($numGroups > 0)
{
$groups["total"] = $numGroups;
+8 -8
View File
@@ -31,9 +31,8 @@ class AssetImporter extends ItemImporter
$this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue;
$this->log('Custom Field '. $customField->name.': '.$customFieldValue);
} else {
// This removes custom fields when updating if the column doesn't exist in file.
// Commented out becausee not sure if it's needed anywhere.
// $this->item['custom_fields'][$customField->db_column_name()] = '';
// Clear out previous data.
$this->item['custom_fields'][$customField->db_column_name()] = null;
}
}
}
@@ -82,8 +81,8 @@ class AssetImporter extends ItemImporter
// We need to save the user if it exists so that we can checkout to user later.
// Sanitizing the item will remove it.
if(array_key_exists('user', $this->item)) {
$user = $this->item['user'];
if(array_key_exists('checkout_target', $this->item)) {
$target = $this->item['checkout_target'];
}
$item = $this->sanitizeItemForStoring($asset, $editingAsset);
// The location id fetched by the csv reader is actually the rtd_location_id.
@@ -106,15 +105,16 @@ class AssetImporter extends ItemImporter
$asset->{$custom_field} = $val;
}
}
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
// $asset->unsetEventDispatcher();
if ($asset->save()) {
$asset->logCreate('Imported using csv importer');
$this->log('Asset ' . $this->item["name"] . ' with serial number ' . $this->item['serial'] . ' was created');
// If we have a user to checkout to, lets do so.
if(isset($user)) {
$asset->fresh()->checkOut($user);
// If we have a target to checkout to, lets do so.
if(isset($target)) {
$asset->fresh()->checkOut($target);
}
return;
}
+6 -3
View File
@@ -14,17 +14,18 @@ class ConsumableImporter extends ItemImporter
protected function handle($row)
{
parent::handle($row); // TODO: Change the autogenerated stub
$this->createConsumableIfNotExists();
parent::handle($row);
$this->createConsumableIfNotExists($row);
}
/**
* Create a consumable if a duplicate does not exist
*
* @author Daniel Melzter
* @param array $row CSV Row Being parsed.
* @since 3.0
*/
public function createConsumableIfNotExists()
public function createConsumableIfNotExists($row)
{
$consumable = Consumable::where('name', $this->item['name'])->first();
if ($consumable) {
@@ -39,6 +40,8 @@ class ConsumableImporter extends ItemImporter
}
$this->log("No matching consumable, creating one");
$consumable = new Consumable();
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");;
$this->item['item_no'] = $this->findCsvMatch($row, "item_number");
$consumable->fill($this->sanitizeItemForStoring($consumable));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$consumable->unsetEventDispatcher();
+47 -23
View File
@@ -30,8 +30,11 @@ abstract class Importer
private $defaultFieldMap = [
'asset_tag' => 'asset tag',
'category' => 'category',
'checkout_class' => 'checkout type', // Supports Location or User for assets. Using checkout_class instead of checkout_type because type exists on asset already.
'checkout_location' => 'checkout location',
'company' => 'company',
'item_name' => 'item name',
'item_number' => "item number",
'image' => 'image',
'expiration_date' => 'expiration date',
'location' => 'location',
@@ -89,11 +92,12 @@ abstract class Importer
public function __construct($file)
{
$this->fieldMap = $this->defaultFieldMap;
// By default the importer passes a url to the file.
// However, for testing we also support passing a string directly
if (! ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
// By default the importer passes a url to the file.
// However, for testing we also support passing a string directly
if (is_file($file)) {
$this->csv = Reader::createFromPath($file);
} else {
@@ -116,6 +120,17 @@ abstract class Importer
$nameLookup[$field['name']] = $field;
return $nameLookup;
});
// Remove any custom fields that do not exist in the header row. This prevents nulling out values that shouldn't exist.
// In detail, we compare the lower case name of custom fields (indexed by name) to the keys in the header row. This
// results in an array with only custom fields that are in the file.
if ($this->customFields) {
$this->customFields = array_intersect_key(
array_change_key_case($this->customFields),
array_change_key_case(array_flip($headerRow))
);
}
DB::transaction(function () use (&$results) {
Model::unguard();
@@ -168,9 +183,8 @@ abstract class Importer
*/
public function lookupCustomKey($key)
{
// dd($this->fieldMap);
if (array_key_exists($key, $this->fieldMap)) {
$this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]);
// $this->log("Found a match in our custom map: {$key} is " . $this->fieldMap[$key]);
return $this->fieldMap[$key];
}
// Otherwise no custom key, return original.
@@ -178,6 +192,8 @@ abstract class Importer
}
/**
* Used to lowercase header values to ensure we're comparing values properly.
*
* @param $results
* @return array
*/
@@ -236,6 +252,18 @@ abstract class Importer
$user_username = $this->findCsvMatch($row, "username");
$first_name = '';
$last_name = '';
if(empty($user_name) && empty($user_email) && empty($user_username)) {
$this->log('No user data provided - skipping user creation, just adding asset');
return false;
}
if( !empty($user_username)) {
// A username was given.
$user = User::where('username', $user_username)->first();
if($user) {
return $user;
}
}
// A number was given instead of a name
if (is_numeric($user_name)) {
$this->log('User '.$user_name.' is not a name - assume this user already exists');
@@ -244,28 +272,24 @@ abstract class Importer
return $user;
}
$this->log('User with id'.$user_name.' does not exist. Continuing through our processes');
} elseif (empty($user_name)) {
$this->log('No user data provided - skipping user creation, just adding asset');
//$user_username = '';
return false;
} else {
$user_email_array = User::generateFormattedNameFromFullName(Setting::getSettings()->email_format, $user_name);
$first_name = $user_email_array['first_name'];
$last_name = $user_email_array['last_name'];
}
// Generate data based on user name.
$user_email_array = User::generateFormattedNameFromFullName(Setting::getSettings()->email_format, $user_name);
$first_name = $user_email_array['first_name'];
$last_name = $user_email_array['last_name'];
if ($user_email=='') {
if (Setting::getSettings()->email_domain) {
$user_email = str_slug($user_email_array['username']).'@'.Setting::getSettings()->email_domain;
}
if (empty($user_email)) {
if (Setting::getSettings()->email_domain) {
$user_email = str_slug($user_email_array['username']).'@'.Setting::getSettings()->email_domain;
}
}
if ($user_username=='') {
if ($this->usernameFormat =='email') {
$user_username = $user_email;
} else {
$user_name_array = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $user_name);
$user_username = $user_name_array['username'];
}
if (empty($user_username)) {
if ($this->usernameFormat =='email') {
$user_username = $user_email;
} else {
$user_name_array = User::generateFormattedNameFromFullName(Setting::getSettings()->username_format, $user_name);
$user_username = $user_name_array['username'];
}
}
$user = new User;
+26 -1
View File
@@ -69,11 +69,36 @@ class ItemImporter extends Importer
$this->item['serial'] = $this->findCsvMatch($row, "serial");
// NO need to call this method if we're running the user import.
// TODO: Merge these methods.
$this->item['checkout_class'] = $this->findCsvMatch($row, "checkout_class");
if(get_class($this) !== UserImporter::class) {
$this->item["user"] = $this->createOrFetchUser($row);
// $this->item["user"] = $this->createOrFetchUser($row);
$this->item["checkout_target"] = $this->determineCheckout($row);
}
}
/**
* Parse row to determine what (if anything) we should checkout to.
* @param array $row CSV Row being parsed
* @return SnipeModel Model to be checked out to
*/
protected function determineCheckout($row)
{
// We only supporty checkout-to-location for asset, so short circuit otherw.
if(get_class($this) != AssetImporter::class) {
return $this->createOrFetchUser($row);
}
if ($this->item['checkout_class'] === 'location') {
// dd($this->findCsvMatch($row, 'checkout_location'));
return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location')));
// dd('here');
}
return $this->createOrFetchUser($row);
}
/**
* Cleanup the $item array before storing.
* We need to remove any values that are not part of the fillable fields.
+3 -3
View File
@@ -71,11 +71,11 @@ class LicenseImporter extends ItemImporter
// Lets try to checkout seats if the fields exist and we have seats.
if ($license->seats > 0) {
$user = $this->item['user'];
$checkout_target = $this->item['checkout_target'];
$asset = Asset::where('asset_tag', $asset_tag)->first();
$targetLicense = $license->licenseSeats()->first();
if ($user) {
$targetLicense->assigned_to = $user->id;
if ($checkout_target) {
$targetLicense->assigned_to = $checkout_target->id;
if ($asset) {
$targetLicense->asset_id = $asset->id;
}
+4 -2
View File
@@ -37,7 +37,6 @@ class UserImporter extends ItemImporter
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
$this->item['password'] = $this->tempPassword;
$user = User::where('username', $this->item['username'])->first();
if ($user) {
if (!$this->updating) {
@@ -45,11 +44,14 @@ class UserImporter extends ItemImporter
return;
}
$this->log('Updating User');
// $user = $this->users[$userId];
$user->update($this->sanitizeItemForUpdating($user));
$user->save();
return;
}
// This needs to be applied after the update logic, otherwise we'll overwrite user passwords
// Issue #5408
$this->item['password'] = $this->tempPassword;
$this->log("No matching user, creating one");
$user = new User();
$user->fill($this->sanitizeItemForStoring($user));
+18
View File
@@ -4,6 +4,8 @@ namespace App\Models;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;
use App\Notifications\CheckinAccessoryNotification;
use App\Notifications\CheckoutAccessoryNotification;
/**
* Model for Accessories.
@@ -23,6 +25,13 @@ class Accessory extends SnipeModel
'requestable' => 'boolean'
];
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutAccessoryNotification::class;
public static $checkinClass = CheckinAccessoryNotification::class;
/**
* Accessory validation rules
*/
@@ -68,6 +77,8 @@ class Accessory extends SnipeModel
];
public function supplier()
{
return $this->belongsTo('\App\Models\Supplier', 'supplier_id');
@@ -106,6 +117,13 @@ class Accessory extends SnipeModel
return $this->hasMany('\App\Models\Actionlog', 'item_id')->where('item_type', Accessory::class)->orderBy('created_at', 'desc')->withTrashed();
}
public function getImageUrl() {
if ($this->image) {
return url('/').'/uploads/accessories/'.$this->image;
}
return false;
}
public function users()
{
+5 -2
View File
@@ -133,8 +133,11 @@ class Actionlog extends SnipeModel
**/
public function get_src($type = 'assets', $fieldname = 'filename')
{
$file = config('app.private_uploads') . '/' . $type . '/' . $this->{$fieldname};
return $file;
if ($this->filename!='') {
$file = config('app.private_uploads') . '/' . $type . '/' . $this->{$fieldname};
return $file;
}
return false;
}
+71 -24
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Exceptions\CheckoutNotAllowed;
use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Presenters\Presentable;
use AssetPresenter;
@@ -11,8 +12,9 @@ use Config;
use Illuminate\Database\Eloquent\SoftDeletes;
use Log;
use Watson\Validating\ValidatingTrait;
use Illuminate\Notifications\Notifiable;
use DB;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\CheckoutAssetNotification;
/**
* Model for Assets.
@@ -22,25 +24,33 @@ use DB;
class Asset extends Depreciable
{
protected $presenter = 'App\Presenters\AssetPresenter';
use Loggable, Requestable, Presentable, Notifiable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait;
use Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
const LOCATION = 'location';
const ASSET = 'asset';
const USER = 'user';
/**
* The database table used by the model.
*
* @var string
*/
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutAssetNotification::class;
public static $checkinClass = CheckinAssetNotification::class;
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'assets';
/**
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* @var boolean
*/
/**
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* @var boolean
*/
protected $injectUniqueIdentifier = true;
// We set these as protected dates so that they will be easily accessible via Carbon
@@ -63,12 +73,13 @@ class Asset extends Depreciable
'status_id' => 'required|integer|exists:status_labels,id',
'company_id' => 'integer|nullable',
'warranty_months' => 'numeric|nullable',
'physical' => 'numeric|max:1|nullable',
'physical' => 'numeric|max:1|nullable',
'checkout_date' => 'date|max:10|min:10|nullable',
'checkin_date' => 'date|max:10|min:10|nullable',
'supplier_id' => 'numeric|nullable',
'asset_tag' => 'required|min:1|max:255|unique_undeleted',
'status' => 'integer',
'serial' => 'unique_serial|nullable',
'purchase_cost' => 'numeric|nullable',
'next_audit_date' => 'date|nullable',
'last_audit_date' => 'date|nullable',
@@ -85,6 +96,7 @@ class Asset extends Depreciable
'assigned_type',
'company_id',
'image',
'location_id',
'model_id',
'name',
'notes',
@@ -179,16 +191,11 @@ class Asset extends Depreciable
$this->location_id = $target->location->id;
}
}
if ($this->requireAcceptance()) {
if(get_class($target) != User::class) {
throw new CheckoutNotAllowed;
}
$this->accepted="pending";
}
if ($this->save()) {
$this->logCheckout($note, $target);
$this->increment('checkout_counter', 1);
return true;
}
return false;
@@ -340,6 +347,38 @@ class Asset extends Depreciable
->withTrashed();
}
/**
* Get checkouts
*/
public function checkouts()
{
return $this->assetlog()->where('action_type', '=', 'checkout')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Get checkins
*/
public function checkins()
{
return $this->assetlog()
->where('action_type', '=', 'checkin from')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Get user requests
*/
public function userRequests()
{
return $this->assetlog()
->where('action_type', '=', 'requested')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* assetmaintenances
@@ -607,7 +646,7 @@ class Asset extends Depreciable
public function scopeRTD($query)
{
return $query->whereNULL('assigned_to')
return $query->whereNULL('assets.assigned_to')
->whereHas('assetstatus', function ($query) {
$query->where('deployable', '=', 1)
->where('pending', '=', 0)
@@ -1008,9 +1047,17 @@ class Asset extends Depreciable
});
});
}
if ($fieldname =='supplier') {
$query->where(function ($query) use ($search_val) {
$query->whereHas('supplier', function ($query) use ($search_val) {
$query->where('suppliers.name', 'LIKE', '%' . $search_val . '%');
});
});
}
}
if (($fieldname!='category') && ($fieldname!='location')
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='location') && ($fieldname!='supplier')
&& ($fieldname!='status_label') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) {
$query->orWhere('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
}
+2 -1
View File
@@ -52,7 +52,8 @@ class AssetMaintenance extends Model implements ICompanyableChild
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.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
'PAT test' => 'PAT test',
];
}
+14 -1
View File
@@ -98,6 +98,19 @@ class AssetModel extends SnipeModel
return $this->belongsTo('\App\Models\CustomFieldset', 'fieldset_id');
}
public function defaultValues()
{
return $this->belongsToMany('\App\Models\CustomField', 'models_custom_fields')->withPivot('default_value');
}
public function getImageUrl() {
if ($this->image) {
return url('/').'/uploads/models/'.$this->image;
}
return false;
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
@@ -140,7 +153,7 @@ class AssetModel extends SnipeModel
* @param $query
*
* @return $query
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @version v3.5
*/
public function scopeRequestableModels($query)
+6 -1
View File
@@ -33,7 +33,7 @@ class Category extends SnipeModel
'name' => 'required|min:1|max:255|unique_undeleted',
'require_acceptance' => 'boolean',
'use_default_eula' => 'boolean',
'category_type' => 'required|in:asset,accessory,consumable,component',
'category_type' => 'required|in:asset,accessory,consumable,component,license',
);
/**
@@ -74,6 +74,11 @@ class Category extends SnipeModel
return $this->hasMany('\App\Models\Accessory');
}
public function licenses()
{
return $this->hasMany('\App\Models\License');
}
public function consumables()
{
return $this->hasMany('\App\Models\Consumable');
+2 -1
View File
@@ -1,12 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class CheckoutRequest extends Model
{
//
use SoftDeletes;
protected $fillable = ['user_id'];
protected $table = 'checkout_requests';
+8
View File
@@ -19,6 +19,14 @@ class Component extends SnipeModel
protected $dates = ['deleted_at', 'purchase_date'];
protected $table = 'components';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = null;
public static $checkinClass = null;
/**
* Category validation rules
*/
+14
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;
use App\Notifications\CheckoutConsumableNotification;
class Consumable extends SnipeModel
{
@@ -18,6 +19,11 @@ class Consumable extends SnipeModel
'requestable' => 'boolean'
];
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutConsumableNotification::class;
public static $checkinClass = null;
/**
@@ -109,6 +115,14 @@ class Consumable extends SnipeModel
return $this->hasMany('\App\Models\Actionlog', 'item_id')->where('item_type', Consumable::class)->orderBy('created_at', 'desc')->withTrashed();
}
public function getImageUrl() {
if ($this->image) {
return url('/').'/uploads/consumables/'.$this->image;
}
return false;
}
public function users()
{
+23 -1
View File
@@ -16,6 +16,7 @@ class CustomField extends Model
public $guarded=["id"];
public static $PredefinedFormats=[
"ANY" => "",
"CUSTOM REGEX" => "",
"ALPHA" => "alpha",
"ALPHA-DASH" => "alpha_dash",
"NUMERIC" => "numeric",
@@ -41,7 +42,8 @@ class CustomField extends Model
'format',
'field_values',
'field_encrypted',
'help_text'
'help_text',
'show_in_email',
];
// This is confusing, since it's actually the custom fields table that
@@ -146,6 +148,26 @@ class CustomField extends Model
return $this->belongsTo('\App\Models\User');
}
public function defaultValues()
{
return $this->belongsToMany('\App\Models\AssetModel', 'models_custom_fields')->withPivot('default_value');
}
/**
* Returns the default value for a given model using the defaultValues
* relationship
*
* @param int $modelId
* @return string
*/
public function defaultValue($modelId)
{
return $this->defaultValues->filter(function ($item) use ($modelId) {
return $item->pivot->asset_model_id == $modelId;
})->map(function ($item) {
return $item->pivot->default_value;
})->first();
}
public function check_format($value)
{
+2 -2
View File
@@ -262,13 +262,13 @@ class Ldap extends Model
$search_results = ldap_search($ldapconn, $base_dn, '('.$filter.')');
if (!$search_results) {
return redirect()->route('users')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn));
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn));
}
// Get results from page
$results = ldap_get_entries($ldapconn, $search_results);
if (!$results) {
return redirect()->route('users')->with('error', trans('admin/users/message.error.ldap_could_not_get_entries').ldap_error($ldapconn));
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_get_entries').ldap_error($ldapconn));
}
// Add results to result set
+39 -2
View File
@@ -16,6 +16,14 @@ use Watson\Validating\ValidatingTrait;
class License extends Depreciable
{
protected $presenter = 'App\Presenters\LicensePresenter';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutLicenseNotification::class;
public static $checkinClass = CheckinLicenseNotification::class;
use SoftDeletes;
use CompanyableTrait;
use Loggable, Presentable;
@@ -41,6 +49,7 @@ class License extends Depreciable
'license_email' => 'email|nullable|max:120',
'license_name' => 'string|nullable|max:100',
'notes' => 'string|nullable',
'category_id' => 'required|exists:categories,id',
'company_id' => 'integer|nullable',
);
@@ -57,6 +66,7 @@ class License extends Depreciable
'license_name', //actually licensed_to
'maintained',
'manufacturer_id',
'category_id',
'name',
'notes',
'order_number',
@@ -83,7 +93,6 @@ class License extends Depreciable
static::updating(function ($license) {
$newSeatCount = $license->getAttributes()['seats'];
$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
// dd($oldSeatCount.' '.$newSeatCount);
return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount);
});
}
@@ -98,7 +107,7 @@ class License extends Depreciable
$change = abs($oldSeats - $newSeats);
if ($oldSeats > $newSeats) {
$license->load('licenseseats.user');
// dd("Here");
// Need to delete seats... lets see if if we have enough.
$seatsAvailableForDelete = $license->licenseseats->reject(function ($seat) {
return (!! $seat->assigned_to) || (!! $seat->asset_id);
@@ -176,11 +185,39 @@ class License extends Depreciable
return $this->belongsTo('\App\Models\Company', 'company_id');
}
public function category()
{
return $this->belongsTo('\App\Models\Category', 'category_id');
}
public function manufacturer()
{
return $this->belongsTo('\App\Models\Manufacturer', 'manufacturer_id');
}
public function checkin_email()
{
return $this->model->category->checkin_email;
}
public function requireAcceptance()
{
return $this->category->require_acceptance;
}
public function getEula()
{
$Parsedown = new \Parsedown();
if ($this->category->eula_text) {
return $Parsedown->text(e($this->category->eula_text));
} elseif ($this->category->use_default_eula == '1') {
return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
} else {
return false;
}
}
/**
* Get the assigned user
*/
+8
View File
@@ -4,6 +4,8 @@ namespace App\Models;
use App\Models\Loggable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Notifications\CheckoutLicenseNotification;
use App\Notifications\CheckinLicenseNotification;
class LicenseSeat extends Model implements ICompanyableChild
{
@@ -15,6 +17,12 @@ class LicenseSeat extends Model implements ICompanyableChild
protected $guarded = 'id';
protected $table = 'license_seats';
/**
* Set static properties to determine which checkout/checkin handlers we should use
*/
public static $checkoutClass = CheckoutLicenseNotification::class;
public static $checkinClass = CheckinLicenseNotification::class;
public function getCompanyableParents()
{
return ['asset', 'license'];
+1 -1
View File
@@ -20,7 +20,7 @@ class Location extends SnipeModel
protected $table = 'locations';
protected $rules = array(
'name' => 'required|min:2|max:255|unique_undeleted',
'city' => 'min:3|max:255|nullable',
'city' => 'min:2|max:255|nullable',
'country' => 'min:2|max:2|nullable',
'address' => 'max:80|nullable',
'address2' => 'max:80|nullable',
+63 -20
View File
@@ -6,16 +6,22 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\CheckoutRequest;
use App\Models\User;
use App\Notifications\CheckinNotification;
use App\Notifications\CheckinAssetNotification;
use App\Notifications\AuditNotification;
use App\Notifications\CheckoutNotification;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CheckoutAccessoryNotification;
use App\Notifications\CheckinAccessoryNotification;
use App\Notifications\CheckoutConsumableNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Notifications\CheckinLicenseNotification;
use Illuminate\Support\Facades\Auth;
trait Loggable
{
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
* @return \App\Models\Actionlog
*/
@@ -26,12 +32,13 @@ trait Loggable
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckout($note, $target /* What are we checking out to? */)
{
$settings = Setting::getSettings();
$log = new Actionlog;
$log = $this->determineLogItemType($log);
$log->user_id = Auth::user()->id;
@@ -43,14 +50,12 @@ trait Loggable
$log->target_type = get_class($target);
$log->target_id = $target->id;
$target_class = get_class($target);
// Figure out what the target is
if ($target_class == Location::class) {
// We can checkout to a location
if ($log->target_type == Location::class) {
$log->location_id = $target->id;
} elseif ($target_class== Asset::class) {
$log->location_id = $target->rtd_location_id;
} elseif ($log->target_type == Asset::class) {
$log->location_id = $target->location_id;
} else {
$log->location_id = $target->location_id;
}
@@ -60,18 +65,25 @@ trait Loggable
$params = [
'item' => $log->item,
'target_type' => $log->target_type,
'target' => $target,
'admin' => $log->user,
'note' => $note,
'log_id' => $log->id
'log_id' => $log->id,
'settings' => $settings,
];
if ($settings = Setting::getSettings()) {
// $settings->notify(new CheckoutNotification($params));
}
$checkoutClass = null;
if (method_exists($target, 'notify')) {
$target->notify(new CheckoutNotification($params));
$target->notify(new static::$checkoutClass($params));
}
// Send to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AdminRecipient();
if (($settings->admin_cc_email!='') && (static::$checkoutClass!='')) {
$recipient->notify(new static::$checkoutClass($params));
}
return $log;
@@ -94,33 +106,62 @@ trait Loggable
return $log;
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckin($target, $note)
{
$settings = Setting::getSettings();
$log = new Actionlog;
$log->target_type = get_class($target);
$log->target_id = $target->id;
if (static::class == LicenseSeat::class) {
$log->item_type = License::class;
$log->item_id = $this->license_id;
} else {
$log->item_type = static::class;
$log->item_id = $this->id;
if (static::class == Asset::class) {
if ($asset = Asset::find($log->item_id)) {
\Log::debug('Increment the checkin count for asset: '.$log->item_id);
$asset->increment('checkin_counter', 1);
}
}
}
$log->location_id = null;
$log->note = $note;
$log->user_id = Auth::user()->id;
$log->logaction('checkin from');
$params = [
'target' => $target,
'item' => $log->item,
'admin' => $log->user,
'note' => $note
'note' => $note,
'target_type' => $log->target_type,
'settings' => $settings,
];
Setting::getSettings()->notify(new CheckinNotification($params));
$checkinClass = null;
if (method_exists($target, 'notify')) {
$target->notify(new static::$checkinClass($params));
}
// Send to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AdminRecipient();
if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) {
$recipient->notify(new static::$checkinClass($params));
}
return $log;
}
@@ -131,7 +172,7 @@ trait Loggable
* @since [v4.0]
* @return \App\Models\Actionlog
*/
public function logAudit($note, $location_id)
public function logAudit($note, $location_id, $filename = null)
{
$log = new Actionlog;
$location = Location::find($location_id);
@@ -145,10 +186,12 @@ trait Loggable
$log->location_id = ($location_id) ? $location_id : null;
$log->note = $note;
$log->user_id = Auth::user()->id;
$log->filename = $filename;
$log->logaction('audit');
$params = [
'item' => $log->item,
'filename' => $log->filename,
'admin' => $log->user,
'location' => ($location) ? $location->name : '',
'note' => $note
@@ -161,7 +204,7 @@ trait Loggable
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.5]
* @return \App\Models\Actionlog
*/
@@ -188,7 +231,7 @@ trait Loggable
}
/**
* @author Daniel Meltzer <parallelgrapefruit@gmail.com
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
* @return \App\Models\Actionlog
*/
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace App\Models\Recipients;
use App\Models\Setting;
class AdminRecipient extends Recipient{
public function __construct()
{
$settings = Setting::getSettings();
$this->email = $settings->admin_cc_email;
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace App\Models\Recipients;
use App\Models\Setting;
class AlertRecipient extends Recipient{
public function __construct()
{
$settings = Setting::getSettings();
$this->email = $settings->alert_email;
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Models\Recipients;
use Illuminate\Notifications\Notifiable;
abstract class Recipient {
use Notifiable;
protected $email;
}
+9 -6
View File
@@ -19,9 +19,7 @@ trait Requestable
public function isRequestedBy(User $user)
{
$requests = $this->requests->where('user_id', $user->id);
return $requests->count() > 0;
return $this->requests->where('canceled_at', NULL)->where('user_id', $user->id)->first();
}
public function scopeRequestedBy($query, User $user)
@@ -31,15 +29,20 @@ trait Requestable
});
}
public function request()
public function request($qty = 1)
{
$this->requests()->save(
new CheckoutRequest(['user_id' => Auth::id()])
new CheckoutRequest(['user_id' => Auth::id(), 'qty' => $qty])
);
}
public function deleteRequest()
{
$this->requests()->where('user_id', Auth::id())->delete();
}
public function cancelRequest()
{
$this->requests()->where('user_id', Auth::id())->delete();
$this->requests()->where('user_id', Auth::id())->update(['canceled_at' => \Carbon\Carbon::now()]);
}
}
+2
View File
@@ -17,6 +17,7 @@ class Setting extends Model
'qr_text' => 'max:31|nullable',
'logo_img' => 'mimes:jpeg,bmp,png,gif',
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email|nullable',
'default_currency' => 'required',
'locale' => 'required',
'slack_endpoint' => 'url|required_with:slack_channel|nullable',
@@ -42,6 +43,7 @@ class Setting extends Model
'audit_warning_days' => 'numeric|nullable',
'audit_interval' => 'numeric|nullable',
'custom_forgot_pass_url' => 'url|nullable',
'privacy_policy_link' => 'nullable|url'
];
protected $fillable = ['site_name','email_domain','email_format','username_format'];
+14 -1
View File
@@ -39,6 +39,7 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
'first_name',
'jobtitle',
'last_name',
'ldap_import',
'locale',
'location_id',
'manager_id',
@@ -149,6 +150,18 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
return $this->last_name . ", " . $this->first_name . " (" . $this->username . ")";
}
/**
* The url for slack notifications.
* Used by Notifiable trait.
* @return mixed
*/
public function routeNotificationForSlack()
{
// At this point the endpoint is the same for everything.
// In the future this may want to be adapted for individual notifications.
$this->endpoint = \App\Models\Setting::getSettings()->slack_endpoint;
return $this->endpoint;
}
/**
@@ -280,7 +293,7 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
*/
public function checkoutRequests()
{
return $this->belongsToMany(Asset::class, 'checkout_requests');
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
}
public function throttle()
@@ -0,0 +1,127 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckinAccessoryNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->target_type = $params['target'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint) {
$notifyBy[] = 'slack';
}
// Make sure the target is a user and that its appropriate to send them an email
if (($this->target_type == \App\Models\User::class) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack()
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
return (new SlackMessage)
->content(':arrow_down: :keyboard: Accessory Checked In')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)->markdown('notifications.markdown.checkin-accessory',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
])
->subject('Accessory checked in');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -0,0 +1,126 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
class CheckinAssetNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false);
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
\Log::debug('use slack');
$notifyBy[] = 'slack';
}
// Make sure the target is a user and that its appropriate to send them an email
if ((($this->target->email!='') && ($this->target_type == 'App\Models\User')) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
\Log::debug('use email');
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack()
{
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname!='') ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
trans('general.status') => $item->assetstatus->name,
trans('general.location') => ($item->location) ? $item->location->name : '',
];
return (new SlackMessage)
->content(':arrow_down: :computer: Asset Checked In')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail()
{
$fields = [];
// Check if the item has custom fields associated with it
if (($this->item->model) && ($this->item->model->fieldset)) {
$fields = $this->item->model->fieldset->fields;
}
$message = (new MailMessage)->markdown('notifications.markdown.checkin-asset',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
'fields' => $fields,
'expected_checkin' => $this->expected_checkin,
])
->subject('Asset checked in');
return $message;
}
}
@@ -0,0 +1,123 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckinLicenseNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->note = '';
$this->settings = $params['settings'];
$this->target_type = $params['target_type'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
$notifyBy[] = 'slack';
}
if (($this->target_type == \App\Models\User::class) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
return (new SlackMessage)
->content(':arrow_down: :floppy_disk: License Checked In')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)->markdown('notifications.markdown.checkin-license',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
])
->subject('License checked in');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
-97
View File
@@ -1,97 +0,0 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class CheckinNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
//
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint) {
$notifyBy[] = 'slack';
}
$item = $this->params['item'];
if (class_basename(get_class($this->params['item']))=='Asset') {
if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance() == '1'))
|| (method_exists($item, 'getEula') && ($item->getEula()))
) {
// $notifyBy[] = 'mail';
}
}
return $notifyBy;
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->success()
->content(class_basename(get_class($this->params['item'])) . " Checked In")
->attachment(function ($attachment) use ($notifiable) {
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$fields = [
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>',
];
array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note'];
$attachment->title($item->name, $item->present()->viewUrl())
->fields($fields);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\sMessages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', 'https://laravel.com')
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -0,0 +1,136 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutAccessoryNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
$notifyBy[] = 'slack';
}
// Make sure the target is a user and that its appropriate to send them an email
if ((($this->target->email!='') && ($this->target_type == 'App\Models\User')) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
return (new SlackMessage)
->content(':arrow_up: :keyboard: Accessory Checked Out')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
\Log::debug($this->item->getImageUrl());
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
return (new MailMessage)->markdown('notifications.markdown.checkout-accessory',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
])
->subject(trans('mail.Confirm_accessory_delivery'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -0,0 +1,146 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
class CheckoutAssetNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
if ($this->item->last_checkout) {
$this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
}
if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false);
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
\Log::debug('use slack');
$notifyBy[] = 'slack';
}
// Make sure the target is a user and that its appropriate to send them an email
if (($this->target_type == \App\Models\User::class) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack()
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
if (($this->expected_checkin) && ($this->expected_checkin!='')) {
$fields['Expected Checkin'] = $this->expected_checkin;
}
return (new SlackMessage)
->content(':arrow_up: :computer: Asset Checked Out')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail()
{
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
$fields = [];
// Check if the item has custom fields associated with it
if (($this->item->model) && ($this->item->model->fieldset)) {
$fields = $this->item->model->fieldset->fields;
}
$message = (new MailMessage)->markdown('notifications.markdown.checkout-asset',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'log_id' => $this->note,
'target' => $this->target,
'fields' => $fields,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
'last_checkout' => $this->last_checkout,
'expected_checkin' => $this->expected_checkin,
])
->subject(trans('mail.Confirm_asset_delivery'));
return $message;
}
}
@@ -0,0 +1,130 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutConsumableNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
$notifyBy[] = 'slack';
}
// Make sure the target is a user and that its appropriate to send them an email
if ((($this->target->email!='') && ($this->target_type == 'App\Models\User')) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
return (new SlackMessage)
->content(':arrow_up: :paperclip: Consumable Checked Out')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
\Log::debug($this->item->getImageUrl());
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
return (new MailMessage)->markdown('notifications.markdown.checkout-consumable',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'log_id' => $this->note,
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
])
->subject(trans('mail.Confirm_consumable_delivery'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -0,0 +1,131 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutLicenseNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->admin = $params['admin'];
$this->log_id = $params['log_id'];
$this->note = '';
$this->target_type = $params['target_type'];
$this->settings = $params['settings'];
$this->target_type = $params['target_type'];
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
$notifyBy[] = 'slack';
}
if (($this->target_type == \App\Models\User::class) && (($this->item->requireAcceptance() == '1') || ($this->item->getEula())))
{
$notifyBy[] = 'mail';
}
return $notifyBy;
}
public function toSlack($notifiable)
{
$target = $this->target;
$admin = $this->admin;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
return (new SlackMessage)
->content(':arrow_up: :floppy_disk: License Checked Out')
->from($botname)
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
return (new MailMessage)->markdown('notifications.markdown.checkout-license',
[
'item' => $this->item,
'admin' => $this->admin,
'note' => $this->note,
'target' => $this->target,
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => url('/').'/account/accept-asset/'.$this->log_id,
])
->subject(trans('mail.Confirm_license_delivery'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
-133
View File
@@ -1,133 +0,0 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CheckoutNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
//
$this->params = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint) {
$notifyBy[] = 'slack';
}
$item = $this->params['item'];
if (class_basename(get_class($this->params['item']))=='Asset') {
if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance() == '1'))
|| (method_exists($item, 'getEula') && ($item->getEula()))
) {
$notifyBy[] = 'mail';
}
}
return $notifyBy;
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->success()
->content(class_basename(get_class($this->params['item'])) . " Checked Out")
->attachment(function ($attachment) use ($notifiable) {
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$target = $this->params['target'];
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin_user->present()->viewUrl().'|'.$admin_user->present()->fullName().'>'
];
array_key_exists('note', $this->params) && $fields['Notes'] = $this->params['note'];
$attachment->title($item->name, $item->present()->viewUrl())
->fields($fields);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (class_basename(get_class($this->params['item']))=='Asset') {
//TODO: Expand for non assets.
$item = $this->params['item'];
$admin_user = $this->params['admin'];
$target = $this->params['target'];
$data = [
'eula' => method_exists($item, 'getEula') ? $item->getEula() : '',
'first_name' => $target->present()->fullName(),
'item_name' => $item->present()->name(),
'checkout_date' => $item->last_checkout,
'expected_checkin' => ($item->expected_checkin) ? $item->expected_checkin->format('Y-m-d') : '',
'item_tag' => $item->asset_tag,
'note' => $this->params['note'],
'item_serial' => $item->serial,
'require_acceptance' => method_exists($item, 'requireAcceptance') ? $item->requireAcceptance() : '',
'log_id' => $this->params['log_id'],
'manufacturer_name' => $item->model->manufacturer->name,
'model_name' => $item->model->name,
'model_number' => $item->model->model_number,
];
if ((method_exists($item, 'requireAcceptance') && ($item->requireAcceptance() == '1'))
|| (method_exists($item, 'getEula') && ($item->getEula()))
) {
return (new MailMessage)
->view('emails.accept-asset', $data)
->subject(trans('mail.Confirm_asset_delivery'));
}
}
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use App\Models\SnipeModel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;
class ExpectedCheckinAdminNotification extends Notification
{
use Queueable;
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->assets = $params;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
$notifyBy = [];
$notifyBy[]='mail';
return $notifyBy;
}
public function toSlack($notifiable)
{
}
/**
* Get the mail representation of the notification.
*
* @param mixed $asset
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($params)
{
$message = (new MailMessage)->markdown('notifications.markdown.report-expected-checkins',
[
'assets' => $this->assets,
])
->subject('Expected asset checkin report');
return $message;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
@@ -61,7 +61,7 @@ class ExpectedCheckinNotification extends Notification
return (new MailMessage)
->error()
->subject('Reminder: '.$this->params->present()->name().' checkin deadline approaching')
->line('Hi, '.$this->params->assignedto->first_name)
->line('Hi, '.$this->params->assignedto->first_name.' '.$this->params->assignedto->last_name)
->greeting('An asset checked out to you is due to be checked back in on '.$formatted_due.'.')
->line('Asset: '.$this->params->present()->name())
->line('Serial: '.$this->params->serial)
@@ -0,0 +1,137 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class RequestAssetCancelationNotification extends Notification
{
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->note = '';
$this->last_checkout = '';
$this->item_quantity = $params['item_quantity'];
$this->expected_checkin = '';
$this->requested_date = \App\Helpers\Helper::getFormattedDateObject($params['requested_date'], 'datetime',
false);
$this->settings = Setting::getSettings();
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
if ($this->item->last_checkout) {
$this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
}
if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false);
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
\Log::debug('use slack');
$notifyBy[] = 'slack';
}
$notifyBy[] = 'mail';
return $notifyBy;
}
public function toSlack()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
$qty = $this->item_quantity;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'QTY' => $qty,
'Canceled By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
];
if (($this->expected_checkin) && ($this->expected_checkin!='')) {
$fields['Expected Checkin'] = $this->expected_checkin;
}
return (new SlackMessage)
->content( trans('mail.a_user_canceled'))
->from($botname)
->attachment(function ($attachment) use ($item, $note, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail()
{
$fields = [];
// Check if the item has custom fields associated with it
if (($this->item->model) && ($this->item->model->fieldset)) {
$fields = $this->item->model->fieldset->fields;
}
$message = (new MailMessage)->markdown('notifications.markdown.asset-requested',
[
'item' => $this->item,
'note' => $this->note,
'requested_by' => $this->target,
'requested_date' => $this->requested_date,
'fields' => $fields,
'qty' => $this->item_quantity,
'last_checkout' => $this->last_checkout,
'expected_checkin' => $this->expected_checkin,
'intro_text' => trans('mail.a_user_canceled'),
])
->subject(trans('Item Request Canceled'));
return $message;
}
}
@@ -0,0 +1,134 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class RequestAssetNotification extends Notification
{
/**
* @var
*/
private $params;
/**
* Create a new notification instance.
*
* @param $params
*/
public function __construct($params)
{
$this->target = $params['target'];
$this->item = $params['item'];
$this->item_type = $params['item_type'];
$this->item_quantity = $params['item_quantity'];
$this->note = '';
$this->last_checkout = '';
$this->expected_checkin = '';
$this->requested_date = \App\Helpers\Helper::getFormattedDateObject($params['requested_date'], 'datetime',
false);
$this->settings = Setting::getSettings();
if (array_key_exists('note', $params)) {
$this->note = $params['note'];
}
if ($this->item->last_checkout) {
$this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
}
if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false);
}
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->slack_endpoint!='') {
\Log::debug('use slack');
$notifyBy[] = 'slack';
}
$notifyBy[] = 'mail';
return $notifyBy;
}
public function toSlack()
{
$target = $this->target;
$qty = $this->item_quantity;
$item = $this->item;
$note = $this->note;
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'QTY' => $qty,
'Requested By' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
];
return (new SlackMessage)
->content(trans('mail.Item_Requested'))
->from($botname)
->attachment(function ($attachment) use ($item, $note, $fields) {
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
->fields($fields)
->content($note);
});
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail()
{
$fields = [];
// Check if the item has custom fields associated with it
if (($this->item->model) && ($this->item->model->fieldset)) {
$fields = $this->item->model->fieldset->fields;
}
$message = (new MailMessage)->markdown('notifications.markdown.asset-requested',
[
'item' => $this->item,
'note' => $this->note,
'requested_by' => $this->target,
'requested_date' => $this->requested_date,
'fields' => $fields,
'last_checkout' => $this->last_checkout,
'expected_checkin' => $this->expected_checkin,
'intro_text' => trans('mail.a_user_requested'),
'qty' => $this->item_quantity,
])
->subject(trans('mail.Item_Requested'));
return $message;
}
}
-7
View File
@@ -1,11 +1,4 @@
<?php
/**
* Created by PhpStorm.
* User: parallelgrapefruit
* Date: 12/23/16
* Time: 11:51 AM
*/
namespace App\Presenters;
use App\Helpers\Helper;
@@ -0,0 +1,118 @@
<?php
namespace App\Presenters;
use App\Helpers\Helper;
/**
* Class AssetModelPresenter
* @package App\Presenters
*/
class AssetMaintenancesPresenter extends Presenter
{
/**
* Json Column Layout for bootstrap table
* @return string
*/
public static function dataTableLayout()
{
$layout = [
[
"field" => "id",
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('general.id'),
"visible" => false
],[
"field" => "company",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('admin/companies/table.title'),
"visible" => false,
"formatter" => "companiesLinkObjFormatter"
], [
"field" => "asset_name",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/table.asset_name'),
"formatter" => "assetNameLinkFormatter"
],[
"field" => "asset_tag",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.asset_tag'),
"formatter" => "assetTagLinkFormatter"
], [
"field" => "supplier",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.supplier'),
"visible" => false,
"formatter" => "suppliersLinkObjFormatter"
], [
"field" => "location",
"searchable" => true,
"sortable" => true,
"title" => trans('general.location'),
"formatter" => "locationsLinkObjFormatter",
], [
"field" => "asset_maintenance_type",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.asset_maintenance_type'),
], [
"field" => "title",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.title'),
], [
"field" => "created_at",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.start_date'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "completion_date",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.completion_date'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "notes",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.notes')
],[
"field" => "is_warranty",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/table.is_warranty')
], [
"field" => "cost",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/asset_maintenances/form.cost')
], [
"field" => "user_id",
"searchable" => true,
"sortable" => true,
"title" => trans('general.admin'),
"formatter" => "usersLinkObjFormatter"
], [
"field" => "actions",
"searchable" => false,
"sortable" => false,
"switchable" => false,
"title" => trans('table.actions'),
"visible" => true,
"formatter" => "maintenancesActionsFormatter",
]
];
return json_encode($layout);
}
}
-6
View File
@@ -1,10 +1,4 @@
<?php
/**
* Created by PhpStorm.
* User: parallelgrapefruit
* Date: 12/23/16
* Time: 12:15 PM
*/
namespace App\Presenters;
+21
View File
@@ -181,6 +181,27 @@ class AssetPresenter extends Presenter
"visible" => false,
"title" => trans('general.notes'),
], [
"field" => "checkout_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.checkouts_count')
],[
"field" => "checkin_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.checkins_count')
], [
"field" => "requests_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.user_requests_count')
], [
"field" => "created_at",
"searchable" => false,
+6
View File
@@ -69,6 +69,12 @@ class CategoryPresenter extends Presenter
"sortable" => true,
"title" => trans('general.components'),
"visible" => true
], [
"field" => "licenses_count",
"searchable" => false,
"sortable" => true,
"title" => trans('general.licenses'),
"visible" => true
], [
"field" => "eula",
"searchable" => false,
+8
View File
@@ -61,6 +61,14 @@ class LicensePresenter extends Presenter
"searchable" => true,
"sortable" => true,
"title" => trans('admin/licenses/form.to_name'),
], [
"field" => "category",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.category'),
"visible" => false,
"formatter" => "categoriesLinkObjFormatter"
], [
"field" => "supplier",
"searchable" => true,
+1
View File
@@ -73,6 +73,7 @@
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true,
"process-timeout":3000,
"platform": {
"php": "5.6.4"
}
+1 -1
View File
@@ -78,7 +78,7 @@ return [
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => env('DB_PREFIX', null),
'strict' => false,
'engine' => null,
'engine' => 'InnoDB',
'unix_socket' => env('DB_SOCKET',''),
'dump_command_path' => env('DB_DUMP_PATH', '/usr/local/bin'), // only the path, so without 'mysqldump'
'dump_command_timeout' => 60 * 5, // 5 minute timeout
+5 -5
View File
@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v4.2.0',
'full_app_version' => 'v4.2.0 - build 3479-gbf74bb1',
'build_version' => '3479',
'app_version' => 'v4.5.0',
'full_app_version' => 'v4.5.0 - build 3701-gbf1e742df',
'build_version' => '3701',
'prerelease_version' => '',
'hash_version' => 'gbf74bb1',
'full_hash' => 'v4.2.0-3479-gbf74bb1',
'hash_version' => 'gbf1e742df',
'full_hash' => 'v4.4.2-63-gbf1e742df',
'branch' => 'master',
);
+15
View File
@@ -114,3 +114,18 @@ $factory->state(App\Models\Category::class, 'consumable-ink-category', function
];
});
$factory->state(App\Models\Category::class, 'license-graphics-category', function ($faker) {
return [
'name' => 'Graphics Software',
'category_type' => 'license',
];
});
$factory->state(App\Models\Category::class, 'license-office-category', function ($faker) {
return [
'name' => 'Office Software',
'category_type' => 'license',
];
});

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