Compare commits

..

1 Commits

Author SHA1 Message Date
snipe 1abd669de5 Refactor custom fields handling for storage
Signed-off-by: snipe <snipe@snipe.net>
2024-07-08 14:00:15 +01:00
2586 changed files with 277704 additions and 48005 deletions
-81
View File
@@ -3136,87 +3136,6 @@
"contributions": [ "contributions": [
"doc" "doc"
] ]
},
{
"login": "FlorentDotMe",
"name": "Florent Bervas",
"avatar_url": "https://avatars.githubusercontent.com/u/292081?v=4",
"profile": "http://spoontux.net",
"contributions": [
"code"
]
},
{
"login": "dbakan",
"name": "Daniel Albertsen",
"avatar_url": "https://avatars.githubusercontent.com/u/4498077?v=4",
"profile": "https://ditscheri.com",
"contributions": [
"code"
]
},
{
"login": "r-xyz",
"name": "r-xyz",
"avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4",
"profile": "https://github.com/r-xyz",
"contributions": [
"code"
]
},
{
"login": "DrekiDegga",
"name": "Steven Mainor",
"avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4",
"profile": "https://github.com/DrekiDegga",
"contributions": [
"code"
]
},
{
"login": "arne-kroeger",
"name": "arne-kroeger",
"avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4",
"profile": "https://github.com/arne-kroeger",
"contributions": [
"code"
]
},
{
"login": "Glukose1",
"name": "Glukose1",
"avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4",
"profile": "https://github.com/Glukose1",
"contributions": [
"code"
]
},
{
"login": "Scarzy",
"name": "Scarzy",
"avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4",
"profile": "https://github.com/Scarzy",
"contributions": [
"code"
]
},
{
"login": "setpill",
"name": "setpill",
"avatar_url": "https://avatars.githubusercontent.com/u/37372069?v=4",
"profile": "https://github.com/setpill",
"contributions": [
"code"
]
},
{
"login": "swift2512",
"name": "swift2512",
"avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4",
"profile": "https://github.com/swift2512",
"contributions": [
"bug"
]
} }
] ]
} }
-2
View File
@@ -1,8 +1,6 @@
# -------------------------------------------- # --------------------------------------------
# REQUIRED: DB SETUP # REQUIRED: DB SETUP
# -------------------------------------------- # --------------------------------------------
# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/
MYSQL_DATABASE=snipeit MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit MYSQL_USER=snipeit
MYSQL_PASSWORD=changeme1234 MYSQL_PASSWORD=changeme1234
+1 -1
View File
@@ -14,7 +14,7 @@ APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ=
APP_URL=http://localhost:8000 APP_URL=http://localhost:8000
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='UTC' APP_TIMEZONE='UTC'
APP_LOCALE=en-US APP_LOCALE=en
MAX_RESULTS=500 MAX_RESULTS=500
# -------------------------------------------- # --------------------------------------------
+1 -1
View File
@@ -6,7 +6,7 @@ APP_DEBUG=false
APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk= APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk=
APP_URL=http://127.0.0.1:8000 APP_URL=http://127.0.0.1:8000
APP_TIMEZONE='US/Eastern' APP_TIMEZONE='US/Eastern'
APP_LOCALE=en-US APP_LOCALE=en
APP_LOCKED=false APP_LOCKED=false
MAX_RESULTS=200 MAX_RESULTS=200
-4
View File
@@ -32,8 +32,6 @@ DB_PREFIX=null
DB_DUMP_PATH='/usr/bin' DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4 DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS # OPTIONAL: SSL DATABASE SETTINGS
@@ -89,7 +87,6 @@ SESSION_LIFETIME=12000
EXPIRE_ON_CLOSE=false EXPIRE_ON_CLOSE=false
ENCRYPT=false ENCRYPT=false
COOKIE_NAME=snipeit_session COOKIE_NAME=snipeit_session
PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null COOKIE_DOMAIN=null
SECURE_COOKIES=false SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15 API_TOKEN_EXPIRATION_YEARS=15
@@ -186,7 +183,6 @@ REPORT_TIME_LIMIT=12000
REQUIRE_SAML=false REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120 API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true CSV_ESCAPE_FORMULAS=true
LIVEWIRE_URL_PREFIX=null
# -------------------------------------------- # --------------------------------------------
# OPTIONAL: HASHING # OPTIONAL: HASHING
+43
View File
@@ -0,0 +1,43 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- :woman_technologist: ready for dev
- :moneybag: bounty
- :hand: bug
- "🔐 security"
- "👩‍💻 ready for dev"
- "💰 bounty"
- "✋ bug"
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: stale
only: issues
# Comment to post when removing the stale label.
unmarkComment: >
Okay, it looks like this issue or feature request might still be important. We'll re-open
it for now. Thank you for letting us know!
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI - name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.4.5 uses: codacy/codacy-analysis-cli-action@v4.4.1
with: with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations # You can also omit the token and run the tools that support default configurations
-40
View File
@@ -1,40 +0,0 @@
name: 'Close stale issues'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
# contents: write # only for delete-branch option
issues: write
# pull-requests: write
steps:
- uses: actions/stale@v9
with:
debug-only: true
ascending: true
operations-per-run: 1000 # just while we're debugging
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 7
exempt-all-milestones: true
stale-issue-message: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
close-issue-message: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.
# There doesn't seem to be a 'reopen issue message'?
# Since there is no 'stale-pr-message' - PR's should not be stale'd
stale-issue-label: stale
exempt-issue-labels: >
pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩‍💻 ready for dev,💰 bounty,✋ bug
-2
View File
@@ -21,9 +21,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
php-version: php-version:
- "8.1"
- "8.2" - "8.2"
- "8.3"
name: PHP ${{ matrix.php-version }} name: PHP ${{ matrix.php-version }}
-4
View File
@@ -47,7 +47,6 @@ storage/private_uploads/users/*
tests/_data/scenarios tests/_data/scenarios
tests/_output/* tests/_output/*
tests/_support/_generated/* tests/_support/_generated/*
tests/coverage/*
/npm-debug.log /npm-debug.log
/storage/oauth-private.key /storage/oauth-private.key
/storage/oauth-public.key /storage/oauth-public.key
@@ -68,6 +67,3 @@ _ide_helper_models.php
/.phplint-cache /.phplint-cache
storage/ldap_client_tls.cert storage/ldap_client_tls.cert
storage/ldap_client_tls.key storage/ldap_client_tls.key
/storage/framework/testing
/.phpunit.cache
+455 -52
View File
@@ -1,58 +1,461 @@
Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far: Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") | <!-- prettier-ignore-start -->
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | <!-- markdownlint-disable -->
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") | <table>
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") | <tbody>
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") | <tr>
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") | <td align="center" valign="top" width="14.28%"><a href="http://www.snipe.net"><img src="https://avatars3.githubusercontent.com/u/197404?v=3?s=110" width="110px;" alt="snipe"/><br /><sub><b>snipe</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Code">💻</a> <a href="#infra-snipe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Documentation">📖</a> <a href="https://github.com/snipe/snipe-it/commits?author=snipe" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Asnipe" title="Bug reports">🐛</a> <a href="#design-snipe" title="Design">🎨</a> <a href="https://github.com/snipe/snipe-it/pulls?q=is%3Apr+reviewed-by%3Asnipe" title="Reviewed Pull Requests">👀</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/1695622?v=3" width="110px;"/><br /><sub>Justin Kerr</sub>](http://jbirdkerr.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [<img src="https://avatars3.githubusercontent.com/u/11426176?v=3" width="110px;"/><br /><sub>Ira W. Snyder</sub>](http://www.irasnyder.com/devel/)<br />[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2475759?v=3" width="110px;"/><br /><sub>Aladin Alaily</sub>](https://github.com/aalaily)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [<img src="https://avatars0.githubusercontent.com/u/10247644?v=3" width="110px;"/><br /><sub>Chase Hansen</sub>](https://github.com/kobie-chasehansen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [<img src="https://avatars2.githubusercontent.com/u/13545400?v=3" width="110px;"/><br /><sub>IDM Helpdesk</sub>](https://github.com/IDM-Helpdesk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [<img src="https://avatars2.githubusercontent.com/u/614439?v=3" width="110px;"/><br /><sub>Kai</sub>](http://balticer.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [<img src="https://avatars1.githubusercontent.com/u/8762511?v=3" width="110px;"/><br /><sub>Michael Daniels</sub>](http://www.michaeldaniels.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") | <td align="center" valign="top" width="14.28%"><a href="http://www.uberbrady.com"><img src="https://avatars0.githubusercontent.com/u/36335?v=3?s=110" width="110px;" alt="Brady Wetherington"/><br /><sub><b>Brady Wetherington</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=uberbrady" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=uberbrady" title="Documentation">📖</a> <a href="#infra-uberbrady" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/snipe/snipe-it/pulls?q=is%3Apr+reviewed-by%3Auberbrady" title="Reviewed Pull Requests">👀</a></td>
| [<img src="https://avatars3.githubusercontent.com/u/1532660?v=3" width="110px;"/><br /><sub>Tom Castleman</sub>](http://tomcastleman.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [<img src="https://avatars3.githubusercontent.com/u/10723243?v=3" width="110px;"/><br /><sub>Daniel Nemanic</sub>](https://github.com/DanielNemanic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [<img src="https://avatars0.githubusercontent.com/u/150648?v=3" width="110px;"/><br /><sub>SouthWolf</sub>](https://github.com/southwolf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [<img src="https://avatars2.githubusercontent.com/u/131616?v=3" width="110px;"/><br /><sub>Ivar Nesje</sub>](https://github.com/ivarne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [<img src="https://avatars1.githubusercontent.com/u/62333?v=3" width="110px;"/><br /><sub>Jérémy Benoist</sub>](http://www.j0k3r.net)<br />[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/724344?v=3" width="110px;"/><br /><sub>Chris Leathley</sub>](https://github.com/cleathley)<br />[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/972498?v=3" width="110px;"/><br /><sub>splaer</sub>](https://github.com/splaer)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/dmeltzer"><img src="https://avatars0.githubusercontent.com/u/3803132?v=3?s=110" width="110px;" alt="Daniel Meltzer"/><br /><sub><b>Daniel Meltzer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/commits?author=dmeltzer" title="Documentation">📖</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/967362?v=3" width="110px;"/><br /><sub>Joe Ferguson</sub>](http://www.joeferguson.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [<img src="https://avatars3.githubusercontent.com/u/6108682?v=3" width="110px;"/><br /><sub>diwanicki</sub>](https://github.com/diwanicki)<br />[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/2527115?v=3" width="110px;"/><br /><sub>Lee Thoong Ching</sub>](https://github.com/pakkua80)<br />[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [<img src="https://avatars1.githubusercontent.com/u/461491?v=3" width="110px;"/><br /><sub>Marek Šuppa</sub>](http://shu.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [<img src="https://avatars1.githubusercontent.com/u/8693762?v=3" width="110px;"/><br /><sub>Juan J. Martinez</sub>](https://github.com/mizar1616)<br />[🌍](#translation-mizar1616 "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1458388?v=3" width="110px;"/><br /><sub>R Ryan Dial</sub>](https://github.com/rrdial)<br />[🌍](#translation-rrdial "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2871745?v=3" width="110px;"/><br /><sub>Andrej Manduch</sub>](https://github.com/burlito)<br />[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") | <td align="center" valign="top" width="14.28%"><a href="http://www.tuckertechonline.com"><img src="https://avatars0.githubusercontent.com/u/1609106?v=3?s=110" width="110px;" alt="Michael T"/><br /><sub><b>Michael T</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mtucker6784" title="Code">💻</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/8341172?v=3" width="110px;"/><br /><sub>Jay Richards</sub>](http://www.cordeos.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [<img src="https://avatars2.githubusercontent.com/u/7295127?v=3" width="110px;"/><br /><sub>Alexander Innes</sub>](https://necurity.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [<img src="https://avatars2.githubusercontent.com/u/334485?v=3" width="110px;"/><br /><sub>Danny Garcia</sub>](https://buzzedword.codes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [<img src="https://avatars2.githubusercontent.com/u/366855?v=3" width="110px;"/><br /><sub>archpoint</sub>](https://github.com/archpoint)<br />[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [<img src="https://avatars1.githubusercontent.com/u/67991?v=3" width="110px;"/><br /><sub>Jake McGraw</sub>](http://www.jakemcgraw.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [<img src="https://avatars1.githubusercontent.com/u/1714374?v=3" width="110px;"/><br /><sub>FleischKarussel</sub>](https://github.com/FleischKarussel)<br />[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/319644?v=3" width="110px;"/><br /><sub>Dylan Yi</sub>](https://github.com/feeva)<br />[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/madd15"><img src="https://avatars2.githubusercontent.com/u/3274937?v=3?s=110" width="110px;" alt="madd15"/><br /><sub><b>madd15</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=madd15" title="Documentation">📖</a> <a href="#question-madd15" title="Answering Questions">💬</a></td>
| [<img src="https://avatars2.githubusercontent.com/u/857740?v=3" width="110px;"/><br /><sub>Gil Rutkowski</sub>](http://FlashingCursor.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [<img src="https://avatars3.githubusercontent.com/u/129360?v=3" width="110px;"/><br /><sub>Desmond Morris</sub>](http://www.desmondmorris.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [<img src="https://avatars2.githubusercontent.com/u/52936?v=3" width="110px;"/><br /><sub>Nick Peelman</sub>](http://peelman.us)<br />[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [<img src="https://avatars0.githubusercontent.com/u/53161?v=3" width="110px;"/><br /><sub>Abraham Vegh</sub>](https://abrahamvegh.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [<img src="https://avatars0.githubusercontent.com/u/2818680?v=3" width="110px;"/><br /><sub>Mohamed Rashid</sub>](https://github.com/rashivkp)<br />[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1509456?v=3" width="110px;"/><br /><sub>Kasey</sub>](http://hinchk.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [<img src="https://avatars2.githubusercontent.com/u/10522541?v=3" width="110px;"/><br /><sub>Brett</sub>](https://github.com/BrettFagerlund)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/vsposato"><img src="https://avatars2.githubusercontent.com/u/894126?v=3?s=110" width="110px;" alt="Vincent Sposato"/><br /><sub><b>Vincent Sposato</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vsposato" title="Code">💻</a></td>
| [<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") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/vjandrea"><img src="https://avatars0.githubusercontent.com/u/1639757?v=3?s=110" width="110px;" alt="Andrea Bergamasco"/><br /><sub><b>Andrea Bergamasco</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vjandrea" title="Code">💻</a></td>
| [<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") | </tr>
| [<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") | <tr>
| [<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") [🌍](#translation-CronKz "Translation") | [<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") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/kpawelski"><img src="https://avatars0.githubusercontent.com/u/10640152?v=3?s=110" width="110px;" alt="Karol"/><br /><sub><b>Karol</b></sub></a><br /><a href="#translation-kpawelski" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=kpawelski" title="Code">💻</a></td>
| [<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") | <td align="center" valign="top" width="14.28%"><a href="http://blog.morph027.de/"><img src="https://avatars3.githubusercontent.com/u/600106?v=3?s=110" width="110px;" alt="morph027"/><br /><sub><b>morph027</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=morph027" title="Code">💻</a></td>
| [<img src="https://avatars2.githubusercontent.com/u/1795149?v=4" width="110px;"/><br /><sub>Till Deeke</sub>](https://tilldeeke.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [<img src="https://avatars0.githubusercontent.com/u/12634129?v=4" width="110px;"/><br /><sub>5quirrel</sub>](https://github.com/5quirrel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [<img src="https://avatars1.githubusercontent.com/u/13071957?v=4" width="110px;"/><br /><sub>Jason</sub>](https://github.com/jasonlshelton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [<img src="https://avatars3.githubusercontent.com/u/7128321?v=4" width="110px;"/><br /><sub>Antti</sub>](https://github.com/chemfy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [<img src="https://avatars3.githubusercontent.com/u/10080364?v=4" width="110px;"/><br /><sub>DeusMaximus</sub>](https://github.com/DeusMaximus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [<img src="https://avatars2.githubusercontent.com/u/16384611?v=4" width="110px;"/><br /><sub>a-royal</sub>](https://github.com/A-ROYAL)<br />[🌍](#translation-A-ROYAL "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5358208?v=4" width="110px;"/><br /><sub>Alberto Aldrigo</sub>](https://github.com/albertoaldrigo)<br />[🌍](#translation-albertoaldrigo "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/fvleminckx"><img src="https://avatars3.githubusercontent.com/u/22935755?v=3?s=110" width="110px;" alt="fvleminckx"/><br /><sub><b>fvleminckx</b></sub></a><br /><a href="#infra-fvleminckx" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/1412342?v=4" width="110px;"/><br /><sub>Alex Stanev</sub>](http://alex.stanev.org/blog)<br />[🌍](#translation-RealEnder "Translation") | [<img src="https://avatars0.githubusercontent.com/u/177295?v=4" width="110px;"/><br /><sub>Andreas Rehm</sub>](http://devel.itsolution2.de)<br />[🌍](#translation-sirrus "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5080535?v=4" width="110px;"/><br /><sub>Andreas Erhard</sub>](https://github.com/xelan)<br />[🌍](#translation-xelan "Translation") | [<img src="https://avatars2.githubusercontent.com/u/142350?v=4" width="110px;"/><br /><sub>Andrés Vanegas Jiménez</sub>](https://github.com/angeldeejay)<br />[🌍](#translation-angeldeejay "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3910403?v=4" width="110px;"/><br /><sub>Antonio Schiavon</sub>](https://github.com/aschiavon91)<br />[🌍](#translation-aschiavon91 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10464547?v=4" width="110px;"/><br /><sub>benunter</sub>](https://github.com/benunter)<br />[🌍](#translation-benunter "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5038647?v=4" width="110px;"/><br /><sub>Borys Żmuda</sub>](http://catweb24.pl)<br />[🌍](#translation-rudashi "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/itsupportcmsukorg"><img src="https://avatars2.githubusercontent.com/u/15633547?v=3?s=110" width="110px;" alt="itsupportcmsukorg"/><br /><sub><b>itsupportcmsukorg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg" title="Bug reports">🐛</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/5539359?v=4" width="110px;"/><br /><sub>chibacityblues</sub>](https://github.com/chibacityblues)<br />[🌍](#translation-chibacityblues "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1954830?v=4" width="110px;"/><br /><sub>Chien Wei Lin</sub>](https://github.com/cwlin0416)<br />[🌍](#translation-cwlin0416 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/11700533?v=4" width="110px;"/><br /><sub>Christian Schuster</sub>](https://github.com/Againstreality)<br />[🌍](#translation-Againstreality "Translation") | [<img src="https://avatars1.githubusercontent.com/u/4308704?v=4" width="110px;"/><br /><sub>Christian Stefanus</sub>](http://chriss.webhostid.com)<br />[🌍](#translation-kopi-item "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3009327?v=4" width="110px;"/><br /><sub>wxcafé</sub>](http://wxcafe.net)<br />[🌍](#translation-wxcafe "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35761525?v=4" width="110px;"/><br /><sub>dpyroc</sub>](https://github.com/dpyroc)<br />[🌍](#translation-dpyroc "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2153639?v=4" width="110px;"/><br /><sub>Daniel Friedlmaier</sub>](http://www.friedlmaier.net)<br />[🌍](#translation-da-friedl "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://override.io"><img src="https://avatars3.githubusercontent.com/u/12373799?v=3?s=110" width="110px;" alt="Frank"/><br /><sub><b>Frank</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=base-zero" title="Code">💻</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/2947640?v=4" width="110px;"/><br /><sub>Daniel Heene</sub>](https://github.com/danielheene)<br />[🌍](#translation-danielheene "Translation") | [<img src="https://avatars3.githubusercontent.com/u/319022?v=4" width="110px;"/><br /><sub>danielcb</sub>](https://github.com/danielcb)<br />[🌍](#translation-danielcb "Translation") | [<img src="https://avatars3.githubusercontent.com/u/15846537?v=4" width="110px;"/><br /><sub>Dominik Senti</sub>](https://github.com/dominiksenti)<br />[🌍](#translation-dominiksenti "Translation") | [<img src="https://avatars0.githubusercontent.com/u/25570954?v=4" width="110px;"/><br /><sub>Eric Gautheron</sub>](http://www.konectik.com)<br />[🌍](#translation-EpixFr "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5732623?v=4" width="110px;"/><br /><sub>Erlend Pilø</sub>](https://erlpil.com)<br />[🌍](#translation-Erlpil "Translation") | [<img src="https://avatars0.githubusercontent.com/u/541832?v=4" width="110px;"/><br /><sub>Fabio Rapposelli</sub>](http://fabio.technology)<br />[🌍](#translation-frapposelli "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3605240?v=4" width="110px;"/><br /><sub>Felipe Barros</sub>](https://github.com/fgbs)<br />[🌍](#translation-fgbs "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/ghost"><img src="https://avatars0.githubusercontent.com/u/10137?v=3?s=110" width="110px;" alt="Deleted user"/><br /><sub><b>Deleted user</b></sub></a><br /><a href="#translation-ghost" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=ghost" title="Code">💻</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/257745?v=4" width="110px;"/><br /><sub>Fernando Possebon</sub>](https://github.com/possebon)<br />[🌍](#translation-possebon "Translation") | [<img src="https://avatars3.githubusercontent.com/u/2540832?v=4" width="110px;"/><br /><sub>gdraque</sub>](https://github.com/gdraque)<br />[🌍](#translation-gdraque "Translation") | [<img src="https://avatars0.githubusercontent.com/u/23440381?v=4" width="110px;"/><br /><sub>Georg Wallisch</sub>](https://github.com/georgwallisch)<br />[🌍](#translation-georgwallisch "Translation") | [<img src="https://avatars1.githubusercontent.com/u/9852832?v=4" width="110px;"/><br /><sub>Gerardo Robles</sub>](https://github.com/jgroblesr85)<br />[🌍](#translation-jgroblesr85 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/11082640?v=4" width="110px;"/><br /><sub>Gluek</sub>](https://t.me/Gluek)<br />[🌍](#translation-mrgluek "Translation") | [<img src="https://avatars0.githubusercontent.com/u/6847946?v=4" width="110px;"/><br /><sub>AdnanAbuShahad</sub>](https://github.com/AdnanAbuShahad)<br />[🌍](#translation-AdnanAbuShahad "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3580608?v=4" width="110px;"/><br /><sub>Hafidzi My</sub>](https://hafidzi.my)<br />[🌍](#translation-hafidzi "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/tiagom62"><img src="https://avatars1.githubusercontent.com/u/10802313?v=3?s=110" width="110px;" alt="tiagom62"/><br /><sub><b>tiagom62</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tiagom62" title="Code">💻</a> <a href="#infra-tiagom62" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
| [<img src="https://avatars2.githubusercontent.com/u/205521?v=4" width="110px;"/><br /><sub>Harim Park</sub>](https://github.com/fofwisdom)<br />[🌍](#translation-fofwisdom "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3333841?v=4" width="110px;"/><br /><sub>Henrik Kentsson</sub>](http://www.kentsson.se)<br />[🌍](#translation-Kentsson "Translation") | [<img src="https://avatars0.githubusercontent.com/u/36551034?v=4" width="110px;"/><br /><sub>Husnul Yaqien</sub>](https://github.com/husnulyaqien)<br />[🌍](#translation-husnulyaqien "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2372747?v=4" width="110px;"/><br /><sub>Ibrahim</sub>](http://abaalkhail.org)<br />[🌍](#translation-abaalkh "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1389334?v=4" width="110px;"/><br /><sub>igolman</sub>](https://github.com/igolman)<br />[🌍](#translation-igolman "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3257070?v=4" width="110px;"/><br /><sub>itangiang</sub>](https://github.com/itangiang)<br />[🌍](#translation-itangiang "Translation") | [<img src="https://avatars2.githubusercontent.com/u/14814254?v=4" width="110px;"/><br /><sub>jarby1211</sub>](https://github.com/jarby1211)<br />[🌍](#translation-jarby1211 "Translation") | </tr>
| [<img src="https://avatars3.githubusercontent.com/u/6719357?v=4" width="110px;"/><br /><sub>Jhonn Willker</sub>](http://jwillker.com)<br />[🌍](#translation-JohnWillker "Translation") | [<img src="https://avatars2.githubusercontent.com/u/10983635?v=4" width="110px;"/><br /><sub>Jose</sub>](https://github.com/joxelito94)<br />[🌍](#translation-joxelito94 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5206122?v=4" width="110px;"/><br /><sub>laopangzi</sub>](https://github.com/laopangzi)<br />[🌍](#translation-laopangzi "Translation") | [<img src="https://avatars2.githubusercontent.com/u/79707?v=4" width="110px;"/><br /><sub>Lars Strojny</sub>](http://usrportage.de)<br />[🌍](#translation-lstrojny "Translation") | [<img src="https://avatars0.githubusercontent.com/u/389801?v=4" width="110px;"/><br /><sub>MarcosBL</sub>](http://twitter.com/marcosbl)<br />[🌍](#translation-MarcosBL "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35664606?v=4" width="110px;"/><br /><sub>marie joy cajes</sub>](https://github.com/mariejoyacajes)<br />[🌍](#translation-mariejoyacajes "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3052816?v=4" width="110px;"/><br /><sub>Mark S. Johansen</sub>](http://www.markjohansen.dk)<br />[🌍](#translation-msjohansen "Translation") | <tr>
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/rystaf"><img src="https://avatars3.githubusercontent.com/u/2389047?v=3?s=110" width="110px;" alt="Ryan Stafford"/><br /><sub><b>Ryan Stafford</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rystaf" title="Code">💻</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/ehanlon"><img src="https://avatars2.githubusercontent.com/u/10345935?v=3?s=110" width="110px;" alt="Eammon Hanlon"/><br /><sub><b>Eammon Hanlon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ehanlon" title="Code">💻</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/zjean"><img src="https://avatars0.githubusercontent.com/u/441924?v=3?s=110" width="110px;" alt="zjean"/><br /><sub><b>zjean</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zjean" title="Code">💻</a></td>
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") | <td align="center" valign="top" width="14.28%"><a href="http://www.frei.media"><img src="https://avatars0.githubusercontent.com/u/12660103?v=3?s=110" width="110px;" alt="Matthias Frei"/><br /><sub><b>Matthias Frei</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FREImedia" title="Code">💻</a></td>
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/opsydev"><img src="https://avatars0.githubusercontent.com/u/3767518?v=3?s=110" width="110px;" alt="opsydev"/><br /><sub><b>opsydev</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=opsydev" title="Code">💻</a></td>
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") | <td align="center" valign="top" width="14.28%"><a href="http://www.ddreier.com"><img src="https://avatars1.githubusercontent.com/u/82290?v=3?s=110" width="110px;" alt="Daniel Dreier"/><br /><sub><b>Daniel Dreier</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ddreier" title="Code">💻</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | <td align="center" valign="top" width="14.28%"><a href="http://rassie.org"><img src="https://avatars0.githubusercontent.com/u/23448?v=3?s=110" width="110px;" alt="Nikolai Prokoschenko"/><br /><sub><b>Nikolai Prokoschenko</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rassie" title="Code">💻</a></td>
| [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") | </tr>
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") | <tr>
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [<img src="https://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/YetAnotherCodeMonkey"><img src="https://avatars0.githubusercontent.com/u/13452757?v=3?s=110" width="110px;" alt="Drew"/><br /><sub><b>Drew</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/merid14"><img src="https://avatars0.githubusercontent.com/u/1342320?v=3?s=110" width="110px;" alt="Walter"/><br /><sub><b>Walter</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=merid14" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [<img src="https://avatars.githubusercontent.com/u/70129?v=4" width="110px;"/><br /><sub>Joel Pittet</sub>](https://pittet.ca)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [<img src="https://avatars.githubusercontent.com/u/792695?v=4" width="110px;"/><br /><sub>Eli Young</sub>](https://elyscape.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [<img src="https://avatars.githubusercontent.com/u/317015?v=4" width="110px;"/><br /><sub>Raell Dottin</sub>](https://github.com/raelldottin)<br />[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [<img src="https://avatars.githubusercontent.com/u/1446856?v=4" width="110px;"/><br /><sub>Tom Misilo</sub>](https://github.com/misilot)<br />[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [<img src="https://avatars.githubusercontent.com/u/4496300?v=4" width="110px;"/><br /><sub>David Davenne</sub>](http://david.davenne.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/balous"><img src="https://avatars3.githubusercontent.com/u/11254614?v=3?s=110" width="110px;" alt="Petr Baloun"/><br /><sub><b>Petr Baloun</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=balous" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/reidblomquist"><img src="https://avatars0.githubusercontent.com/u/6117660?v=3?s=110" width="110px;" alt="reidblomquist"/><br /><sub><b>reidblomquist</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=reidblomquist" title="Documentation">📖</a></td>
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/mathieuk"><img src="https://avatars0.githubusercontent.com/u/539914?v=3?s=110" width="110px;" alt="Mathieu Kooiman"/><br /><sub><b>Mathieu Kooiman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mathieuk" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/csayre"><img src="https://avatars3.githubusercontent.com/u/6606421?v=3?s=110" width="110px;" alt="csayre"/><br /><sub><b>csayre</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=csayre" title="Documentation">📖</a></td>
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/adamdunson"><img src="https://avatars1.githubusercontent.com/u/768488?v=3?s=110" width="110px;" alt="Adam Dunson"/><br /><sub><b>Adam Dunson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adamdunson" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") | </tr>
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") | <tr>
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/thehereward"><img src="https://avatars0.githubusercontent.com/u/5547470?v=3?s=110" width="110px;" alt="Hereward"/><br /><sub><b>Hereward</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thehereward" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/swoopdk"><img src="https://avatars0.githubusercontent.com/u/5802977?v=3?s=110" width="110px;" alt="swoopdk"/><br /><sub><b>swoopdk</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=swoopdk" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | <td align="center" valign="top" width="14.28%"><a href="https://linkedin.com/in/ahimta"><img src="https://avatars1.githubusercontent.com/u/3470403?v=3?s=110" width="110px;" alt="Abdullah Alansari"/><br /><sub><b>Abdullah Alansari</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Ahimta" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/MicaelRodrigues"><img src="https://avatars0.githubusercontent.com/u/796443?v=3?s=110" width="110px;" alt="Micael Rodrigues"/><br /><sub><b>Micael Rodrigues</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | <td align="center" valign="top" width="14.28%"><a href="http://macadmincorner.com"><img src="https://avatars0.githubusercontent.com/u/614564?v=3?s=110" width="110px;" alt="Patrick Gallagher"/><br /><sub><b>Patrick Gallagher</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=patgmac" title="Documentation">📖</a></td>
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/Miliamber"><img src="https://avatars3.githubusercontent.com/u/7165922?v=3?s=110" width="110px;" alt="Miliamber"/><br /><sub><b>Miliamber</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Miliamber" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | <td align="center" valign="top" width="14.28%"><a href="https://github.com/hawk554"><img src="https://avatars3.githubusercontent.com/u/861766?v=3?s=110" width="110px;" alt="hawk554"/><br /><sub><b>hawk554</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=hawk554" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | </tr>
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | <tr>
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | <td align="center" valign="top" width="14.28%"><a href="http://jbirdkerr.net"><img src="https://avatars1.githubusercontent.com/u/1695622?v=3?s=110" width="110px;" alt="Justin Kerr"/><br /><sub><b>Justin Kerr</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jbirdkerr" title="Code">💻</a></td>
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | <td align="center" valign="top" width="14.28%"><a href="http://www.irasnyder.com/devel/"><img src="https://avatars3.githubusercontent.com/u/11426176?v=3?s=110" width="110px;" alt="Ira W. Snyder"/><br /><sub><b>Ira W. Snyder</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=irasnyd" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aalaily"><img src="https://avatars2.githubusercontent.com/u/2475759?v=3?s=110" width="110px;" alt="Aladin Alaily"/><br /><sub><b>Aladin Alaily</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=aalaily" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kobie-chasehansen"><img src="https://avatars0.githubusercontent.com/u/10247644?v=3?s=110" width="110px;" alt="Chase Hansen"/><br /><sub><b>Chase Hansen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen" title="Code">💻</a> <a href="#question-kobie-chasehansen" title="Answering Questions">💬</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/IDM-Helpdesk"><img src="https://avatars2.githubusercontent.com/u/13545400?v=3?s=110" width="110px;" alt="IDM Helpdesk"/><br /><sub><b>IDM Helpdesk</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://balticer.de"><img src="https://avatars2.githubusercontent.com/u/614439?v=3?s=110" width="110px;" alt="Kai"/><br /><sub><b>Kai</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=balticer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.michaeldaniels.me"><img src="https://avatars1.githubusercontent.com/u/8762511?v=3?s=110" width="110px;" alt="Michael Daniels"/><br /><sub><b>Michael Daniels</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mdaniels5757" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://tomcastleman.me"><img src="https://avatars3.githubusercontent.com/u/1532660?v=3?s=110" width="110px;" alt="Tom Castleman"/><br /><sub><b>Tom Castleman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tomcastleman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DanielNemanic"><img src="https://avatars3.githubusercontent.com/u/10723243?v=3?s=110" width="110px;" alt="Daniel Nemanic"/><br /><sub><b>Daniel Nemanic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DanielNemanic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/southwolf"><img src="https://avatars0.githubusercontent.com/u/150648?v=3?s=110" width="110px;" alt="SouthWolf"/><br /><sub><b>SouthWolf</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=southwolf" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ivarne"><img src="https://avatars2.githubusercontent.com/u/131616?v=3?s=110" width="110px;" alt="Ivar Nesje"/><br /><sub><b>Ivar Nesje</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ivarne" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.j0k3r.net"><img src="https://avatars1.githubusercontent.com/u/62333?v=3?s=110" width="110px;" alt="Jérémy Benoist"/><br /><sub><b>Jérémy Benoist</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=j0k3r" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cleathley"><img src="https://avatars2.githubusercontent.com/u/724344?v=3?s=110" width="110px;" alt="Chris Leathley"/><br /><sub><b>Chris Leathley</b></sub></a><br /><a href="#infra-cleathley" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/splaer"><img src="https://avatars0.githubusercontent.com/u/972498?v=3?s=110" width="110px;" alt="splaer"/><br /><sub><b>splaer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/issues?q=author%3Asplaer" title="Bug reports">🐛</a> <a href="https://github.com/snipe/snipe-it/commits?author=splaer" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.joeferguson.me"><img src="https://avatars1.githubusercontent.com/u/967362?v=3?s=110" width="110px;" alt="Joe Ferguson"/><br /><sub><b>Joe Ferguson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=svpernova09" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/diwanicki"><img src="https://avatars3.githubusercontent.com/u/6108682?v=3?s=110" width="110px;" alt="diwanicki"/><br /><sub><b>diwanicki</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=diwanicki" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=diwanicki" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pakkua80"><img src="https://avatars3.githubusercontent.com/u/2527115?v=3?s=110" width="110px;" alt="Lee Thoong Ching"/><br /><sub><b>Lee Thoong Ching</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=pakkua80" title="Documentation">📖</a> <a href="https://github.com/snipe/snipe-it/commits?author=pakkua80" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://shu.io"><img src="https://avatars1.githubusercontent.com/u/461491?v=3?s=110" width="110px;" alt="Marek Šuppa"/><br /><sub><b>Marek Šuppa</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mrshu" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mizar1616"><img src="https://avatars1.githubusercontent.com/u/8693762?v=3?s=110" width="110px;" alt="Juan J. Martinez"/><br /><sub><b>Juan J. Martinez</b></sub></a><br /><a href="#translation-mizar1616" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rrdial"><img src="https://avatars1.githubusercontent.com/u/1458388?v=3?s=110" width="110px;" alt="R Ryan Dial"/><br /><sub><b>R Ryan Dial</b></sub></a><br /><a href="#translation-rrdial" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/burlito"><img src="https://avatars2.githubusercontent.com/u/2871745?v=3?s=110" width="110px;" alt="Andrej Manduch"/><br /><sub><b>Andrej Manduch</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=burlito" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.cordeos.com"><img src="https://avatars0.githubusercontent.com/u/8341172?v=3?s=110" width="110px;" alt="Jay Richards"/><br /><sub><b>Jay Richards</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=technogenus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://necurity.co.uk"><img src="https://avatars2.githubusercontent.com/u/7295127?v=3?s=110" width="110px;" alt="Alexander Innes"/><br /><sub><b>Alexander Innes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=leostat" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://buzzedword.codes"><img src="https://avatars2.githubusercontent.com/u/334485?v=3?s=110" width="110px;" alt="Danny Garcia"/><br /><sub><b>Danny Garcia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=buzzedword" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/archpoint"><img src="https://avatars2.githubusercontent.com/u/366855?v=3?s=110" width="110px;" alt="archpoint"/><br /><sub><b>archpoint</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=archpoint" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jakemcgraw.com"><img src="https://avatars1.githubusercontent.com/u/67991?v=3?s=110" width="110px;" alt="Jake McGraw"/><br /><sub><b>Jake McGraw</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jakemcgraw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FleischKarussel"><img src="https://avatars1.githubusercontent.com/u/1714374?v=3?s=110" width="110px;" alt="FleischKarussel"/><br /><sub><b>FleischKarussel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FleischKarussel" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/feeva"><img src="https://avatars3.githubusercontent.com/u/319644?v=3?s=110" width="110px;" alt="Dylan Yi"/><br /><sub><b>Dylan Yi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=feeva" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://FlashingCursor.com"><img src="https://avatars2.githubusercontent.com/u/857740?v=3?s=110" width="110px;" alt="Gil Rutkowski"/><br /><sub><b>Gil Rutkowski</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=flashingcursor" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.desmondmorris.com"><img src="https://avatars3.githubusercontent.com/u/129360?v=3?s=110" width="110px;" alt="Desmond Morris"/><br /><sub><b>Desmond Morris</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=desmondmorris" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://peelman.us"><img src="https://avatars2.githubusercontent.com/u/52936?v=3?s=110" width="110px;" alt="Nick Peelman"/><br /><sub><b>Nick Peelman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=peelman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://abrahamvegh.com"><img src="https://avatars0.githubusercontent.com/u/53161?v=3?s=110" width="110px;" alt="Abraham Vegh"/><br /><sub><b>Abraham Vegh</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=abrahamvegh" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rashivkp"><img src="https://avatars0.githubusercontent.com/u/2818680?v=3?s=110" width="110px;" alt="Mohamed Rashid"/><br /><sub><b>Mohamed Rashid</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rashivkp" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hinchk.github.io"><img src="https://avatars3.githubusercontent.com/u/1509456?v=3?s=110" width="110px;" alt="Kasey"/><br /><sub><b>Kasey</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=HinchK" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BrettFagerlund"><img src="https://avatars2.githubusercontent.com/u/10522541?v=3?s=110" width="110px;" alt="Brett"/><br /><sub><b>Brett</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BrettFagerlund" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://jasonspriggs.com"><img src="https://avatars2.githubusercontent.com/u/16108587?v=3?s=110" width="110px;" alt="Jason Spriggs"/><br /><sub><b>Jason Spriggs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jasonspriggs" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://n8felton.wordpress.com"><img src="https://avatars2.githubusercontent.com/u/1134568?v=3?s=110" width="110px;" alt="Nate Felton"/><br /><sub><b>Nate Felton</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=n8felton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://homepages.dcc.ufmg.br/~manassesferreira"><img src="https://avatars2.githubusercontent.com/u/14036694?v=3?s=110" width="110px;" alt="Manasses Ferreira"/><br /><sub><b>Manasses Ferreira</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=manassesferreira" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/steveelwood"><img src="https://avatars0.githubusercontent.com/u/15913949?v=3?s=110" width="110px;" alt="Steve"/><br /><sub><b>Steve</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=steveelwood" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/matc"><img src="https://avatars1.githubusercontent.com/u/3361683?v=3?s=110" width="110px;" alt="matc"/><br /><sub><b>matc</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=matc" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.davisracingteam.com"><img src="https://avatars3.githubusercontent.com/u/7405702?v=3?s=110" width="110px;" alt="Cole R. Davis"/><br /><sub><b>Cole R. Davis</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gibsonjoshua55"><img src="https://avatars2.githubusercontent.com/u/10167681?v=3?s=110" width="110px;" alt="gibsonjoshua55"/><br /><sub><b>gibsonjoshua55</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zwerch"><img src="https://avatars2.githubusercontent.com/u/2809241?v=4?s=110" width="110px;" alt="Robin Temme"/><br /><sub><b>Robin Temme</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zwerch" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imanghafoori1"><img src="https://avatars0.githubusercontent.com/u/6961695?v=4?s=110" width="110px;" alt="Iman"/><br /><sub><b>Iman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=imanghafoori1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/richardhofman6"><img src="https://avatars1.githubusercontent.com/u/6551003?v=4?s=110" width="110px;" alt="Richard Hofman"/><br /><sub><b>Richard Hofman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=richardhofman6" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gizzmojr"><img src="https://avatars0.githubusercontent.com/u/3697569?v=4?s=110" width="110px;" alt="gizzmojr"/><br /><sub><b>gizzmojr</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gizzmojr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/imjennyli"><img src="https://avatars3.githubusercontent.com/u/404729?v=4?s=110" width="110px;" alt="Jenny Li"/><br /><sub><b>Jenny Li</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=imjennyli" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeoffYoung"><img src="https://avatars0.githubusercontent.com/u/869227?v=4?s=110" width="110px;" alt="Geoff Young"/><br /><sub><b>Geoff Young</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=GeoffYoung" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.elliotblackburn.com"><img src="https://avatars3.githubusercontent.com/u/1068477?v=4?s=110" width="110px;" alt="Elliot Blackburn"/><br /><sub><b>Elliot Blackburn</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BlueHatbRit" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://andmemasin.eu"><img src="https://avatars1.githubusercontent.com/u/6357451?v=4?s=110" width="110px;" alt="Tõnis Ormisson"/><br /><sub><b>Tõnis Ormisson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TonisOrmisson" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.nicolai-essig.de"><img src="https://avatars0.githubusercontent.com/u/449411?v=4?s=110" width="110px;" alt="Nicolai Essig"/><br /><sub><b>Nicolai Essig</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thakilla" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/techincolor"><img src="https://avatars1.githubusercontent.com/u/14809698?v=4?s=110" width="110px;" alt="Danielle"/><br /><sub><b>Danielle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=techincolor" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TheVakman"><img src="https://avatars1.githubusercontent.com/u/18545156?v=4?s=110" width="110px;" alt="Lawrence"/><br /><sub><b>Lawrence</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TheVakman" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/uknzaeinozpas"><img src="https://avatars1.githubusercontent.com/u/22473767?v=4?s=110" width="110px;" alt="uknzaeinozpas"/><br /><sub><b>uknzaeinozpas</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas" title="Tests">⚠️</a> <a href="https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Gelob"><img src="https://avatars3.githubusercontent.com/u/422752?v=4?s=110" width="110px;" alt="Ryan"/><br /><sub><b>Ryan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Gelob" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vcordes79"><img src="https://avatars1.githubusercontent.com/u/10672546?v=4?s=110" width="110px;" alt="vcordes79"/><br /><sub><b>vcordes79</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vcordes79" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fordster78"><img src="https://avatars3.githubusercontent.com/u/27958330?v=4?s=110" width="110px;" alt="fordster78"/><br /><sub><b>fordster78</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fordster78" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CronKz"><img src="https://avatars0.githubusercontent.com/u/34064225?v=4?s=110" width="110px;" alt="CronKz"/><br /><sub><b>CronKz</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=CronKz" title="Code">💻</a> <a href="#translation-CronKz" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tdb"><img src="https://avatars1.githubusercontent.com/u/585486?v=4?s=110" width="110px;" alt="Tim Bishop"/><br /><sub><b>Tim Bishop</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tdb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.seanmcilvenna.com"><img src="https://avatars2.githubusercontent.com/u/5384694?v=4?s=110" width="110px;" alt="Sean McIlvenna"/><br /><sub><b>Sean McIlvenna</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=seanmcilvenna" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cepacs"><img src="https://avatars3.githubusercontent.com/u/36515590?v=4?s=110" width="110px;" alt="cepacs"/><br /><sub><b>cepacs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/issues?q=author%3Acepacs" title="Bug reports">🐛</a> <a href="https://github.com/snipe/snipe-it/commits?author=cepacs" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lea-mink"><img src="https://avatars2.githubusercontent.com/u/37537300?v=4?s=110" width="110px;" alt="lea-mink"/><br /><sub><b>lea-mink</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lea-mink" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hannahtinkler"><img src="https://avatars0.githubusercontent.com/u/7140719?v=4?s=110" width="110px;" alt="Hannah Tinkler"/><br /><sub><b>Hannah Tinkler</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=hannahtinkler" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doekman"><img src="https://avatars1.githubusercontent.com/u/1086388?v=4?s=110" width="110px;" alt="Doeke Zanstra"/><br /><sub><b>Doeke Zanstra</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=doekman" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.sdhd.nl/"><img src="https://avatars1.githubusercontent.com/u/4325936?v=4?s=110" width="110px;" alt="Djamon Staal"/><br /><sub><b>Djamon Staal</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=SjamonDaal" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EarlRamirez"><img src="https://avatars3.githubusercontent.com/u/12306859?v=4?s=110" width="110px;" alt="Earl Ramirez"/><br /><sub><b>Earl Ramirez</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=EarlRamirez" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RichardRay"><img src="https://avatars2.githubusercontent.com/u/8671456?v=4?s=110" width="110px;" alt="Richard Ray Thomas"/><br /><sub><b>Richard Ray Thomas</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=RichardRay" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.taisun.io/"><img src="https://avatars3.githubusercontent.com/u/1852688?v=4?s=110" width="110px;" alt="Ryan Kuba"/><br /><sub><b>Ryan Kuba</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=thelamer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ParadoxGuitarist"><img src="https://avatars1.githubusercontent.com/u/6751928?v=4?s=110" width="110px;" alt="Brian Monroe"/><br /><sub><b>Brian Monroe</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/plexorama"><img src="https://avatars1.githubusercontent.com/u/605167?v=4?s=110" width="110px;" alt="plexorama"/><br /><sub><b>plexorama</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=plexorama" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://tilldeeke.de"><img src="https://avatars2.githubusercontent.com/u/1795149?v=4?s=110" width="110px;" alt="Till Deeke"/><br /><sub><b>Till Deeke</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tilldeeke" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/5quirrel"><img src="https://avatars0.githubusercontent.com/u/12634129?v=4?s=110" width="110px;" alt="5quirrel"/><br /><sub><b>5quirrel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=5quirrel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jasonlshelton"><img src="https://avatars1.githubusercontent.com/u/13071957?v=4?s=110" width="110px;" alt="Jason"/><br /><sub><b>Jason</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jasonlshelton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chemfy"><img src="https://avatars3.githubusercontent.com/u/7128321?v=4?s=110" width="110px;" alt="Antti"/><br /><sub><b>Antti</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chemfy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DeusMaximus"><img src="https://avatars3.githubusercontent.com/u/10080364?v=4?s=110" width="110px;" alt="DeusMaximus"/><br /><sub><b>DeusMaximus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=DeusMaximus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/A-ROYAL"><img src="https://avatars2.githubusercontent.com/u/16384611?v=4?s=110" width="110px;" alt="a-royal"/><br /><sub><b>a-royal</b></sub></a><br /><a href="#translation-A-ROYAL" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/albertoaldrigo"><img src="https://avatars0.githubusercontent.com/u/5358208?v=4?s=110" width="110px;" alt="Alberto Aldrigo"/><br /><sub><b>Alberto Aldrigo</b></sub></a><br /><a href="#translation-albertoaldrigo" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://alex.stanev.org/blog"><img src="https://avatars0.githubusercontent.com/u/1412342?v=4?s=110" width="110px;" alt="Alex Stanev"/><br /><sub><b>Alex Stanev</b></sub></a><br /><a href="#translation-RealEnder" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://devel.itsolution2.de"><img src="https://avatars0.githubusercontent.com/u/177295?v=4?s=110" width="110px;" alt="Andreas Rehm"/><br /><sub><b>Andreas Rehm</b></sub></a><br /><a href="#translation-sirrus" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xelan"><img src="https://avatars0.githubusercontent.com/u/5080535?v=4?s=110" width="110px;" alt="Andreas Erhard"/><br /><sub><b>Andreas Erhard</b></sub></a><br /><a href="#translation-xelan" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/angeldeejay"><img src="https://avatars2.githubusercontent.com/u/142350?v=4?s=110" width="110px;" alt="Andrés Vanegas Jiménez"/><br /><sub><b>Andrés Vanegas Jiménez</b></sub></a><br /><a href="#translation-angeldeejay" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aschiavon91"><img src="https://avatars0.githubusercontent.com/u/3910403?v=4?s=110" width="110px;" alt="Antonio Schiavon"/><br /><sub><b>Antonio Schiavon</b></sub></a><br /><a href="#translation-aschiavon91" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benunter"><img src="https://avatars0.githubusercontent.com/u/10464547?v=4?s=110" width="110px;" alt="benunter"/><br /><sub><b>benunter</b></sub></a><br /><a href="#translation-benunter" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://catweb24.pl"><img src="https://avatars1.githubusercontent.com/u/5038647?v=4?s=110" width="110px;" alt="Borys Żmuda"/><br /><sub><b>Borys Żmuda</b></sub></a><br /><a href="#translation-rudashi" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chibacityblues"><img src="https://avatars0.githubusercontent.com/u/5539359?v=4?s=110" width="110px;" alt="chibacityblues"/><br /><sub><b>chibacityblues</b></sub></a><br /><a href="#translation-chibacityblues" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cwlin0416"><img src="https://avatars1.githubusercontent.com/u/1954830?v=4?s=110" width="110px;" alt="Chien Wei Lin"/><br /><sub><b>Chien Wei Lin</b></sub></a><br /><a href="#translation-cwlin0416" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Againstreality"><img src="https://avatars3.githubusercontent.com/u/11700533?v=4?s=110" width="110px;" alt="Christian Schuster"/><br /><sub><b>Christian Schuster</b></sub></a><br /><a href="#translation-Againstreality" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://chriss.webhostid.com"><img src="https://avatars1.githubusercontent.com/u/4308704?v=4?s=110" width="110px;" alt="Christian Stefanus"/><br /><sub><b>Christian Stefanus</b></sub></a><br /><a href="#translation-kopi-item" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://wxcafe.net"><img src="https://avatars3.githubusercontent.com/u/3009327?v=4?s=110" width="110px;" alt="wxcafé"/><br /><sub><b>wxcafé</b></sub></a><br /><a href="#translation-wxcafe" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dpyroc"><img src="https://avatars3.githubusercontent.com/u/35761525?v=4?s=110" width="110px;" alt="dpyroc"/><br /><sub><b>dpyroc</b></sub></a><br /><a href="#translation-dpyroc" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.friedlmaier.net"><img src="https://avatars1.githubusercontent.com/u/2153639?v=4?s=110" width="110px;" alt="Daniel Friedlmaier"/><br /><sub><b>Daniel Friedlmaier</b></sub></a><br /><a href="#translation-da-friedl" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/danielheene"><img src="https://avatars1.githubusercontent.com/u/2947640?v=4?s=110" width="110px;" alt="Daniel Heene"/><br /><sub><b>Daniel Heene</b></sub></a><br /><a href="#translation-danielheene" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/danielcb"><img src="https://avatars3.githubusercontent.com/u/319022?v=4?s=110" width="110px;" alt="danielcb"/><br /><sub><b>danielcb</b></sub></a><br /><a href="#translation-danielcb" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dominiksenti"><img src="https://avatars3.githubusercontent.com/u/15846537?v=4?s=110" width="110px;" alt="Dominik Senti"/><br /><sub><b>Dominik Senti</b></sub></a><br /><a href="#translation-dominiksenti" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.konectik.com"><img src="https://avatars0.githubusercontent.com/u/25570954?v=4?s=110" width="110px;" alt="Eric Gautheron"/><br /><sub><b>Eric Gautheron</b></sub></a><br /><a href="#translation-EpixFr" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://erlpil.com"><img src="https://avatars1.githubusercontent.com/u/5732623?v=4?s=110" width="110px;" alt="Erlend Pilø"/><br /><sub><b>Erlend Pilø</b></sub></a><br /><a href="#translation-Erlpil" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://fabio.technology"><img src="https://avatars0.githubusercontent.com/u/541832?v=4?s=110" width="110px;" alt="Fabio Rapposelli"/><br /><sub><b>Fabio Rapposelli</b></sub></a><br /><a href="#translation-frapposelli" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fgbs"><img src="https://avatars2.githubusercontent.com/u/3605240?v=4?s=110" width="110px;" alt="Felipe Barros"/><br /><sub><b>Felipe Barros</b></sub></a><br /><a href="#translation-fgbs" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/possebon"><img src="https://avatars0.githubusercontent.com/u/257745?v=4?s=110" width="110px;" alt="Fernando Possebon"/><br /><sub><b>Fernando Possebon</b></sub></a><br /><a href="#translation-possebon" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gdraque"><img src="https://avatars3.githubusercontent.com/u/2540832?v=4?s=110" width="110px;" alt="gdraque"/><br /><sub><b>gdraque</b></sub></a><br /><a href="#translation-gdraque" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/georgwallisch"><img src="https://avatars0.githubusercontent.com/u/23440381?v=4?s=110" width="110px;" alt="Georg Wallisch"/><br /><sub><b>Georg Wallisch</b></sub></a><br /><a href="#translation-georgwallisch" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jgroblesr85"><img src="https://avatars1.githubusercontent.com/u/9852832?v=4?s=110" width="110px;" alt="Gerardo Robles"/><br /><sub><b>Gerardo Robles</b></sub></a><br /><a href="#translation-jgroblesr85" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://t.me/Gluek"><img src="https://avatars2.githubusercontent.com/u/11082640?v=4?s=110" width="110px;" alt="Gluek"/><br /><sub><b>Gluek</b></sub></a><br /><a href="#translation-mrgluek" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AdnanAbuShahad"><img src="https://avatars0.githubusercontent.com/u/6847946?v=4?s=110" width="110px;" alt="AdnanAbuShahad"/><br /><sub><b>AdnanAbuShahad</b></sub></a><br /><a href="#translation-AdnanAbuShahad" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://hafidzi.my"><img src="https://avatars1.githubusercontent.com/u/3580608?v=4?s=110" width="110px;" alt="Hafidzi My"/><br /><sub><b>Hafidzi My</b></sub></a><br /><a href="#translation-hafidzi" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fofwisdom"><img src="https://avatars2.githubusercontent.com/u/205521?v=4?s=110" width="110px;" alt="Harim Park"/><br /><sub><b>Harim Park</b></sub></a><br /><a href="#translation-fofwisdom" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.kentsson.se"><img src="https://avatars2.githubusercontent.com/u/3333841?v=4?s=110" width="110px;" alt="Henrik Kentsson"/><br /><sub><b>Henrik Kentsson</b></sub></a><br /><a href="#translation-Kentsson" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/husnulyaqien"><img src="https://avatars0.githubusercontent.com/u/36551034?v=4?s=110" width="110px;" alt="Husnul Yaqien"/><br /><sub><b>Husnul Yaqien</b></sub></a><br /><a href="#translation-husnulyaqien" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://abaalkhail.org"><img src="https://avatars1.githubusercontent.com/u/2372747?v=4?s=110" width="110px;" alt="Ibrahim"/><br /><sub><b>Ibrahim</b></sub></a><br /><a href="#translation-abaalkh" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/igolman"><img src="https://avatars0.githubusercontent.com/u/1389334?v=4?s=110" width="110px;" alt="igolman"/><br /><sub><b>igolman</b></sub></a><br /><a href="#translation-igolman" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/itangiang"><img src="https://avatars1.githubusercontent.com/u/3257070?v=4?s=110" width="110px;" alt="itangiang"/><br /><sub><b>itangiang</b></sub></a><br /><a href="#translation-itangiang" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jarby1211"><img src="https://avatars2.githubusercontent.com/u/14814254?v=4?s=110" width="110px;" alt="jarby1211"/><br /><sub><b>jarby1211</b></sub></a><br /><a href="#translation-jarby1211" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://jwillker.com"><img src="https://avatars3.githubusercontent.com/u/6719357?v=4?s=110" width="110px;" alt="Jhonn Willker"/><br /><sub><b>Jhonn Willker</b></sub></a><br /><a href="#translation-JohnWillker" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/joxelito94"><img src="https://avatars2.githubusercontent.com/u/10983635?v=4?s=110" width="110px;" alt="Jose"/><br /><sub><b>Jose</b></sub></a><br /><a href="#translation-joxelito94" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/laopangzi"><img src="https://avatars0.githubusercontent.com/u/5206122?v=4?s=110" width="110px;" alt="laopangzi"/><br /><sub><b>laopangzi</b></sub></a><br /><a href="#translation-laopangzi" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://usrportage.de"><img src="https://avatars2.githubusercontent.com/u/79707?v=4?s=110" width="110px;" alt="Lars Strojny"/><br /><sub><b>Lars Strojny</b></sub></a><br /><a href="#translation-lstrojny" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/marcosbl"><img src="https://avatars0.githubusercontent.com/u/389801?v=4?s=110" width="110px;" alt="MarcosBL"/><br /><sub><b>MarcosBL</b></sub></a><br /><a href="#translation-MarcosBL" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mariejoyacajes"><img src="https://avatars3.githubusercontent.com/u/35664606?v=4?s=110" width="110px;" alt="marie joy cajes"/><br /><sub><b>marie joy cajes</b></sub></a><br /><a href="#translation-mariejoyacajes" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.markjohansen.dk"><img src="https://avatars2.githubusercontent.com/u/3052816?v=4?s=110" width="110px;" alt="Mark S. Johansen"/><br /><sub><b>Mark S. Johansen</b></sub></a><br /><a href="#translation-msjohansen" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://martinstub.dk"><img src="https://avatars2.githubusercontent.com/u/982885?v=4?s=110" width="110px;" alt="Martin Stub"/><br /><sub><b>Martin Stub</b></sub></a><br /><a href="#translation-stubben" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/meyerf99"><img src="https://avatars2.githubusercontent.com/u/28959963?v=4?s=110" width="110px;" alt="Meyer Flavio"/><br /><sub><b>Meyer Flavio</b></sub></a><br /><a href="#translation-meyerf99" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MicaelRodrigues"><img src="https://avatars3.githubusercontent.com/u/796443?v=4?s=110" width="110px;" alt="Micael Rodrigues"/><br /><sub><b>Micael Rodrigues</b></sub></a><br /><a href="#translation-MicaelRodrigues" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://rubixy.com/"><img src="https://avatars0.githubusercontent.com/u/10481331?v=4?s=110" width="110px;" alt="Mikael Rasmussen"/><br /><sub><b>Mikael Rasmussen</b></sub></a><br /><a href="#translation-mikaelssen" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/IxFail"><img src="https://avatars1.githubusercontent.com/u/1544552?v=4?s=110" width="110px;" alt="IxFail"/><br /><sub><b>IxFail</b></sub></a><br /><a href="#translation-IxFail" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.mohammedfota.com"><img src="https://avatars3.githubusercontent.com/u/18483118?v=4?s=110" width="110px;" alt="Mohammed Fota"/><br /><sub><b>Mohammed Fota</b></sub></a><br /><a href="#translation-MohammedFota" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/omego"><img src="https://avatars0.githubusercontent.com/u/227080?v=4?s=110" width="110px;" alt="Moayad Alserihi"/><br /><sub><b>Moayad Alserihi</b></sub></a><br /><a href="#translation-omego" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/saymd"><img src="https://avatars0.githubusercontent.com/u/1680266?v=4?s=110" width="110px;" alt="saymd"/><br /><sub><b>saymd</b></sub></a><br /><a href="#translation-saymd" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nordsken.se"><img src="https://avatars0.githubusercontent.com/u/1826808?v=4?s=110" width="110px;" alt="Patrik Larsson"/><br /><sub><b>Patrik Larsson</b></sub></a><br /><a href="#translation-pooot" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/drcryo"><img src="https://avatars1.githubusercontent.com/u/20584746?v=4?s=110" width="110px;" alt="drcryo"/><br /><sub><b>drcryo</b></sub></a><br /><a href="#translation-drcryo" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pawel1615"><img src="https://avatars1.githubusercontent.com/u/19408004?v=4?s=110" width="110px;" alt="pawel1615"/><br /><sub><b>pawel1615</b></sub></a><br /><a href="#translation-pawel1615" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bodrovics"><img src="https://avatars2.githubusercontent.com/u/23340468?v=4?s=110" width="110px;" alt="bodrovics"/><br /><sub><b>bodrovics</b></sub></a><br /><a href="#translation-bodrovics" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/priatna"><img src="https://avatars0.githubusercontent.com/u/3257654?v=4?s=110" width="110px;" alt="priatna"/><br /><sub><b>priatna</b></sub></a><br /><a href="#translation-priatna" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://amayume.net"><img src="https://avatars1.githubusercontent.com/u/5358374?v=4?s=110" width="110px;" alt="Fan Jiang"/><br /><sub><b>Fan Jiang</b></sub></a><br /><a href="#translation-ProfFan" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ragnarcx"><img src="https://avatars1.githubusercontent.com/u/22555451?v=4?s=110" width="110px;" alt="ragnarcx"/><br /><sub><b>ragnarcx</b></sub></a><br /><a href="#translation-ragnarcx" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.reinvanhaaren.nl/"><img src="https://avatars2.githubusercontent.com/u/18654582?v=4?s=110" width="110px;" alt="Rein van Haaren"/><br /><sub><b>Rein van Haaren</b></sub></a><br /><a href="#translation-reinvanhaaren" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://dheche.songolimo.net"><img src="https://avatars1.githubusercontent.com/u/386672?v=4?s=110" width="110px;" alt="Teguh Dwicaksana"/><br /><sub><b>Teguh Dwicaksana</b></sub></a><br /><a href="#translation-dheche" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/FRaccie"><img src="https://avatars2.githubusercontent.com/u/2572552?v=4?s=110" width="110px;" alt="fraccie"/><br /><sub><b>fraccie</b></sub></a><br /><a href="#translation-FRaccie" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinzruzell"><img src="https://avatars0.githubusercontent.com/u/35182720?v=4?s=110" width="110px;" alt="vinzruzell"/><br /><sub><b>vinzruzell</b></sub></a><br /><a href="#translation-vinzruzell" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://kevinaustin.com"><img src="https://avatars1.githubusercontent.com/u/7883603?v=4?s=110" width="110px;" alt="Kevin Austin"/><br /><sub><b>Kevin Austin</b></sub></a><br /><a href="#translation-vipsystem" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://azuraweb.xyz"><img src="https://avatars3.githubusercontent.com/u/3861828?v=4?s=110" width="110px;" alt="Wira Sandy"/><br /><sub><b>Wira Sandy</b></sub></a><br /><a href="#translation-wira-sandy" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GrayHoax"><img src="https://avatars2.githubusercontent.com/u/8663789?v=4?s=110" width="110px;" alt="Илья"/><br /><sub><b>Илья</b></sub></a><br /><a href="#translation-GrayHoax" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/godusevpn"><img src="https://avatars3.githubusercontent.com/u/30119111?v=4?s=110" width="110px;" alt="GodUseVPN"/><br /><sub><b>GodUseVPN</b></sub></a><br /><a href="#translation-godusevpn" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EngrZhou"><img src="https://avatars1.githubusercontent.com/u/745576?v=4?s=110" width="110px;" alt="周周"/><br /><sub><b>周周</b></sub></a><br /><a href="#translation-EngrZhou" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/takuy"><img src="https://avatars3.githubusercontent.com/u/1631095?v=4?s=110" width="110px;" alt="Sam"/><br /><sub><b>Sam</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=takuy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.illisian.com.au"><img src="https://avatars1.githubusercontent.com/u/264022?v=4?s=110" width="110px;" alt="Azerothian"/><br /><sub><b>Azerothian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Azerothian" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://macfoo.wordpress.com/"><img src="https://avatars1.githubusercontent.com/u/4930051?v=4?s=110" width="110px;" alt="Wes Hulette"/><br /><sub><b>Wes Hulette</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jwhulette" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patrict"><img src="https://avatars0.githubusercontent.com/u/8134591?v=4?s=110" width="110px;" alt="patrict"/><br /><sub><b>patrict</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=patrict" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VELIKII-DIVAN"><img src="https://avatars3.githubusercontent.com/u/2611616?v=4?s=110" width="110px;" alt="Dmitriy Minaev"/><br /><sub><b>Dmitriy Minaev</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/liquidhorse"><img src="https://avatars0.githubusercontent.com/u/5132245?v=4?s=110" width="110px;" alt="liquidhorse"/><br /><sub><b>liquidhorse</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=liquidhorse" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://seld.be/"><img src="https://avatars1.githubusercontent.com/u/183678?v=4?s=110" width="110px;" alt="Jordi Boggiano"/><br /><sub><b>Jordi Boggiano</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Seldaek" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/inietov"><img src="https://avatars0.githubusercontent.com/u/653557?v=4?s=110" width="110px;" alt="Ivan Nieto"/><br /><sub><b>Ivan Nieto</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=inietov" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benrubson"><img src="https://avatars2.githubusercontent.com/u/6764151?v=4?s=110" width="110px;" alt="Ben RUBSON"/><br /><sub><b>Ben RUBSON</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=benrubson" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NMathar"><img src="https://avatars2.githubusercontent.com/u/8554558?v=4?s=110" width="110px;" alt="NMathar"/><br /><sub><b>NMathar</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=NMathar" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/smb"><img src="https://avatars1.githubusercontent.com/u/139566?v=4?s=110" width="110px;" alt="Steffen"/><br /><sub><b>Steffen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=smb" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Sxderp"><img src="https://avatars0.githubusercontent.com/u/6609453?v=4?s=110" width="110px;" alt="Sxderp"/><br /><sub><b>Sxderp</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Sxderp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fanta8897"><img src="https://avatars1.githubusercontent.com/u/4807843?v=4?s=110" width="110px;" alt="fanta8897"/><br /><sub><b>fanta8897</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fanta8897" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://andreybolonin.com/phpconsulting/"><img src="https://avatars2.githubusercontent.com/u/2576509?v=4?s=110" width="110px;" alt="Andrey Bolonin"/><br /><sub><b>Andrey Bolonin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andreybolonin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.shinayoshi.net/"><img src="https://avatars3.githubusercontent.com/u/2173307?v=4?s=110" width="110px;" alt="shinayoshi"/><br /><sub><b>shinayoshi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=shinayoshi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/reuser"><img src="https://avatars3.githubusercontent.com/u/2130159?v=4?s=110" width="110px;" alt="Hubert"/><br /><sub><b>Hubert</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=reuser" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://brashear.me"><img src="https://avatars0.githubusercontent.com/u/6865789?v=4?s=110" width="110px;" alt="KeenRivals"/><br /><sub><b>KeenRivals</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=KeenRivals" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/omyno"><img src="https://avatars3.githubusercontent.com/u/2902513?v=4?s=110" width="110px;" alt="omyno"/><br /><sub><b>omyno</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=omyno" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jackka"><img src="https://avatars1.githubusercontent.com/u/6271335?v=4?s=110" width="110px;" alt="Evgeny"/><br /><sub><b>Evgeny</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jackka" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://digitalist.se"><img src="https://avatars2.githubusercontent.com/u/1169963?v=4?s=110" width="110px;" alt="Colin Campbell"/><br /><sub><b>Colin Campbell</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=colin-campbell" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lubo"><img src="https://avatars3.githubusercontent.com/u/2872098?v=4?s=110" width="110px;" alt="Ľubomír Kučera"/><br /><sub><b>Ľubomír Kučera</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lubo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.sourceguru.net"><img src="https://avatars3.githubusercontent.com/u/570639?v=4?s=110" width="110px;" alt="Martin Meredith"/><br /><sub><b>Martin Meredith</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Mezzle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/timothyfarmer"><img src="https://avatars1.githubusercontent.com/u/7632599?v=4?s=110" width="110px;" alt="Tim Farmer"/><br /><sub><b>Tim Farmer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=timothyfarmer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mskrip"><img src="https://avatars0.githubusercontent.com/u/17459600?v=4?s=110" width="110px;" alt="Marián Skrip"/><br /><sub><b>Marián Skrip</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mskrip" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Godmartinz"><img src="https://avatars2.githubusercontent.com/u/47435081?v=4?s=110" width="110px;" alt="Godfrey Martinez"/><br /><sub><b>Godfrey Martinez</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Godmartinz" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bigtreeEdo"><img src="https://avatars1.githubusercontent.com/u/2075128?v=4?s=110" width="110px;" alt="bigtreeEdo"/><br /><sub><b>bigtreeEdo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bigtreeEdo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://colinmcneil.me/"><img src="https://avatars0.githubusercontent.com/u/5000430?v=4?s=110" width="110px;" alt="Colin McNeil"/><br /><sub><b>Colin McNeil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ColinMcNeil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JoKneeMo"><img src="https://avatars0.githubusercontent.com/u/421625?v=4?s=110" width="110px;" alt="JoKneeMo"/><br /><sub><b>JoKneeMo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JoKneeMo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.redbridge.se"><img src="https://avatars0.githubusercontent.com/u/54849013?v=4?s=110" width="110px;" alt="Joshi"/><br /><sub><b>Joshi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=joshi-redbridge" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anthonypburns"><img src="https://avatars2.githubusercontent.com/u/15731458?v=4?s=110" width="110px;" alt="Anthony Burns"/><br /><sub><b>Anthony Burns</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=anthonypburns" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/johnson-yi"><img src="https://avatars1.githubusercontent.com/u/63399474?v=4?s=110" width="110px;" alt="johnson-yi"/><br /><sub><b>johnson-yi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=johnson-yi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://tangentmc.net"><img src="https://avatars1.githubusercontent.com/u/1862720?v=4?s=110" width="110px;" alt="Sanjay Govind"/><br /><sub><b>Sanjay Govind</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sanjay900" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://peter.upfold.org.uk/"><img src="https://avatars0.githubusercontent.com/u/1255375?v=4?s=110" width="110px;" alt="Peter Upfold"/><br /><sub><b>Peter Upfold</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PeterUpfold" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jbiel"><img src="https://avatars2.githubusercontent.com/u/961717?v=4?s=110" width="110px;" alt="Jared Biel"/><br /><sub><b>Jared Biel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jbiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dampfklon"><img src="https://avatars1.githubusercontent.com/u/1733625?v=4?s=110" width="110px;" alt="Dampfklon"/><br /><sub><b>Dampfklon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dampfklon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://communityclosing.com"><img src="https://avatars2.githubusercontent.com/u/52973156?v=4?s=110" width="110px;" alt="Charles Hamilton"/><br /><sub><b>Charles Hamilton</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chamilton-ccn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/giannello"><img src="https://avatars.githubusercontent.com/u/551789?v=4?s=110" width="110px;" alt="Giuseppe Iannello"/><br /><sub><b>Giuseppe Iannello</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=giannello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.peterdavehello.org/"><img src="https://avatars.githubusercontent.com/u/3691490?v=4?s=110" width="110px;" alt="Peter Dave Hello"/><br /><sub><b>Peter Dave Hello</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PeterDaveHello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sigmoidal"><img src="https://avatars.githubusercontent.com/u/6106332?v=4?s=110" width="110px;" alt="sigmoidal"/><br /><sub><b>sigmoidal</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sigmoidal" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/phenixdotnet"><img src="https://avatars.githubusercontent.com/u/2082554?v=4?s=110" width="110px;" alt="Vincent Lainé"/><br /><sub><b>Vincent Lainé</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=phenixdotnet" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.lucas-pless.com"><img src="https://avatars.githubusercontent.com/u/1943040?v=4?s=110" width="110px;" alt="Lucas Pleß"/><br /><sub><b>Lucas Pleß</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=derlucas" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://twitter.com/iansltx"><img src="https://avatars.githubusercontent.com/u/472804?v=4?s=110" width="110px;" alt="Ian Littman"/><br /><sub><b>Ian Littman</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=iansltx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PauloLuna"><img src="https://avatars.githubusercontent.com/u/3519029?v=4?s=110" width="110px;" alt="João Paulo"/><br /><sub><b>João Paulo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PauloLuna" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ThoBur"><img src="https://avatars.githubusercontent.com/u/70443365?v=4?s=110" width="110px;" alt="ThoBur"/><br /><sub><b>ThoBur</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ThoBur" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://phpprofi.ru/"><img src="https://avatars.githubusercontent.com/u/1972329?v=4?s=110" width="110px;" alt="Alexander Chibrikin"/><br /><sub><b>Alexander Chibrikin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=alek13" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/winstan"><img src="https://avatars.githubusercontent.com/u/438332?v=4?s=110" width="110px;" alt="Anthony Winstanley"/><br /><sub><b>Anthony Winstanley</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=winstan" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fashberg"><img src="https://avatars.githubusercontent.com/u/3075214?v=4?s=110" width="110px;" alt="Folke"/><br /><sub><b>Folke</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fashberg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/benwa"><img src="https://avatars.githubusercontent.com/u/1351571?v=4?s=110" width="110px;" alt="Bennett Blodinger"/><br /><sub><b>Bennett Blodinger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=benwa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://nmc.dev"><img src="https://avatars.githubusercontent.com/u/2974631?v=4?s=110" width="110px;" alt="NMC"/><br /><sub><b>NMC</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ncareau" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andres-baller"><img src="https://avatars.githubusercontent.com/u/52182449?v=4?s=110" width="110px;" alt="andres-baller"/><br /><sub><b>andres-baller</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andres-baller" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sean-borg"><img src="https://avatars.githubusercontent.com/u/67109348?v=4?s=110" width="110px;" alt="sean-borg"/><br /><sub><b>sean-borg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sean-borg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EDVLeer"><img src="https://avatars.githubusercontent.com/u/32170051?v=4?s=110" width="110px;" alt="EDVLeer"/><br /><sub><b>EDVLeer</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=EDVLeer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Kurokat"><img src="https://avatars.githubusercontent.com/u/23075196?v=4?s=110" width="110px;" alt="Kurokat"/><br /><sub><b>Kurokat</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Kurokat" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://www.kevinkoellmann.de"><img src="https://avatars.githubusercontent.com/u/915514?v=4?s=110" width="110px;" alt="Kevin Köllmann"/><br /><sub><b>Kevin Köllmann</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koelle25" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sw-mreyes"><img src="https://avatars.githubusercontent.com/u/49025941?v=4?s=110" width="110px;" alt="sw-mreyes"/><br /><sub><b>sw-mreyes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sw-mreyes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://pittet.ca"><img src="https://avatars.githubusercontent.com/u/70129?v=4?s=110" width="110px;" alt="Joel Pittet"/><br /><sub><b>Joel Pittet</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=joelpittet" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://elyscape.com"><img src="https://avatars.githubusercontent.com/u/792695?v=4?s=110" width="110px;" alt="Eli Young"/><br /><sub><b>Eli Young</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=elyscape" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raelldottin"><img src="https://avatars.githubusercontent.com/u/317015?v=4?s=110" width="110px;" alt="Raell Dottin"/><br /><sub><b>Raell Dottin</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=raelldottin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/misilot"><img src="https://avatars.githubusercontent.com/u/1446856?v=4?s=110" width="110px;" alt="Tom Misilo"/><br /><sub><b>Tom Misilo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=misilot" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://david.davenne.be"><img src="https://avatars.githubusercontent.com/u/4496300?v=4?s=110" width="110px;" alt="David Davenne"/><br /><sub><b>David Davenne</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JuustoMestari" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://markstenglein.com"><img src="https://avatars.githubusercontent.com/u/9255772?v=4?s=110" width="110px;" alt="Mark Stenglein"/><br /><sub><b>Mark Stenglein</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ocelotsloth" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ajsy"><img src="https://avatars.githubusercontent.com/u/35658596?v=4?s=110" width="110px;" alt="ajsy"/><br /><sub><b>ajsy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ajsy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/t3easy"><img src="https://avatars.githubusercontent.com/u/3628035?v=4?s=110" width="110px;" alt="Jan Kiesewetter"/><br /><sub><b>Jan Kiesewetter</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=t3easy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tetrachloromethane250"><img src="https://avatars.githubusercontent.com/u/79449630?v=4?s=110" width="110px;" alt="Tetrachloromethane250"/><br /><sub><b>Tetrachloromethane250</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.kajes.se/"><img src="https://avatars.githubusercontent.com/u/22004482?v=4?s=110" width="110px;" alt="Lars Kajes"/><br /><sub><b>Lars Kajes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kajes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Joly0"><img src="https://avatars.githubusercontent.com/u/13993216?v=4?s=110" width="110px;" alt="Joly0"/><br /><sub><b>Joly0</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Joly0" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/limeless"><img src="https://avatars.githubusercontent.com/u/1501022?v=4?s=110" width="110px;" alt="theburger"/><br /><sub><b>theburger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=limeless" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/deivishome"><img src="https://avatars.githubusercontent.com/u/36065681?v=4?s=110" width="110px;" alt="David Valin Alonso"/><br /><sub><b>David Valin Alonso</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=deivishome" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/andreaci"><img src="https://avatars.githubusercontent.com/u/8290389?v=4?s=110" width="110px;" alt="andreaci"/><br /><sub><b>andreaci</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=andreaci" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jellesebreghts.be"><img src="https://avatars.githubusercontent.com/u/1828542?v=4?s=110" width="110px;" alt="Jelle Sebreghts"/><br /><sub><b>Jelle Sebreghts</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Jelle-S" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Skywalker-11"><img src="https://avatars.githubusercontent.com/u/11180862?v=4?s=110" width="110px;" alt="Michael Pietsch"/><br /><sub><b>Michael Pietsch</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sh1hab"><img src="https://avatars.githubusercontent.com/u/22068886?v=4?s=110" width="110px;" alt="Masudul Haque Shihab"/><br /><sub><b>Masudul Haque Shihab</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sh1hab" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.freedomdive.com/"><img src="https://avatars.githubusercontent.com/u/16099942?v=4?s=110" width="110px;" alt="Supapong Areeprasertkul"/><br /><sub><b>Supapong Areeprasertkul</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zybersup" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/psarossy"><img src="https://avatars.githubusercontent.com/u/207358?v=4?s=110" width="110px;" alt="Peter Sarossy"/><br /><sub><b>Peter Sarossy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=psarossy" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nepella"><img src="https://avatars.githubusercontent.com/u/11823649?v=4?s=110" width="110px;" alt="Renee Margaret McConahy"/><br /><sub><b>Renee Margaret McConahy</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nepella" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JohnnyPicnic"><img src="https://avatars.githubusercontent.com/u/5553884?v=4?s=110" width="110px;" alt="JohnnyPicnic"/><br /><sub><b>JohnnyPicnic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/markbrule"><img src="https://avatars.githubusercontent.com/u/8799594?v=4?s=110" width="110px;" alt="markbrule"/><br /><sub><b>markbrule</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=markbrule" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikecmpbll"><img src="https://avatars.githubusercontent.com/u/1962801?v=4?s=110" width="110px;" alt="Mike Campbell"/><br /><sub><b>Mike Campbell</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mikecmpbll" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tbrconnect"><img src="https://avatars.githubusercontent.com/u/11973217?v=4?s=110" width="110px;" alt="tbrconnect"/><br /><sub><b>tbrconnect</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=tbrconnect" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kcoyo"><img src="https://avatars.githubusercontent.com/u/12447225?v=4?s=110" width="110px;" alt="kcoyo"/><br /><sub><b>kcoyo</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kcoyo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://travismiller.com/"><img src="https://avatars.githubusercontent.com/u/494017?v=4?s=110" width="110px;" alt="Travis Miller"/><br /><sub><b>Travis Miller</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=travismiller" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Delta5"><img src="https://avatars.githubusercontent.com/u/1975640?v=4?s=110" width="110px;" alt="Evan Taylor"/><br /><sub><b>Evan Taylor</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Delta5" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PetriAsi"><img src="https://avatars.githubusercontent.com/u/8735148?v=4?s=110" width="110px;" alt="Petri Asikainen"/><br /><sub><b>Petri Asikainen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PetriAsi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/derdeagle"><img src="https://avatars.githubusercontent.com/u/11424540?v=4?s=110" width="110px;" alt="derdeagle"/><br /><sub><b>derdeagle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=derdeagle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://wh0rd.org/"><img src="https://avatars.githubusercontent.com/u/176950?v=4?s=110" width="110px;" alt="Mike Frysinger"/><br /><sub><b>Mike Frysinger</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vapier" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AL4AL"><img src="https://avatars.githubusercontent.com/u/22044358?v=4?s=110" width="110px;" alt="ALPHA"/><br /><sub><b>ALPHA</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AL4AL" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.ifern.de"><img src="https://avatars.githubusercontent.com/u/1042587?v=4?s=110" width="110px;" alt="FliegenKLATSCH"/><br /><sub><b>FliegenKLATSCH</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jerm"><img src="https://avatars.githubusercontent.com/u/442138?v=4?s=110" width="110px;" alt="Jeremy Price"/><br /><sub><b>Jeremy Price</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jerm" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Toreg87"><img src="https://avatars.githubusercontent.com/u/84392209?v=4?s=110" width="110px;" alt="Toreg87"/><br /><sub><b>Toreg87</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Toreg87" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Computroniks"><img src="https://avatars.githubusercontent.com/u/67638596?v=4?s=110" width="110px;" alt="Matthew Nickson"/><br /><sub><b>Matthew Nickson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Computroniks" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jethron.id.au"><img src="https://avatars.githubusercontent.com/u/1646397?v=4?s=110" width="110px;" alt="Jethro Nederhof"/><br /><sub><b>Jethro Nederhof</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jethron" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/01ste02"><img src="https://avatars.githubusercontent.com/u/23289826?v=4?s=110" width="110px;" alt="Oskar Stenberg"/><br /><sub><b>Oskar Stenberg</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=01ste02" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Robert-Azelis"><img src="https://avatars.githubusercontent.com/u/82208283?v=4?s=110" width="110px;" alt="Robert-Azelis"/><br /><sub><b>Robert-Azelis</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Robert-Azelis" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/alwism"><img src="https://avatars.githubusercontent.com/u/60648387?v=4?s=110" width="110px;" alt="Alexander William Smith"/><br /><sub><b>Alexander William Smith</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=alwism" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.leitwerk.de/"><img src="https://avatars.githubusercontent.com/u/24418301?v=4?s=110" width="110px;" alt="LEITWERK AG"/><br /><sub><b>LEITWERK AG</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=leitwerk-ag" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.aboutcher.co.uk"><img src="https://avatars.githubusercontent.com/u/1911435?v=4?s=110" width="110px;" alt="Adam"/><br /><sub><b>Adam</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adamboutcher" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://snksrv.com"><img src="https://avatars.githubusercontent.com/u/16104273?v=4?s=110" width="110px;" alt="Ian"/><br /><sub><b>Ian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sneak-it" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://blog.bestlong.idv.tw/"><img src="https://avatars.githubusercontent.com/u/4023909?v=4?s=110" width="110px;" alt="Shao Yu-Lung (Allen)"/><br /><sub><b>Shao Yu-Lung (Allen)</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bestlong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Haxatron"><img src="https://avatars.githubusercontent.com/u/76475453?v=4?s=110" width="110px;" alt="Haxatron"/><br /><sub><b>Haxatron</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Haxatron" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PlaneNuts"><img src="https://avatars.githubusercontent.com/u/88776392?v=4?s=110" width="110px;" alt="PlaneNuts"/><br /><sub><b>PlaneNuts</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PlaneNuts" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://bjcpgd.cias.rit.edu"><img src="https://avatars.githubusercontent.com/u/3842948?v=4?s=110" width="110px;" alt="Bradley Coudriet"/><br /><sub><b>Bradley Coudriet</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=exula" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://daltondur.st"><img src="https://avatars.githubusercontent.com/u/21966173?v=4?s=110" width="110px;" alt="Dalton Durst"/><br /><sub><b>Dalton Durst</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://adagiohealth.org"><img src="https://avatars.githubusercontent.com/u/38761237?v=4?s=110" width="110px;" alt="Alex Janes"/><br /><sub><b>Alex Janes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=adagioajanes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nuraeil"><img src="https://avatars.githubusercontent.com/u/32387849?v=4?s=110" width="110px;" alt="Nuraeil"/><br /><sub><b>Nuraeil</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nuraeil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TenOfTens"><img src="https://avatars.githubusercontent.com/u/48162670?v=4?s=110" width="110px;" alt="TenOfTens"/><br /><sub><b>TenOfTens</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=TenOfTens" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ditisjens.be/"><img src="https://avatars.githubusercontent.com/u/9415391?v=4?s=110" width="110px;" alt="waffle"/><br /><sub><b>waffle</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=insert-waffle" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QveenSi"><img src="https://avatars.githubusercontent.com/u/19945501?v=4?s=110" width="110px;" alt="Yevhenii Huzii"/><br /><sub><b>Yevhenii Huzii</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=QveenSi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/veenone"><img src="https://avatars.githubusercontent.com/u/3839381?v=4?s=110" width="110px;" alt="Achmad Fienan Rahardianto"/><br /><sub><b>Achmad Fienan Rahardianto</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=veenone" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QveenSi"><img src="https://avatars.githubusercontent.com/u/19945501?v=4?s=110" width="110px;" alt="Yevhenii Huzii"/><br /><sub><b>Yevhenii Huzii</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=QveenSi" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chrisweirich"><img src="https://avatars.githubusercontent.com/u/97299851?v=4?s=110" width="110px;" alt="Christian Weirich"/><br /><sub><b>Christian Weirich</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chrisweirich" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/denzfarid"><img src="https://avatars.githubusercontent.com/u/1294403?v=4?s=110" width="110px;" alt="denzfarid"/><br /><sub><b>denzfarid</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ntbutler-nbcs"><img src="https://avatars.githubusercontent.com/u/94018771?v=4?s=110" width="110px;" alt="ntbutler-nbcs"/><br /><sub><b>ntbutler-nbcs</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://naveensrinivasan.dev"><img src="https://avatars.githubusercontent.com/u/172697?v=4?s=110" width="110px;" alt="Naveen"/><br /><sub><b>Naveen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=naveensrinivasan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikeroq"><img src="https://avatars.githubusercontent.com/u/55674383?v=4?s=110" width="110px;" alt="Mike Roquemore"/><br /><sub><b>Mike Roquemore</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mikeroq" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/reederda"><img src="https://avatars.githubusercontent.com/u/7991086?v=4?s=110" width="110px;" alt="Daniel Reeder"/><br /><sub><b>Daniel Reeder</b></sub></a><br /><a href="#translation-reederda" title="Translation">🌍</a> <a href="#translation-reederda" title="Translation">🌍</a> <a href="https://github.com/snipe/snipe-it/commits?author=reederda" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vickyjaura183"><img src="https://avatars.githubusercontent.com/u/109422491?v=4?s=110" width="110px;" alt="vickyjaura183"/><br /><sub><b>vickyjaura183</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vickyjaura183" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/julian-piehl"><img src="https://avatars.githubusercontent.com/u/32363424?v=4?s=110" width="110px;" alt="Peace"/><br /><sub><b>Peace</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=julian-piehl" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kylegordon"><img src="https://avatars.githubusercontent.com/u/231528?v=4?s=110" width="110px;" alt="Kyle Gordon"/><br /><sub><b>Kyle Gordon</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kylegordon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.bfh.ch"><img src="https://avatars.githubusercontent.com/u/53009155?v=4?s=110" width="110px;" alt="Katharina Drexel"/><br /><sub><b>Katharina Drexel</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=sunflowerbofh" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://david.sferruzza.fr/"><img src="https://avatars.githubusercontent.com/u/1931963?v=4?s=110" width="110px;" alt="David Sferruzza"/><br /><sub><b>David Sferruzza</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dsferruzza" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rnelsonee"><img src="https://avatars.githubusercontent.com/u/19511639?v=4?s=110" width="110px;" alt="Rick Nelson"/><br /><sub><b>Rick Nelson</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=rnelsonee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BasO12"><img src="https://avatars.githubusercontent.com/u/94169344?v=4?s=110" width="110px;" alt="BasO12"/><br /><sub><b>BasO12</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=BasO12" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vautia"><img src="https://avatars.githubusercontent.com/u/111710123?v=4?s=110" width="110px;" alt="Vautia"/><br /><sub><b>Vautia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Vautia" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://www.littlehart.net/atthekeyboard"><img src="https://avatars.githubusercontent.com/u/28321?v=4?s=110" width="110px;" alt="Chris Hartjes"/><br /><sub><b>Chris Hartjes</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chartjes" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geo-chen"><img src="https://avatars.githubusercontent.com/u/2404584?v=4?s=110" width="110px;" alt="geo-chen"/><br /><sub><b>geo-chen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=geo-chen" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nh314"><img src="https://avatars.githubusercontent.com/u/6006620?v=4?s=110" width="110px;" alt="Phan Nguyen"/><br /><sub><b>Phan Nguyen</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=nh314" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/StarlessNights"><img src="https://avatars.githubusercontent.com/u/115993812?v=4?s=110" width="110px;" alt="Iisakki Jaakkola"/><br /><sub><b>Iisakki Jaakkola</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=StarlessNights" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=110" width="110px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=eltociear" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukasfehling"><img src="https://avatars.githubusercontent.com/u/56871540?v=4?s=110" width="110px;" alt="Lukas Fehling"/><br /><sub><b>Lukas Fehling</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=lukasfehling" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fernando-almeida"><img src="https://avatars.githubusercontent.com/u/1975990?v=4?s=110" width="110px;" alt="Fernando Almeida"/><br /><sub><b>Fernando Almeida</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=fernando-almeida" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/akemidx"><img src="https://avatars.githubusercontent.com/u/116301219?v=4?s=110" width="110px;" alt="akemidx"/><br /><sub><b>akemidx</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=akemidx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://oguz.site"><img src="https://avatars.githubusercontent.com/u/144778?v=4?s=110" width="110px;" alt="Oguz Bilgic"/><br /><sub><b>Oguz Bilgic</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=oguzbilgic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/scoo73r"><img src="https://avatars.githubusercontent.com/u/9262438?v=4?s=110" width="110px;" alt="Scooter Crawford"/><br /><sub><b>Scooter Crawford</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=scoo73r" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/subdriven"><img src="https://avatars.githubusercontent.com/u/5957345?v=4?s=110" width="110px;" alt="subdriven"/><br /><sub><b>subdriven</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=subdriven" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AndrewSav"><img src="https://avatars.githubusercontent.com/u/658865?v=4?s=110" width="110px;" alt="Andrew Savinykh"/><br /><sub><b>Andrew Savinykh</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=AndrewSav" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://kenchan0130.github.io"><img src="https://avatars.githubusercontent.com/u/1155067?v=4?s=110" width="110px;" alt="Tadayuki Onishi"/><br /><sub><b>Tadayuki Onishi</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=kenchan0130" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/floschoepfer"><img src="https://avatars.githubusercontent.com/u/112496896?v=4?s=110" width="110px;" alt="Florian"/><br /><sub><b>Florian</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=floschoepfer" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://spencerlong.com"><img src="https://avatars.githubusercontent.com/u/7305753?v=4?s=110" width="110px;" alt="Spencer Long"/><br /><sub><b>Spencer Long</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=spencerrlongg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marcusmoore"><img src="https://avatars.githubusercontent.com/u/1141514?v=4?s=110" width="110px;" alt="Marcus Moore"/><br /><sub><b>Marcus Moore</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=marcusmoore" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mezzle"><img src="https://avatars.githubusercontent.com/u/570639?v=4?s=110" width="110px;" alt="Martin Meredith"/><br /><sub><b>Martin Meredith</b></sub></a><br /></td>
<td align="center" valign="top" width="14.28%"><a href="http://dboth.de"><img src="https://avatars.githubusercontent.com/u/5731963?v=4?s=110" width="110px;" alt="dboth"/><br /><sub><b>dboth</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=dboth" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zacharyfleck"><img src="https://avatars.githubusercontent.com/u/87536651?v=4?s=110" width="110px;" alt="Zachary Fleck"/><br /><sub><b>Zachary Fleck</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=zacharyfleck" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vikaas-cyper"><img src="https://avatars.githubusercontent.com/u/74609912?v=4?s=110" width="110px;" alt="VIKAAS-A"/><br /><sub><b>VIKAAS-A</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=vikaas-cyper" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ak-piracha"><img src="https://avatars.githubusercontent.com/u/88882041?v=4?s=110" width="110px;" alt="Abdul Kareem"/><br /><sub><b>Abdul Kareem</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=ak-piracha" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NojoudAlshehri"><img src="https://avatars.githubusercontent.com/u/111287779?v=4?s=110" width="110px;" alt="NojoudAlshehri"/><br /><sub><b>NojoudAlshehri</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stefanstidlffg"><img src="https://avatars.githubusercontent.com/u/54367449?v=4?s=110" width="110px;" alt="Stefan Stidl"/><br /><sub><b>Stefan Stidl</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=stefanstidlffg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qay21"><img src="https://avatars.githubusercontent.com/u/87803479?v=4?s=110" width="110px;" alt="Quentin Aymard"/><br /><sub><b>Quentin Aymard</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=qay21" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cram42"><img src="https://avatars.githubusercontent.com/u/5396871?v=4?s=110" width="110px;" alt="Grant Le Roux"/><br /><sub><b>Grant Le Roux</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=cram42" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://@singrity"><img src="https://avatars.githubusercontent.com/u/58479551?v=4?s=110" width="110px;" alt="Bogdan"/><br /><sub><b>Bogdan</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Singrity" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mmanjos"><img src="https://avatars.githubusercontent.com/u/3483684?v=4?s=110" width="110px;" alt="mmanjos"/><br /><sub><b>mmanjos</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mmanjos" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://azooz2014.github.io/"><img src="https://avatars.githubusercontent.com/u/7429229?v=4?s=110" width="110px;" alt="Abdelaziz Faki"/><br /><sub><b>Abdelaziz Faki</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Azooz2014" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bilias"><img src="https://avatars.githubusercontent.com/u/47315739?v=4?s=110" width="110px;" alt="bilias"/><br /><sub><b>bilias</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bilias" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coach1988"><img src="https://avatars.githubusercontent.com/u/2565989?v=4?s=110" width="110px;" alt="coach1988"/><br /><sub><b>coach1988</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=coach1988" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mauro-miatello"><img src="https://avatars.githubusercontent.com/u/11910225?v=4?s=110" width="110px;" alt="MrM"/><br /><sub><b>MrM</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mauro-miatello" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/koiakoia"><img src="https://avatars.githubusercontent.com/u/60405354?v=4?s=110" width="110px;" alt="koiakoia"/><br /><sub><b>koiakoia</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=koiakoia" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mustafa-online"><img src="https://avatars.githubusercontent.com/u/5323832?v=4?s=110" width="110px;" alt="Mustafa Online"/><br /><sub><b>Mustafa Online</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=mustafa-online" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franceslui"><img src="https://avatars.githubusercontent.com/u/104601439?v=4?s=110" width="110px;" alt="franceslui"/><br /><sub><b>franceslui</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=franceslui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Q4kK"><img src="https://avatars.githubusercontent.com/u/125313163?v=4?s=110" width="110px;" alt="Q4kK"/><br /><sub><b>Q4kK</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Q4kK" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/squintfox"><img src="https://avatars.githubusercontent.com/u/55590532?v=4?s=110" width="110px;" alt="squintfox"/><br /><sub><b>squintfox</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=squintfox" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jeffclay"><img src="https://avatars.githubusercontent.com/u/1380084?v=4?s=110" width="110px;" alt="Jeff Clay"/><br /><sub><b>Jeff Clay</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=jeffclay" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PP-JN-RL"><img src="https://avatars.githubusercontent.com/u/52716446?v=4?s=110" width="110px;" alt="Phil J R"/><br /><sub><b>Phil J R</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=PP-JN-RL" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.corelight.com/"><img src="https://avatars.githubusercontent.com/u/1496725?v=4?s=110" width="110px;" alt="i_virus"/><br /><sub><b>i_virus</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=chandanchowdhury" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gitgrimbo"><img src="https://avatars.githubusercontent.com/u/1020541?v=4?s=110" width="110px;" alt="Paul Grime"/><br /><sub><b>Paul Grime</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=gitgrimbo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://leeporte.co.uk"><img src="https://avatars.githubusercontent.com/u/922815?v=4?s=110" width="110px;" alt="Lee Porte"/><br /><sub><b>Lee Porte</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=LeePorte" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bryanlopezinc"><img src="https://avatars.githubusercontent.com/u/23613427?v=4?s=110" width="110px;" alt="BRYAN "/><br /><sub><b>BRYAN </b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=bryanlopezinc" title="Code">💻</a> <a href="https://github.com/snipe/snipe-it/commits?author=bryanlopezinc" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/U-H-T"><img src="https://avatars.githubusercontent.com/u/64061710?v=4?s=110" width="110px;" alt="U-H-T"/><br /><sub><b>U-H-T</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=U-H-T" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tyree"><img src="https://avatars.githubusercontent.com/u/5395363?v=4?s=110" width="110px;" alt="Matt Tyree"/><br /><sub><b>Matt Tyree</b></sub></a><br /><a href="https://github.com/snipe/snipe-it/commits?author=Tyree" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
+34 -34
View File
@@ -1,35 +1,35 @@
FROM alpine:3.19 FROM alpine:3.18.6
# Apache + PHP # Apache + PHP
RUN apk add --no-cache \ RUN apk add --no-cache \
apache2 \ apache2 \
php82 \ php81 \
php82-common \ php81-common \
php82-apache2 \ php81-apache2 \
php82-curl \ php81-curl \
php82-ldap \ php81-ldap \
php82-mysqli \ php81-mysqli \
php82-gd \ php81-gd \
php82-xml \ php81-xml \
php82-mbstring \ php81-mbstring \
php82-zip \ php81-zip \
php82-ctype \ php81-ctype \
php82-tokenizer \ php81-tokenizer \
php82-pdo_mysql \ php81-pdo_mysql \
php82-openssl \ php81-openssl \
php82-bcmath \ php81-bcmath \
php82-phar \ php81-phar \
php82-json \ php81-json \
php82-iconv \ php81-iconv \
php82-fileinfo \ php81-fileinfo \
php82-simplexml \ php81-simplexml \
php82-session \ php81-session \
php82-dom \ php81-dom \
php82-xmlwriter \ php81-xmlwriter \
php82-xmlreader \ php81-xmlreader \
php82-sodium \ php81-sodium \
php82-redis \ php81-redis \
php82-pecl-memcached \ php81-pecl-memcached \
php82-exif \ php81-exif \
curl \ curl \
wget \ wget \
vim \ vim \
@@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives # Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2 RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite # Enable mod_rewrite
@@ -79,12 +79,12 @@ USER root
VOLUME ["/var/lib/snipeit"] VOLUME ["/var/lib/snipeit"]
# Startup script # Entrypoints
COPY docker/startup_alpine.sh /startup.sh COPY docker/entrypoint_alpine.sh /entrypoint.sh
RUN chmod +x /startup.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/sbin/tini", "--"] ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/startup.sh"] CMD ["/entrypoint.sh"]
EXPOSE 80 EXPOSE 80
+3 -3
View File
@@ -97,7 +97,7 @@ RUN set -eux; \
VOLUME [ "/var/lib/snipeit" ] VOLUME [ "/var/lib/snipeit" ]
COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env
COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint
COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
ENTRYPOINT [ "/startup.sh" ] ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ]
CMD [ "/startup.sh", "php-fpm" ] CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ]
+3 -4
View File
@@ -72,13 +72,12 @@ Since the release of the JSON REST API, several third-party developers have been
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira) - [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. - [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit). - [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT. - [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT. - [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API - [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT. - [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT. - [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT. - [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
----- -----
+1 -1
View File
@@ -20,7 +20,7 @@ APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU= APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000 APP_URL=http://localhost:8000
APP_TIMEZONE='UTC' APP_TIMEZONE='UTC'
APP_LOCALE=en-US APP_LOCALE=en
# -------------------------------------------- # --------------------------------------------
# REQUIRED: DATABASE SETTINGS # REQUIRED: DATABASE SETTINGS
@@ -1,66 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use Illuminate\Console\Command;
class FixupAssignedToWithoutAssignedType extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:assigned-to-fixup
{--debug : Display debugging output}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fixes up assets that have an assigned_to but no assigned_type';
/**
* Execute the console command.
*/
public function handle()
{
$assets = Asset::whereNull("assigned_type")->whereNotNull("assigned_to")->withTrashed();
$this->withProgressBar($assets->get(), function (Asset $asset) {
//now check each action log, from the most recent backwards, to find the last checkin or checkout
foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
if($this->option("debug")) {
$this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
}
switch($action_log->action_type) {
case 'checkin from':
if($this->option("debug")) {
$this->info("Doing a checkin for ".$asset->id);
}
$asset->assigned_to = null;
// if you have a required custom field, we still want to save, and we *don't* want an action_log
$asset->saveQuietly();
return;
case 'checkout':
if($this->option("debug")) {
$this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
}
if($asset->assigned_to != $action_log->target_id) {
$this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
//FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
}
$asset->assigned_type = $action_log->target_type;
$asset->saveQuietly(); // see above
return;
}
}
$asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
$asset->saveQuietly(); //see above
});
$this->newLine();
$this->info("Assets assigned_type are fixed");
}
}
+47 -59
View File
@@ -53,22 +53,18 @@ class LdapSync extends Command
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
$ldap_result_username = Setting::getSettings()->ldap_username_field;
$ldap_map = [ $ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
"username" => Setting::getSettings()->ldap_username_field, $ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
"last_name" => Setting::getSettings()->ldap_lname_field, $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag;
"first_name" => Setting::getSettings()->ldap_fname_field, $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
"active_flag" => Setting::getSettings()->ldap_active_flag, $ldap_result_email = Setting::getSettings()->ldap_email;
"emp_num" => Setting::getSettings()->ldap_emp_num, $ldap_result_phone = Setting::getSettings()->ldap_phone_field;
"email" => Setting::getSettings()->ldap_email, $ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle;
"phone" => Setting::getSettings()->ldap_phone_field, $ldap_result_country = Setting::getSettings()->ldap_country;
"jobtitle" => Setting::getSettings()->ldap_jobtitle, $ldap_result_location = Setting::getSettings()->ldap_location;
"country" => Setting::getSettings()->ldap_country, $ldap_result_dept = Setting::getSettings()->ldap_dept;
"location" => Setting::getSettings()->ldap_location, $ldap_result_manager = Setting::getSettings()->ldap_manager;
"dept" => Setting::getSettings()->ldap_dept,
"manager" => Setting::getSettings()->ldap_manager,
];
$ldap_default_group = Setting::getSettings()->ldap_default_group; $ldap_default_group = Setting::getSettings()->ldap_default_group;
$search_base = Setting::getSettings()->ldap_base_dn; $search_base = Setting::getSettings()->ldap_base_dn;
@@ -111,21 +107,14 @@ class LdapSync extends Command
} }
/** /**
* If a filter has been specified, use that, otherwise default to null * If a filter has been specified, use that
*/ */
if ($this->option('filter') != '') { if ($this->option('filter') != '') {
$filter = $this->option('filter'); $results = Ldap::findLdapUsers($search_base, -1, $this->option('filter'));
} else { } else {
$filter = null; $results = Ldap::findLdapUsers($search_base);
} }
/**
* We only need to request the LDAP attributes that we process
*/
$attributes = array_values(array_filter($ldap_map));
$results = Ldap::findLdapUsers($search_base, -1, $filter, $attributes);
} catch (\Exception $e) { } catch (\Exception $e) {
if ($this->option('json_summary')) { if ($this->option('json_summary')) {
$json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []]; $json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []];
@@ -194,17 +183,17 @@ class LdapSync extends Command
} }
$usernames = []; $usernames = [];
for ($i = 0; $i < $location_users['count']; $i++) { for ($i = 0; $i < $location_users['count']; $i++) {
if (array_key_exists($ldap_map["username"], $location_users[$i])) { if (array_key_exists($ldap_result_username, $location_users[$i])) {
$location_users[$i]['ldap_location_override'] = true; $location_users[$i]['ldap_location_override'] = true;
$location_users[$i]['location_id'] = $ldap_loc['id']; $location_users[$i]['location_id'] = $ldap_loc['id'];
$usernames[] = $location_users[$i][$ldap_map["username"]][0]; $usernames[] = $location_users[$i][$ldap_result_username][0];
} }
} }
// Delete located users from the general group. // Delete located users from the general group.
foreach ($results as $key => $generic_entry) { foreach ($results as $key => $generic_entry) {
if ((is_array($generic_entry)) && (array_key_exists($ldap_map["username"], $generic_entry))) { if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) {
if (in_array($generic_entry[$ldap_map["username"]][0], $usernames)) { if (in_array($generic_entry[$ldap_result_username][0], $usernames)) {
unset($results[$key]); unset($results[$key]);
} }
} }
@@ -230,22 +219,22 @@ class LdapSync extends Command
for ($i = 0; $i < $results['count']; $i++) { for ($i = 0; $i < $results['count']; $i++) {
$item = []; $item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? ''; $item['username'] = $results[$i][$ldap_result_username][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? ''; $item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? '';
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? ''; $item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? '';
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? ''; $item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? '';
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? ''; $item['email'] = $results[$i][$ldap_result_email][0] ?? '';
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? ''; $item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? ''; $item['telephone'] = $results[$i][$ldap_result_phone][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? ''; $item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? '';
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? ''; $item['country'] = $results[$i][$ldap_result_country][0] ?? '';
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? ''; $item['department'] = $results[$i][$ldap_result_dept][0] ?? '';
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? ''; $item['manager'] = $results[$i][$ldap_result_manager][0] ?? '';
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? ''; $item['location'] = $results[$i][$ldap_result_location][0] ?? '';
// ONLY if you are using the "ldap_location" option *AND* you have an actual result // ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_map["location"] && $item['location']) { if ($ldap_result_location && $item['location']) {
$location = Location::firstOrCreate([ $location = Location::firstOrCreate([
'name' => $item['location'], 'name' => $item['location'],
]); ]);
@@ -262,44 +251,43 @@ class LdapSync extends Command
// Creating a new user. // Creating a new user.
$user = new User; $user = new User;
$user->password = $user->noPassword(); $user->password = $user->noPassword();
$user->locale = app()->getLocale();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created'; $item['createorupdate'] = 'created';
} }
//If a sync option is not filled in on the LDAP settings don't populate the user field //If a sync option is not filled in on the LDAP settings don't populate the user field
if($ldap_map["username"] != null){ if($ldap_result_username != null){
$user->username = $item['username']; $user->username = $item['username'];
} }
if($ldap_map["last_name"] != null){ if($ldap_result_last_name != null){
$user->last_name = $item['lastname']; $user->last_name = $item['lastname'];
} }
if($ldap_map["first_name"] != null){ if($ldap_result_first_name != null){
$user->first_name = $item['firstname']; $user->first_name = $item['firstname'];
} }
if($ldap_map["emp_num"] != null){ if($ldap_result_emp_num != null){
$user->employee_num = e($item['employee_number']); $user->employee_num = e($item['employee_number']);
} }
if($ldap_map["email"] != null){ if($ldap_result_email != null){
$user->email = $item['email']; $user->email = $item['email'];
} }
if($ldap_map["phone"] != null){ if($ldap_result_phone != null){
$user->phone = $item['telephone']; $user->phone = $item['telephone'];
} }
if($ldap_map["jobtitle"] != null){ if($ldap_result_jobtitle != null){
$user->jobtitle = $item['jobtitle']; $user->jobtitle = $item['jobtitle'];
} }
if($ldap_map["country"] != null){ if($ldap_result_country != null){
$user->country = $item['country']; $user->country = $item['country'];
} }
if($ldap_map["dept"] != null){ if($ldap_result_dept != null){
$user->department_id = $department->id; $user->department_id = $department->id;
} }
if($ldap_map["location"] != null){ if($ldap_result_location != null){
$user->location_id = $location ? $location->id : null; $user->location_id = $location ? $location->id : null;
} }
if($ldap_map["manager"] != null){ if($ldap_result_manager != null){
if($item['manager'] != null) { if($item['manager'] != null) {
// Check Cache first // Check Cache first
if (isset($manager_cache[$item['manager']])) { if (isset($manager_cache[$item['manager']])) {
@@ -316,7 +304,7 @@ class LdapSync extends Command
$ldap_manager = [ $ldap_manager = [
"count" => 1, "count" => 1,
0 => [ 0 => [
$ldap_map["username"] => [$item['manager']] $ldap_result_username => [$item['manager']]
] ]
]; ];
} }
@@ -325,7 +313,7 @@ class LdapSync extends Command
// Get the Manager's username // Get the Manager's username
// PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array.
$ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0]; $ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0];
// Get User from Manager username. // Get User from Manager username.
$ldap_manager = User::where('username', $ldapManagerUsername)->first(); $ldap_manager = User::where('username', $ldapManagerUsername)->first();
@@ -342,10 +330,10 @@ class LdapSync extends Command
} }
// Sync activated state for Active Directory. // Sync activated state for Active Directory.
if ( !empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set.... if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set....
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them. // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
// (Specifically, we don't handle a value of '0.0' correctly) // (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0]; $raw_value = @$results[$i][$ldap_result_active_flag][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool)$raw_value; $boolean_cast = (bool)$raw_value;
-17
View File
@@ -3,7 +3,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel; use App\Models\AssetModel;
use App\Models\Category; use App\Models\Category;
@@ -16,8 +15,6 @@ use App\Models\Statuslabel;
use App\Models\Supplier; use App\Models\Supplier;
use App\Models\User; use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class Purge extends Command class Purge extends Command
{ {
@@ -144,20 +141,6 @@ class Purge extends Command
$this->info($users->count().' users purged.'); $this->info($users->count().' users purged.');
$user_assoc = 0; $user_assoc = 0;
foreach ($users as $user) { foreach ($users as $user) {
$rel_path = 'private_uploads/users';
$filenames = Actionlog::where('action_type', 'uploaded')
->where('item_id', $user->id)
->pluck('filename');
foreach($filenames as $filename) {
try {
if (Storage::exists($rel_path . '/' . $filename)) {
Storage::delete($rel_path . '/' . $filename);
}
} catch (\Exception $e) {
Log::info('An error occurred while deleting files: ' . $e->getMessage());
}
}
$this->info('- User "'.$user->username.'" deleted.'); $this->info('- User "'.$user->username.'" deleted.');
$user_assoc += $user->userlog()->count(); $user_assoc += $user->userlog()->count();
$user->userlog()->forceDelete(); $user->userlog()->forceDelete();
@@ -1,60 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Console\Command;
class RemoveExplicitEols extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:remove-explicit-eols {--model_name= : The name of the asset model to update (use "all" to update all models)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes explicit EOLs on assets with selected model so they may inherit the asset model EOL';
/**
* Execute the console command.
*/
public function handle()
{
$startTime = microtime(true);
if ($this->option('model_name') == 'all') {
$assets = Asset::all();
$this->updateAssets($assets);
} else {
$assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first();
if ($assetModel) {
$assets = Asset::where('model_id', '=', $assetModel->id)->get();
$this->updateAssets($assets);
} else {
$this->error('Asset model not found');
}
}
$endTime = microtime(true);
$executionTime = ($endTime - $startTime);
$this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
}
private function updateAssets($assets)
{
foreach ($assets as $asset) {
$asset->eol_explicit = 0;
$asset->asset_eol_date = null;
$asset->save();
}
$this->info($assets->count() . ' Assets updated successfully');
}
}
@@ -73,7 +73,6 @@ class ResetDemoSettings extends Command
$settings->saml_forcelogin = '0'; $settings->saml_forcelogin = '0';
$settings->saml_slo = null; $settings->saml_slo = null;
$settings->saml_custom_settings = null; $settings->saml_custom_settings = null;
$settings->default_avatar = 'default.png';
$settings->save(); $settings->save();
+6 -26
View File
@@ -30,11 +30,8 @@ class SQLStreamer {
public function parse_sql(string $line): string { public function parse_sql(string $line): string {
// take into account the 'start of line or not' setting as an instance variable? // take into account the 'start of line or not' setting as an instance variable?
// 'continuation' lines for a permitted statement are PERMITTED. // 'continuation' lines for a permitted statement are PERMITTED.
// remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
// Windows dumps
$line = trim($line, "\r\n");
if($this->statement_is_permitted && $line[0] === ' ') { if($this->statement_is_permitted && $line[0] === ' ') {
return $line . "\n"; //re-add the newline return $line;
} }
$table_regex = '`?([a-zA-Z0-9_]+)`?'; $table_regex = '`?([a-zA-Z0-9_]+)`?';
@@ -45,12 +42,8 @@ class SQLStreamer {
"/^(INSERT INTO )$table_regex(.*)$/" => false, "/^(INSERT INTO )$table_regex(.*)$/" => false,
"/^UNLOCK TABLES/" => false, "/^UNLOCK TABLES/" => false,
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here? // "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
"/^\\)[a-zA-Z0-9_= ]*;$/" => false, "/^\\)[a-zA-Z0-9_= ]*;$/" => false
// ^^^^^^ that bit should *exit* the 'permitted' block // ^^^^^^ that bit should *exit* the 'perimitted' black
"/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
/* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
]; ];
foreach($allowed_statements as $statement => $statechange) { foreach($allowed_statements as $statement => $statechange) {
@@ -74,7 +67,7 @@ class SQLStreamer {
} }
//how do we *replace* the tablename? //how do we *replace* the tablename?
// print "RETURNING LINE: $line"; // print "RETURNING LINE: $line";
return $line . "\n"; //re-add newline return $line;
} }
} }
// all that is not allowed is denied. // all that is not allowed is denied.
@@ -92,7 +85,7 @@ class SQLStreamer {
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting! $parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics? $check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
//can't use 'users' because the 'accessories_checkout' table? //can't use 'users' because the 'accessories_users' table?
// can't use 'assets' because 'ver1_components_assets' // can't use 'assets' because 'ver1_components_assets'
foreach($check_tables as $check_table => $_ignore) { foreach($check_tables as $check_table => $_ignore) {
foreach ($parser->tablenames as $tablename => $_count) { foreach ($parser->tablenames as $tablename => $_count) {
@@ -171,8 +164,7 @@ class RestoreFromBackup extends Command
{filename : The zip file to be migrated} {filename : The zip file to be migrated}
{--no-progress : Don\'t show a progress bar} {--no-progress : Don\'t show a progress bar}
{--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL} {--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL}
{--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix} {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}';
{--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }';
/** /**
* The console command description. * The console command description.
@@ -373,15 +365,6 @@ class RestoreFromBackup extends Command
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL."); return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL.");
} }
// If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
if ($this->option('sql-stdout-only')) {
$sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
$bytes_read = $sql_importer->line_aware_piping();
return $this->warn("$bytes_read total bytes read");
//TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
// which would be good for redirecting to a file, and not having to trim the last line off of it
}
//how to invoke the restore? //how to invoke the restore?
$pipes = []; $pipes = [];
@@ -483,9 +466,6 @@ class RestoreFromBackup extends Command
$ugly_file_name = $za->statIndex($file_details['index'])['name']; $ugly_file_name = $za->statIndex($file_details['index'])['name'];
$fp = $za->getStream($ugly_file_name); $fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true)); //$this->info("Weird problem, here are file details? ".print_r($file_details,true));
if (!is_dir($file_details['dest'])) {
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
}
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w'); $migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) { while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
fwrite($migrated_file, $buffer); fwrite($migrated_file, $buffer);
@@ -1,106 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CurrentInventory;
use App\Notifications\UnacceptedAssetReminderNotification;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class SendAcceptanceReminder extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:acceptance-reminder';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This will resend users with unaccepted assets a reminder to accept or decline them.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
->whereHas('checkoutable', function($query) {
$query->where('accepted_at', null)
->where('declined_at', null);
})
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
->get();
$count = 0;
$unacceptedAssetGroups = $pending
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
})
->groupBy(function($item) {
return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
});
$no_mail_address = [];
foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
$item_count = $unacceptedAssetGroup->count();
foreach ($unacceptedAssetGroup as $unacceptedAsset) {
// if ($unacceptedAsset['acceptance']->assignedTo->email == ''){
// $no_mail_address[] = $unacceptedAsset['checkoutable']->assignedTo->present()->fullName;
// }
if ($unacceptedAsset['acceptance']->assignedTo) {
if (!$unacceptedAsset['acceptance']->assignedTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send(
$unacceptedAsset['acceptance']->assignedTo,
new UnacceptedAssetReminderNotification($unacceptedAsset['assetItem'], $count)
);
} else {
Notification::send(
$unacceptedAsset['acceptance']->assignedTo,
new UnacceptedAssetReminderNotification($unacceptedAsset, $item_count)
);
}
$count++;
}
}
}
if (!empty($no_mail_address)) {
foreach($no_mail_address as $user) {
return $user.' has no email.';
}
}
$this->info($count.' users notified.');
}
}
+21 -62
View File
@@ -62,9 +62,8 @@ class Helper
'mn' => 'mn-MN', // Mongolian 'mn' => 'mn-MN', // Mongolian
'ms' => 'ms-MY', // Malay 'ms' => 'ms-MY', // Malay
'nl' => 'nl-NL', // Dutch 'nl' => 'nl-NL', // Dutch
'no' => 'nb-NO', // Norwegian Bokmål 'no' => 'no-NO', // Norwegian
'pl' => 'pl-PL', // Polish 'pl' => 'pl-PL', // Polish
'pt' => 'pt-PT', // Portuguese
'ro' => 'ro-RO', // Romanian 'ro' => 'ro-RO', // Romanian
'ru' => 'ru-RU', // Russian 'ru' => 'ru-RU', // Russian
'sk' => 'sk-SK', // Slovak 'sk' => 'sk-SK', // Slovak
@@ -721,7 +720,7 @@ class Helper
{ {
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold; $alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get(); $consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get(); $accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get(); $components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get(); $asset_models = AssetModel::where('min_amt', '>', 0)->get();
$licenses = License::where('min_amt', '>', 0)->get(); $licenses = License::where('min_amt', '>', 0)->get();
@@ -749,7 +748,7 @@ class Helper
} }
foreach ($accessories as $accessory) { foreach ($accessories as $accessory) {
$avail = $accessory->qty - $accessory->checkouts_count; $avail = $accessory->qty - $accessory->users_count;
if ($avail < ($accessory->min_amt) + $alert_threshold) { if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) { if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0); $percent = number_format((($avail / $accessory->qty) * 100), 0);
@@ -914,22 +913,13 @@ class Helper
$rules = $class::rules(); $rules = $class::rules();
foreach ($rules as $rule_name => $rule) { foreach ($rules as $rule_name => $rule) {
if ($rule_name == $field) { if ($rule_name == $field) {
if (is_array($rule)) { if (strpos($rule, 'required') === false) {
if (in_array('required', $rule)) { return false;
return true;
} else {
return false;
}
} else { } else {
if (strpos($rule, 'required') === false) { return true;
return false; }
} else {
return true;
}
}
} }
} }
return false;
} }
/** /**
@@ -1450,6 +1440,7 @@ class Helper
foreach (self::$language_map as $legacy => $new) { foreach (self::$language_map as $legacy => $new) {
if ($language_code == $legacy) { if ($language_code == $legacy) {
Log::debug('Current language is '.$legacy.', using '.$new.' instead');
return $new; return $new;
} }
} }
@@ -1460,7 +1451,6 @@ class Helper
public static function mapBackToLegacyLocale($new_locale = null) public static function mapBackToLegacyLocale($new_locale = null)
{ {
if (strlen($new_locale) <= 4) { if (strlen($new_locale) <= 4) {
return $new_locale; //"new locale" apparently wasn't quite so new return $new_locale; //"new locale" apparently wasn't quite so new
} }
@@ -1468,73 +1458,42 @@ class Helper
// This does a *reverse* search against our new language map array - given the value, find the *key* for it // This does a *reverse* search against our new language map array - given the value, find the *key* for it
$legacy_locale = array_search($new_locale, self::$language_map); $legacy_locale = array_search($new_locale, self::$language_map);
if ($legacy_locale !== false) { if($legacy_locale !== false) {
return $legacy_locale; return $legacy_locale;
} }
return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void' return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void'
} }
public static function determineLanguageDirection() {
return in_array(app()->getLocale(),
[
'ar-SA',
'fa-IR',
'he-IL'
]) ? 'rtl' : 'ltr';
}
static public function getRedirectOption($request, $id, $table, $asset_id = null)
static public function getRedirectOption($request, $id, $table, $item_id = null)
{ {
$redirect_option = Session::get('redirect_option'); $redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type'); $checkout_to_type = Session::get('checkout_to_type');
// return to index //return to index
if ($redirect_option == 'index') { if ($redirect_option == '0') {
switch ($table) { switch ($table) {
case "Assets": case "Assets":
return route('hardware.index'); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
case "Users":
return route('users.index');
case "Licenses":
return route('licenses.index');
case "Accessories":
return route('accessories.index');
case "Components":
return route('components.index');
case "Consumables":
return route('consumables.index');
} }
} }
//return to thing being assigned
// return to thing being assigned if ($redirect_option == '1') {
if ($redirect_option == 'item') {
switch ($table) { switch ($table) {
case "Assets": case "Assets":
return route('hardware.show', $id ?? $item_id); return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success'));
case "Users":
return route('users.show', $id ?? $item_id);
case "Licenses":
return route('licenses.show', $id ?? $item_id);
case "Accessories":
return route('accessories.show', $id ?? $item_id);
case "Components":
return route('components.show', $id ?? $item_id);
case "Consumables":
return route('consumables.show', $id ?? $item_id);
} }
} }
//return to thing being assigned to
// return to assignment target if ($redirect_option == '2') {
if ($redirect_option == 'target') {
switch ($checkout_to_type) { switch ($checkout_to_type) {
case 'user': case 'user':
return route('users.show', ['user' => $request->assigned_user]); return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success'));
case 'location': case 'location':
return route('locations.show', ['location' => $request->assigned_location]); return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success'));
case 'asset': case 'asset':
return route('hardware.show', ['hardware' => $request->assigned_asset]); return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success'));
} }
} }
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error')); return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
-190
View File
@@ -1,190 +0,0 @@
<?php
namespace App\Helpers;
class IconHelper
{
public static function icon($type) {
switch ($type) {
case 'checkout':
return 'fa-solid fa-rotate-left';
case 'checkin':
return 'fa-solid fa-rotate-right';
case 'edit':
return 'fas fa-pencil-alt';
case 'clone':
return 'far fa-clone';
case 'delete':
return 'fas fa-trash';
case 'create':
return 'fa-solid fa-plus';
case 'audit':
return 'fa-solid fa-clipboard-check';
case '2fa reset':
return 'fa-solid fa-mobile-screen';
case 'new-user':
return 'fa-solid fa-user-plus';
case 'merged-user':
return 'fa-solid fa-people-arrows';
case 'delete-user':
return 'fa-solid fa-user-minus';
case 'update-user':
return 'fa-solid fa-user-pen';
case 'user':
return 'fa-solid fa-user';
case 'users':
return 'fas fa-users';
case 'restore':
return 'fa-solid fa-trash-arrow-up';
case 'external-link':
return 'fa fa-external-link';
case 'email':
return 'fa-regular fa-envelope';
case 'phone':
return 'fa-solid fa-phone';
case 'long-arrow-right':
return 'fas fa-long-arrow-alt-right';
case 'download':
return 'fas fa-download';
case 'checkmark':
return 'fas fa-check icon-white';
case 'x':
return 'fas fa-times';
case 'logout':
return 'fa fa-sign-out';
case 'admin-settings':
return 'fas fa-cogs';
case 'settings':
return 'fas fa-cog';
case 'angle-left':
return 'fas fa-angle-left';
case 'warning':
return 'fas fa-exclamation-triangle';
case 'kits':
return 'fas fa-object-group';
case 'assets':
case 'asset':
return 'fas fa-barcode';
case 'accessories':
case 'accessory':
return 'far fa-keyboard';
case 'components':
case 'component':
return 'far fa-hdd';
case 'consumables':
case 'consumable':
return 'fas fa-tint';
case 'licenses':
case 'license':
return 'far fa-save';
case 'requestable':
return 'fas fa-laptop';
case 'reports':
return 'fas fa-chart-bar';
case 'heart':
return 'fas fa-heart';
case 'circle':
return 'fa-regular fa-circle';
case 'circle-solid':
return 'fa-solid fa-circle';
case 'due':
return 'fas fa-history';
case 'import':
return 'fas fa-cloud-upload-alt';
case 'search':
return 'fas fa-search';
case 'alerts':
return 'far fa-flag';
case 'password':
return 'fa-solid fa-key';
case 'api-key':
return 'fa-solid fa-user-secret';
case 'nav-toggle':
return 'fas fa-bars';
case 'dashboard':
return 'fas fa-tachometer-alt';
case 'info-circle':
return 'fas fa-info-circle';
case 'caret-right':
return 'fa fa-caret-right';
case 'caret-up':
return 'fa fa-caret-up';
case 'caret-down':
return 'fa fa-caret-down';
case 'arrow-circle-right':
return 'fa fa-arrow-circle-right';
case 'minus':
return 'fas fa-minus';
case 'spinner':
return 'fas fa-spinner fa-spin';
case 'copy-clipboard':
return 'fa-regular fa-clipboard';
case 'paperclip':
return 'fas fa-paperclip';
case 'files':
return 'fa-regular fa-file';
case 'more-info':
return 'far fa-life-ring';
case 'calendar':
return 'fas fa-calendar';
case 'plus':
return 'fas fa-plus';
case 'history':
return 'fas fa-history';
case 'more-files':
return 'fa-solid fa-laptop-file';
case 'maintenances':
return 'fas fa-wrench';
case 'seats':
return 'far fa-list-alt';
case 'globe-us':
return 'fas fa-globe-americas';
case 'locked':
return 'fas fa-lock';
case 'unlocked':
return 'fas fa-lock';
case 'locations':
return 'fas fa-map-marker-alt';
case 'location':
return 'fas fa-map-marker-alt';
case 'superadmin':
return 'fas fa-crown';
case 'print':
return 'fa-solid fa-print';
case 'checkin-and-delete':
return 'fa-solid fa-user-xmark';
case 'branding':
return 'fas fa-copyright';
case 'general-settings':
return 'fa-solid fa-list-check';
case 'groups':
return 'fa-solid fa-user-group';
case 'bell':
return 'fa-solid fa-bell';
case 'hashtag':
return 'fa-solid fa-hashtag';
case 'asset-tags':
return 'fas fa-list-ol';
case 'labels':
return 'fas fa-tags';
case 'ldap':
return 'fas fa-sitemap';
case 'google':
return 'fa-brands fa-google';
case 'saml':
return 'fas fa-sign-in-alt';
case 'backups':
return 'fas fa-file-archive';
case 'logins':
return 'fas fa-crosshairs';
case 'oauth':
return 'fas fa-user-secret';
case 'employee_num' :
return 'fa-regular fa-id-card';
case 'department' :
return 'fa-solid fa-building-user';
}
}
}
+2 -5
View File
@@ -3,13 +3,10 @@
namespace App\Helpers; namespace App\Helpers;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Response;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class StorageHelper class StorageHelper
{ {
public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse public static function downloader($filename, $disk = 'default')
{ {
if ($disk == 'default') { if ($disk == 'default') {
$disk = config('filesystems.default'); $disk = config('filesystems.default');
@@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\Company; use App\Models\Company;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use \Illuminate\Contracts\View\View; use Redirect;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
/** This controller handles all actions related to Accessories for /** This controller handles all actions related to Accessories for
@@ -27,10 +27,13 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see AccessoriesController::getDatatable() method that generates the JSON response * @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('index', Accessory::class); $this->authorize('index', Accessory::class);
return view('accessories/index'); return view('accessories/index');
} }
@@ -38,8 +41,10 @@ class AccessoriesController extends Controller
* Returns a view with a form to create a new Accessory. * Returns a view with a form to create a new Accessory.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
$category_type = 'accessory'; $category_type = 'accessory';
@@ -53,8 +58,10 @@ class AccessoriesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize(Accessory::class); $this->authorize(Accessory::class);
@@ -73,17 +80,16 @@ class AccessoriesController extends Controller
$accessory->purchase_date = request('purchase_date'); $accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost'); $accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty'); $accessory->qty = request('qty');
$accessory->created_by = auth()->id(); $accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes'); $accessory->notes = request('notes');
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the accessory created? // Was the accessory created?
if ($accessory->save()) { if ($accessory->save()) {
// Redirect to the new accessory page // Redirect to the new accessory page
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success')); return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success'));
} }
return redirect()->back()->withInput()->withErrors($accessory->getErrors()); return redirect()->back()->withInput()->withErrors($accessory->getErrors());
@@ -94,12 +100,15 @@ class AccessoriesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId * @param int $accessoryId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($accessoryId = null) : View | RedirectResponse public function edit($accessoryId = null)
{ {
if ($item = Accessory::find($accessoryId)) { if ($item = Accessory::find($accessoryId)) {
$this->authorize($item); $this->authorize($item);
return view('accessories/edit', compact('item'))->with('category_type', 'accessory'); return view('accessories/edit', compact('item'))->with('category_type', 'accessory');
} }
@@ -113,8 +122,9 @@ class AccessoriesController extends Controller
* @author [J. Vinsmoke] * @author [J. Vinsmoke]
* @param int $accessoryId * @param int $accessoryId
* @since [v6.0] * @since [v6.0]
* @return View
*/ */
public function getClone($accessoryId = null) : View | RedirectResponse public function getClone($accessoryId = null)
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
@@ -141,15 +151,17 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @param int $accessoryId * @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(ImageUploadRequest $request, $accessoryId = null) : RedirectResponse public function update(ImageUploadRequest $request, $accessoryId = null)
{ {
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId)) { if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) {
$this->authorize($accessory); $this->authorize($accessory);
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->checkouts_count" "qty" => "required|numeric|min:$accessory->users_count"
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@@ -177,10 +189,9 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
session()->put(['redirect_option' => $request->get('redirect_option')]); // Was the accessory updated?
if ($accessory->save()) { if ($accessory->save()) {
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success')); return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success'));
} }
} else { } else {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
@@ -194,8 +205,10 @@ class AccessoriesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId * @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($accessoryId) : RedirectResponse public function destroy($accessoryId)
{ {
if (is_null($accessory = Accessory::find($accessoryId))) { if (is_null($accessory = Accessory::find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
@@ -230,10 +243,12 @@ class AccessoriesController extends Controller
* @param int $accessoryID * @param int $accessoryID
* @see AccessoriesController::getDataView() method that generates the JSON response * @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($accessoryID = null) : View | RedirectResponse public function show($accessoryID = null)
{ {
$accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryID); $accessory = Accessory::withCount('users as users_count')->find($accessoryID);
$this->authorize('view', $accessory); $this->authorize('view', $accessory);
if (isset($accessory->id)) { if (isset($accessory->id)) {
return view('accessories/view', compact('accessory')); return view('accessories/view', compact('accessory'));
@@ -7,13 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest; use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Accessory; use App\Models\Accessory;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Accessory\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log;
class AccessoriesFilesController extends Controller class AccessoriesFilesController extends Controller
{ {
@@ -22,17 +19,20 @@ class AccessoriesFilesController extends Controller
* *
* @param UploadFileRequest $request * @param UploadFileRequest $request
* @param int $accessoryId * @param int $accessoryId
* @author [A. Gianotto] [<snipe@snipe.net>] * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator. * @todo Switch to using the AssetFileRequest form request validator.
*/ */
public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse public function store(UploadFileRequest $request, $accessoryId = null)
{ {
if (config('app.lock_passwords')) { if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled')); return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
} }
$accessory = Accessory::find($accessoryId); $accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) { if (isset($accessory->id)) {
@@ -69,8 +69,10 @@ class AccessoriesFilesController extends Controller
* @since [v1.0] * @since [v1.0]
* @param int $accessoryId * @param int $accessoryId
* @param int $fileId * @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($accessoryId = null, $fileId = null) : RedirectResponse public function destroy($accessoryId = null, $fileId = null)
{ {
$accessory = Accessory::find($accessoryId); $accessory = Accessory::find($accessoryId);
@@ -105,8 +107,10 @@ class AccessoriesFilesController extends Controller
* @since [v1.4] * @since [v1.4]
* @param int $accessoryId * @param int $accessoryId
* @param int $fileId * @param int $fileId
* @return \Symfony\Accessory\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse public function show($accessoryId = null, $fileId = null, $download = true)
{ {
Log::debug('Private filesystem is: '.config('filesystems.default')); Log::debug('Private filesystem is: '.config('filesystems.default'));
@@ -3,15 +3,12 @@
namespace App\Http\Controllers\Accessories; namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AccessoryCheckinController extends Controller class AccessoryCheckinController extends Controller
{ {
@@ -22,10 +19,15 @@ class AccessoryCheckinController extends Controller
* @param Request $request * @param Request $request
* @param int $accessoryUserId * @param int $accessoryUserId
* @param string $backto * @param string $backto
* @return View
* @internal param int $accessoryId
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse public function create($accessoryUserId = null, $backto = null)
{ {
if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) { // Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
} }
@@ -40,16 +42,21 @@ class AccessoryCheckinController extends Controller
* *
* @uses Accessory::checkin_email() to determine if an email can and should be sent * @uses Accessory::checkin_email() to determine if an email can and should be sent
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param null $accessoryCheckoutId * @param null $accessoryUserId
* @param string $backto * @param string $backto
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param int $accessoryId
*/ */
public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse public function store(Request $request, $accessoryUserId = null, $backto = null)
{ {
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) { // Check if the accessory exists
if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
} }
$accessory = Accessory::find($accessory_checkout->accessory_id); $accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory); $this->authorize('checkin', $accessory);
@@ -60,12 +67,12 @@ class AccessoryCheckinController extends Controller
} }
// Was the accessory updated? // Was the accessory updated?
if ($accessory_checkout->delete()) { if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at)); $return_to = e($accessory_user->assigned_to);
session()->put(['redirect_option' => $request->get('redirect_option')]); event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at));
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success')); return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success'));
} }
// Redirect to the accessory management page with error // Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
@@ -3,34 +3,29 @@
namespace App\Http\Controllers\Accessories; namespace App\Http\Controllers\Accessories;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AccessoryCheckoutRequest;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use \Illuminate\Contracts\View\View; use Illuminate\Support\Facades\DB;
use \Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Input;
class AccessoryCheckoutController extends Controller class AccessoryCheckoutController extends Controller
{ {
use CheckInOutRequest;
/** /**
* Return the form to checkout an Accessory to a user. * Return the form to checkout an Accessory to a user.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create($id) : View | RedirectResponse public function create($id)
{ {
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) { if ($accessory = Accessory::withCount('users as users_count')->find($id)) {
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
@@ -63,38 +58,46 @@ class AccessoryCheckoutController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request * @param Request $request
* @param Accessory $accessory * @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse public function store(Request $request, $accessoryId)
{ {
// Check if the accessory exists
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found'));
}
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
$target = $this->determineCheckoutTarget(); if (!$user = User::find($request->input('assigned_to'))) {
return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist'));
$accessory->checkout_qty = $request->input('checkout_qty', 1);
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
AccessoryCheckout::create([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'created_by' => auth()->id(),
'assigned_to' => $target->id,
'assigned_type' => $target::class,
'note' => $request->input('note'),
]);
} }
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
// Set this as user since we only allow checkout to user for this item type // Make sure there is at least one available to checkout
$request->request->add(['checkout_to_type' => request('checkout_to_type')]); if ($accessory->numRemaining() <= 0){
$request->request->add(['assigned_user' => $target->id]); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
// Update the accessory data
$accessory->assigned_to = e($request->input('assigned_to'));
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to'),
'note' => $request->input('note'),
]);
DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first();
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
// Redirect to the new accessory page // Redirect to the new accessory page
return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories')) return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success'));
->with('success', trans('admin/accessories/message.checkout.success'));
} }
} }
@@ -28,18 +28,20 @@ use Illuminate\Support\Str;
use App\Http\Controllers\SettingsController; use App\Http\Controllers\SettingsController;
use Barryvdh\DomPDF\Facade\Pdf; use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon; use Carbon\Carbon;
use \Illuminate\Contracts\View\View; use phpDocumentor\Reflection\Types\Compound;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class AcceptanceController extends Controller class AcceptanceController extends Controller
{ {
/** /**
* Show a listing of pending checkout acceptances for the current user * Show a listing of pending checkout acceptances for the current user
*
* @return View
*/ */
public function index() : View public function index()
{ {
$acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get(); $acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get();
return view('account/accept.index', compact('acceptances')); return view('account/accept.index', compact('acceptances'));
} }
@@ -47,8 +49,9 @@ class AcceptanceController extends Controller
* Shows a form to either accept or decline the checkout acceptance * Shows a form to either accept or decline the checkout acceptance
* *
* @param int $id * @param int $id
* @return mixed
*/ */
public function create($id) : View | RedirectResponse public function create($id)
{ {
$acceptance = CheckoutAcceptance::find($id); $acceptance = CheckoutAcceptance::find($id);
@@ -61,7 +64,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
} }
if (! $acceptance->isCheckedOutTo(auth()->user())) { if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
} }
@@ -77,8 +80,9 @@ class AcceptanceController extends Controller
* *
* @param Request $request * @param Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\RedirectResponse
*/ */
public function store(Request $request, $id) : RedirectResponse public function store(Request $request, $id)
{ {
$acceptance = CheckoutAcceptance::find($id); $acceptance = CheckoutAcceptance::find($id);
@@ -90,7 +94,7 @@ class AcceptanceController extends Controller
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
} }
if (! $acceptance->isCheckedOutTo(auth()->user())) { if (! $acceptance->isCheckedOutTo(Auth::user())) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted'));
} }
@@ -218,7 +222,6 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag, 'item_tag' => $item->asset_tag,
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'eula' => $item->getEula(), 'eula' => $item->getEula(),
'note' => $request->input('note'), 'note' => $request->input('note'),
'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'),
@@ -237,11 +240,7 @@ class AcceptanceController extends Controller
} }
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
try { $acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
$acceptance->notify(new AcceptanceAssetAcceptedNotification($data));
} catch (\Exception $e) {
Log::warning($e);
}
event(new CheckoutAccepted($acceptance)); event(new CheckoutAccepted($acceptance));
$return_msg = trans('admin/users/message.accepted'); $return_msg = trans('admin/users/message.accepted');
@@ -313,7 +312,6 @@ class AcceptanceController extends Controller
'item_tag' => $item->asset_tag, 'item_tag' => $item->asset_tag,
'item_model' => $display_model, 'item_model' => $display_model,
'item_serial' => $item->serial, 'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
'note' => $request->input('note'), 'note' => $request->input('note'),
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
+7 -9
View File
@@ -3,14 +3,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use Illuminate\Http\RedirectResponse; use App\Models\Actionlog;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use \Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ActionlogController extends Controller class ActionlogController extends Controller
{ {
public function displaySig($filename) : RedirectResponse | Response | bool public function displaySig($filename)
{ {
// PHP doesn't let you handle file not found errors well with // PHP doesn't let you handle file not found errors well with
// file_get_contents, so we set the error reporting for just this class // file_get_contents, so we set the error reporting for just this class
@@ -18,7 +17,6 @@ class ActionlogController extends Controller
$disk = config('filesystems.default'); $disk = config('filesystems.default');
switch (config("filesystems.disks.$disk.driver")) { switch (config("filesystems.disks.$disk.driver")) {
case 's3': case 's3':
$file = 'private_uploads/signatures/'.$filename; $file = 'private_uploads/signatures/'.$filename;
return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5))); return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5)));
@@ -32,15 +30,15 @@ class ActionlogController extends Controller
Log::warning('File '.$file.' not found'); Log::warning('File '.$file.' not found');
return false; return false;
} else { } else {
return response()->make($contents)->header('Content-Type', $filetype); return Response::make($contents)->header('Content-Type', $filetype);
} }
} }
} }
public function getStoredEula($filename) : Response | BinaryFileResponse public function getStoredEula($filename){
{
$this->authorize('view', \App\Models\Asset::class); $this->authorize('view', \App\Models\Asset::class);
$file = config('app.private_uploads').'/eula-pdfs/'.$filename; $file = config('app.private_uploads').'/eula-pdfs/'.$filename;
return response()->download($file);
return Response::download($file);
} }
} }
@@ -4,10 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AccessoryCheckoutRequest;
use App\Http\Requests\StoreAccessoryRequest;
use App\Http\Transformers\AccessoriesTransformer; use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory; use App\Models\Accessory;
@@ -18,12 +15,9 @@ use Carbon\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\AccessoryCheckout;
class AccessoriesController extends Controller class AccessoriesController extends Controller
{ {
use CheckInOutRequest;
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
@@ -51,14 +45,13 @@ class AccessoriesController extends Controller
'min_amt', 'min_amt',
'company_id', 'company_id',
'notes', 'notes',
'checkouts_count', 'users_count',
'qty', 'qty',
]; ];
$accessories = Accessory::select('accessories.*') $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier')
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser') ->withCount('users as users_count');
->withCount('checkouts as checkouts_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search')); $accessories = $accessories->TextSearch($request->input('search'));
@@ -111,10 +104,7 @@ class AccessoriesController extends Controller
break; break;
case 'supplier': case 'supplier':
$accessories = $accessories->OrderSupplier($order); $accessories = $accessories->OrderSupplier($order);
break; break;
case 'created_by':
$accessories = $accessories->OrderByCreatedByName($order);
break;
default: default:
$accessories = $accessories->orderBy($column_sort, $order); $accessories = $accessories->orderBy($column_sort, $order);
break; break;
@@ -130,12 +120,12 @@ class AccessoriesController extends Controller
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(StoreAccessoryRequest $request) public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
$accessory = new Accessory; $accessory = new Accessory;
@@ -153,15 +143,15 @@ class AccessoriesController extends Controller
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param int $id
* @return array
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) public function show($id)
{ {
$this->authorize('view', Accessory::class); $this->authorize('view', Accessory::class);
$accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id); $accessory = Accessory::withCount('users as users_count')->findOrFail($id);
return (new AccessoriesTransformer)->transformAccessory($accessory); return (new AccessoriesTransformer)->transformAccessory($accessory);
} }
@@ -170,10 +160,10 @@ class AccessoriesController extends Controller
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param int $id
* @return array
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id
* @return \Illuminate\Http\Response
*/ */
public function accessory_detail($id) public function accessory_detail($id)
{ {
@@ -204,23 +194,28 @@ class AccessoriesController extends Controller
$offset = request('offset', 0); $offset = request('offset', 0);
$limit = request('limit', 50); $limit = request('limit', 50);
$accessory_checkouts = $accessory->checkouts; $accessory_users = $accessory->users;
$total = $accessory_checkouts->count(); $total = $accessory_users->count();
if ($total < $offset) { if ($total < $offset) {
$offset = 0; $offset = 0;
} }
$accessory_checkouts = $accessory->checkouts()->skip($offset)->take($limit)->get(); $accessory_users = $accessory->users()->skip($offset)->take($limit)->get();
if ($request->filled('search')) { if ($request->filled('search')) {
$accessory_users = $accessory->users()
$accessory_checkouts = $accessory->checkouts()->TextSearch($request->input('search')) ->where(function ($query) use ($request) {
$search_str = '%' . $request->input('search') . '%';
$query->where('first_name', 'like', $search_str)
->orWhere('last_name', 'like', $search_str)
->orWhere('note', 'like', $search_str);
})
->get(); ->get();
$total = $accessory_checkouts->count(); $total = $accessory_users->count();
} }
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_checkouts, $total); return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
} }
@@ -277,31 +272,43 @@ class AccessoriesController extends Controller
* If Slack is enabled and/or asset acceptance is enabled, it will also * If Slack is enabled and/or asset acceptance is enabled, it will also
* trigger a Slack message and send an email. * trigger a Slack message and send an email.
* *
* @param int $accessoryId
* @return \Illuminate\Http\JsonResponse
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
*/ */
public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory) public function checkout(Request $request, $accessoryId)
{ {
$this->authorize('checkout', $accessory); // Check if the accessory exists
$target = $this->determineCheckoutTarget(); if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
$accessory->checkout_qty = $request->input('checkout_qty', 1); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
AccessoryCheckout::create([
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'created_by' => auth()->id(),
'assigned_to' => $target->id,
'assigned_type' => $target::class,
'note' => $request->input('note'),
]);
} }
// Set this value to be able to pass the qty through to the event $this->authorize('checkout', $accessory);
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
if ($accessory->numRemaining() > 0) {
if (! $user = User::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
}
// Update the accessory data
$accessory->assigned_to = $request->input('assigned_to');
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => Auth::id(),
'assigned_to' => $request->get('assigned_to'),
'note' => $request->get('note'),
]);
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
} }
@@ -318,19 +325,19 @@ class AccessoriesController extends Controller
*/ */
public function checkin(Request $request, $accessoryUserId = null) public function checkin(Request $request, $accessoryUserId = null)
{ {
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) { if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
} }
$accessory = Accessory::find($accessory_checkout->accessory_id); $accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory); $this->authorize('checkin', $accessory);
$logaction = $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note')); $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note'));
// Was the accessory updated? // Was the accessory updated?
if ($accessory_checkout->delete()) { if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) {
if (! is_null($accessory_checkout->assigned_to)) { if (! is_null($accessory_user->assigned_to)) {
$user = User::find($accessory_checkout->assigned_to); $user = User::find($accessory_user->assigned_to);
} }
$data['log_id'] = $logaction->id; $data['log_id'] = $logaction->id;
@@ -4,15 +4,28 @@ namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper; use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Actionlog; use App\Models\Actionlog;
use \Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use DB;
use Illuminate\Http\Request;
use App\Http\Requests\UploadFileRequest; use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse; use Input;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Paginator;
use Slack;
use Str;
use TCPDF;
use Validator;
use Route;
/** /**
@@ -31,10 +44,12 @@ class AssetFilesController extends Controller
* *
* @param \App\Http\Requests\UploadFileRequest $request * @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId * @param int $assetId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0] * @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>] * @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/ */
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse public function store(UploadFileRequest $request, $assetId = null)
{ {
// Start by checking if the asset being acted upon exists // Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
@@ -44,7 +59,7 @@ class AssetFilesController extends Controller
// Make sure we are allowed to update this asset // Make sure we are allowed to update this asset
$this->authorize('update', $asset); $this->authorize('update', $asset);
if ($request->hasFile('file')) { if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it // If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assets')) { if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775); Storage::makeDirectory('private_uploads/assets', 775);
@@ -69,10 +84,12 @@ class AssetFilesController extends Controller
* List the files for an asset. * List the files for an asset.
* *
* @param int $assetId * @param int $assetId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0] * @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>] * @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/ */
public function list($assetId = null) : JsonResponse public function list($assetId = null)
{ {
// Start by checking if the asset being acted upon exists // Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
@@ -111,7 +128,7 @@ class AssetFilesController extends Controller
* @since [v6.0] * @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>] * @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/ */
public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse public function show($assetId = null, $fileId = null)
{ {
// Start by checking if the asset being acted upon exists // Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
@@ -129,7 +146,7 @@ class AssetFilesController extends Controller
// Form the full filename with path // Form the full filename with path
$file = 'private_uploads/assets/'.$log->filename; $file = 'private_uploads/assets/'.$log->filename;
Log::debug('Checking for '.$file); \Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') { if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename; $file = 'private_uploads/audits/'.$log->filename;
@@ -161,10 +178,12 @@ class AssetFilesController extends Controller
* *
* @param int $assetId * @param int $assetId
* @param int $fileId * @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0] * @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>] * @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/ */
public function destroy($assetId = null, $fileId = null) : JsonResponse public function destroy($assetId = null, $fileId = null)
{ {
// Start by checking if the asset being acted upon exists // Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
@@ -9,8 +9,9 @@ use App\Models\Asset;
use App\Models\AssetMaintenance; use App\Models\AssetMaintenance;
use App\Models\Company; use App\Models\Company;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Input;
/** /**
* This controller handles all actions related to Asset Maintenance for * This controller handles all actions related to Asset Maintenance for
@@ -21,6 +22,7 @@ use Illuminate\Http\JsonResponse;
class AssetMaintenancesController extends Controller class AssetMaintenancesController extends Controller
{ {
/** /**
* Generates the JSON response for asset maintenances listing view. * Generates the JSON response for asset maintenances listing view.
* *
@@ -28,13 +30,14 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return string JSON
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*') $maintenances = AssetMaintenance::select('asset_maintenances.*')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser'); ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
if ($request->filled('search')) { if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search')); $maintenances = $maintenances->TextSearch($request->input('search'));
@@ -48,10 +51,6 @@ class AssetMaintenancesController extends Controller
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id')); $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
} }
if ($request->filled('created_by')) {
$maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by'));
}
if ($request->filled('asset_maintenance_type')) { if ($request->filled('asset_maintenance_type')) {
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type')); $maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
} }
@@ -73,7 +72,7 @@ class AssetMaintenancesController extends Controller
'asset_tag', 'asset_tag',
'asset_name', 'asset_name',
'serial', 'serial',
'created_by', 'user_id',
'supplier', 'supplier',
'is_warranty', 'is_warranty',
'status_label', 'status_label',
@@ -83,8 +82,8 @@ class AssetMaintenancesController extends Controller
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
switch ($sort) { switch ($sort) {
case 'created_by': case 'user_id':
$maintenances = $maintenances->OrderByCreatedBy($order); $maintenances = $maintenances->OrderAdmin($order);
break; break;
case 'supplier': case 'supplier':
$maintenances = $maintenances->OrderBySupplier($order); $maintenances = $maintenances->OrderBySupplier($order);
@@ -121,14 +120,15 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return string JSON
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// create a new model instance // create a new model instance
$maintenance = new AssetMaintenance(); $maintenance = new AssetMaintenance();
$maintenance->fill($request->all()); $maintenance->fill($request->all());
$maintenance->created_by = auth()->id(); $maintenance->user_id = Auth::id();
// Was the asset maintenance created? // Was the asset maintenance created?
if ($maintenance->save()) { if ($maintenance->save()) {
@@ -148,8 +148,9 @@ class AssetMaintenancesController extends Controller
* @param int $request * @param int $request
* @version v1.0 * @version v1.0
* @since [v4.0] * @since [v4.0]
* @return string JSON
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
@@ -185,14 +186,18 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v4.0] * @since [v4.0]
* @return string JSON
*/ */
public function destroy($assetMaintenanceId) : JsonResponse public function destroy($assetMaintenanceId)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset'));
}
$assetMaintenance->delete(); $assetMaintenance->delete();
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success'))); return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success')));
@@ -207,8 +212,9 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v4.0] * @since [v4.0]
* @return string JSON
*/ */
public function show($assetMaintenanceId) : JsonResponse public function show($assetMaintenanceId)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId);
@@ -1,200 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\AssetModel;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetModelFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $assetModel);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
$assetModel->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function list($assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that there are some uploads on this asset that can be listed
if ($assetModel->uploads->count() > 0) {
$files = array();
foreach ($assetModel->uploads as $upload) {
array_push($files, $upload);
}
// Give the list of files back to the user
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/models/message.upload.success')));
}
// There are no files.
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/models/message.upload.success')));
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error')), 500);
}
/**
* Check for permissions and display the file.
*
* @param int $assetModelId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assetmodels/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetModelId
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('update', $assetModel);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
}
@@ -4,16 +4,15 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\StoreAssetModelRequest;
use App\Http\Transformers\AssetModelsTransformer; use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel; use App\Models\AssetModel;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
/** /**
* This class controls all actions related to asset models for * This class controls all actions related to asset models for
@@ -29,8 +28,9 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$allowed_columns = $allowed_columns =
@@ -48,8 +48,6 @@ class AssetModelsController extends Controller
'assets_count', 'assets_count',
'category', 'category',
'fieldset', 'fieldset',
'deleted_at',
'updated_at',
]; ];
$assetmodels = AssetModel::select([ $assetmodels = AssetModel::select([
@@ -69,7 +67,7 @@ class AssetModelsController extends Controller
'models.deleted_at', 'models.deleted_at',
'models.updated_at', 'models.updated_at',
]) ])
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues','adminuser') ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues')
->withCount('assets as assets_count'); ->withCount('assets as assets_count');
if ($request->input('status')=='deleted') { if ($request->input('status')=='deleted') {
@@ -80,10 +78,6 @@ class AssetModelsController extends Controller
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id')); $assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
} }
if ($request->filled('depreciation_id')) {
$assetmodels = $assetmodels->where('models.depreciation_id', '=', $request->input('depreciation_id'));
}
if ($request->filled('search')) { if ($request->filled('search')) {
$assetmodels->TextSearch($request->input('search')); $assetmodels->TextSearch($request->input('search'));
} }
@@ -122,9 +116,10 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\StoreAssetModelRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(StoreAssetModelRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
$assetmodel = new AssetModel; $assetmodel = new AssetModel;
@@ -145,8 +140,9 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id); $assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
@@ -160,8 +156,9 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function assets($id) : array public function assets($id)
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$assets = Asset::where('model_id', '=', $id)->get(); $assets = Asset::where('model_id', '=', $id)->get();
@@ -179,7 +176,7 @@ class AssetModelsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function update(StoreAssetModelRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id); $assetmodel = AssetModel::findOrFail($id);
@@ -212,8 +209,9 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id); $assetmodel = AssetModel::findOrFail($id);
@@ -243,7 +241,7 @@ class AssetModelsController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
+85 -166
View File
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest; use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations; use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Models\CheckoutAcceptance; use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
@@ -26,12 +25,17 @@ use App\Models\License;
use App\Models\Location; use App\Models\Location;
use App\Models\Setting; use App\Models\Setting;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use \Illuminate\Support\Facades\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Paginator;
use Slack;
use Str;
use TCPDF;
use Validator;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -52,15 +56,11 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array public function index(Request $request, $action = null, $upcoming_status = null)
{ {
// This handles the legacy audit endpoints :(
if ($action == 'audit') {
$action = 'audits';
}
$filter_non_deprecable_assets = false; $filter_non_deprecable_assets = false;
/** /**
@@ -126,7 +126,7 @@ class AssetsController extends Controller
} }
$assets = Asset::select('assets.*') $assets = Asset::select('assets.*')
->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', 'adminuser','model.depreciation', ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo',
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
@@ -159,8 +159,8 @@ class AssetsController extends Controller
* Handle due and overdue audits and checkin dates * Handle due and overdue audits and checkin dates
*/ */
switch ($action) { switch ($action) {
// Audit (singular) is left over from earlier legacy APIs case 'audits':
case 'audits' :
switch ($upcoming_status) { switch ($upcoming_status) {
case 'due': case 'due':
$assets->DueForAudit($settings); $assets->DueForAudit($settings);
@@ -376,33 +376,8 @@ class AssetsController extends Controller
case 'assigned_to': case 'assigned_to':
$assets->OrderAssigned($order); $assets->OrderAssigned($order);
break; break;
case 'created_by':
$assets->OrderByCreatedByName($order);
break;
default: default:
$numeric_sort = false; $assets->orderBy($column_sort, $order);
// Search through the custom fields array to see if we're sorting on a custom field
if (array_search($column_sort, $all_custom_fields->pluck('db_column')->toArray()) !== false) {
// Check to see if this is a numeric field type
foreach ($all_custom_fields as $field) {
if (($field->db_column == $sort_override) && ($field->format == 'NUMERIC')) {
$numeric_sort = true;
break;
}
}
// This may not work for all databases, but it works for MySQL
if ($numeric_sort) {
$assets->orderByRaw($sort_override . ' * 1 ' . $order);
} else {
$assets->orderBy($sort_override, $order);
}
} else {
$assets->orderBy($column_sort, $order);
}
break; break;
} }
@@ -440,8 +415,9 @@ class AssetsController extends Controller
* @param string $tag * @param string $tag
* @since [v4.2.1] * @since [v4.2.1]
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @return \Illuminate\Http\JsonResponse
*/ */
public function showByTag(Request $request, $tag) : JsonResponse | array public function showByTag(Request $request, $tag)
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
$assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo'); $assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
@@ -479,7 +455,7 @@ class AssetsController extends Controller
* @since [v4.2.1] * @since [v4.2.1]
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function showBySerial(Request $request, $serial) : JsonResponse | array public function showBySerial(Request $request, $serial)
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo'); $assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
@@ -506,20 +482,19 @@ class AssetsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function show(Request $request, $id) : JsonResponse | array public function show(Request $request, $id)
{ {
if ($asset = Asset::with('assetstatus') if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
->with('assignedTo')->withTrashed() ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)) {
$this->authorize('view', $asset); $this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset, $request->input('components') ); return (new AssetsTransformer)->transformAsset($asset, $request->input('components') );
} }
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
} }
public function licenses(Request $request, $id) : array public function licenses(Request $request, $id)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
@@ -536,8 +511,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
* @return \Illuminate\Http\JsonResponse
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$assets = Asset::select([ $assets = Asset::select([
@@ -598,7 +574,7 @@ class AssetsController extends Controller
$asset->model()->associate(AssetModel::find((int) $request->get('model_id'))); $asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->fill($request->validated()); $asset->fill($request->validated());
$asset->created_by = auth()->id(); $asset->user_id = Auth::id();
/** /**
* this is here just legacy reasons. Api\AssetController * this is here just legacy reasons. Api\AssetController
@@ -609,49 +585,8 @@ class AssetsController extends Controller
} }
$asset = $request->handleImages($asset); $asset = $request->handleImages($asset);
$asset = $asset->handleCustomFieldsForStoring($request);
// Update custom fields in the database.
$model = AssetModel::find($request->input('model_id'));
// Check that it's an object and not a collection
// (Sometimes people send arrays here and they shouldn't
if (($model) && ($model instanceof AssetModel) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
// Set the field value based on what was sent in the request
$field_val = $request->input($field->db_column, null);
// If input value is null, use custom field's default value
if ($field_val == null) {
Log::debug('Field value for '.$field->db_column.' is null');
$field_val = $field->defaultValue($request->get('model_id'));
Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
if ($field->field_encrypted == '1') {
Log::debug('This model field is encrypted in this fieldset.');
if (Gate::allows('assets.view.encrypted_custom_fields')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
$field_val = Crypt::encrypt($field->defaultValue($request->get('model_id')));
} else {
$field_val = Crypt::encrypt($request->input($field->db_column));
}
}
}
if ($field->element == 'checkbox') {
if(is_array($field_val)) {
$field_val = implode(',', $field_val);
}
}
$asset->{$field->db_column} = $field_val;
}
}
if ($asset->save()) { if ($asset->save()) {
if ($request->get('assigned_user')) { if ($request->get('assigned_user')) {
@@ -662,7 +597,7 @@ class AssetsController extends Controller
$target = Location::find(request('assigned_location')); $target = Location::find(request('assigned_location'));
} }
if (isset($target)) { if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name'))); $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
} }
if ($asset->image) { if ($asset->image) {
@@ -682,87 +617,70 @@ class AssetsController extends Controller
* Accepts a POST request to update an asset * Accepts a POST request to update an asset
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$asset->fill($request->validated()); $this->authorize('update', Asset::class);
if ($request->has('model_id')) { if ($asset = Asset::find($id)) {
$asset->model()->associate(AssetModel::find($request->validated()['model_id'])); $asset->fill($request->all());
}
if ($request->has('company_id')) {
$asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
}
if ($request->has('rtd_location_id') && !$request->has('location_id')) {
$asset->location_id = $request->validated()['rtd_location_id'];
}
if ($request->input('last_audit_date')) {
$asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s');
}
/** ($request->filled('model_id')) ?
* this is here just legacy reasons. Api\AssetController $asset->model()->associate(AssetModel::find($request->get('model_id'))) : null;
* used image_source once to allow encoded image uploads. ($request->filled('rtd_location_id')) ?
*/ $asset->location_id = $request->get('rtd_location_id') : '';
if ($request->has('image_source')) { ($request->filled('company_id')) ?
$request->offsetSet('image', $request->offsetGet('image_source')); $asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : '';
}
($request->filled('rtd_location_id')) ?
$asset->location_id = $request->get('rtd_location_id') : null;
/**
* this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads.
*/
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
}
$asset = $request->handleImages($asset);
$asset = $asset->handleCustomFieldsForStoring($request);
$asset = $request->handleImages($asset);
$model = $asset->model;
// Update custom fields
$problems_updating_encrypted_custom_fields = false;
if (($model) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
$field_val = $request->input($field->db_column, null);
if ($request->has($field->db_column)) {
if ($field->element == 'checkbox') {
if(is_array($field_val)) {
$field_val = implode(',', $field_val);
}
}
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
$field_val = Crypt::encrypt($field_val);
} else {
$problems_updating_encrypted_custom_fields = true;
continue;
}
}
$asset->{$field->db_column} = $field_val;
}
}
}
if ($asset->save()) { if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id; $location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id; $location = $target->location_id;
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id) Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id)
->update(['location_id' => $target->location_id]); ->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
$location = $target->id; $location = $target->id;
} }
if (isset($target)) { if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location); $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
} }
if ($asset->image) { if ($asset->image) {
$asset->image = $asset->getImageUrl(); $asset->image = $asset->getImageUrl();
} }
if ($problems_updating_encrypted_custom_fields) { if ($problems_updating_encrypted_custom_fields) {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning'))); return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning')));
} else { } else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
}
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
} }
@@ -772,24 +690,18 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Asset::class); $this->authorize('delete', Asset::class);
if ($asset = Asset::find($id)) { if ($asset = Asset::find($id)) {
$this->authorize('delete', $asset); $this->authorize('delete', $asset);
if ($asset->assignedTo) { DB::table('assets')
->where('id', $asset->id)
$target = $asset->assignedTo; ->update(['assigned_to' => null]);
$checkin_at = date('Y-m-d H:i:s');
$originalValues = $asset->getRawOriginal();
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues));
DB::table('assets')
->where('id', $asset->id)
->update(['assigned_to' => null]);
}
$asset->delete(); $asset->delete();
@@ -807,8 +719,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v5.1.18] * @since [v5.1.18]
* @return \Illuminate\Http\JsonResponse
*/ */
public function restore(Request $request, $assetId = null) : JsonResponse public function restore(Request $request, $assetId = null)
{ {
if ($asset = Asset::withTrashed()->find($assetId)) { if ($asset = Asset::withTrashed()->find($assetId)) {
@@ -836,8 +749,9 @@ class AssetsController extends Controller
* @author [N. Butler] * @author [N. Butler]
* @param string $tag * @param string $tag
* @since [v6.0.5] * @since [v6.0.5]
* @return \Illuminate\Http\JsonResponse
*/ */
public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonResponse public function checkoutByTag(AssetCheckoutRequest $request, $tag)
{ {
if ($asset = Asset::where('asset_tag', $tag)->first()) { if ($asset = Asset::where('asset_tag', $tag)->first()) {
return $this->checkout($request, $asset->id); return $this->checkout($request, $asset->id);
@@ -851,8 +765,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonResponse public function checkout(AssetCheckoutRequest $request, $asset_id)
{ {
$this->authorize('checkout', Asset::class); $this->authorize('checkout', Asset::class);
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
@@ -914,7 +829,7 @@ class AssetsController extends Controller
// $asset->location_id = $target->rtd_location_id; // $asset->location_id = $target->rtd_location_id;
// } // }
if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) { if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
} }
@@ -928,8 +843,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function checkin(Request $request, $asset_id) : JsonResponse public function checkin(Request $request, $asset_id)
{ {
$asset = Asset::with('model')->findOrFail($asset_id); $asset = Asset::with('model')->findOrFail($asset_id);
$this->authorize('checkin', $asset); $this->authorize('checkin', $asset);
@@ -965,7 +881,7 @@ class AssetsController extends Controller
} }
} }
if ($request->filled('status_id')) { if ($request->has('status_id')) {
$asset->status_id = $request->input('status_id'); $asset->status_id = $request->input('status_id');
} }
@@ -994,7 +910,7 @@ class AssetsController extends Controller
}); });
if ($asset->save()) { if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
return response()->json(Helper::formatStandardApiResponse('success', [ return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag'=> e($asset->asset_tag), 'asset_tag'=> e($asset->asset_tag),
@@ -1011,11 +927,12 @@ class AssetsController extends Controller
* *
* @author [A. Janes] [<ajanes@adagiohealth.org>] * @author [A. Janes] [<ajanes@adagiohealth.org>]
* @since [v6.0] * @since [v6.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function checkinByTag(Request $request, $tag = null) : JsonResponse public function checkinByTag(Request $request, $tag = null)
{ {
$this->authorize('checkin', Asset::class); $this->authorize('checkin', Asset::class);
if (null == $tag && null !== ($request->input('asset_tag'))) { if(null == $tag && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag'); $tag = $request->input('asset_tag');
} }
$asset = Asset::where('asset_tag', $tag)->first(); $asset = Asset::where('asset_tag', $tag)->first();
@@ -1036,8 +953,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function audit(Request $request) : JsonResponse public function audit(Request $request)
{ {
$this->authorize('audit', Asset::class); $this->authorize('audit', Asset::class);
@@ -1128,8 +1046,9 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function requestable(Request $request) : JsonResponse | array public function requestable(Request $request)
{ {
$this->authorize('viewRequestable', Asset::class); $this->authorize('viewRequestable', Asset::class);
@@ -8,9 +8,9 @@ use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Category; use App\Models\Category;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class CategoriesController extends Controller class CategoriesController extends Controller
{ {
@@ -21,7 +21,7 @@ class CategoriesController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : array public function index(Request $request)
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
$allowed_columns = [ $allowed_columns = [
@@ -43,7 +43,6 @@ class CategoriesController extends Controller
$categories = Category::select([ $categories = Category::select([
'id', 'id',
'created_by',
'created_at', 'created_at',
'updated_at', 'updated_at',
'name', 'category_type', 'name', 'category_type',
@@ -51,10 +50,8 @@ class CategoriesController extends Controller
'eula_text', 'eula_text',
'require_acceptance', 'require_acceptance',
'checkin_email', 'checkin_email',
'image', 'image'
]) ])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
/* /*
@@ -94,33 +91,13 @@ class CategoriesController extends Controller
$categories->where('checkin_email', '=', $request->input('checkin_email')); $categories->where('checkin_email', '=', $request->input('checkin_email'));
} }
if ($request->filled('created_by')) {
$categories->where('created_by', '=', $request->input('created_by'));
}
if ($request->filled('created_at')) {
$categories->where('created_at', '=', $request->input('created_at'));
}
if ($request->filled('updated_at')) {
$categories->where('updated_at', '=', $request->input('updated_at'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value'); $offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets_count';
switch ($sort_override) { $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
case 'created_by': $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
$categories = $categories->OrderByCreatedBy($order); $categories->orderBy($sort, $order);
break;
default:
$categories = $categories->orderBy($column_sort, $order);
break;
}
$total = $categories->count(); $total = $categories->count();
$categories = $categories->skip($offset)->take($limit)->get(); $categories = $categories->skip($offset)->take($limit)->get();
@@ -138,7 +115,7 @@ class CategoriesController extends Controller
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
$category = new Category; $category = new Category;
@@ -159,8 +136,9 @@ class CategoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id); $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
@@ -178,7 +156,7 @@ class CategoriesController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
$category = Category::findOrFail($id); $category = Category::findOrFail($id);
@@ -186,7 +164,7 @@ class CategoriesController extends Controller
// Don't allow the user to change the category_type once it's been created // Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) { if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
return response()->json( return response()->json(
Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422) Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
); );
} }
$category->fill($request->all()); $category->fill($request->all());
@@ -207,7 +185,7 @@ class CategoriesController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Category::class); $this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id); $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
@@ -230,7 +208,7 @@ class CategoriesController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request, $category_type = 'asset') : array public function selectlist(Request $request, $category_type = 'asset')
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
$categories = Category::select([ $categories = Category::select([
@@ -10,7 +10,6 @@ use App\Models\Company;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
class CompaniesController extends Controller class CompaniesController extends Controller
{ {
@@ -19,8 +18,9 @@ class CompaniesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
@@ -42,7 +42,7 @@ class CompaniesController extends Controller
$companies = Company::withCount(['assets as assets_count' => function ($query) { $companies = Company::withCount(['assets as assets_count' => function ($query) {
$query->AssetsForShow(); $query->AssetsForShow();
}])->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count'); }])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$companies->TextSearch($request->input('search')); $companies->TextSearch($request->input('search'));
@@ -56,29 +56,17 @@ class CompaniesController extends Controller
$companies->where('email', '=', $request->input('email')); $companies->where('email', '=', $request->input('email'));
} }
if ($request->filled('created_by')) {
$companies->where('created_by', '=', $request->input('created_by'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value'); $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
case 'created_by': $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$companies = $companies->OrderByCreatedBy($order); $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
break; $companies->orderBy($sort, $order);
default:
$companies = $companies->orderBy($column_sort, $order);
break;
}
$total = $companies->count(); $total = $companies->count();
$companies = $companies->skip($offset)->take($limit)->get(); $companies = $companies->skip($offset)->take($limit)->get();
return (new CompaniesTransformer)->transformCompanies($companies, $total); return (new CompaniesTransformer)->transformCompanies($companies, $total);
@@ -91,8 +79,9 @@ class CompaniesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
$company = new Company; $company = new Company;
@@ -113,8 +102,9 @@ class CompaniesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@@ -130,8 +120,9 @@ class CompaniesController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Company::class); $this->authorize('update', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@@ -153,8 +144,9 @@ class CompaniesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Company::class); $this->authorize('delete', Company::class);
$company = Company::findOrFail($id); $company = Company::findOrFail($id);
@@ -177,7 +169,7 @@ class CompaniesController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
$companies = Company::select([ $companies = Company::select([
@@ -5,17 +5,16 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Transformers\ComponentsTransformer; use App\Http\Transformers\ComponentsTransformer;
use App\Models\Company;
use App\Models\Component; use App\Models\Component;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class ComponentsController extends Controller class ComponentsController extends Controller
{ {
@@ -25,8 +24,9 @@ class ComponentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* *
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Component::class); $this->authorize('view', Component::class);
@@ -47,7 +47,7 @@ class ComponentsController extends Controller
]; ];
$components = Component::select('components.*') $components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser'); ->with('company', 'location', 'category', 'assets', 'supplier');
if ($request->filled('search')) { if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search')); $components = $components->TextSearch($request->input('search'));
@@ -98,9 +98,6 @@ class ComponentsController extends Controller
case 'supplier': case 'supplier':
$components = $components->OrderSupplier($order); $components = $components->OrderSupplier($order);
break; break;
case 'created_by':
$components = $components->OrderByCreatedBy($order);
break;
default: default:
$components = $components->orderBy($column_sort, $order); $components = $components->orderBy($column_sort, $order);
break; break;
@@ -119,8 +116,9 @@ class ComponentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Component::class); $this->authorize('create', Component::class);
$component = new Component; $component = new Component;
@@ -139,8 +137,9 @@ class ComponentsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Component::class); $this->authorize('view', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@@ -157,8 +156,9 @@ class ComponentsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Component::class); $this->authorize('update', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@@ -179,8 +179,9 @@ class ComponentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Component::class); $this->authorize('delete', Component::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
@@ -197,8 +198,9 @@ class ComponentsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param Request $request * @param Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function getAssets(Request $request, $id) : array public function getAssets(Request $request, $id)
{ {
$this->authorize('view', \App\Models\Asset::class); $this->authorize('view', \App\Models\Asset::class);
@@ -239,8 +241,10 @@ class ComponentsController extends Controller
* @since [v5.1.8] * @since [v5.1.8]
* @param Request $request * @param Request $request
* @param int $componentId * @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function checkout(Request $request, $componentId) : JsonResponse public function checkout(Request $request, $componentId)
{ {
// Check if the component exists // Check if the component exists
if (!$component = Component::find($componentId)) { if (!$component = Component::find($componentId)) {
@@ -271,9 +275,9 @@ class ComponentsController extends Controller
$component->assets()->attach($component->id, [ $component->assets()->attach($component->id, [
'component_id' => $component->id, 'component_id' => $component->id,
'created_at' => Carbon::now(), 'created_at' => \Carbon::now(),
'assigned_qty' => $request->get('assigned_qty', 1), 'assigned_qty' => $request->get('assigned_qty', 1),
'created_by' => auth()->id(), 'user_id' => \Auth::id(),
'asset_id' => $request->get('assigned_to'), 'asset_id' => $request->get('assigned_to'),
'note' => $request->get('note'), 'note' => $request->get('note'),
]); ]);
@@ -293,10 +297,12 @@ class ComponentsController extends Controller
* @since [v5.1.8] * @since [v5.1.8]
* @param Request $request * @param Request $request
* @param $component_asset_id * @param $component_asset_id
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function checkin(Request $request, $component_asset_id) : JsonResponse public function checkin(Request $request, $component_asset_id)
{ {
if ($component_assets = DB::table('components_assets')->find($component_asset_id)) { if ($component_assets = \DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) { if (is_null($component = Component::find($component_assets->component_id))) {
@@ -309,7 +315,7 @@ class ComponentsController extends Controller
if ($max_to_checkin > 1) { if ($max_to_checkin > 1) {
$validator = Validator::make($request->all(), [ $validator = \Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin" "checkin_qty" => "required|numeric|between:1,$max_to_checkin"
]); ]);
@@ -328,19 +334,19 @@ class ComponentsController extends Controller
Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id); Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
DB::table('components_assets')->where('id', \DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
// If the checked-in qty is exactly the same as the assigned_qty, // If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record // we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) { if ($qty_remaining_in_checkout == 0) {
DB::table('components_assets')->where('id', '=', $component_asset_id)->delete(); \DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
} }
$asset = Asset::find($component_assets->asset_id); $asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); event(new CheckoutableCheckedIn($component, $asset, \Auth::user(), $request->input('note'), \Carbon::now()));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\StoreConsumableRequest;
use App\Http\Transformers\ConsumablesTransformer; use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company; use App\Models\Company;
@@ -13,8 +12,8 @@ use App\Models\Consumable;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
class ConsumablesController extends Controller class ConsumablesController extends Controller
{ {
@@ -23,13 +22,34 @@ class ConsumablesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
*
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : array public function index(Request $request)
{ {
$this->authorize('index', Consumable::class); $this->authorize('index', Consumable::class);
$consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer') // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
->withCount('users as consumables_users_count'); // Relations will be handled in query scopes a little further down.
$allowed_columns =
[
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'qty',
'image',
'notes',
];
$consumables = Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer');
if ($request->filled('search')) { if ($request->filled('search')) {
$consumables = $consumables->TextSearch(e($request->input('search'))); $consumables = $consumables->TextSearch(e($request->input('search')));
@@ -71,9 +91,15 @@ class ConsumablesController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value'); $offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($request->input('sort')) { $sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
case 'category': case 'category':
$consumables = $consumables->OrderCategory($order); $consumables = $consumables->OrderCategory($order);
break; break;
@@ -86,37 +112,11 @@ class ConsumablesController extends Controller
case 'company': case 'company':
$consumables = $consumables->OrderCompany($order); $consumables = $consumables->OrderCompany($order);
break; break;
case 'remaining':
$consumables = $consumables->OrderRemaining($order);
break;
case 'supplier': case 'supplier':
$consumables = $consumables->OrderSupplier($order); $components = $consumables->OrderSupplier($order);
break;
case 'created_by':
$consumables = $consumables->OrderByCreatedBy($order);
break; break;
default: default:
// This array is what determines which fields should be allowed to be sorted on ON the table itself. $consumables = $consumables->orderBy($column_sort, $order);
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'manufacturer',
'location',
'qty',
'image'
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$consumables = $consumables->orderBy($sort, $order);
break; break;
} }
@@ -132,8 +132,9 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(StoreConsumableRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Consumable::class); $this->authorize('create', Consumable::class);
$consumable = new Consumable; $consumable = new Consumable;
@@ -152,8 +153,9 @@ class ConsumablesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Consumable::class); $this->authorize('view', Consumable::class);
$consumable = Consumable::with('users')->findOrFail($id); $consumable = Consumable::with('users')->findOrFail($id);
@@ -168,8 +170,9 @@ class ConsumablesController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(StoreConsumableRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Consumable::class); $this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id); $consumable = Consumable::findOrFail($id);
@@ -189,8 +192,9 @@ class ConsumablesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Consumable::class); $this->authorize('delete', Consumable::class);
$consumable = Consumable::findOrFail($id); $consumable = Consumable::findOrFail($id);
@@ -207,13 +211,14 @@ class ConsumablesController extends Controller
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form. * @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0] * @since [v1.0]
* @param int $consumableId * @param int $consumableId
* @return array
*/ */
public function getDataView($consumableId) : array public function getDataView($consumableId)
{ {
$consumable = Consumable::with(['consumableAssignments'=> function ($query) { $consumable = Consumable::with(['consumableAssignments'=> function ($query) {
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC'); $query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
}, },
'consumableAssignments.adminuser'=> function ($query) { 'consumableAssignments.admin'=> function ($query) {
}, },
'consumableAssignments.user'=> function ($query) { 'consumableAssignments.user'=> function ($query) {
}, },
@@ -231,8 +236,7 @@ class ConsumablesController extends Controller
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User', 'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null, 'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response 'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null,
'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null,
]; ];
} }
@@ -248,8 +252,9 @@ class ConsumablesController extends Controller
* @author [A. Gutierrez] [<andres@baller.tv>] * @author [A. Gutierrez] [<andres@baller.tv>]
* @param int $id * @param int $id
* @since [v4.9.5] * @since [v4.9.5]
* @return JsonResponse
*/ */
public function checkout(Request $request, $id) : JsonResponse public function checkout(Request $request, $id)
{ {
// Check if the consumable exists // Check if the consumable exists
if (!$consumable = Consumable::with('users')->find($id)) { if (!$consumable = Consumable::with('users')->find($id)) {
@@ -273,6 +278,7 @@ class ConsumablesController extends Controller
if (!$user = User::find($request->input('assigned_to'))) { if (!$user = User::find($request->input('assigned_to'))) {
// Return error message // Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found')); return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
Log::debug('No valid user');
} }
// Update the consumable data // Update the consumable data
@@ -281,13 +287,13 @@ class ConsumablesController extends Controller
$consumable->users()->attach($consumable->id, $consumable->users()->attach($consumable->id,
[ [
'consumable_id' => $consumable->id, 'consumable_id' => $consumable->id,
'created_by' => $user->id, 'user_id' => $user->id,
'assigned_to' => $request->input('assigned_to'), 'assigned_to' => $request->input('assigned_to'),
'note' => $request->input('note'), 'note' => $request->input('note'),
] ]
); );
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
@@ -298,7 +304,7 @@ class ConsumablesController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$consumables = Consumable::select([ $consumables = Consumable::select([
'consumables.id', 'consumables.id',
@@ -8,8 +8,7 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField; use App\Models\CustomField;
use App\Models\CustomFieldset; use App\Models\CustomFieldset;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator; use Validator;
use Illuminate\Http\JsonResponse;
class CustomFieldsController extends Controller class CustomFieldsController extends Controller
{ {
@@ -21,7 +20,7 @@ class CustomFieldsController extends Controller
* @since [v3.0] * @since [v3.0]
* @return array * @return array
*/ */
public function index() : array public function index()
{ {
$this->authorize('index', CustomField::class); $this->authorize('index', CustomField::class);
$fields = CustomField::get(); $fields = CustomField::get();
@@ -34,8 +33,9 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] [<volker@fdatek.de>] * @author [V. Cordes] [<volker@fdatek.de>]
* @param int $id * @param int $id
* @since [v4.1.10] * @since [v4.1.10]
* @return View
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) { if ($field = CustomField::find($id)) {
@@ -52,8 +52,9 @@ class CustomFieldsController extends Controller
* @since [v4.1.10] * @since [v4.1.10]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id); $field = CustomField::findOrFail($id);
@@ -85,8 +86,9 @@ class CustomFieldsController extends Controller
* @author [V. Cordes] [<volker@fdatek.de>] * @author [V. Cordes] [<volker@fdatek.de>]
* @since [v4.1.10] * @since [v4.1.10]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$field = new CustomField; $field = new CustomField;
@@ -134,7 +136,7 @@ class CustomFieldsController extends Controller
return $fieldset->fields()->sync($fields); return $fieldset->fields()->sync($fields);
} }
public function associate(Request $request, $field_id) : JsonResponse public function associate(Request $request, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomFieldset::class);
@@ -153,9 +155,10 @@ class CustomFieldsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
} }
public function disassociate(Request $request, $field_id) : JsonResponse public function disassociate(Request $request, $field_id)
{ {
$this->authorize('update', CustomFieldset::class); $this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
$fieldset_id = $request->input('fieldset_id'); $fieldset_id = $request->input('fieldset_id');
@@ -176,8 +179,9 @@ class CustomFieldsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy($field_id) : JsonResponse public function destroy($field_id)
{ {
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
@@ -9,7 +9,8 @@ use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomFieldset; use App\Models\CustomFieldset;
use App\Models\CustomField; use App\Models\CustomField;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Redirect;
use View;
/** /**
* This controller handles all actions related to Custom Asset Fieldsets for * This controller handles all actions related to Custom Asset Fieldsets for
@@ -29,8 +30,9 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson] * @author [Josh Gibson]
* @param int $id * @param int $id
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
public function index() : array public function index()
{ {
$this->authorize('index', CustomField::class); $this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
@@ -44,8 +46,9 @@ class CustomFieldsetsController extends Controller
* @author [Josh Gibson] * @author [Josh Gibson]
* @param int $id * @param int $id
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) { if ($fieldset = CustomFieldset::find($id)) {
@@ -62,8 +65,9 @@ class CustomFieldsetsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
@@ -82,8 +86,9 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset; $fieldset = new CustomFieldset;
@@ -113,8 +118,9 @@ class CustomFieldsetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', CustomField::class); $this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id); $fieldset = CustomFieldset::findOrFail($id);
@@ -141,7 +147,7 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId * @param $fieldsetId
* @return string JSON * @return string JSON
*/ */
public function fields($id) : array public function fields($id)
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id); $set = CustomFieldset::findOrFail($id);
@@ -158,11 +164,14 @@ class CustomFieldsetsController extends Controller
* @param $fieldsetId * @param $fieldsetId
* @return string JSON * @return string JSON
*/ */
public function fieldsWithDefaultValues($fieldsetId, $modelId) : array public function fieldsWithDefaultValues($fieldsetId, $modelId)
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($fieldsetId); $set = CustomFieldset::findOrFail($fieldsetId);
$fields = $set->fields; $fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count()); return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
} }
} }
@@ -6,11 +6,12 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Transformers\DepartmentsTransformer; use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use App\Models\Department; use App\Models\Department;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
class DepartmentsController extends Controller class DepartmentsController extends Controller
{ {
@@ -19,8 +20,9 @@ class DepartmentsController extends Controller
* *
* @author [Godfrey Martinez] [<snipe@snipe.net>] * @author [Godfrey Martinez] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Department::class); $this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count']; $allowed_columns = ['id', 'name', 'image', 'users_count'];
@@ -89,15 +91,16 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Department::class); $this->authorize('create', Department::class);
$department = new Department; $department = new Department;
$department->fill($request->all()); $department->fill($request->all());
$department = $request->handleImages($department); $department = $request->handleImages($department);
$department->created_by = auth()->id(); $department->user_id = Auth::user()->id;
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
if ($department->save()) { if ($department->save()) {
@@ -113,11 +116,13 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Department::class); $this->authorize('view', Department::class);
$department = Department::findOrFail($id); $department = Department::findOrFail($id);
return (new DepartmentsTransformer)->transformDepartment($department); return (new DepartmentsTransformer)->transformDepartment($department);
} }
@@ -128,8 +133,9 @@ class DepartmentsController extends Controller
* @since [v5.0] * @since [v5.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Department::class); $this->authorize('update', Department::class);
$department = Department::findOrFail($id); $department = Department::findOrFail($id);
@@ -150,8 +156,9 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId * @param int $locationId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$department = Department::findOrFail($id); $department = Department::findOrFail($id);
@@ -173,7 +180,7 @@ class DepartmentsController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
@@ -7,7 +7,6 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\DepreciationsTransformer; use App\Http\Transformers\DepreciationsTransformer;
use App\Models\Depreciation; use App\Models\Depreciation;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class DepreciationsController extends Controller class DepreciationsController extends Controller
{ {
@@ -16,27 +15,14 @@ class DepreciationsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Depreciation::class); $this->authorize('view', Depreciation::class);
$allowed_columns = [ $allowed_columns = ['id','name','months','depreciation_min','created_at'];
'id',
'name',
'months',
'depreciation_min',
'depreciation_type',
'created_at',
'assets_count',
'models_count',
'licenses_count',
];
$depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','created_at','updated_at', 'created_by') $depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at');
->with('adminuser')
->withCount('assets as assets_count')
->withCount('models as models_count')
->withCount('licenses as licenses_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$depreciations = $depreciations->TextSearch($request->input('search')); $depreciations = $depreciations->TextSearch($request->input('search'));
@@ -45,18 +31,10 @@ class DepreciationsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value'); $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) { $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
case 'created_by': $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$depreciations = $depreciations->OrderByCreatedBy($order); $depreciations->orderBy($sort, $order);
break;
default:
$depreciations = $depreciations->orderBy($column_sort, $order);
break;
}
$total = $depreciations->count(); $total = $depreciations->count();
$depreciations = $depreciations->skip($offset)->take($limit)->get(); $depreciations = $depreciations->skip($offset)->take($limit)->get();
@@ -70,8 +48,9 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('create', Depreciation::class); $this->authorize('create', Depreciation::class);
$depreciation = new Depreciation; $depreciation = new Depreciation;
@@ -90,8 +69,9 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', Depreciation::class); $this->authorize('view', Depreciation::class);
$depreciation = Depreciation::findOrFail($id); $depreciation = Depreciation::findOrFail($id);
@@ -106,8 +86,9 @@ class DepreciationsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', Depreciation::class); $this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id); $depreciation = Depreciation::findOrFail($id);
@@ -126,8 +107,9 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Depreciation::class); $this->authorize('delete', Depreciation::class);
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id); $depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);
+18 -28
View File
@@ -7,7 +7,7 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\GroupsTransformer; use App\Http\Transformers\GroupsTransformer;
use App\Models\Group; use App\Models\Group;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth;
class GroupsController extends Controller class GroupsController extends Controller
@@ -17,14 +17,16 @@ class GroupsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$this->authorize('view', Group::class); $this->authorize('view', Group::class);
$allowed_columns = ['id', 'name', 'created_at', 'users_count'];
$groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count'); $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search')); $groups = $groups->TextSearch($request->input('search'));
@@ -34,29 +36,13 @@ class GroupsController extends Controller
$groups->where('name', '=', $request->input('name')); $groups->where('name', '=', $request->input('name'));
} }
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value'); $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($request->input('sort')) { $groups->orderBy($sort, $order);
case 'created_by':
$groups = $groups->OrderByCreatedBy($order);
break;
default:
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'created_at',
'users_count',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$groups = $groups->orderBy($sort, $order);
break;
}
$total = $groups->count(); $total = $groups->count();
$groups = $groups->skip($offset)->take($limit)->get(); $groups = $groups->skip($offset)->take($limit)->get();
@@ -70,8 +56,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = new Group; $group = new Group;
@@ -80,7 +67,7 @@ class GroupsController extends Controller
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions); $groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$group->name = $request->input('name'); $group->name = $request->input('name');
$group->created_by = auth()->id(); $group->created_by = Auth::user()->id;
$group->permissions = json_encode($request->input('permissions', $groupPermissions)); $group->permissions = json_encode($request->input('permissions', $groupPermissions));
if ($group->save()) { if ($group->save()) {
@@ -96,8 +83,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
@@ -111,8 +99,9 @@ class GroupsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
@@ -133,8 +122,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('superadmin'); $this->authorize('superadmin');
$group = Group::findOrFail($id); $group = Group::findOrFail($id);
@@ -17,15 +17,15 @@ use Illuminate\Support\Facades\Storage;
use League\Csv\Reader; use League\Csv\Reader;
use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
class ImportController extends Controller class ImportController extends Controller
{ {
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @return \Illuminate\Http\Response
*/ */
public function index() : JsonResponse | array public function index()
{ {
$this->authorize('import'); $this->authorize('import');
$imports = Import::latest()->get(); $imports = Import::latest()->get();
@@ -37,8 +37,9 @@ class ImportController extends Controller
* Process and store a CSV upload file. * Process and store a CSV upload file.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/ */
public function store() : JsonResponse public function store()
{ {
$this->authorize('import'); $this->authorize('import');
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
@@ -151,8 +152,9 @@ class ImportController extends Controller
* Processes the specified Import. * Processes the specified Import.
* *
* @param int $import_id * @param int $import_id
* @return \Illuminate\Http\Response
*/ */
public function process(ItemImportRequest $request, $import_id) : JsonResponse public function process(ItemImportRequest $request, $import_id)
{ {
$this->authorize('import'); $this->authorize('import');
@@ -210,8 +212,9 @@ class ImportController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $import_id * @param int $import_id
* @return \Illuminate\Http\Response
*/ */
public function destroy($import_id) : JsonResponse public function destroy($import_id)
{ {
$this->authorize('create', Asset::class); $this->authorize('create', Asset::class);
@@ -228,8 +231,6 @@ class ImportController extends Controller
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning'))); return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
} }
} }
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
} }
} }
@@ -8,7 +8,7 @@ use App\Http\Transformers\LabelsTransformer;
use App\Models\Labels\Label; use App\Models\Labels\Label;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\ItemNotFoundException; use Illuminate\Support\ItemNotFoundException;
use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth;
class LabelsController extends Controller class LabelsController extends Controller
{ {
@@ -16,8 +16,9 @@ class LabelsController extends Controller
* Returns JSON listing of all labels. * Returns JSON listing of all labels.
* *
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com> * @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @return JsonResponse
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Label::class); $this->authorize('view', Label::class);
@@ -49,8 +50,9 @@ class LabelsController extends Controller
* *
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com> * @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @param string $labelName * @param string $labelName
* @return JsonResponse
*/ */
public function show(string $labelName) : JsonResponse | array public function show(string $labelName)
{ {
$labelName = str_replace('/', '\\', $labelName); $labelName = str_replace('/', '\\', $labelName);
try { try {
@@ -9,7 +9,7 @@ use App\Models\Asset;
use App\Models\License; use App\Models\License;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
use App\Models\User; use App\Models\User;
use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class LicenseSeatsController extends Controller class LicenseSeatsController extends Controller
@@ -19,10 +19,11 @@ class LicenseSeatsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $licenseId * @param int $licenseId
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request, $licenseId) : JsonResponse | array public function index(Request $request, $licenseId)
{ {
//
if ($license = License::find($licenseId)) { if ($license = License::find($licenseId)) {
$this->authorize('view', $license); $this->authorize('view', $license);
@@ -63,10 +64,11 @@ class LicenseSeatsController extends Controller
* *
* @param int $licenseId * @param int $licenseId
* @param int $seatId * @param int $seatId
* @return \Illuminate\Http\Response
*/ */
public function show($licenseId, $seatId) : JsonResponse | array public function show($licenseId, $seatId)
{ {
//
$this->authorize('view', License::class); $this->authorize('view', License::class);
// sanity checks: // sanity checks:
// 1. does the license seat exist? // 1. does the license seat exist?
@@ -87,18 +89,19 @@ class LicenseSeatsController extends Controller
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $licenseId * @param int $licenseId
* @param int $seatId * @param int $seatId
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $licenseId, $seatId) : JsonResponse | array public function update(Request $request, $licenseId, $seatId)
{ {
$this->authorize('checkout', License::class); $this->authorize('checkout', License::class);
// sanity checks:
// 1. does the license seat exist?
if (! $licenseSeat = LicenseSeat::find($seatId)) { if (! $licenseSeat = LicenseSeat::find($seatId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
} }
// 2. does the seat belong to the specified license?
$license = $licenseSeat->license()->first(); if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
if (!$license || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
} }
@@ -107,7 +110,7 @@ class LicenseSeatsController extends Controller
// attempt to update the license seat // attempt to update the license seat
$licenseSeat->fill($request->all()); $licenseSeat->fill($request->all());
$licenseSeat->created_by = auth()->id(); $licenseSeat->user_id = Auth::user()->id;
// check if this update is a checkin operation // check if this update is a checkin operation
// 1. are relevant fields touched at all? // 1. are relevant fields touched at all?
+19 -16
View File
@@ -4,12 +4,14 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Transformers\LicenseSeatsTransformer;
use App\Http\Transformers\LicensesTransformer; use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use App\Models\License; use App\Models\License;
use App\Models\LicenseSeat;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
class LicensesController extends Controller class LicensesController extends Controller
{ {
@@ -19,15 +21,16 @@ class LicensesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* *
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', License::class); $this->authorize('view', License::class);
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count'); $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count');
if ($request->filled('company_id')) { if ($request->filled('company_id')) {
$licenses->where('licenses.company_id', '=', $request->input('company_id')); $licenses->where('company_id', '=', $request->input('company_id'));
} }
if ($request->filled('name')) { if ($request->filled('name')) {
@@ -70,9 +73,6 @@ class LicensesController extends Controller
$licenses->where('depreciation_id', '=', $request->input('depreciation_id')); $licenses->where('depreciation_id', '=', $request->input('depreciation_id'));
} }
if ($request->filled('created_by')) {
$licenses->where('created_by', '=', $request->input('created_by'));
}
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) { if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
$licenses->where('maintained','=',1); $licenses->where('maintained','=',1);
@@ -116,9 +116,6 @@ class LicensesController extends Controller
case 'company': case 'company':
$licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order); $licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order);
break; break;
case 'created_by':
$licenses = $licenses->OrderByCreatedBy($order);
break;
default: default:
$allowed_columns = $allowed_columns =
[ [
@@ -159,9 +156,11 @@ class LicensesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
//
$this->authorize('create', License::class); $this->authorize('create', License::class);
$license = new License; $license = new License;
$license->fill($request->all()); $license->fill($request->all());
@@ -178,11 +177,12 @@ class LicensesController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', License::class); $this->authorize('view', License::class);
$license = License::withCount('freeSeats as free_seats_count')->findOrFail($id); $license = License::withCount('freeSeats')->findOrFail($id);
$license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset'); $license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset');
return (new LicensesTransformer)->transformLicense($license); return (new LicensesTransformer)->transformLicense($license);
@@ -195,8 +195,9 @@ class LicensesController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse | array public function update(Request $request, $id)
{ {
// //
$this->authorize('update', License::class); $this->authorize('update', License::class);
@@ -217,9 +218,11 @@ class LicensesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
//
$license = License::findOrFail($id); $license = License::findOrFail($id);
$this->authorize('delete', $license); $this->authorize('delete', $license);
@@ -245,7 +248,7 @@ class LicensesController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$licenses = License::select([ $licenses = License::select([
'licenses.id', 'licenses.id',
@@ -5,15 +5,12 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer; use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\Location; use App\Models\Location;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Http\JsonResponse;
class LocationsController extends Controller class LocationsController extends Controller
{ {
@@ -24,7 +21,7 @@ class LocationsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Location::class); $this->authorize('view', Location::class);
$allowed_columns = [ $allowed_columns = [
@@ -141,8 +138,9 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Location::class); $this->authorize('create', Location::class);
$location = new Location; $location = new Location;
@@ -162,8 +160,9 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', Location::class); $this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children') $location = Location::with('parent', 'manager', 'children')
@@ -200,8 +199,9 @@ class LocationsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\JsonResponse
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Location::class); $this->authorize('update', Location::class);
$location = Location::findOrFail($id); $location = Location::findOrFail($id);
@@ -224,23 +224,15 @@ class LocationsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
} }
public function assets(Request $request, Location $location) : JsonResponse | array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $location);
$assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
}
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Location::class); $this->authorize('delete', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count') $location = Location::withCount('assignedAssets as assigned_assets_count')
@@ -248,7 +240,6 @@ class LocationsController extends Controller
->withCount('rtd_assets as rtd_assets_count') ->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count') ->withCount('children as children_count')
->withCount('users as users_count') ->withCount('users as users_count')
->withCount('accessories as accessories_count')
->findOrFail($id); ->findOrFail($id);
if (! $location->isDeletable()) { if (! $location->isDeletable()) {
@@ -289,7 +280,7 @@ class LocationsController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
// If a user is in the process of editing their profile, as determined by the referrer, // If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location. // then we check that they have permission to edit their own location.
@@ -334,6 +325,7 @@ class LocationsController extends Controller
$paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []); $paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []);
//return [];
return (new SelectlistTransformer)->transformSelectlist($paginated_results); return (new SelectlistTransformer)->transformSelectlist($paginated_results);
} }
} }
@@ -10,8 +10,8 @@ use App\Models\Actionlog;
use App\Models\Manufacturer; use App\Models\Manufacturer;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
class ManufacturersController extends Controller class ManufacturersController extends Controller
{ {
@@ -22,45 +22,14 @@ class ManufacturersController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', Manufacturer::class); $this->authorize('view', Manufacturer::class);
$allowed_columns = [ $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count'];
'id',
'name',
'url',
'support_url',
'support_email',
'warranty_lookup_url',
'support_phone',
'created_at',
'updated_at',
'image',
'assets_count',
'consumables_count',
'components_count',
'licenses_count'
];
$manufacturers = Manufacturer::select([ $manufacturers = Manufacturer::select(
'id', ['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at']
'name', )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count');
'url',
'support_url',
'warranty_lookup_url',
'support_email',
'support_phone',
'created_by',
'created_at',
'updated_at',
'image',
'deleted_at',
])
->with('adminuser')
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('consumables as consumables_count')
->withCount('accessories as accessories_count');
if ($request->input('deleted') == 'true') { if ($request->input('deleted') == 'true') {
$manufacturers->onlyTrashed(); $manufacturers->onlyTrashed();
@@ -97,18 +66,10 @@ class ManufacturersController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value'); $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) { $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
case 'created_by': $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$manufacturers = $manufacturers->OrderByCreatedBy($order); $manufacturers->orderBy($sort, $order);
break;
default:
$manufacturers = $manufacturers->orderBy($column_sort, $order);
break;
}
$total = $manufacturers->count(); $total = $manufacturers->count();
$manufacturers = $manufacturers->skip($offset)->take($limit)->get(); $manufacturers = $manufacturers->skip($offset)->take($limit)->get();
@@ -122,8 +83,9 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Manufacturer::class); $this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer; $manufacturer = new Manufacturer;
@@ -143,8 +105,9 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', Manufacturer::class); $this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id); $manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
@@ -159,8 +122,9 @@ class ManufacturersController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Manufacturer::class); $this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id); $manufacturer = Manufacturer::findOrFail($id);
@@ -180,8 +144,9 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id); $manufacturer = Manufacturer::findOrFail($id);
@@ -202,9 +167,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.4] * @since [v6.3.4]
* @param int $id * @param int $id
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function restore($id) : JsonResponse public function restore($id)
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
@@ -220,7 +186,7 @@ class ManufacturersController extends Controller
$logaction->item_type = Manufacturer::class; $logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id; $logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id(); $logaction->user_id = Auth::user()->id;
$logaction->logaction('restore'); $logaction->logaction('restore');
return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200); return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200);
@@ -240,7 +206,7 @@ class ManufacturersController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
@@ -7,8 +7,6 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\PredefinedKitsTransformer; use App\Http\Transformers\PredefinedKitsTransformer;
use App\Models\PredefinedKit; use App\Models\PredefinedKit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Transformers\SelectlistTransformer;
/** /**
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
@@ -20,11 +18,12 @@ class PredefinedKitsController extends Controller
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$allowed_columns = ['id', 'name'];
$kits = PredefinedKit::query()->with('adminuser'); $kits = PredefinedKit::query();
if ($request->filled('search')) { if ($request->filled('search')) {
$kits = $kits->TextSearch($request->input('search')); $kits = $kits->TextSearch($request->input('search'));
@@ -35,25 +34,8 @@ class PredefinedKitsController extends Controller
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'desc' ? 'desc' : 'asc'; $order = $request->input('order') === 'desc' ? 'desc' : 'asc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name';
switch ($request->input('sort')) { $kits->orderBy($sort, $order);
case 'created_by':
$kits = $kits->OrderByCreatedBy($order);
break;
default:
// This array is what determines which fields should be allowed to be sorted on ON the table itself.
// These must match a column on the consumables table directly.
$allowed_columns = [
'id',
'name',
'created_at',
'updated_at',
];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$kits = $kits->orderBy($sort, $order);
break;
}
$total = $kits->count(); $total = $kits->count();
$kits = $kits->skip($offset)->take($limit)->get(); $kits = $kits->skip($offset)->take($limit)->get();
@@ -65,8 +47,9 @@ class PredefinedKitsController extends Controller
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('create', PredefinedKit::class); $this->authorize('create', PredefinedKit::class);
$kit = new PredefinedKit; $kit = new PredefinedKit;
@@ -83,8 +66,9 @@ class PredefinedKitsController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@@ -97,8 +81,9 @@ class PredefinedKitsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id kit id * @param int $id kit id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@@ -115,8 +100,9 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', PredefinedKit::class); $this->authorize('delete', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($id); $kit = PredefinedKit::findOrFail($id);
@@ -137,7 +123,7 @@ class PredefinedKitsController extends Controller
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$kits = PredefinedKit::select([ $kits = PredefinedKit::select([
'id', 'id',
@@ -159,7 +145,7 @@ class PredefinedKitsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function indexLicenses($kit_id) : array public function indexLicenses($kit_id)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -174,7 +160,7 @@ class PredefinedKitsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function storeLicense(Request $request, $kit_id) : JsonResponse public function storeLicense(Request $request, $kit_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@@ -200,8 +186,9 @@ class PredefinedKitsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function updateLicense(Request $request, $kit_id, $license_id) : JsonResponse public function updateLicense(Request $request, $kit_id, $license_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -218,8 +205,9 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function detachLicense($kit_id, $license_id) : JsonResponse public function detachLicense($kit_id, $license_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -233,8 +221,9 @@ class PredefinedKitsController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function indexModels($kit_id) : array public function indexModels($kit_id)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -247,8 +236,9 @@ class PredefinedKitsController extends Controller
* Store the specified resource. * Store the specified resource.
* *
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function storeModel(Request $request, $kit_id) : JsonResponse public function storeModel(Request $request, $kit_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@@ -262,7 +252,7 @@ class PredefinedKitsController extends Controller
$relation = $kit->models(); $relation = $kit->models();
if ($relation->find($model_id)) { if ($relation->find($model_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')])); return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit']));
} }
$relation->attach($model_id, ['quantity' => $quantity]); $relation->attach($model_id, ['quantity' => $quantity]);
@@ -274,8 +264,9 @@ class PredefinedKitsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function updateModel(Request $request, $kit_id, $model_id) : JsonResponse public function updateModel(Request $request, $kit_id, $model_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -292,8 +283,9 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function detachModel($kit_id, $model_id) : JsonResponse public function detachModel($kit_id, $model_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -307,8 +299,9 @@ class PredefinedKitsController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function indexConsumables($kit_id) : array public function indexConsumables($kit_id)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -321,8 +314,9 @@ class PredefinedKitsController extends Controller
* Store the specified resource. * Store the specified resource.
* *
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function storeConsumable(Request $request, $kit_id) : JsonResponse public function storeConsumable(Request $request, $kit_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@@ -348,8 +342,9 @@ class PredefinedKitsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function updateConsumable(Request $request, $kit_id, $consumable_id) : JsonResponse public function updateConsumable(Request $request, $kit_id, $consumable_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -366,8 +361,9 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function detachConsumable($kit_id, $consumable_id) : JsonResponse public function detachConsumable($kit_id, $consumable_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -381,8 +377,9 @@ class PredefinedKitsController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function indexAccessories($kit_id) : array public function indexAccessories($kit_id)
{ {
$this->authorize('view', PredefinedKit::class); $this->authorize('view', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -395,8 +392,9 @@ class PredefinedKitsController extends Controller
* Store the specified resource. * Store the specified resource.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function storeAccessory(Request $request, $kit_id) : JsonResponse public function storeAccessory(Request $request, $kit_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
@@ -422,8 +420,9 @@ class PredefinedKitsController extends Controller
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function updateAccessory(Request $request, $kit_id, $accessory_id) : JsonResponse public function updateAccessory(Request $request, $kit_id, $accessory_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
@@ -440,8 +439,9 @@ class PredefinedKitsController extends Controller
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Http\Response
*/ */
public function detachAccessory($kit_id, $accessory_id) : JsonResponse public function detachAccessory($kit_id, $accessory_id)
{ {
$this->authorize('update', PredefinedKit::class); $this->authorize('update', PredefinedKit::class);
$kit = PredefinedKit::findOrFail($kit_id); $kit = PredefinedKit::findOrFail($kit_id);
+19 -14
View File
@@ -6,13 +6,13 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\CheckoutRequest; use App\Models\CheckoutRequest;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Laravel\Passport\TokenRepository; use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use App\Models\CustomField; use App\Models\CustomField;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
class ProfileController extends Controller class ProfileController extends Controller
{ {
@@ -42,10 +42,12 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.3.0] * @since [v4.3.0]
*
* @return array
*/ */
public function requestedAssets() : array public function requestedAssets()
{ {
$checkoutRequests = CheckoutRequest::where('user_id', '=', auth()->id())->get(); $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get();
$results = array(); $results = array();
$show_field = array(); $show_field = array();
@@ -93,9 +95,10 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.5] * @since [v6.0.5]
*
* @return \Illuminate\Http\Response
*/ */
public function createApiToken(Request $request) : JsonResponse public function createApiToken(Request $request) {
{
if (!Gate::allows('self.api')) { if (!Gate::allows('self.api')) {
abort(403); abort(403);
@@ -103,14 +106,14 @@ class ProfileController extends Controller
$accessTokenName = $request->input('name', 'Auth Token'); $accessTokenName = $request->input('name', 'Auth Token');
if ($accessToken = auth()->user()->createToken($accessTokenName)->accessToken) { if ($accessToken = Auth::user()->createToken($accessTokenName)->accessToken) {
// Get the ID so we can return that with the payload // Get the ID so we can return that with the payload
$token = DB::table('oauth_access_tokens')->where('user_id', '=', auth()->id())->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first(); $token = DB::table('oauth_access_tokens')->where('user_id', '=', Auth::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first();
$accessTokenData['id'] = $token->id; $accessTokenData['id'] = $token->id;
$accessTokenData['token'] = $accessToken; $accessTokenData['token'] = $accessToken;
$accessTokenData['name'] = $accessTokenName; $accessTokenData['name'] = $accessTokenName;
return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, trans('account/general.personal_api_keys_success', ['key' => $accessTokenName]))); return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, 'Personal access token '.$accessTokenName.' created successfully'));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.'));
@@ -122,16 +125,17 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.5] * @since [v6.0.5]
*
* @return \Illuminate\Http\Response
*/ */
public function deleteApiToken($tokenId) : Response public function deleteApiToken($tokenId) {
{
if (!Gate::allows('self.api')) { if (!Gate::allows('self.api')) {
abort(403); abort(403);
} }
$token = $this->tokenRepository->findForUser( $token = $this->tokenRepository->findForUser(
$tokenId, auth()->user()->getAuthIdentifier() $tokenId, Auth::user()->getAuthIdentifier()
); );
if (is_null($token)) { if (is_null($token)) {
@@ -150,15 +154,16 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.5] * @since [v6.0.5]
*
* @return \Illuminate\Http\Response
*/ */
public function showApiTokens() : JsonResponse public function showApiTokens(Request $request) {
{
if (!Gate::allows('self.api')) { if (!Gate::allows('self.api')) {
abort(403); abort(403);
} }
$tokens = $this->tokenRepository->forUser(auth()->user()->getAuthIdentifier()); $tokens = $this->tokenRepository->forUser(Auth::user()->getAuthIdentifier());
$token_values = $tokens->load('client')->filter(function ($token) { $token_values = $tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked; return $token->client->personal_access_client && ! $token->revoked;
})->values(); })->values();
+8 -17
View File
@@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\ActionlogsTransformer; use App\Http\Transformers\ActionlogsTransformer;
use App\Models\Actionlog; use App\Models\Actionlog;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class ReportsController extends Controller class ReportsController extends Controller
{ {
@@ -15,12 +14,13 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return View
*/ */
public function index(Request $request) : JsonResponse | array public function index(Request $request)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location'); $actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location');
if ($request->filled('search')) { if ($request->filled('search')) {
$actionlogs = $actionlogs->TextSearch(e($request->input('search'))); $actionlogs = $actionlogs->TextSearch(e($request->input('search')));
@@ -48,8 +48,8 @@ class ReportsController extends Controller
$actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc'); $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc');
} }
if ($request->filled('created_by')) { if ($request->filled('user_id')) {
$actionlogs = $actionlogs->where('created_by', '=', $request->input('created_by')); $actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id'));
} }
if ($request->filled('action_source')) { if ($request->filled('action_source')) {
@@ -68,14 +68,13 @@ class ReportsController extends Controller
'id', 'id',
'created_at', 'created_at',
'target_id', 'target_id',
'created_by', 'user_id',
'accept_signature', 'accept_signature',
'action_type', 'action_type',
'note', 'note',
'remote_ip', 'remote_ip',
'user_agent', 'user_agent',
'action_source', 'action_source',
'action_date',
]; ];
@@ -84,19 +83,11 @@ class ReportsController extends Controller
$offset = ($request->input('offset') > $total) ? $total : app('api_offset_value'); $offset = ($request->input('offset') > $total) ? $total : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$order = ($request->input('order') == 'asc') ? 'asc' : 'desc'; $order = ($request->input('order') == 'asc') ? 'asc' : 'desc';
switch ($request->input('sort')) {
case 'created_by':
$actionlogs->OrderByCreatedBy($order);
break;
default:
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
$actionlogs = $actionlogs->orderBy($sort, $order);
break;
}
$actionlogs = $actionlogs->skip($offset)->take($limit)->get(); $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get();
return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE); return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE);
} }
+20 -13
View File
@@ -9,22 +9,26 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Ldap; use App\Models\Ldap;
use App\Models\Setting; use App\Models\Setting;
use Mail;
use App\Notifications\SlackTest;
use App\Notifications\MailTest; use App\Notifications\MailTest;
use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use App\Http\Requests\SlackSettingsRequest;
use App\Http\Transformers\LoginAttemptsTransformer; use App\Http\Transformers\LoginAttemptsTransformer;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class SettingsController extends Controller class SettingsController extends Controller
{ {
public function ldaptest() : JsonResponse public function ldaptest()
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
@@ -84,7 +88,7 @@ class SettingsController extends Controller
} }
public function ldaptestlogin(Request $request) : JsonResponse public function ldaptestlogin(Request $request)
{ {
if (Setting::getSettings()->ldap_enabled != '1') { if (Setting::getSettings()->ldap_enabled != '1') {
@@ -144,8 +148,9 @@ class SettingsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return JsonResponse
*/ */
public function ajaxTestEmail() : JsonResponse public function ajaxTestEmail()
{ {
if (!config('app.lock_passwords')) { if (!config('app.lock_passwords')) {
try { try {
@@ -165,8 +170,9 @@ class SettingsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.0] * @since [v5.0.0]
* @return JsonResponse
*/ */
public function purgeBarcodes() : JsonResponse public function purgeBarcodes()
{ {
$file_count = 0; $file_count = 0;
$files = Storage::disk('public')->files('barcodes'); $files = Storage::disk('public')->files('barcodes');
@@ -205,8 +211,9 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.0] * @since [v5.0.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array | JsonResponse
*/ */
public function showLoginAttempts(Request $request) : array public function showLoginAttempts(Request $request)
{ {
$allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at']; $allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at'];
@@ -226,9 +233,9 @@ class SettingsController extends Controller
* Lists backup files * Lists backup files
* *
* @author [A. Gianotto] * @author [A. Gianotto]
* @return array | JsonResponse
*/ */
public function listBackups() : array public function listBackups() {
{
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$path = 'app/backups'; $path = 'app/backups';
$backup_files = Storage::files($path); $backup_files = Storage::files($path);
@@ -269,9 +276,9 @@ class SettingsController extends Controller
* exhausts memory on larger files. * exhausts memory on larger files.
* *
* @author [A. Gianotto] * @author [A. Gianotto]
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/ */
public function downloadBackup($file) : JsonResponse | BinaryFileResponse public function downloadBackup($file) {
{
$path = storage_path('app/backups'); $path = storage_path('app/backups');
@@ -289,9 +296,9 @@ class SettingsController extends Controller
* *
* @author [A. Gianotto] * @author [A. Gianotto]
* @since [v6.3.1] * @since [v6.3.1]
* @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/ */
public function downloadLatestBackup() : JsonResponse | BinaryFileResponse public function downloadLatestBackup() {
{
$fileData = collect(); $fileData = collect();
foreach (Storage::files('app/backups') as $file) { foreach (Storage::files('app/backups') as $file) {
@@ -8,11 +8,10 @@ use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\StatuslabelsTransformer; use App\Http\Transformers\StatuslabelsTransformer;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Setting;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Transformers\PieChartTransformer; use App\Http\Transformers\PieChartTransformer;
use Illuminate\Http\JsonResponse; use Illuminate\Support\Arr;
class StatuslabelsController extends Controller class StatuslabelsController extends Controller
{ {
@@ -21,21 +20,14 @@ class StatuslabelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response
*/ */
public function index(Request $request) : array public function index(Request $request)
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$allowed_columns = [ $allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label'];
'id',
'name',
'created_at',
'assets_count',
'color',
'notes',
'default_label'
];
$statuslabels = Statuslabel::with('adminuser')->withCount('assets as assets_count'); $statuslabels = Statuslabel::withCount('assets as assets_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$statuslabels = $statuslabels->TextSearch($request->input('search')); $statuslabels = $statuslabels->TextSearch($request->input('search'));
@@ -62,18 +54,10 @@ class StatuslabelsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits // Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value'); $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value');
$limit = app('api_limit_value'); $limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) { $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
case 'created_by': $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$statuslabels = $statuslabels->OrderByCreatedBy($order); $statuslabels->orderBy($sort, $order);
break;
default:
$statuslabels = $statuslabels->orderBy($column_sort, $order);
break;
}
$total = $statuslabels->count(); $total = $statuslabels->count();
$statuslabels = $statuslabels->skip($offset)->take($limit)->get(); $statuslabels = $statuslabels->skip($offset)->take($limit)->get();
@@ -88,8 +72,9 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function store(Request $request) : JsonResponse public function store(Request $request)
{ {
$this->authorize('create', Statuslabel::class); $this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending', 'archived'); $request->except('deployable', 'pending', 'archived');
@@ -123,8 +108,9 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@@ -140,8 +126,9 @@ class StatuslabelsController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) : JsonResponse public function update(Request $request, $id)
{ {
$this->authorize('update', Statuslabel::class); $this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@@ -176,8 +163,9 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Statuslabel::class); $this->authorize('delete', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
@@ -200,18 +188,13 @@ class StatuslabelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return array
*/ */
public function getAssetCountByStatuslabel() : array public function getAssetCountByStatuslabel()
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::withCount('assets')->get();
if (Setting::getSettings()->show_archived_in_list == 0 ) { $total = Array();
$statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
} else {
$statuslabels = Statuslabel::withCount('assets')->get();
}
$total = [];
foreach ($statuslabels as $statuslabel) { foreach ($statuslabels as $statuslabel) {
@@ -232,8 +215,9 @@ class StatuslabelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.11] * @since [v6.0.11]
* @return array
*/ */
public function getAssetCountByMetaStatus() : array public function getAssetCountByMetaStatus()
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
@@ -261,8 +245,9 @@ class StatuslabelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function assets(Request $request, $id) : array public function assets(Request $request, $id)
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
@@ -296,8 +281,9 @@ class StatuslabelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return bool
*/ */
public function checkIfDeployable($id) : string public function checkIfDeployable($id)
{ {
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') { if ($statuslabel->getStatuslabelType() == 'deployable') {
@@ -314,7 +300,7 @@ class StatuslabelsController extends Controller
* @since [v6.1.1] * @since [v6.1.1]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
@@ -10,7 +10,6 @@ use App\Models\Supplier;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
class SuppliersController extends Controller class SuppliersController extends Controller
{ {
@@ -21,7 +20,7 @@ class SuppliersController extends Controller
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function index(Request $request): array public function index(Request $request)
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$allowed_columns = [' $allowed_columns = ['
@@ -115,8 +114,9 @@ class SuppliersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*/ */
public function store(ImageUploadRequest $request) : JsonResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Supplier::class); $this->authorize('create', Supplier::class);
$supplier = new Supplier; $supplier = new Supplier;
@@ -136,8 +136,9 @@ class SuppliersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function show($id) : array public function show($id)
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$supplier = Supplier::findOrFail($id); $supplier = Supplier::findOrFail($id);
@@ -153,8 +154,9 @@ class SuppliersController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request * @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function update(ImageUploadRequest $request, $id) : JsonResponse public function update(ImageUploadRequest $request, $id)
{ {
$this->authorize('update', Supplier::class); $this->authorize('update', Supplier::class);
$supplier = Supplier::findOrFail($id); $supplier = Supplier::findOrFail($id);
@@ -174,8 +176,9 @@ class SuppliersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\Response
*/ */
public function destroy($id) : JsonResponse public function destroy($id)
{ {
$this->authorize('delete', Supplier::class); $this->authorize('delete', Supplier::class);
$supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id); $supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id);
@@ -206,7 +209,7 @@ class SuppliersController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$this->authorize('view.selectlists'); $this->authorize('view.selectlists');
+49 -28
View File
@@ -24,7 +24,6 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Http\Requests\DeleteUserRequest; use App\Http\Requests\DeleteUserRequest;
use Illuminate\Http\JsonResponse;
class UsersController extends Controller class UsersController extends Controller
{ {
@@ -36,7 +35,7 @@ class UsersController extends Controller
* *
* @return array * @return array
*/ */
public function index(Request $request) : array public function index(Request $request)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
@@ -206,10 +205,6 @@ class UsersController extends Controller
$users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses'));
} }
if ($request->filled('locale')) {
$users = $users->where('users.locale', '=', $request->input('locale'));
}
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) { if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed(); $users = $users->onlyTrashed();
@@ -280,7 +275,6 @@ class UsersController extends Controller
'end_date', 'end_date',
'autoassign_licenses', 'autoassign_licenses',
'website', 'website',
'locale',
]; ];
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name';
@@ -307,7 +301,7 @@ class UsersController extends Controller
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) : array public function selectlist(Request $request)
{ {
$users = User::select( $users = User::select(
[ [
@@ -363,20 +357,21 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array | \Illuminate\Http\JsonResponse
*/ */
public function store(SaveUserRequest $request) : JsonResponse public function store(SaveUserRequest $request)
{ {
$this->authorize('create', User::class); $this->authorize('create', User::class);
$user = new User; $user = new User;
$user->fill($request->all()); $user->fill($request->all());
$user->created_by = auth()->id(); $user->created_by = Auth::user()->id;
if ($request->has('permissions')) { if ($request->has('permissions')) {
$permissions_array = $request->input('permissions'); $permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin // Strip out the superuser permission if the API user isn't a superadmin
if (! auth()->user()->isSuperUser()) { if (! Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']); unset($permissions_array['superuser']);
} }
$user->permissions = $permissions_array; $user->permissions = $permissions_array;
@@ -409,8 +404,9 @@ class UsersController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @return array | \Illuminate\Http\JsonResponse
*/ */
public function show($id) : JsonResponse | array public function show($id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
@@ -431,11 +427,15 @@ class UsersController extends Controller
* @since [v4.0] * @since [v4.0]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\JsonResponse
*/ */
public function update(SaveUserRequest $request, User $user): JsonResponse public function update(SaveUserRequest $request, $id)
{ {
$this->authorize('update', User::class); $this->authorize('update', User::class);
if ($user = User::find($id)) {
$this->authorize('update', $user); $this->authorize('update', $user);
/** /**
@@ -445,10 +445,12 @@ class UsersController extends Controller
* *
*/ */
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
} }
$user->fill($request->all()); $user->fill($request->all());
if ($user->id == $request->input('manager_id')) { if ($user->id == $request->input('manager_id')) {
@@ -466,22 +468,25 @@ class UsersController extends Controller
$permissions_array = $request->input('permissions'); $permissions_array = $request->input('permissions');
// Strip out the individual superuser permission if the API user isn't a superadmin // Strip out the individual superuser permission if the API user isn't a superadmin
if (!auth()->user()->isSuperUser()) { if (!Auth::user()->isSuperUser()) {
unset($permissions_array['superuser']); unset($permissions_array['superuser']);
} }
$user->permissions = $permissions_array; $user->permissions = $permissions_array;
} }
// Update the location of any assets checked out to this user // Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class) Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
if ($user->save()) { if ($user->save()) {
// Check if the request has groups passed and has a value, AND that the user us a superuser // Check if the request has groups passed and has a value, AND that the user us a superuser
if (($request->has('groups')) && (auth()->user()->isSuperUser())) { if (($request->has('groups')) && (Auth::user()->isSuperUser())) {
$validator = Validator::make($request->only('groups'), [ $validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id', 'groups.*' => 'integer|exists:permission_groups,id',
@@ -493,10 +498,18 @@ class UsersController extends Controller
// Sync the groups since the user is a superuser and the groups pass validation // Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups')); $user->groups()->sync($request->input('groups'));
} }
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))));
} }
/** /**
@@ -505,8 +518,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\JsonResponse
*/ */
public function destroy(DeleteUserRequest $request, $id) : JsonResponse public function destroy(DeleteUserRequest $request, $id)
{ {
$this->authorize('delete', User::class); $this->authorize('delete', User::class);
@@ -542,8 +556,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @param $userId * @param $userId
* @return array | \Illuminate\Http\JsonResponse
*/ */
public function assets(Request $request, $id) : JsonResponse | array public function assets(Request $request, $id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
@@ -586,9 +601,9 @@ class UsersController extends Controller
* @since [v6.0.13] * @since [v6.0.13]
* @param Request $request * @param Request $request
* @param $id * @param $id
* @return string JSON
*/ */
public function emailAssetList(Request $request, $id) : JsonResponse public function emailAssetList(Request $request, $id)
{ {
$this->authorize('update', User::class); $this->authorize('update', User::class);
@@ -614,8 +629,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @param $userId * @param $userId
* @return array | \Illuminate\Http\JsonResponse
*/ */
public function consumables(Request $request, $id) : array public function consumables(Request $request, $id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', Consumable::class); $this->authorize('view', Consumable::class);
@@ -631,8 +647,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14] * @since [v4.6.14]
* @param $userId * @param $userId
* @return array
*/ */
public function accessories($id) : array public function accessories($id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$user = User::findOrFail($id); $user = User::findOrFail($id);
@@ -649,8 +666,9 @@ class UsersController extends Controller
* @author [N. Mathar] [<snipe@snipe.net>] * @author [N. Mathar] [<snipe@snipe.net>]
* @since [v5.0] * @since [v5.0]
* @param $userId * @param $userId
* @return array | \Illuminate\Http\JsonResponse
*/ */
public function licenses($id) : JsonResponse | array public function licenses($id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
@@ -671,8 +689,9 @@ class UsersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @param $userId * @param $userId
* @return string JSON
*/ */
public function postTwoFactorReset(Request $request) : JsonResponse public function postTwoFactorReset(Request $request)
{ {
$this->authorize('update', User::class); $this->authorize('update', User::class);
@@ -691,7 +710,7 @@ class UsersController extends Controller
$logaction->item_type = User::class; $logaction->item_type = User::class;
$logaction->item_id = $user->id; $logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id(); $logaction->user_id = Auth::user()->id;
$logaction->logaction('2FA reset'); $logaction->logaction('2FA reset');
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200); return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200);
@@ -710,8 +729,9 @@ class UsersController extends Controller
* @author [Juan Font] [<juanfontalonso@gmail.com>] * @author [Juan Font] [<juanfontalonso@gmail.com>]
* @since [v4.4.2] * @since [v4.4.2]
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return array
*/ */
public function getCurrentUserInfo(Request $request) : array public function getCurrentUserInfo(Request $request)
{ {
return (new UsersTransformer)->transformUser($request->user()); return (new UsersTransformer)->transformUser($request->user());
} }
@@ -722,8 +742,9 @@ class UsersController extends Controller
* @author [E. Taylor] [<dev@evantaylor.name>] * @author [E. Taylor] [<dev@evantaylor.name>]
* @param int $userId * @param int $userId
* @since [v6.0.0] * @since [v6.0.0]
* @return \Illuminate\Http\JsonResponse
*/ */
public function restore($userId) : JsonResponse public function restore($userId)
{ {
$this->authorize('delete', User::class); $this->authorize('delete', User::class);
@@ -741,7 +762,7 @@ class UsersController extends Controller
$logaction->item_type = User::class; $logaction->item_type = User::class;
$logaction->item_id = $user->id; $logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id(); $logaction->user_id = Auth::user()->id;
$logaction->logaction('restore'); $logaction->logaction('restore');
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200);
@@ -2,14 +2,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetMaintenance; use App\Models\AssetMaintenance;
use App\Models\Company; use App\Models\Company;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use \Illuminate\Contracts\View\View; use Slack;
use \Illuminate\Http\RedirectResponse; use Str;
use TCPDF;
use View;
/** /**
* This controller handles all actions related to Asset Maintenance for * This controller handles all actions related to Asset Maintenance for
@@ -26,8 +29,9 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
private static function getInsufficientPermissionsRedirect(): RedirectResponse private static function getInsufficientPermissionsRedirect()
{ {
return redirect()->route('maintenances.index') return redirect()->route('maintenances.index')
->with('error', trans('general.insufficient_permissions')); ->with('error', trans('general.insufficient_permissions'));
@@ -42,8 +46,9 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
public function index() : View public function index()
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
return view('asset_maintenances/index'); return view('asset_maintenances/index');
@@ -58,7 +63,7 @@ class AssetMaintenancesController extends Controller
* @since [v1.8] * @since [v1.8]
* @return mixed * @return mixed
*/ */
public function create() : View public function create()
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$asset = null; $asset = null;
@@ -87,8 +92,9 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed
*/ */
public function store(Request $request) : RedirectResponse public function store(Request $request)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// create a new model instance // create a new model instance
@@ -109,7 +115,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->title = $request->input('title'); $assetMaintenance->title = $request->input('title');
$assetMaintenance->start_date = $request->input('start_date'); $assetMaintenance->start_date = $request->input('start_date');
$assetMaintenance->completion_date = $request->input('completion_date'); $assetMaintenance->completion_date = $request->input('completion_date');
$assetMaintenance->created_by = auth()->id(); $assetMaintenance->user_id = Auth::id();
if (($assetMaintenance->completion_date !== null) if (($assetMaintenance->completion_date !== null)
&& ($assetMaintenance->start_date !== '') && ($assetMaintenance->start_date !== '')
@@ -138,8 +144,9 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed
*/ */
public function edit($assetMaintenanceId = null) : View | RedirectResponse public function edit($assetMaintenanceId = null)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@@ -155,9 +162,14 @@ class AssetMaintenancesController extends Controller
return static::getInsufficientPermissionsRedirect(); return static::getInsufficientPermissionsRedirect();
} }
// Prepare Improvement Type List
$assetMaintenanceType = ['' => 'Select an improvement type'] + AssetMaintenance::getImprovementOptions();
// Prepare Improvement Type List
$assetMaintenanceType = [
'' => 'Select an improvement type',
] + AssetMaintenance::getImprovementOptions();
// Get Supplier List
// Render the view
return view('asset_maintenances/edit') return view('asset_maintenances/edit')
->with('selectedAsset', null) ->with('selectedAsset', null)
->with('assetMaintenanceType', $assetMaintenanceType) ->with('assetMaintenanceType', $assetMaintenanceType)
@@ -171,10 +183,11 @@ class AssetMaintenancesController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @param Request $request * @param Request $request
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @return mixed
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
*/ */
public function update(Request $request, $assetMaintenanceId = null) : View | RedirectResponse public function update(Request $request, $assetMaintenanceId = null)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@@ -242,8 +255,9 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed
*/ */
public function destroy($assetMaintenanceId) : RedirectResponse public function destroy($assetMaintenanceId)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
// Check if the asset maintenance exists // Check if the asset maintenance exists
@@ -270,8 +284,9 @@ class AssetMaintenancesController extends Controller
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
public function show($assetMaintenanceId) : View | RedirectResponse public function show($assetMaintenanceId)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
+65 -37
View File
@@ -4,21 +4,19 @@ namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreAssetModelRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel; use App\Models\AssetModel;
use App\Models\CustomField; use App\Models\CustomField;
use App\Models\SnipeModel;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
/** /**
* This class controls all actions related to asset models for * This class controls all actions related to asset models for
@@ -35,8 +33,10 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('index', AssetModel::class); $this->authorize('index', AssetModel::class);
@@ -48,8 +48,10 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
@@ -64,12 +66,16 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(StoreAssetModelRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
// Create a new asset model
$model = new AssetModel; $model = new AssetModel;
// Save the model data
$model->eol = $request->input('eol'); $model->eol = $request->input('eol');
$model->depreciation_id = $request->input('depreciation_id'); $model->depreciation_id = $request->input('depreciation_id');
$model->name = $request->input('name'); $model->name = $request->input('name');
@@ -78,7 +84,7 @@ class AssetModelsController extends Controller
$model->manufacturer_id = $request->input('manufacturer_id'); $model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id'); $model->category_id = $request->input('category_id');
$model->notes = $request->input('notes'); $model->notes = $request->input('notes');
$model->created_by = auth()->id(); $model->user_id = Auth::id();
$model->requestable = $request->has('requestable'); $model->requestable = $request->has('requestable');
if ($request->input('fieldset_id') != '') { if ($request->input('fieldset_id') != '') {
@@ -87,6 +93,7 @@ class AssetModelsController extends Controller
$model = $request->handleImages($model); $model = $request->handleImages($model);
// Was it created?
if ($model->save()) { if ($model->save()) {
if ($this->shouldAddDefaultValues($request->input())) { if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){ if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
@@ -106,14 +113,18 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($modelId = null) : View | RedirectResponse public function edit($modelId = null)
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
if ($item = AssetModel::find($modelId)) { if ($item = AssetModel::find($modelId)) {
$category_type = 'asset'; $category_type = 'asset';
return view('models/edit', compact('item', 'category_type'))->with('depreciation_list', Helper::depreciationList()); $view = View::make('models/edit', compact('item', 'category_type'));
$view->with('depreciation_list', Helper::depreciationList());
return $view;
} }
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
@@ -131,11 +142,12 @@ class AssetModelsController extends Controller
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(StoreAssetModelRequest $request, $modelId) : RedirectResponse public function update(ImageUploadRequest $request, $modelId = null)
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
// Check if the model exists
if (is_null($model = AssetModel::find($modelId))) { if (is_null($model = AssetModel::find($modelId))) {
// Redirect to the models management page
return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist'));
} }
@@ -151,17 +163,20 @@ class AssetModelsController extends Controller
$model->notes = $request->input('notes'); $model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0'); $model->requestable = $request->input('requestable', '0');
$this->removeCustomFieldsDefaultValues($model);
$model->fieldset_id = $request->input('fieldset_id'); $model->fieldset_id = $request->input('fieldset_id');
if ($model->save()) { if ($this->shouldAddDefaultValues($request->input())) {
$this->removeCustomFieldsDefaultValues($model); if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
if ($this->shouldAddDefaultValues($request->input())) {
if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))) {
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error'));
}
} }
}
if ($model->save()) {
if ($model->wasChanged('eol')) { if ($model->wasChanged('eol')) {
if ($model->eol > 0) { if ($model->eol > 0) {
$newEol = $model->eol; $newEol = $model->eol;
@@ -185,8 +200,10 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($modelId) : RedirectResponse public function destroy($modelId)
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);
// Check if the model exists // Check if the model exists
@@ -202,7 +219,6 @@ class AssetModelsController extends Controller
if ($model->image) { if ($model->image) {
try { try {
Storage::disk('public')->delete('models/'.$model->image); Storage::disk('public')->delete('models/'.$model->image);
$model->update(['image' => null]);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::info($e); Log::info($e);
} }
@@ -221,8 +237,10 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $id * @param int $id
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function getRestore($id) : RedirectResponse public function getRestore($id)
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
@@ -234,10 +252,10 @@ class AssetModelsController extends Controller
if ($model->restore()) { if ($model->restore()) {
$logaction = new Actionlog(); $logaction = new Actionlog();
$logaction->item_type = AssetModel::class; $logaction->item_type = User::class;
$logaction->item_id = $model->id; $logaction->item_id = $model->id;
$logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id(); $logaction->user_id = Auth::user()->id;
$logaction->logaction('restore'); $logaction->logaction('restore');
@@ -264,8 +282,10 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($modelId = null) : View | RedirectResponse public function show($modelId = null)
{ {
$this->authorize('view', AssetModel::class); $this->authorize('view', AssetModel::class);
$model = AssetModel::withTrashed()->find($modelId); $model = AssetModel::withTrashed()->find($modelId);
@@ -283,8 +303,9 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return View
*/ */
public function getClone($modelId = null) : View | RedirectResponse public function getClone($modelId = null)
{ {
$this->authorize('create', AssetModel::class); $this->authorize('create', AssetModel::class);
// Check if the model exists // Check if the model exists
@@ -310,8 +331,9 @@ class AssetModelsController extends Controller
* @author [B. Wetherington] [<uberbrady@gmail.com>] * @author [B. Wetherington] [<uberbrady@gmail.com>]
* @since [v2.0] * @since [v2.0]
* @param int $modelId * @param int $modelId
* @return View
*/ */
public function getCustomFields($modelId) : View public function getCustomFields($modelId)
{ {
return view('models.custom_fields_form')->with('model', AssetModel::find($modelId)); return view('models.custom_fields_form')->with('model', AssetModel::find($modelId));
} }
@@ -323,8 +345,9 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7] * @since [v1.7]
* @return \Illuminate\Contracts\View\View
*/ */
public function postBulkEdit(Request $request) : View | RedirectResponse public function postBulkEdit(Request $request)
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@@ -366,8 +389,9 @@ class AssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7] * @since [v1.7]
* @return \Illuminate\Contracts\View\View
*/ */
public function postBulkEditSave(Request $request) : RedirectResponse public function postBulkEditSave(Request $request)
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
$update_array = []; $update_array = [];
@@ -405,8 +429,9 @@ class AssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postBulkDelete(Request $request) : RedirectResponse public function postBulkDelete(Request $request)
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@@ -445,8 +470,9 @@ class AssetModelsController extends Controller
* any default values were entered into the form. * any default values were entered into the form.
* *
* @param array $input * @param array $input
* @return bool
*/ */
private function shouldAddDefaultValues(array $input) : bool private function shouldAddDefaultValues(array $input)
{ {
return ! empty($input['add_default_values']) return ! empty($input['add_default_values'])
&& ! empty($input['default_values']) && ! empty($input['default_values'])
@@ -458,8 +484,9 @@ class AssetModelsController extends Controller
* *
* @param AssetModel $model * @param AssetModel $model
* @param array $defaultValues * @param array $defaultValues
* @return void
*/ */
private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, array $defaultValues): bool private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool
{ {
$data = array(); $data = array();
foreach ($defaultValues as $customFieldId => $defaultValue) { foreach ($defaultValues as $customFieldId => $defaultValue) {
@@ -468,17 +495,17 @@ class AssetModelsController extends Controller
$data[$customField->db_column] = $defaultValue; $data[$customField->db_column] = $defaultValue;
} }
$allRules = $model->fieldset->validation_rules(); $fieldsets = $model->fieldset->validation_rules();
$rules = array(); $rules = array();
foreach ($allRules as $field => $validation) { foreach ($fieldsets as $fieldset => $validation){
// If the field is marked as required, eliminate the rule so it doesn't interfere with the default values // If the field is marked as required, eliminate the rule so it doesn't interfere with the default values
// (we are at model level, the rule still applies when creating a new asset using this model) // (we are at model level, the rule still applies when creating a new asset using this model)
$index = array_search('required', $validation); $index = array_search('required', $validation);
if ($index !== false){ if ($index !== false){
$validation[$index] = 'nullable'; $validation[$index] = 'nullable';
} }
$rules[$field] = $validation; $rules[$fieldset] = $validation;
} }
$validator = Validator::make($data, $rules); $validator = Validator::make($data, $rules);
@@ -500,8 +527,9 @@ class AssetModelsController extends Controller
/** /**
* Removes all default values * Removes all default values
* *
* @return void
*/ */
private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void private function removeCustomFieldsDefaultValues(AssetModel $model)
{ {
$model->defaultValues()->detach(); $model->defaultValues()->detach();
} }
@@ -6,11 +6,8 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest; use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\AssetModel; use App\Models\AssetModel;
use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Response;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use \Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetModelsFilesController extends Controller class AssetModelsFilesController extends Controller
{ {
@@ -24,7 +21,7 @@ class AssetModelsFilesController extends Controller
*@since [v1.0] *@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*/ */
public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse public function store(UploadFileRequest $request, $modelId = null)
{ {
if (! $model = AssetModel::find($modelId)) { if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist')); return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -57,8 +54,10 @@ class AssetModelsFilesController extends Controller
* @param int $modelId * @param int $modelId
* @param int $fileId * @param int $fileId
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($modelId = null, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse public function show($modelId = null, $fileId = null)
{ {
$model = AssetModel::find($modelId); $model = AssetModel::find($modelId);
// the asset is valid // the asset is valid
@@ -102,8 +101,10 @@ class AssetModelsFilesController extends Controller
* @param int $modelId * @param int $modelId
* @param int $fileId * @param int $fileId
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($modelId = null, $fileId = null) : RedirectResponse public function destroy($modelId = null, $fileId = null)
{ {
$model = AssetModel::find($modelId); $model = AssetModel::find($modelId);
$this->authorize('update', $model); $this->authorize('update', $model);
@@ -11,9 +11,10 @@ use App\Models\Asset;
use App\Models\CheckoutAcceptance; use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AssetCheckinController extends Controller class AssetCheckinController extends Controller
{ {
@@ -25,9 +26,11 @@ class AssetCheckinController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @param string $backto * @param string $backto
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0] * @since [v1.0]
*/ */
public function create($assetId, $backto = null) : View | RedirectResponse public function create($assetId, $backto = null)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@@ -57,9 +60,11 @@ class AssetCheckinController extends Controller
* @param AssetCheckinRequest $request * @param AssetCheckinRequest $request
* @param int $assetId * @param int $assetId
* @param null $backto * @param null $backto
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0] * @since [v1.0]
*/ */
public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) : RedirectResponse public function store(AssetCheckinRequest $request, $assetId = null, $backto = null)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@@ -82,6 +87,7 @@ class AssetCheckinController extends Controller
} }
$asset->expected_checkin = null; $asset->expected_checkin = null;
//$asset->last_checkout = null;
$asset->last_checkin = now(); $asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset); $asset->assignedTo()->disassociate($asset);
$asset->accepted = null; $asset->accepted = null;
@@ -126,12 +132,12 @@ class AssetCheckinController extends Controller
$acceptance->delete(); $acceptance->delete();
}); });
session()->put('redirect_option', $request->get('redirect_option')); Session::put('redirect_option', $request->get('redirect_option'));
// Was the asset updated?
if ($asset->save()) { if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success')); return Helper::getRedirectOption($asset, $assetId, 'Assets');
} }
// Redirect to the asset management page with error // Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors()); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors());
@@ -9,9 +9,8 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest; use App\Http\Requests\AssetCheckoutRequest;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AssetCheckoutController extends Controller class AssetCheckoutController extends Controller
{ {
@@ -24,9 +23,9 @@ class AssetCheckoutController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function create($assetId) : View | RedirectResponse public function create($assetId)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::with('company')->find(e($assetId)))) { if (is_null($asset = Asset::with('company')->find(e($assetId)))) {
@@ -54,9 +53,11 @@ class AssetCheckoutController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetCheckoutRequest $request * @param AssetCheckoutRequest $request
* @param int $assetId
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0] * @since [v1.0]
*/ */
public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse public function store(AssetCheckoutRequest $request, $assetId)
{ {
try { try {
// Check if the asset exists // Check if the asset exists
@@ -71,7 +72,7 @@ class AssetCheckoutController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix')); return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
} }
$admin = auth()->user(); $admin = Auth::user();
$target = $this->determineCheckoutTarget(); $target = $this->determineCheckoutTarget();
@@ -109,11 +110,10 @@ class AssetCheckoutController extends Controller
} }
} }
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) { if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) return Helper::getRedirectOption($request, $assetId, 'Assets');
->with('success', trans('admin/hardware/message.checkout.success'));
} }
// Redirect to the asset management page with error // Redirect to the asset management page with error
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors()); return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors());
@@ -7,12 +7,8 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest; use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use \Illuminate\Http\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AssetFilesController extends Controller class AssetFilesController extends Controller
{ {
@@ -26,7 +22,7 @@ class AssetFilesController extends Controller
*@since [v1.0] *@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*/ */
public function store(UploadFileRequest $request, $assetId = null) : RedirectResponse public function store(UploadFileRequest $request, $assetId = null)
{ {
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -58,8 +54,10 @@ class AssetFilesController extends Controller
* @param int $assetId * @param int $assetId
* @param int $fileId * @param int $fileId
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse public function show($assetId = null, $fileId = null)
{ {
$asset = Asset::find($assetId); $asset = Asset::find($assetId);
// the asset is valid // the asset is valid
@@ -107,8 +105,10 @@ class AssetFilesController extends Controller
* @param int $assetId * @param int $assetId
* @param int $fileId * @param int $fileId
* @since [v1.0] * @since [v1.0]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($assetId = null, $fileId = null) : RedirectResponse public function destroy($assetId = null, $fileId = null)
{ {
$asset = Asset::find($assetId); $asset = Asset::find($assetId);
$this->authorize('update', $asset); $this->authorize('update', $asset);
@@ -131,6 +131,7 @@ class AssetFilesController extends Controller
->with('success', trans('admin/hardware/message.deletefile.success')); ->with('success', trans('admin/hardware/message.deletefile.success'));
} }
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
} }
} }
+74 -144
View File
@@ -2,7 +2,6 @@
namespace App\Http\Controllers\Assets; namespace App\Http\Controllers\Assets;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
@@ -21,16 +20,14 @@ use Illuminate\Support\Facades\Auth;
use App\View\Label; use App\View\Label;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use League\Csv\Reader; use League\Csv\Reader;
use Illuminate\Http\Response; use Illuminate\Support\Facades\Redirect;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/** /**
* This class controls all actions related to assets for * This class controls all actions related to assets for
@@ -58,8 +55,10 @@ class AssetsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response * @see AssetController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @param Request $request * @param Request $request
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index(Request $request) : View public function index(Request $request)
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
$company = Company::find($request->input('company_id')); $company = Company::find($request->input('company_id'));
@@ -73,12 +72,13 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param Request $request * @param Request $request
* @return View
* @internal param int $model_id * @internal param int $model_id
*/ */
public function create(Request $request) : View public function create(Request $request)
{ {
$this->authorize('create', Asset::class); $this->authorize('create', Asset::class);
$view = view('hardware/edit') $view = View::make('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_list', Helper::statusLabelList())
->with('item', new Asset) ->with('item', new Asset)
->with('statuslabel_types', Helper::statusTypeList()); ->with('statuslabel_types', Helper::statusTypeList());
@@ -96,8 +96,9 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize(Asset::class); $this->authorize(Asset::class);
@@ -133,7 +134,7 @@ class AssetsController extends Controller
$asset->model_id = $request->input('model_id'); $asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number'); $asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes'); $asset->notes = $request->input('notes');
$asset->created_by = auth()->id(); $asset->user_id = Auth::id();
$asset->status_id = request('status_id'); $asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null); $asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost'); $asset->purchase_cost = request('purchase_cost');
@@ -159,29 +160,7 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset); $asset = $request->handleImages($asset);
} }
// Update custom fields in the database. $asset = $asset->handleCustomFieldsForStoring($request);
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
// Validate the asset before saving // Validate the asset before saving
if ($asset->isValid() && $asset->save()) { if ($asset->isValid() && $asset->save()) {
@@ -197,7 +176,7 @@ class AssetsController extends Controller
} }
if (isset($target)) { if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
} }
$success = true; $success = true;
@@ -205,13 +184,10 @@ class AssetsController extends Controller
} }
} }
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($success) { if ($success) {
Log::debug(e($asset->asset_tag));
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) return redirect()->route('hardware.index')
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)])); ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)]));
} }
@@ -219,6 +195,11 @@ class AssetsController extends Controller
return redirect()->back()->withInput()->withErrors($asset->getErrors()); return redirect()->back()->withInput()->withErrors($asset->getErrors());
} }
public function getOptionCookie(Request $request){
$value = $request->cookie('optional_info');
echo $value;
return $value;
}
/** /**
* Returns a view that presents a form to edit an existing asset. * Returns a view that presents a form to edit an existing asset.
@@ -226,9 +207,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function edit($assetId = null) : View | RedirectResponse public function edit($assetId = null)
{ {
if (! $item = Asset::find($assetId)) { if (! $item = Asset::find($assetId)) {
// Redirect to the asset management page with error // Redirect to the asset management page with error
@@ -249,9 +230,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function show($assetId = null) : View | RedirectResponse public function show($assetId = null)
{ {
$asset = Asset::withTrashed()->find($assetId); $asset = Asset::withTrashed()->find($assetId);
$this->authorize('view', $asset); $this->authorize('view', $asset);
@@ -289,12 +270,12 @@ class AssetsController extends Controller
* Validate and process asset edit form. * Validate and process asset edit form.
* *
* @param int $assetId * @param int $assetId
* @return \Illuminate\Http\RedirectResponse|Redirect
* @since [v1.0] * @since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*/ */
public function update(ImageUploadRequest $request, $assetId = null) : RedirectResponse public function update(ImageUploadRequest $request, $assetId = null)
{ {
// Check if the asset exists // Check if the asset exists
if (! $asset = Asset::find($assetId)) { if (! $asset = Asset::find($assetId)) {
// Redirect to the asset management page with error // Redirect to the asset management page with error
@@ -329,21 +310,16 @@ class AssetsController extends Controller
} }
$asset->supplier_id = $request->input('supplier_id', null); $asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null); $asset->expected_checkin = $request->input('expected_checkin', null);
$asset->requestable = $request->input('requestable', 0);
// If the box isn't checked, it's not in the request at all.
$asset->requestable = $request->filled('requestable');
$asset->rtd_location_id = $request->input('rtd_location_id', null); $asset->rtd_location_id = $request->input('rtd_location_id', null);
$asset->byod = $request->input('byod', 0); $asset->byod = $request->input('byod', 0);
$status = Statuslabel::find($request->input('status_id')); $status = Statuslabel::find($asset->status_id);
// This is a non-deployable status label - we should check the asset back in. if($status->archived){
if (($status && $status->getStatuslabelType() != 'deployable') && ($target = $asset->assignedTo)) {
$originalValues = $asset->getRawOriginal();
$asset->assigned_to = null; $asset->assigned_to = null;
$asset->assigned_type = null;
$asset->accepted = null;
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update', date('Y-m-d H:i:s'), $originalValues));
} }
if ($asset->assigned_to == '') { if ($asset->assigned_to == '') {
@@ -361,60 +337,21 @@ class AssetsController extends Controller
} }
// Update the asset data // Update the asset data
$asset_tag = $request->input('asset_tags');
$serial = $request->input('serials'); $serial = $request->input('serials');
$asset->serial = $request->input('serials');
if (is_array($request->input('serials'))) {
$asset->serial = $serial[1];
}
$asset->name = $request->input('name'); $asset->name = $request->input('name');
$asset->serial = $serial[1];
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id'); $asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number'); $asset->order_number = $request->input('order_number');
$asset->asset_tag = $asset_tag[1];
$asset_tags = $request->input('asset_tags');
$asset->asset_tag = $request->input('asset_tags');
if (is_array($request->input('asset_tags'))) {
$asset->asset_tag = $asset_tags[1];
}
$asset->notes = $request->input('notes'); $asset->notes = $request->input('notes');
$asset = $request->handleImages($asset); $asset = $request->handleImages($asset);
$asset = $asset->handleCustomFieldsForStoring($request);
// Update custom fields in the database.
// Validation for these fields is handlded through the AssetRequest form request
// FIXME: No idea why this is returning a Builder error on db_column_name.
// Need to investigate and fix. Using static method for now.
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($asset->save()) { if ($asset->save()) {
return redirect()->to(Helper::getRedirectOption($request, $assetId, 'Assets')) return redirect()->route('hardware.show', $assetId)
->with('success', trans('admin/hardware/message.update.success')); ->with('success', trans('admin/hardware/message.update.success'));
} }
@@ -427,8 +364,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Request $request, $assetId) : RedirectResponse public function destroy($assetId)
{ {
// Check if the asset exists // Check if the asset exists
if (is_null($asset = Asset::find($assetId))) { if (is_null($asset = Asset::find($assetId))) {
@@ -438,17 +376,9 @@ class AssetsController extends Controller
$this->authorize('delete', $asset); $this->authorize('delete', $asset);
if ($asset->assignedTo) { DB::table('assets')
->where('id', $asset->id)
$target = $asset->assignedTo; ->update(['assigned_to' => null]);
$checkin_at = date('Y-m-d H:i:s');
$originalValues = $asset->getRawOriginal();
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues));
DB::table('assets')
->where('id', $asset->id)
->update(['assigned_to' => null]);
}
if ($asset->image) { if ($asset->image) {
try { try {
@@ -468,8 +398,9 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function getAssetBySerial(Request $request) : RedirectResponse public function getAssetBySerial(Request $request)
{ {
$topsearch = ($request->get('topsearch')=="true"); $topsearch = ($request->get('topsearch')=="true");
@@ -487,21 +418,14 @@ class AssetsController extends Controller
* @since [v3.0] * @since [v3.0]
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function getAssetByTag(Request $request, $tag=null) : RedirectResponse public function getAssetByTag(Request $request, $tag=null)
{ {
$tag = $tag ? $tag : $request->get('assetTag'); $tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true'); $topsearch = ($request->get('topsearch') == 'true');
// Search for an exact and unique asset tag match if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
$assets = Asset::where('asset_tag', '=', $tag); return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
// If not a unique result, redirect to the index view
if ($assets->count() != 1) {
return redirect()->route('hardware.index')
->with('search', $tag)
->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ]));
} }
$asset = $assets->first();
$this->authorize('view', $asset); $this->authorize('view', $asset);
return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch);
@@ -514,8 +438,9 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return Response
*/ */
public function getQrCode($assetId = null) : Response | BinaryFileResponse | string | bool public function getQrCode($assetId = null)
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
@@ -542,7 +467,6 @@ class AssetsController extends Controller
return 'That asset is invalid'; return 'That asset is invalid';
} }
return false;
} }
/** /**
@@ -590,7 +514,7 @@ class AssetsController extends Controller
* *
* @author [L. Swartzendruber] [<logan.swartzendruber@gmail.com> * @author [L. Swartzendruber] [<logan.swartzendruber@gmail.com>
* @param int $assetId * @param int $assetId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function getLabel($assetId = null) public function getLabel($assetId = null)
{ {
@@ -614,22 +538,28 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function getClone(Asset $asset) public function getClone($assetId = null)
{ {
$this->authorize('create', $asset); // Check if the asset exists
$cloned = clone $asset; if (is_null($asset_to_clone = Asset::find($assetId))) {
$cloned->id = null; // Redirect to the asset management page
$cloned->asset_tag = ''; return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
$cloned->serial = ''; }
$cloned->assigned_to = '';
$cloned->deleted_at = ''; $this->authorize('create', $asset_to_clone);
$asset = clone $asset_to_clone;
$asset->id = null;
$asset->asset_tag = '';
$asset->serial = '';
$asset->assigned_to = '';
return view('hardware/edit') return view('hardware/edit')
->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_list', Helper::statusLabelList())
->with('statuslabel_types', Helper::statusTypeList()) ->with('statuslabel_types', Helper::statusTypeList())
->with('item', $cloned); ->with('item', $asset);
} }
/** /**
@@ -637,7 +567,7 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function getImportHistory() public function getImportHistory()
{ {
@@ -659,7 +589,7 @@ class AssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.3] * @since [v3.3]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function postImportHistory(Request $request) public function postImportHistory(Request $request)
{ {
@@ -753,8 +683,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([ Actionlog::firstOrCreate([
'item_id' => $asset->id, 'item_id' => $asset->id,
'item_type' => Asset::class, 'item_type' => Asset::class,
'created_by' => auth()->id(), 'user_id' => Auth::user()->id,
'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer', 'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer',
'target_id' => $item[$asset_tag][$batch_counter]['user_id'], 'target_id' => $item[$asset_tag][$batch_counter]['user_id'],
'target_type' => User::class, 'target_type' => User::class,
'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'], 'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'],
@@ -781,8 +711,8 @@ class AssetsController extends Controller
Actionlog::firstOrCreate([ Actionlog::firstOrCreate([
'item_id' => $item[$asset_tag][$batch_counter]['asset_id'], 'item_id' => $item[$asset_tag][$batch_counter]['asset_id'],
'item_type' => Asset::class, 'item_type' => Asset::class,
'created_by' => auth()->id(), 'user_id' => Auth::user()->id,
'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer', 'note' => 'Checkin imported by '.Auth::user()->present()->fullName().' from history importer',
'target_id' => null, 'target_id' => null,
'created_at' => $checkin_date, 'created_at' => $checkin_date,
'action_type' => 'checkin', 'action_type' => 'checkin',
@@ -819,7 +749,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId * @param int $assetId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function getRestore($assetId = null) public function getRestore($assetId = null)
{ {
@@ -858,7 +788,7 @@ class AssetsController extends Controller
{ {
$this->authorize('checkin', Asset::class); $this->authorize('checkin', Asset::class);
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList()); return view('hardware/quickscan-checkin');
} }
public function audit($id) public function audit($id)
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Assets; namespace App\Http\Controllers\Assets;
use App\Models\Actionlog;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -10,17 +11,15 @@ use App\Models\AssetModel;
use App\Models\Statuslabel; use App\Models\Statuslabel;
use App\Models\Setting; use App\Models\Setting;
use App\View\Label; use App\View\Label;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest; use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField; use App\Models\CustomField;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class BulkAssetsController extends Controller class BulkAssetsController extends Controller
{ {
@@ -37,10 +36,12 @@ class BulkAssetsController extends Controller
* action would make a lot more sense here and make things a lot more clear. * action would make a lot more sense here and make things a lot more clear.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @return View
* @internal param int $assetId * @internal param int $assetId
* @since [v2.0] * @since [v2.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit(Request $request) : View | RedirectResponse public function edit(Request $request)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
@@ -93,9 +94,7 @@ class BulkAssetsController extends Controller
// This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array) // This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id'; $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id';
$assets = Asset::with('assignedTo', 'location', 'model') $assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids);
->whereIn('assets.id', $asset_ids)
->withTrashed();
$assets = $assets->get(); $assets = $assets->get();
@@ -195,7 +194,7 @@ class BulkAssetsController extends Controller
* @internal param array $assets * @internal param array $assets
* @since [v2.0] * @since [v2.0]
*/ */
public function update(Request $request) : RedirectResponse public function update(Request $request)
{ {
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$has_errors = 0; $has_errors = 0;
@@ -228,8 +227,7 @@ class BulkAssetsController extends Controller
* its checkout status. * its checkout status.
*/ */
if (($request->filled('name')) if (($request->filled('purchase_date'))
|| ($request->filled('purchase_date'))
|| ($request->filled('expected_checkin')) || ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost')) || ($request->filled('purchase_cost'))
|| ($request->filled('supplier_id')) || ($request->filled('supplier_id'))
@@ -241,7 +239,6 @@ class BulkAssetsController extends Controller
|| ($request->filled('status_id')) || ($request->filled('status_id'))
|| ($request->filled('model_id')) || ($request->filled('model_id'))
|| ($request->filled('next_audit_date')) || ($request->filled('next_audit_date'))
|| ($request->filled('null_name'))
|| ($request->filled('null_purchase_date')) || ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date')) || ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date')) || ($request->filled('null_next_audit_date'))
@@ -254,14 +251,13 @@ class BulkAssetsController extends Controller
$this->update_array = []; $this->update_array = [];
/** /**
* Leave out model_id and status here because we do math on that later. We have to do some * Leave out model_id and status here because we do math on that later. We have to do some extra
* extra validation and checks on those two. * validation and checks on those two.
* *
* It's tempting to make these match the request check above, but some of these values require * It's tempting to make these match the request check above, but some of these values require
* extra work to make sure the data makes sense. * extra work to make sure the data makes sense.
*/ */
$this->conditionallyAddItem('name') $this->conditionallyAddItem('purchase_date')
->conditionallyAddItem('purchase_date')
->conditionallyAddItem('expected_checkin') ->conditionallyAddItem('expected_checkin')
->conditionallyAddItem('order_number') ->conditionallyAddItem('order_number')
->conditionallyAddItem('requestable') ->conditionallyAddItem('requestable')
@@ -272,36 +268,11 @@ class BulkAssetsController extends Controller
$this->conditionallyAddItem($custom_field_column); $this->conditionallyAddItem($custom_field_column);
} }
if (!($asset->eol_explicit)) {
if ($request->filled('model_id')) {
$model = AssetModel::find($request->input('model_id'));
if ($model->eol > 0) {
if ($request->filled('purchase_date')) {
$this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($model->eol)->format('Y-m-d');
} else {
$this->update_array['asset_eol_date'] = Carbon::parse($asset->purchase_date)->addMonths($model->eol)->format('Y-m-d');
}
} else {
$this->update_array['asset_eol_date'] = null;
}
} elseif (($request->filled('purchase_date')) && ($asset->model->eol > 0)) {
$this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
}
}
/** /**
* Blank out fields that were requested to be blanked out via checkbox * Blank out fields that were requested to be blanked out via checkbox
*/ */
if ($request->input('null_name')=='1') {
$this->update_array['name'] = null;
}
if ($request->input('null_purchase_date')=='1') { if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null; $this->update_array['purchase_date'] = null;
if (!($asset->eol_explicit)) {
$this->update_array['asset_eol_date'] = null;
}
} }
if ($request->input('null_expected_checkin_date')=='1') { if ($request->input('null_expected_checkin_date')=='1') {
@@ -484,8 +455,9 @@ class BulkAssetsController extends Controller
/** /**
* Adds parameter to update array for an item if it exists in request * Adds parameter to update array for an item if it exists in request
* @param string $field field name * @param string $field field name
* @return BulkAssetsController Model for Chaining
*/ */
protected function conditionallyAddItem($field) : BulkAssetsController protected function conditionallyAddItem($field)
{ {
if (request()->filled($field)) { if (request()->filled($field)) {
$this->update_array[$field] = request()->input($field); $this->update_array[$field] = request()->input($field);
@@ -499,10 +471,12 @@ class BulkAssetsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request * @param Request $request
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @internal param array $assets * @internal param array $assets
* @since [v2.0] * @since [v2.0]
*/ */
public function destroy(Request $request) : RedirectResponse public function destroy(Request $request)
{ {
$this->authorize('delete', Asset::class); $this->authorize('delete', Asset::class);
@@ -514,7 +488,12 @@ class BulkAssetsController extends Controller
if ($request->filled('ids')) { if ($request->filled('ids')) {
$assets = Asset::find($request->get('ids')); $assets = Asset::find($request->get('ids'));
foreach ($assets as $asset) { foreach ($assets as $asset) {
$asset->delete(); $update_array['deleted_at'] = date('Y-m-d H:i:s');
$update_array['assigned_to'] = null;
DB::table('assets')
->where('id', $asset->id)
->update($update_array);
} // endforeach } // endforeach
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success')); return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success'));
@@ -526,23 +505,27 @@ class BulkAssetsController extends Controller
/** /**
* Show Bulk Checkout Page * Show Bulk Checkout Page
* @return View View to checkout multiple assets
*/ */
public function showCheckout() : View public function showCheckout()
{ {
$this->authorize('checkout', Asset::class); $this->authorize('checkout', Asset::class);
// Filter out assets that are not deployable.
return view('hardware/bulk-checkout'); return view('hardware/bulk-checkout');
} }
/** /**
* Process Multiple Checkout Request * Process Multiple Checkout Request
* @return View
*/ */
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException public function storeCheckout(AssetCheckoutRequest $request)
{ {
$this->authorize('checkout', Asset::class); $this->authorize('checkout', Asset::class);
try { try {
$admin = auth()->user(); $admin = Auth::user();
$target = $this->determineCheckoutTarget(); $target = $this->determineCheckoutTarget();
@@ -601,19 +584,17 @@ class BulkAssetsController extends Controller
} }
} }
public function restore(Request $request) : RedirectResponse public function restore(Request $request) {
{
$this->authorize('update', Asset::class); $this->authorize('update', Asset::class);
$assetIds = $request->get('ids'); $assetIds = $request->get('ids');
if (empty($assetIds)) {
if (empty($assetIds)) { return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
} else { } else {
foreach ($assetIds as $key => $assetId) { foreach ($assetIds as $key => $assetId) {
$asset = Asset::withTrashed()->find($assetId); $asset = Asset::withTrashed()->find($assetId);
$asset->restore(); $asset->restore();
} }
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success'));
} }
} }
} }
@@ -127,7 +127,7 @@ class LoginController extends Controller
$saml->clearData(); $saml->clearData();
} }
if ($user = auth()->user()) { if ($user = Auth::user()) {
$user->last_login = \Carbon::now(); $user->last_login = \Carbon::now();
$user->saveQuietly(); $user->saveQuietly();
} }
@@ -326,7 +326,7 @@ class LoginController extends Controller
} }
} }
if ($user = auth()->user()) { if ($user = Auth::user()) {
$user->last_login = \Carbon::now(); $user->last_login = \Carbon::now();
$user->activated = 1; $user->activated = 1;
$user->saveQuietly(); $user->saveQuietly();
@@ -350,7 +350,7 @@ class LoginController extends Controller
} }
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$user = auth()->user(); $user = Auth::user();
// We wouldn't normally see this page if 2FA isn't enforced via the // We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled, // \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
@@ -398,7 +398,7 @@ class LoginController extends Controller
return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
} }
$user = auth()->user(); $user = Auth::user();
// Check whether there is a device enrolled. // Check whether there is a device enrolled.
// This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware // This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware
@@ -427,7 +427,7 @@ class LoginController extends Controller
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required')); return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
} }
$user = auth()->user(); $user = Auth::user();
$secret = $request->input('two_factor_secret'); $secret = $request->input('two_factor_secret');
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) { if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
@@ -508,8 +508,8 @@ class LoginController extends Controller
protected function validator(array $data) protected function validator(array $data)
{ {
return Validator::make($data, [ return Validator::make($data, [
'username' => 'required|not_array', 'username' => 'required',
'password' => 'required|not_array', 'password' => 'required',
]); ]);
} }
@@ -87,7 +87,7 @@ class ResetPasswordController extends Controller
'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'), 'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'),
]; ];
$request->validate($this->rules()); $request->validate($this->rules(), $request->all(), $this->validationErrorMessages());
Log::debug('Checking if '.$request->input('username').' exists'); Log::debug('Checking if '.$request->input('username').' exists');
// Check to see if the user even exists - we'll treat the response the same to prevent user sniffing // Check to see if the user even exists - we'll treat the response the same to prevent user sniffing
+8 -20
View File
@@ -99,18 +99,12 @@ class SamlController extends Controller
{ {
$saml = $this->saml; $saml = $this->saml;
$auth = $saml->getAuth(); $auth = $saml->getAuth();
$saml_exception = false; $auth->processResponse();
try {
$auth->processResponse();
} catch (\Exception $e) {
Log::warning("Exception caught in SAML login: " . $e->getMessage());
$saml_exception = true;
}
$errors = $auth->getErrors(); $errors = $auth->getErrors();
if (!empty($errors) || $saml_exception) { if (! empty($errors)) {
Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors)); Log::error('There was an error with SAML ACS: '.implode(', ', $errors));
Log::warning('Reason: ' . $auth->getLastErrorReason()); Log::error('Reason: '.$auth->getLastErrorReason());
return redirect()->route('login')->with('error', trans('auth/message.signin.error')); return redirect()->route('login')->with('error', trans('auth/message.signin.error'));
} }
@@ -138,18 +132,12 @@ class SamlController extends Controller
{ {
$auth = $this->saml->getAuth(); $auth = $this->saml->getAuth();
$retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false); $retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false);
$saml_exception = false; $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
try {
$sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true);
} catch (\Exception $e) {
Log::warning("Exception caught in SAML single-logout: " . $e->getMessage());
$saml_exception = true;
}
$errors = $auth->getErrors(); $errors = $auth->getErrors();
if (!empty($errors) || $saml_exception) { if (! empty($errors)) {
Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors)); Log::error('There was an error with SAML SLS: '.implode(', ', $errors));
Log::warning('Reason: ' . $auth->getLastErrorReason()); Log::error('Reason: '.$auth->getLastErrorReason());
return view('errors.403'); return view('errors.403');
} }
@@ -5,8 +5,8 @@ namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\AssetModel; use App\Models\AssetModel;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Input;
use \Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Redirect;
class BulkAssetModelsController extends Controller class BulkAssetModelsController extends Controller
{ {
@@ -16,8 +16,9 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7] * @since [v1.7]
* @param Request $request * @param Request $request
* @return \Illuminate\Contracts\View\View
*/ */
public function edit(Request $request) : View | RedirectResponse public function edit(Request $request)
{ {
$models_raw_array = $request->input('ids'); $models_raw_array = $request->input('ids');
@@ -60,8 +61,9 @@ class BulkAssetModelsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.7] * @since [v1.7]
* @param Request $request * @param Request $request
* @return \Illuminate\Contracts\View\View
*/ */
public function update(Request $request): View | RedirectResponse public function update(Request $request)
{ {
$this->authorize('update', AssetModel::class); $this->authorize('update', AssetModel::class);
@@ -103,8 +105,9 @@ class BulkAssetModelsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function destroy(Request $request) : RedirectResponse public function destroy(Request $request)
{ {
$this->authorize('delete', AssetModel::class); $this->authorize('delete', AssetModel::class);
+25 -22
View File
@@ -4,11 +4,10 @@ namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Category; use App\Models\Category as Category;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\RedirectResponse; use Str;
use \Illuminate\Contracts\View\View;
/** /**
* This class controls all actions related to Categories for * This class controls all actions related to Categories for
@@ -26,8 +25,10 @@ class CategoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDatatable() method that generates the JSON response * @see CategoriesController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
// Show the page // Show the page
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
@@ -41,8 +42,10 @@ class CategoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::store() method that stores the data * @see CategoriesController::store() method that stores the data
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
// Show the page // Show the page
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
@@ -58,8 +61,10 @@ class CategoriesController extends Controller
* @see CategoriesController::create() method that makes the form. * @see CategoriesController::create() method that makes the form.
* @since [v1.0] * @since [v1.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
$category = new Category(); $category = new Category();
@@ -69,7 +74,7 @@ class CategoriesController extends Controller
$category->use_default_eula = $request->input('use_default_eula', '0'); $category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0'); $category->require_acceptance = $request->input('require_acceptance', '0');
$category->checkin_email = $request->input('checkin_email', '0'); $category->checkin_email = $request->input('checkin_email', '0');
$category->created_by = auth()->id(); $category->user_id = Auth::id();
$category = $request->handleImages($category); $category = $request->handleImages($category);
if ($category->save()) { if ($category->save()) {
@@ -86,8 +91,10 @@ class CategoriesController extends Controller
* @see CategoriesController::postEdit() method saves the data * @see CategoriesController::postEdit() method saves the data
* @param int $categoryId * @param int $categoryId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($categoryId = null) : RedirectResponse | View public function edit($categoryId = null)
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
if (is_null($item = Category::find($categoryId))) { if (is_null($item = Category::find($categoryId))) {
@@ -105,31 +112,23 @@ class CategoriesController extends Controller
* @see CategoriesController::getEdit() method that makes the form. * @see CategoriesController::getEdit() method that makes the form.
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @param int $categoryId * @param int $categoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0] * @since [v1.0]
*/ */
public function update(ImageUploadRequest $request, $categoryId = null) : RedirectResponse public function update(ImageUploadRequest $request, $categoryId = null)
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
if (is_null($category = Category::find($categoryId))) { if (is_null($category = Category::find($categoryId))) {
// Redirect to the categories management page // Redirect to the categories management page
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist')); return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist'));
} }
// Update the category data // Update the category data
$category->name = $request->input('name'); $category->name = $request->input('name');
// If the item count is > 0, we disable the category type in the edit. Disabled items // If the item count is > 0, we disable the category type in the edit. Disabled items
// don't POST, so if the category_type is blank we just set it to the default. // don't POST, so if the category_type is blank we just set it to the default.
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type') && ($category->itemCount() > 0))) {
$request->validate(['category_type' => 'in:'.$category->category_type]);
}
$category->category_type = $request->input('category_type', $category->category_type); $category->category_type = $request->input('category_type', $category->category_type);
$category->fill($request->all());
$category->eula_text = $request->input('eula_text'); $category->eula_text = $request->input('eula_text');
$category->use_default_eula = $request->input('use_default_eula', '0'); $category->use_default_eula = $request->input('use_default_eula', '0');
$category->require_acceptance = $request->input('require_acceptance', '0'); $category->require_acceptance = $request->input('require_acceptance', '0');
@@ -151,8 +150,10 @@ class CategoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @param int $categoryId * @param int $categoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($categoryId) : RedirectResponse public function destroy($categoryId)
{ {
$this->authorize('delete', Category::class); $this->authorize('delete', Category::class);
// Check if the category exists // Check if the category exists
@@ -177,9 +178,11 @@ class CategoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see CategoriesController::getDataView() method that generates the JSON response * @see CategoriesController::getDataView() method that generates the JSON response
* @param $id * @param $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8] * @since [v1.8]
*/ */
public function show($id) : View | RedirectResponse public function show($id)
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
if ($category = Category::find($id)) { if ($category = Category::find($id)) {
+4 -3
View File
@@ -11,8 +11,9 @@ trait CheckInOutRequest
{ {
/** /**
* Find target for checkout * Find target for checkout
* @return SnipeModel Target asset is being checked out to.
*/ */
protected function determineCheckoutTarget() : ?SnipeModel protected function determineCheckoutTarget()
{ {
// This item is checked out to a location // This item is checked out to a location
switch (request('checkout_to_type')) { switch (request('checkout_to_type')) {
@@ -20,7 +21,7 @@ trait CheckInOutRequest
return Location::findOrFail(request('assigned_location')); return Location::findOrFail(request('assigned_location'));
case 'asset': case 'asset':
return Asset::findOrFail(request('assigned_asset')); return Asset::findOrFail(request('assigned_asset'));
default: case 'user':
return User::findOrFail(request('assigned_user')); return User::findOrFail(request('assigned_user'));
} }
@@ -33,7 +34,7 @@ trait CheckInOutRequest
* @param SnipeModel $target Target with location * @param SnipeModel $target Target with location
* @return Asset Asset being updated * @return Asset Asset being updated
*/ */
protected function updateAssetLocation($asset, $target) : Asset protected function updateAssetLocation($asset, $target)
{ {
switch (request('checkout_to_type')) { switch (request('checkout_to_type')) {
case 'location': case 'location':
+19 -10
View File
@@ -7,8 +7,6 @@ use App\Models\Company;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Companies for * This controller handles all actions related to Companies for
@@ -23,8 +21,10 @@ final class CompaniesController extends Controller
* *
* @author [Abdullah Alansari] [<ahimta@gmail.com>] * @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
@@ -36,8 +36,10 @@ final class CompaniesController extends Controller
* *
* @author [Abdullah Alansari] [<ahimta@gmail.com>] * @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
@@ -50,8 +52,10 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] [<ahimta@gmail.com>] * @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @param Request $request * @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Company::class); $this->authorize('create', Company::class);
@@ -60,7 +64,6 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone'); $company->phone = $request->input('phone');
$company->fax = $request->input('fax'); $company->fax = $request->input('fax');
$company->email = $request->input('email'); $company->email = $request->input('email');
$company->created_by = auth()->id();
$company = $request->handleImages($company); $company = $request->handleImages($company);
@@ -78,8 +81,10 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] [<ahimta@gmail.com>] * @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @param int $companyId * @param int $companyId
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($companyId) : View | RedirectResponse public function edit($companyId)
{ {
if (is_null($item = Company::find($companyId))) { if (is_null($item = Company::find($companyId))) {
return redirect()->route('companies.index') return redirect()->route('companies.index')
@@ -98,8 +103,10 @@ final class CompaniesController extends Controller
* @since [v1.8] * @since [v1.8]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @param int $companyId * @param int $companyId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(ImageUploadRequest $request, $companyId) : RedirectResponse public function update(ImageUploadRequest $request, $companyId)
{ {
if (is_null($company = Company::find($companyId))) { if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist')); return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist'));
@@ -128,8 +135,10 @@ final class CompaniesController extends Controller
* @author [Abdullah Alansari] [<ahimta@gmail.com>] * @author [Abdullah Alansari] [<ahimta@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @param int $companyId * @param int $companyId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($companyId) : RedirectResponse public function destroy($companyId)
{ {
if (is_null($company = Company::find($companyId))) { if (is_null($company = Company::find($companyId))) {
return redirect()->route('companies.index') return redirect()->route('companies.index')
@@ -156,7 +165,7 @@ final class CompaniesController extends Controller
->with('success', trans('admin/companies/message.delete.success')); ->with('success', trans('admin/companies/message.delete.success'));
} }
public function show($id) : View | RedirectResponse public function show($id)
{ {
$this->authorize('view', Company::class); $this->authorize('view', Company::class);
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn; use App\Events\ComponentCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Component; use App\Models\Component;
@@ -96,11 +95,13 @@ class ComponentCheckinController extends Controller
$asset = Asset::find($component_assets->asset_id); $asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now()));
if ($backto == 'asset'){
return redirect()->route('hardware.show', $asset->id)->with('success',
trans('admin/components/message.checkin.success'));
}
session()->put(['redirect_option' => $request->get('redirect_option')]); return redirect()->route('components.index')->with('success',
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success',
trans('admin/components/message.checkin.success')); trans('admin/components/message.checkin.success'));
} }
@@ -4,11 +4,9 @@ namespace App\Http\Controllers\Components;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Events\ComponentCheckedOut; use App\Events\ComponentCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Component; use App\Models\Component;
use App\Models\Setting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
@@ -95,31 +93,22 @@ class ComponentCheckoutController extends Controller
->withInput(); ->withInput();
} }
// Check if the asset exists // Check if the user exists
$asset = Asset::find($request->input('asset_id')); $asset = Asset::find($request->input('asset_id'));
if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) {
return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company'));
}
// Update the component data // Update the component data
$component->asset_id = $request->input('asset_id'); $component->asset_id = $request->input('asset_id');
$component->assets()->attach($component->id, [ $component->assets()->attach($component->id, [
'component_id' => $component->id, 'component_id' => $component->id,
'created_by' => auth()->user()->id, 'user_id' => Auth::user(),
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => $request->input('assigned_qty'), 'assigned_qty' => $request->input('assigned_qty'),
'asset_id' => $request->input('asset_id'), 'asset_id' => $request->input('asset_id'),
'note' => $request->input('note'), 'note' => $request->input('note'),
]); ]);
event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note'))); event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note')));
$request->request->add(['checkout_to_type' => 'asset']); return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success'));
$request->request->add(['assigned_asset' => $asset->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success'));
} }
} }
@@ -81,15 +81,13 @@ class ComponentsController extends Controller
$component->purchase_date = $request->input('purchase_date', null); $component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null); $component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty'); $component->qty = $request->input('qty');
$component->created_by = auth()->id(); $component->user_id = Auth::id();
$component->notes = $request->input('notes'); $component->notes = $request->input('notes');
$component = $request->handleImages($component); $component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) { if ($component->save()) {
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success')); return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success'));
} }
return redirect()->back()->withInput()->withErrors($component->getErrors()); return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -162,10 +160,8 @@ class ComponentsController extends Controller
$component = $request->handleImages($component); $component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($component->save()) { if ($component->save()) {
return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success')); return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success'));
} }
return redirect()->back()->withInput()->withErrors($component->getErrors()); return redirect()->back()->withInput()->withErrors($component->getErrors());
@@ -3,13 +3,13 @@
namespace App\Http\Controllers\Consumables; namespace App\Http\Controllers\Consumables;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\Consumable; use App\Models\Consumable;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use \Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Auth;
use \Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Input;
class ConsumableCheckoutController extends Controller class ConsumableCheckoutController extends Controller
{ {
@@ -20,11 +20,13 @@ class ConsumableCheckoutController extends Controller
* @see ConsumableCheckoutController::store() method that stores the data. * @see ConsumableCheckoutController::store() method that stores the data.
* @since [v1.0] * @since [v1.0]
* @param int $id * @param int $id
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create($id) : View | RedirectResponse public function create($id)
{ {
if ($consumable = Consumable::find($id)) { if ($consumable = Consumable::with('users')->find($id)) {
$this->authorize('checkout', $consumable); $this->authorize('checkout', $consumable);
@@ -34,7 +36,7 @@ class ConsumableCheckoutController extends Controller
// Make sure there is at least one available to checkout // Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0){ if ($consumable->numRemaining() <= 0){
return redirect()->route('consumables.index') return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()])); ->with('error', trans('admin/consumables/message.checkout.unavailable'));
} }
// Return the checkout view // Return the checkout view
@@ -77,10 +79,10 @@ class ConsumableCheckoutController extends Controller
// Make sure there is at least one available to checkout // Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) { if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ])); return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
} }
$admin_user = auth()->user(); $admin_user = Auth::user();
$assigned_to = e($request->input('assigned_to')); $assigned_to = e($request->input('assigned_to'));
// Check if the user exists // Check if the user exists
@@ -95,20 +97,14 @@ class ConsumableCheckoutController extends Controller
for($i = 0; $i < $quantity; $i++){ for($i = 0; $i < $quantity; $i++){
$consumable->users()->attach($consumable->id, [ $consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id, 'consumable_id' => $consumable->id,
'created_by' => $admin_user->id, 'user_id' => $admin_user->id,
'assigned_to' => e($request->input('assigned_to')), 'assigned_to' => e($request->input('assigned_to')),
'note' => $request->input('note'), 'note' => $request->input('note'),
]); ]);
} }
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
$request->request->add(['checkout_to_type' => 'user']);
$request->request->add(['assigned_user' => $user->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
// Redirect to the new consumable page // Redirect to the new consumable page
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success')); return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success'));
} }
} }
@@ -8,10 +8,8 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company; use App\Models\Company;
use App\Models\Consumable; use App\Models\Consumable;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use App\Http\Requests\StoreConsumableRequest;
/** /**
* This controller handles all actions related to Consumables for * This controller handles all actions related to Consumables for
@@ -64,7 +62,7 @@ class ConsumablesController extends Controller
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(StoreConsumableRequest $request) public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Consumable::class); $this->authorize('create', Consumable::class);
$consumable = new Consumable(); $consumable = new Consumable();
@@ -81,16 +79,14 @@ class ConsumablesController extends Controller
$consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = $request->input('purchase_cost'); $consumable->purchase_cost = $request->input('purchase_cost');
$consumable->qty = $request->input('qty'); $consumable->qty = $request->input('qty');
$consumable->created_by = auth()->id(); $consumable->user_id = Auth::id();
$consumable->notes = $request->input('notes'); $consumable->notes = $request->input('notes');
$consumable = $request->handleImages($consumable); $consumable = $request->handleImages($consumable);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) { if ($consumable->save()) {
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success')); return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success'));
} }
return redirect()->back()->withInput()->withErrors($consumable->getErrors()); return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -103,8 +99,10 @@ class ConsumablesController extends Controller
* @param int $consumableId * @param int $consumableId
* @see ConsumablesController::postEdit() method that stores the form data. * @see ConsumablesController::postEdit() method that stores the form data.
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($consumableId = null) : View | RedirectResponse public function edit($consumableId = null)
{ {
if ($item = Consumable::find($consumableId)) { if ($item = Consumable::find($consumableId)) {
$this->authorize($item); $this->authorize($item);
@@ -126,7 +124,7 @@ class ConsumablesController extends Controller
* @see ConsumablesController::getEdit() method that stores the form data. * @see ConsumablesController::getEdit() method that stores the form data.
* @since [v1.0] * @since [v1.0]
*/ */
public function update(StoreConsumableRequest $request, $consumableId = null) public function update(ImageUploadRequest $request, $consumableId = null)
{ {
if (is_null($consumable = Consumable::find($consumableId))) { if (is_null($consumable = Consumable::find($consumableId))) {
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist')); return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist'));
@@ -162,10 +160,8 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable); $consumable = $request->handleImages($consumable);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($consumable->save()) { if ($consumable->save()) {
return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success')); return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success'));
} }
return redirect()->back()->withInput()->withErrors($consumable->getErrors()); return redirect()->back()->withInput()->withErrors($consumable->getErrors());
@@ -186,7 +182,6 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found')); return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found'));
} }
$this->authorize($consumable); $this->authorize($consumable);
$consumable->delete(); $consumable->delete();
// Redirect to the locations management page // Redirect to the locations management page
return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success')); return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success'));
@@ -204,7 +199,7 @@ class ConsumablesController extends Controller
*/ */
public function show($consumableId = null) public function show($consumableId = null)
{ {
$consumable = Consumable::withCount('users as users_consumables')->find($consumableId); $consumable = Consumable::find($consumableId);
$this->authorize($consumable); $this->authorize($consumable);
if (isset($consumable->id)) { if (isset($consumable->id)) {
return view('consumables/view', compact('consumable')); return view('consumables/view', compact('consumable'));
@@ -213,16 +208,4 @@ class ConsumablesController extends Controller
return redirect()->route('consumables.index') return redirect()->route('consumables.index')
->with('error', trans('admin/consumables/message.does_not_exist')); ->with('error', trans('admin/consumables/message.does_not_exist'));
} }
public function clone(Consumable $consumable) : View
{
$this->authorize('create', $consumable);
$consumable_to_close = $consumable;
$consumable = clone $consumable_to_close;
$consumable->id = null;
$consumable->image = null;
$consumable->created_by = null;
return view('consumables/edit')->with('item', $consumable);
}
} }
+1 -1
View File
@@ -35,6 +35,6 @@ abstract class Controller extends BaseController
public function __construct() public function __construct()
{ {
view()->share('signedIn', Auth::check()); view()->share('signedIn', Auth::check());
view()->share('user', auth()->user()); view()->share('user', Auth::user());
} }
} }
+24 -12
View File
@@ -8,8 +8,6 @@ use App\Models\CustomField;
use App\Models\CustomFieldset; use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Custom Asset Fields for * This controller handles all actions related to Custom Asset Fields for
@@ -27,8 +25,10 @@ class CustomFieldsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('view', CustomField::class); $this->authorize('view', CustomField::class);
@@ -45,8 +45,10 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField() * @see CustomFieldsController::storeField()
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.1.5] * @since [v5.1.5]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show() : RedirectResponse public function show()
{ {
return redirect()->route('fields.index'); return redirect()->route('fields.index');
} }
@@ -58,8 +60,10 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::storeField() * @see CustomFieldsController::storeField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create(Request $request) : View public function create(Request $request)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get(); $fieldsets = CustomFieldset::get();
@@ -78,8 +82,10 @@ class CustomFieldsController extends Controller
* @see CustomFieldsController::createField() * @see CustomFieldsController::createField()
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(CustomFieldRequest $request) : RedirectResponse public function store(CustomFieldRequest $request)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
@@ -104,7 +110,7 @@ class CustomFieldsController extends Controller
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0), "show_in_listview" => $request->get("show_in_listview", 0),
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0), "show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"user_id" => auth()->id() "user_id" => Auth::id()
]); ]);
@@ -138,8 +144,10 @@ class CustomFieldsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0] * @since [v3.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse public function deleteFieldFromFieldset($field_id, $fieldset_id)
{ {
$field = CustomField::find($field_id); $field = CustomField::find($field_id);
@@ -168,8 +176,10 @@ class CustomFieldsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($field_id) : RedirectResponse public function destroy($field_id)
{ {
if ($field = CustomField::find($field_id)) { if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field); $this->authorize('delete', $field);
@@ -192,8 +202,10 @@ class CustomFieldsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit(Request $request, $id) : View | RedirectResponse public function edit(Request $request, $id)
{ {
if ($field = CustomField::find($id)) { if ($field = CustomField::find($id)) {
@@ -229,7 +241,7 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(CustomFieldRequest $request, $id) : RedirectResponse public function update(CustomFieldRequest $request, $id)
{ {
$field = CustomField::find($id); $field = CustomField::find($id);
@@ -248,7 +260,7 @@ class CustomFieldsController extends Controller
$field->name = trim(e($request->get("name"))); $field->name = trim(e($request->get("name")));
$field->element = e($request->get("element")); $field->element = e($request->get("element"));
$field->field_values = $request->get("field_values"); $field->field_values = $request->get("field_values");
$field->created_by = auth()->id(); $field->user_id = Auth::id();
$field->help_text = $request->get("help_text"); $field->help_text = $request->get("help_text");
$field->show_in_email = $show_in_email; $field->show_in_email = $show_in_email;
$field->is_unique = $request->get("is_unique", 0); $field->is_unique = $request->get("is_unique", 0);
@@ -6,9 +6,10 @@ use App\Models\AssetModel;
use App\Models\CustomField; use App\Models\CustomField;
use App\Models\CustomFieldset; use App\Models\CustomFieldset;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Http\RedirectResponse; use Redirect;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Custom Asset Fields for * This controller handles all actions related to Custom Asset Fields for
@@ -22,7 +23,7 @@ use \Illuminate\Contracts\View\View;
class CustomFieldsetsController extends Controller class CustomFieldsetsController extends Controller
{ {
public function index() : RedirectResponse public function index()
{ {
return redirect()->route("fields.index") return redirect()->route("fields.index")
->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist')); ->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist'));
@@ -33,9 +34,11 @@ class CustomFieldsetsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8] * @since [v1.8]
*/ */
public function show($id) : View | RedirectResponse public function show($id)
{ {
$cfset = CustomFieldset::with('fields') $cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first(); ->where('id', '=', $id)->orderBy('id', 'ASC')->first();
@@ -67,8 +70,10 @@ class CustomFieldsetsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
@@ -84,13 +89,13 @@ class CustomFieldsetsController extends Controller
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(Request $request) : RedirectResponse public function store(Request $request)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset([ $fieldset = new CustomFieldset([
'name' => $request->get('name'), 'name' => $request->get('name'),
'created_by' => auth()->id(), 'user_id' => Auth::user()->id,
]); ]);
$validator = Validator::make($request->all(), $fieldset->rules); $validator = Validator::make($request->all(), $fieldset->rules);
@@ -121,8 +126,10 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v6.0.14] * @since [v6.0.14]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($id) : View | RedirectResponse public function edit($id)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
@@ -140,8 +147,10 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v6.0.14] * @since [v6.0.14]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(Request $request, $id) : RedirectResponse public function update(Request $request, $id)
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
@@ -166,8 +175,10 @@ class CustomFieldsetsController extends Controller
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @param int $id * @param int $id
* @since [v1.8] * @since [v1.8]
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($id) : RedirectResponse public function destroy($id)
{ {
$fieldset = CustomFieldset::find($id); $fieldset = CustomFieldset::find($id);
@@ -192,8 +203,9 @@ class CustomFieldsetsController extends Controller
* *
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v1.8] * @since [v1.8]
* @return View
*/ */
public function associate(Request $request, $id) : RedirectResponse public function associate(Request $request, $id)
{ {
$set = CustomFieldset::find($id); $set = CustomFieldset::find($id);
@@ -211,7 +223,7 @@ class CustomFieldsetsController extends Controller
return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success')); return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success'));
} }
return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected')); return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.');
} }
/** /**
@@ -220,7 +232,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0] * @since [v5.0]
*/ */
public function makeFieldRequired($fieldset_id, $field_id) : RedirectResponse public function makeFieldRequired($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
@@ -238,7 +250,7 @@ class CustomFieldsetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0] * @since [v5.0]
*/ */
public function makeFieldOptional($fieldset_id, $field_id) : RedirectResponse public function makeFieldOptional($fieldset_id, $field_id)
{ {
$this->authorize('update', CustomField::class); $this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($field_id); $field = CustomField::findOrFail($field_id);
+5 -8
View File
@@ -2,10 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Session;
/** /**
@@ -23,11 +21,12 @@ class DashboardController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
*/ */
public function index() : View | RedirectResponse public function index()
{ {
// Show the page // Show the page
if (auth()->user()->hasAccess('admin')) { if (Auth::user()->hasAccess('admin')) {
$asset_stats = null; $asset_stats = null;
$counts['asset'] = \App\Models\Asset::count(); $counts['asset'] = \App\Models\Asset::count();
@@ -35,7 +34,7 @@ class DashboardController extends Controller
$counts['license'] = \App\Models\License::assetcount(); $counts['license'] = \App\Models\License::assetcount();
$counts['consumable'] = \App\Models\Consumable::count(); $counts['consumable'] = \App\Models\Consumable::count();
$counts['component'] = \App\Models\Component::count(); $counts['component'] = \App\Models\Component::count();
$counts['user'] = \App\Models\Company::scopeCompanyables(auth()->user())->count(); $counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count();
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable']; $counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) { if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
@@ -45,8 +44,6 @@ class DashboardController extends Controller
return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts); return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts);
} else { } else {
Session::reflash();
// Redirect to the profile page // Redirect to the profile page
return redirect()->intended('account/view-assets'); return redirect()->intended('account/view-assets');
} }
+21 -19
View File
@@ -4,10 +4,8 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Department; use App\Models\Department;
use App\Models\Company;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -27,8 +25,10 @@ class DepartmentsController extends Controller
* @see AssetController::getDatatable() method that generates the JSON response * @see AssetController::getDatatable() method that generates the JSON response
* @since [v4.0] * @since [v4.0]
* @param Request $request * @param Request $request
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index(Request $request) : View public function index(Request $request)
{ {
$this->authorize('index', Department::class); $this->authorize('index', Department::class);
$company = null; $company = null;
@@ -45,13 +45,15 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Department::class); $this->authorize('create', Department::class);
$department = new Department; $department = new Department;
$department->fill($request->all()); $department->fill($request->all());
$department->created_by = auth()->id(); $department->user_id = Auth::user()->id;
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null); $department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null); $department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
@@ -71,8 +73,10 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($id) : View | RedirectResponse public function show($id)
{ {
$department = Department::find($id); $department = Department::find($id);
@@ -91,8 +95,10 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see DepartmentsController::postCreate() method that validates and stores the data * @see DepartmentsController::postCreate() method that validates and stores the data
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Department::class); $this->authorize('create', Department::class);
@@ -105,8 +111,10 @@ class DepartmentsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId * @param int $locationId
* @since [v4.0] * @since [v4.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($id) : RedirectResponse public function destroy($id)
{ {
if (is_null($department = Department::find($id))) { if (is_null($department = Department::find($id))) {
return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found')); return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found'));
@@ -137,8 +145,10 @@ class DepartmentsController extends Controller
* @see LocationsController::postCreate() method that validates and stores * @see LocationsController::postCreate() method that validates and stores
* @param int $departmentId * @param int $departmentId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($departmentId = null) : View | RedirectResponse public function edit($departmentId = null)
{ {
if (is_null($item = Department::find($departmentId))) { if (is_null($item = Department::find($departmentId))) {
return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist')); return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist'));
@@ -149,15 +159,7 @@ class DepartmentsController extends Controller
return view('departments/edit', compact('item')); return view('departments/edit', compact('item'));
} }
/** public function update(ImageUploadRequest $request, $id)
* Save updated Department information.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores
* @param int $departmentId
* @since [v1.0]
*/
public function update(ImageUploadRequest $request, $id) : RedirectResponse
{ {
if (is_null($department = Department::find($id))) { if (is_null($department = Department::find($id))) {
return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist')); return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist'));
@@ -5,8 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Depreciation; use App\Models\Depreciation;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Depreciations for * This controller handles all actions related to Depreciations for
* the Snipe-IT Asset Management application. * the Snipe-IT Asset Management application.
@@ -22,10 +21,14 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::getDatatable() method that generates the JSON response * @see DepreciationsController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('view', Depreciation::class); $this->authorize('view', Depreciation::class);
// Show the page
return view('depreciations/index'); return view('depreciations/index');
} }
@@ -35,8 +38,10 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @see DepreciationsController::postCreate() * @see DepreciationsController::postCreate()
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Depreciation::class); $this->authorize('create', Depreciation::class);
@@ -51,8 +56,10 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postCreate() * @see DepreciationsController::postCreate()
* @since [v1.0] * @since [v1.0]
* @param Request $request * @param Request $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(Request $request) : RedirectResponse public function store(Request $request)
{ {
$this->authorize('create', Depreciation::class); $this->authorize('create', Depreciation::class);
@@ -61,21 +68,7 @@ class DepreciationsController extends Controller
// Depreciation data // Depreciation data
$depreciation->name = $request->input('name'); $depreciation->name = $request->input('name');
$depreciation->months = $request->input('months'); $depreciation->months = $request->input('months');
$depreciation->created_by = auth()->id(); $depreciation->user_id = Auth::id();
$request->validate([
'depreciation_min' => [
'required',
'numeric',
function ($attribute, $value, $fail) use ($request) {
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
$fail(trans('validation.percent'));
}
},
],
'depreciation_type' => 'required|in:amount,percent',
]);
$depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min'); $depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created? // Was the asset created?
@@ -94,8 +87,10 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit() * @see DepreciationsController::postEdit()
* @param int $depreciationId * @param int $depreciationId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($depreciationId = null) : RedirectResponse | View public function edit($depreciationId = null)
{ {
// Check if the depreciation exists // Check if the depreciation exists
if (is_null($item = Depreciation::find($depreciationId))) { if (is_null($item = Depreciation::find($depreciationId))) {
@@ -115,9 +110,11 @@ class DepreciationsController extends Controller
* @see DepreciationsController::getEdit() * @see DepreciationsController::getEdit()
* @param Request $request * @param Request $request
* @param int $depreciationId * @param int $depreciationId
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0] * @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(Request $request, $depreciationId = null) : RedirectResponse public function update(Request $request, $depreciationId = null)
{ {
// Check if the depreciation exists // Check if the depreciation exists
if (is_null($depreciation = Depreciation::find($depreciationId))) { if (is_null($depreciation = Depreciation::find($depreciationId))) {
@@ -130,20 +127,6 @@ class DepreciationsController extends Controller
// Depreciation data // Depreciation data
$depreciation->name = $request->input('name'); $depreciation->name = $request->input('name');
$depreciation->months = $request->input('months'); $depreciation->months = $request->input('months');
$request->validate([
'depreciation_min' => [
'required',
'numeric',
function ($attribute, $value, $fail) use ($request) {
if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) {
$fail(trans('validation.percent'));
}
},
],
'depreciation_type' => 'required|in:amount,percent',
]);
$depreciation->depreciation_type = $request->input('depreciation_type');
$depreciation->depreciation_min = $request->input('depreciation_min'); $depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created? // Was the asset created?
@@ -163,8 +146,10 @@ class DepreciationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @since [v1.0] * @since [v1.0]
* @param int $depreciationId * @param int $depreciationId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($depreciationId) : RedirectResponse public function destroy($depreciationId)
{ {
// Check if the depreciation exists // Check if the depreciation exists
if (is_null($depreciation = Depreciation::withCount('models as models_count')->find($depreciationId))) { if (is_null($depreciation = Depreciation::withCount('models as models_count')->find($depreciationId))) {
@@ -190,23 +175,18 @@ class DepreciationsController extends Controller
* @see DepreciationsController::postEdit() * @see DepreciationsController::postEdit()
* @param int $depreciationId * @param int $depreciationId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($id) : View | RedirectResponse public function show($id)
{ {
$depreciation = Depreciation::withCount('assets as assets_count') if (is_null($depreciation = Depreciation::find($id))) {
->withCount('models as models_count') // Redirect to the blogs management page
->withCount('licenses as licenses_count') return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
->find($id); }
$this->authorize('view', $depreciation); $this->authorize('view', $depreciation);
if ($depreciation) { return view('depreciations/view', compact('depreciation'));
return view('depreciations/view', compact('depreciation'));
}
return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist'));
} }
} }
@@ -2,7 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Facades\Socialite;
@@ -30,7 +30,7 @@ class GoogleAuthController extends Controller
return Socialite::driver('google')->redirect(); return Socialite::driver('google')->redirect();
} }
public function handleGoogleCallback() : RedirectResponse public function handleGoogleCallback()
{ {
try { try {
$socialUser = Socialite::driver('google')->user(); $socialUser = Socialite::driver('google')->user();
+18 -10
View File
@@ -5,8 +5,7 @@ namespace App\Http\Controllers;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\Group; use App\Models\Group;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to User Groups for * This controller handles all actions related to User Groups for
@@ -23,9 +22,11 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @see GroupsController::getDatatable() method that generates the JSON response * @see GroupsController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/ */
public function index(): View public function index()
{ {
// Show the page
return view('groups/index'); return view('groups/index');
} }
@@ -35,8 +36,9 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @see GroupsController::postCreate() * @see GroupsController::postCreate()
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/ */
public function create(Request $request) : View public function create(Request $request)
{ {
$group = new Group; $group = new Group;
// Get all the available permissions // Get all the available permissions
@@ -54,14 +56,15 @@ class GroupsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @see GroupsController::getCreate() * @see GroupsController::getCreate()
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function store(Request $request) : RedirectResponse public function store(Request $request)
{ {
// create a new group instance // create a new group instance
$group = new Group(); $group = new Group();
$group->name = $request->input('name'); $group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission')); $group->permissions = json_encode($request->input('permission'));
$group->created_by = auth()->id(); $group->created_by = Auth::user()->id;
if ($group->save()) { if ($group->save()) {
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create')); return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
@@ -77,8 +80,9 @@ class GroupsController extends Controller
* @see GroupsController::postEdit() * @see GroupsController::postEdit()
* @param int $id * @param int $id
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/ */
public function edit($id) : View | RedirectResponse public function edit($id)
{ {
$group = Group::find($id); $group = Group::find($id);
@@ -100,8 +104,9 @@ class GroupsController extends Controller
* @see GroupsController::getEdit() * @see GroupsController::getEdit()
* @param int $id * @param int $id
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function update(Request $request, $id = null) : RedirectResponse public function update(Request $request, $id = null)
{ {
if (! $group = Group::find($id)) { if (! $group = Group::find($id)) {
return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id])); return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id]));
@@ -127,8 +132,10 @@ class GroupsController extends Controller
* @see GroupsController::getEdit() * @see GroupsController::getEdit()
* @param int $id * @param int $id
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Exception
*/ */
public function destroy($id) : RedirectResponse public function destroy($id)
{ {
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
if (! $group = Group::find($id)) { if (! $group = Group::find($id)) {
@@ -147,9 +154,10 @@ class GroupsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param $id * @param $id
* @return \Illuminate\Contracts\View\View
* @since [v4.0.11] * @since [v4.0.11]
*/ */
public function show($id) : View | RedirectResponse public function show($id)
{ {
$group = Group::find($id); $group = Group::find($id);
+2 -5
View File
@@ -5,13 +5,10 @@ namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
/** /**
* This controller provide the health route for * This controller provide the healthz route for
* the Snipe-IT Asset Management application. * the Snipe-IT Asset Management application.
* *
* @version v1.0 * @version v1.0
*
* @return \Illuminate\Http\JsonResponse
*/ */
class HealthController extends BaseController class HealthController extends BaseController
{ {
@@ -33,7 +33,7 @@ class CheckoutKitController extends Controller
* Show Bulk Checkout Page * Show Bulk Checkout Page
* *
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
* @return \Illuminate\Contracts\View\View View to checkout * @return View View to checkout
*/ */
public function showCheckout($kit_id) public function showCheckout($kit_id)
{ {
@@ -62,10 +62,10 @@ class CheckoutKitController extends Controller
$checkout_result = $this->kitService->checkout($request, $kit, $user); $checkout_result = $this->kitService->checkout($request, $kit, $user);
if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) { if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) {
return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']); return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']);
} }
return redirect()->back()->with('success', trans('admin/kits/general.checkout_success')) return redirect()->back()->with('success', trans('general.checkout_success'))
->with('assets', Arr::get($checkout_result, 'assets', null)) ->with('assets', Arr::get($checkout_result, 'assets', null))
->with('accessories', Arr::get($checkout_result, 'accessories', null)) ->with('accessories', Arr::get($checkout_result, 'accessories', null))
->with('consumables', Arr::get($checkout_result, 'consumables', null)); ->with('consumables', Arr::get($checkout_result, 'consumables', null));
@@ -55,7 +55,6 @@ class PredefinedKitsController extends Controller
// Create a new Predefined Kit // Create a new Predefined Kit
$kit = new PredefinedKit; $kit = new PredefinedKit;
$kit->name = $request->input('name'); $kit->name = $request->input('name');
$kit->created_by = auth()->id();
if (! $kit->save()) { if (! $kit->save()) {
return redirect()->back()->withInput()->withErrors($kit->getErrors()); return redirect()->back()->withInput()->withErrors($kit->getErrors());
@@ -74,7 +73,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @since [v1.0] * @since [v1.0]
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function edit($kit_id = null) public function edit($kit_id = null)
{ {
@@ -151,7 +150,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @since [v1.0] * @since [v1.0]
* @param int $modelId * @param int $modelId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function show($kit_id = null) public function show($kit_id = null)
{ {
@@ -163,7 +162,7 @@ class PredefinedKitsController extends Controller
* *
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function editModel($kit_id, $model_id) public function editModel($kit_id, $model_id)
{ {
@@ -185,7 +184,7 @@ class PredefinedKitsController extends Controller
* *
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $modelId * @param int $modelId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function updateModel(Request $request, $kit_id, $model_id) public function updateModel(Request $request, $kit_id, $model_id)
{ {
@@ -215,7 +214,7 @@ class PredefinedKitsController extends Controller
* *
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $modelId * @param int $modelId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function detachModel($kit_id, $model_id) public function detachModel($kit_id, $model_id)
{ {
@@ -238,7 +237,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $license_id * @param int $license_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function editLicense($kit_id, $license_id) public function editLicense($kit_id, $license_id)
{ {
@@ -263,7 +262,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $license_id * @param int $license_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function updateLicense(Request $request, $kit_id, $license_id) public function updateLicense(Request $request, $kit_id, $license_id)
{ {
@@ -294,7 +293,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $license_id * @param int $license_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function detachLicense($kit_id, $license_id) public function detachLicense($kit_id, $license_id)
{ {
@@ -317,7 +316,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $accessoryId * @param int $accessoryId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function editAccessory($kit_id, $accessory_id) public function editAccessory($kit_id, $accessory_id)
{ {
@@ -342,7 +341,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $accessory_id * @param int $accessory_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function updateAccessory(Request $request, $kit_id, $accessory_id) public function updateAccessory(Request $request, $kit_id, $accessory_id)
{ {
@@ -372,7 +371,7 @@ class PredefinedKitsController extends Controller
* *
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $accessory_id * @param int $accessory_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function detachAccessory($kit_id, $accessory_id) public function detachAccessory($kit_id, $accessory_id)
{ {
@@ -395,7 +394,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $consumable_id * @param int $consumable_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function editConsumable($kit_id, $consumable_id) public function editConsumable($kit_id, $consumable_id)
{ {
@@ -420,7 +419,7 @@ class PredefinedKitsController extends Controller
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $kit_id * @param int $kit_id
* @param int $consumableId * @param int $consumableId
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function updateConsumable(Request $request, $kit_id, $consumable_id) public function updateConsumable(Request $request, $kit_id, $consumable_id)
{ {
@@ -450,7 +449,7 @@ class PredefinedKitsController extends Controller
* *
* @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>] * @author [D. Minaev] [<dmitriy.minaev.v@gmail.com>]
* @param int $consumable_id * @param int $consumable_id
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function detachConsumable($kit_id, $consumable_id) public function detachConsumable($kit_id, $consumable_id)
{ {
@@ -14,6 +14,8 @@ use App\Models\Setting;
use App\Models\Supplier; use App\Models\Supplier;
use App\Models\User; use App\Models\User;
use App\View\Label as LabelView; use App\View\Label as LabelView;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class LabelsController extends Controller class LabelsController extends Controller
{ {
@@ -21,6 +23,7 @@ class LabelsController extends Controller
* Returns the Label view with test data * Returns the Label view with test data
* *
* @param string $labelName * @param string $labelName
* @return \Illuminate\Contracts\View\View
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com> * @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
*/ */
public function show(string $labelName) public function show(string $labelName)
@@ -93,5 +96,6 @@ class LabelsController extends Controller
->with('bulkedit', false) ->with('bulkedit', false)
->with('count', 0); ->with('count', 0);
return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist'));
} }
} }
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Licenses; namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedIn; use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\License; use App\Models\License;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
@@ -101,15 +100,15 @@ class LicenseCheckinController extends Controller
$licenseSeat->asset_id = null; $licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes'); $licenseSeat->notes = $request->input('notes');
session()->put(['redirect_option' => $request->get('redirect_option')]);
// Was the asset updated? // Was the asset updated?
if ($licenseSeat->save()) { if ($licenseSeat->save()) {
event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes'))); event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes')));
if ($backTo == 'user') {
return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success')); return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success'));
} }
// Redirect to the license page with error // Redirect to the license page with error
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Licenses; namespace App\Http\Controllers\Licenses;
use App\Events\CheckoutableCheckedOut; use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\LicenseCheckoutRequest; use App\Http\Requests\LicenseCheckoutRequest;
use App\Models\Accessory; use App\Models\Accessory;
@@ -77,32 +76,15 @@ class LicenseCheckoutController extends Controller
$this->authorize('checkout', $license); $this->authorize('checkout', $license);
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId); $licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
$licenseSeat->created_by = auth()->id(); $licenseSeat->user_id = Auth::id();
$licenseSeat->notes = $request->input('notes'); $licenseSeat->notes = $request->input('notes');
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type')); $checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));
if ($this->$checkoutMethod($licenseSeat)) {
if ($request->filled('asset_id')) { return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success'));
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
} elseif ($request->filled('assigned_to')) {
$checkoutTarget = $this->checkoutToUser($licenseSeat);
$request->request->add(['assigned_user' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
} }
if ($checkoutTarget) {
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success'));
}
return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.')); return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.'));
} }
@@ -112,14 +94,14 @@ class LicenseCheckoutController extends Controller
if (! $licenseSeat) { if (! $licenseSeat) {
if ($seatId) { if ($seatId) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable'))); throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.'));
} }
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'))); throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.'));
} }
if (! $licenseSeat->license->is($license)) { if (! $licenseSeat->license->is($license)) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch'))); throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.'));
} }
return $licenseSeat; return $licenseSeat;
@@ -137,8 +119,9 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = $target->assigned_to; $licenseSeat->assigned_to = $target->assigned_to;
} }
if ($licenseSeat->save()) { if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return $target;
return true;
} }
return false; return false;
@@ -153,8 +136,9 @@ class LicenseCheckoutController extends Controller
$licenseSeat->assigned_to = request('assigned_to'); $licenseSeat->assigned_to = request('assigned_to');
if ($licenseSeat->save()) { if ($licenseSeat->save()) {
event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes')));
return $target;
return true;
} }
return false; return false;
@@ -99,13 +99,11 @@ class LicensesController extends Controller
$license->supplier_id = $request->input('supplier_id'); $license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id'); $license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date'); $license->termination_date = $request->input('termination_date');
$license->created_by = auth()->id(); $license->user_id = Auth::id();
$license->min_amt = $request->input('min_amt'); $license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) { if ($license->save()) {
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success')); return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
} }
return redirect()->back()->withInput()->withErrors($license->getErrors()); return redirect()->back()->withInput()->withErrors($license->getErrors());
@@ -182,10 +180,8 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id'); $license->category_id = $request->input('category_id');
$license->min_amt = $request->input('min_amt'); $license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($license->save()) { if ($license->save()) {
return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success')); return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));
} }
// If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php // If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php
return redirect()->back()->withInput()->withErrors($license->getErrors()); return redirect()->back()->withInput()->withErrors($license->getErrors());
+32 -60
View File
@@ -3,15 +3,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Location; use App\Models\Location;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Locations for * This controller handles all actions related to Locations for
* the Snipe-IT Asset Management application. * the Snipe-IT Asset Management application.
@@ -27,8 +26,10 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::getDatatable() method that generates the JSON response * @see LocationsController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
// Grab all the locations // Grab all the locations
$this->authorize('view', Location::class); $this->authorize('view', Location::class);
@@ -42,8 +43,10 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see LocationsController::postCreate() method that validates and stores the data * @see LocationsController::postCreate() method that validates and stores the data
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Location::class); $this->authorize('create', Location::class);
@@ -59,8 +62,10 @@ class LocationsController extends Controller
* @see LocationsController::getCreate() method that makes the form * @see LocationsController::getCreate() method that makes the form
* @since [v1.0] * @since [v1.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Location::class); $this->authorize('create', Location::class);
$location = new Location(); $location = new Location();
@@ -75,7 +80,7 @@ class LocationsController extends Controller
$location->zip = $request->input('zip'); $location->zip = $request->input('zip');
$location->ldap_ou = $request->input('ldap_ou'); $location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id'); $location->manager_id = $request->input('manager_id');
$location->created_by = auth()->id(); $location->user_id = Auth::id();
$location->phone = request('phone'); $location->phone = request('phone');
$location->fax = request('fax'); $location->fax = request('fax');
@@ -95,8 +100,10 @@ class LocationsController extends Controller
* @see LocationsController::postCreate() method that validates and stores * @see LocationsController::postCreate() method that validates and stores
* @param int $locationId * @param int $locationId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($locationId = null) : View | RedirectResponse public function edit($locationId = null)
{ {
$this->authorize('update', Location::class); $this->authorize('update', Location::class);
// Check if the location exists // Check if the location exists
@@ -114,9 +121,11 @@ class LocationsController extends Controller
* @see LocationsController::getEdit() method that makes the form view * @see LocationsController::getEdit() method that makes the form view
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @param int $locationId * @param int $locationId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.0] * @since [v1.0]
*/ */
public function update(ImageUploadRequest $request, $locationId = null) : RedirectResponse public function update(ImageUploadRequest $request, $locationId = null)
{ {
$this->authorize('update', Location::class); $this->authorize('update', Location::class);
// Check if the location exists // Check if the location exists
@@ -154,8 +163,10 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId * @param int $locationId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($locationId) : RedirectResponse public function destroy($locationId)
{ {
$this->authorize('delete', Location::class); $this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) { if (is_null($location = Location::find($locationId))) {
@@ -191,16 +202,11 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id * @param int $id
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/ */
public function show($id = null) : View | RedirectResponse public function show($id = null)
{ {
$location = Location::withCount('assignedAssets as assigned_assets_count') $location = Location::find($id);
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
->withCount('children as children_count')
->withCount('users as users_count')
->withTrashed()
->find($id);
if (isset($location->id)) { if (isset($location->id)) {
return view('locations/view', compact('location')); return view('locations/view', compact('location'));
@@ -209,7 +215,7 @@ class LocationsController extends Controller
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
} }
public function print_assigned($id) : View | RedirectResponse public function print_assigned($id)
{ {
if ($location = Location::where('id', $id)->first()) { if ($location = Location::where('id', $id)->first()) {
@@ -234,8 +240,9 @@ class LocationsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId * @param int $locationId
* @since [v6.0.14] * @since [v6.0.14]
* @return \Illuminate\Contracts\View\View
*/ */
public function getClone($locationId = null) : View | RedirectResponse public function getClone($locationId = null)
{ {
$this->authorize('create', Location::class); $this->authorize('create', Location::class);
@@ -256,42 +263,7 @@ class LocationsController extends Controller
} }
/** public function print_all_assigned($id)
* Restore a given Asset Model (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $id
*/
public function postRestore($id) : RedirectResponse
{
$this->authorize('create', Location::class);
if ($location = Location::withTrashed()->find($id)) {
if ($location->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
}
if ($location->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Location::class;
$logaction->item_id = $location->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id();
$logaction->logaction('restore');
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
}
// Check validation
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
}
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}
public function print_all_assigned($id) : View | RedirectResponse
{ {
if ($location = Location::where('id', $id)->first()) { if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first(); $parent = Location::where('id', $location->parent_id)->first();
@@ -310,8 +282,9 @@ class LocationsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.1] * @since [v6.3.1]
* @return \Illuminate\Contracts\View\View
*/ */
public function postBulkDelete(Request $request) : View | RedirectResponse public function postBulkDelete(Request $request)
{ {
$locations_raw_array = $request->input('ids'); $locations_raw_array = $request->input('ids');
@@ -342,10 +315,9 @@ class LocationsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.3.1] * @since [v6.3.1]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postBulkDeleteStore(Request $request) : RedirectResponse public function postBulkDeleteStore(Request $request) {
{
$locations_raw_array = $request->input('ids'); $locations_raw_array = $request->input('ids');
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) { if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
@@ -2,15 +2,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Manufacturer; use App\Models\Manufacturer;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Redirect;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to Manufacturers for * This controller handles all actions related to Manufacturers for
@@ -27,10 +29,13 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see Api\ManufacturersController::index() method that generates the JSON response * @see Api\ManufacturersController::index() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function index() : View public function index()
{ {
$this->authorize('index', Manufacturer::class); $this->authorize('index', Manufacturer::class);
return view('manufacturers/index'); return view('manufacturers/index');
} }
@@ -40,8 +45,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::store() * @see ManufacturersController::store()
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function create() : View public function create()
{ {
$this->authorize('create', Manufacturer::class); $this->authorize('create', Manufacturer::class);
@@ -55,13 +62,15 @@ class ManufacturersController extends Controller
* @see ManufacturersController::create() * @see ManufacturersController::create()
* @since [v1.0] * @since [v1.0]
* @param ImageUploadRequest $request * @param ImageUploadRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function store(ImageUploadRequest $request) : RedirectResponse public function store(ImageUploadRequest $request)
{ {
$this->authorize('create', Manufacturer::class); $this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer; $manufacturer = new Manufacturer;
$manufacturer->name = $request->input('name'); $manufacturer->name = $request->input('name');
$manufacturer->created_by = auth()->id(); $manufacturer->user_id = Auth::id();
$manufacturer->url = $request->input('url'); $manufacturer->url = $request->input('url');
$manufacturer->support_url = $request->input('support_url'); $manufacturer->support_url = $request->input('support_url');
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
@@ -83,8 +92,10 @@ class ManufacturersController extends Controller
* @see ManufacturersController::update() * @see ManufacturersController::update()
* @param int $manufacturerId * @param int $manufacturerId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function edit($manufacturerId = null) : View | RedirectResponse public function edit($manufacturerId = null)
{ {
// Handles manufacturer checks and permissions. // Handles manufacturer checks and permissions.
$this->authorize('update', Manufacturer::class); $this->authorize('update', Manufacturer::class);
@@ -105,9 +116,11 @@ class ManufacturersController extends Controller
* @see ManufacturersController::getEdit() * @see ManufacturersController::getEdit()
* @param Request $request * @param Request $request
* @param int $manufacturerId * @param int $manufacturerId
* @return \Illuminate\Http\RedirectResponse
* @since [v1.0] * @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function update(ImageUploadRequest $request, $manufacturerId = null) : RedirectResponse public function update(ImageUploadRequest $request, $manufacturerId = null)
{ {
$this->authorize('update', Manufacturer::class); $this->authorize('update', Manufacturer::class);
// Check if the manufacturer exists // Check if the manufacturer exists
@@ -144,8 +157,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId * @param int $manufacturerId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function destroy($manufacturerId) : RedirectResponse public function destroy($manufacturerId)
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) { if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) {
@@ -182,8 +197,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $manufacturerId * @param int $manufacturerId
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function show($manufacturerId = null) : View | RedirectResponse public function show($manufacturerId = null)
{ {
$this->authorize('view', Manufacturer::class); $this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::find($manufacturerId); $manufacturer = Manufacturer::find($manufacturerId);
@@ -203,8 +220,10 @@ class ManufacturersController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.1.15] * @since [v4.1.15]
* @param int $manufacturers_id * @param int $manufacturers_id
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/ */
public function restore($id) : RedirectResponse public function restore($id)
{ {
$this->authorize('delete', Manufacturer::class); $this->authorize('delete', Manufacturer::class);
@@ -219,7 +238,7 @@ class ManufacturersController extends Controller
$logaction->item_type = Manufacturer::class; $logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id; $logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id(); $logaction->user_id = Auth::user()->id;
$logaction->logaction('restore'); $logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index // Redirect them to the deleted page if there are more, otherwise the section index
+1 -1
View File
@@ -15,7 +15,7 @@ class ModalController extends Controller
* @version v5.3.7-pre * @version v5.3.7-pre
* @author [Brady Wetherington] [<uberbrady@gmail.com>] * @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @author [A. Gianotto] [<snipe@snipe.net] * @author [A. Gianotto] [<snipe@snipe.net]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function show ($type, $itemId = null) { public function show ($type, $itemId = null) {
+25 -19
View File
@@ -10,8 +10,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
/** /**
* This controller handles all actions related to User Profiles for * This controller handles all actions related to User Profiles for
* the Snipe-IT Asset Management application. * the Snipe-IT Asset Management application.
@@ -25,11 +24,12 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View
*/ */
public function getIndex() : View public function getIndex()
{ {
$this->authorize('self.profile'); $this->authorize('self.profile');
$user = auth()->user(); $user = Auth::user();
return view('account/profile', compact('user')); return view('account/profile', compact('user'));
} }
@@ -38,19 +38,18 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postIndex(ImageUploadRequest $request) : RedirectResponse public function postIndex(ImageUploadRequest $request)
{ {
$this->authorize('self.profile'); $this->authorize('self.profile');
$user = auth()->user(); $user = Auth::user();
$user->first_name = $request->input('first_name'); $user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name'); $user->last_name = $request->input('last_name');
$user->website = $request->input('website'); $user->website = $request->input('website');
$user->gravatar = $request->input('gravatar'); $user->gravatar = $request->input('gravatar');
$user->skin = $request->input('skin'); $user->skin = $request->input('skin');
$user->phone = $request->input('phone'); $user->phone = $request->input('phone');
$user->enable_sounds = $request->input('enable_sounds', false);
$user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
$user->locale = $request->input('locale', 'en-US'); $user->locale = $request->input('locale', 'en-US');
@@ -85,7 +84,7 @@ class ProfileController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
*/ */
public function api(): View public function api(): \Illuminate\Contracts\View\View
{ {
// Make sure the self.api permission has been granted // Make sure the self.api permission has been granted
if (!Gate::allows('self.api')) { if (!Gate::allows('self.api')) {
@@ -98,23 +97,27 @@ class ProfileController extends Controller
/** /**
* User change email page. * User change email page.
* *
* @return View
*/ */
public function password() : View public function password()
{ {
$user = auth()->user(); $user = Auth::user();
return view('account/change-password', compact('user')); return view('account/change-password', compact('user'));
} }
/** /**
* Users change password form processing page. * Users change password form processing page.
*
* @return \Illuminate\Http\RedirectResponse
*/ */
public function passwordSave(Request $request) : RedirectResponse public function passwordSave(Request $request)
{ {
if (config('app.lock_passwords')) { if (config('app.lock_passwords')) {
return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords')); return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords'));
} }
$user = auth()->user(); $user = Auth::user();
if ($user->ldap_import == '1') { if ($user->ldap_import == '1') {
return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap')); return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap'));
} }
@@ -175,8 +178,9 @@ class ProfileController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return View
*/ */
public function getMenuState(Request $request) : void public function getMenuState(Request $request)
{ {
if ($request->input('state') == 'open') { if ($request->input('state') == 'open') {
$request->session()->put('menu_state', 'open'); $request->session()->put('menu_state', 'open');
@@ -191,13 +195,14 @@ class ProfileController extends Controller
* *
* @author A. Gianotto * @author A. Gianotto
* @since [v6.0.12] * @since [v6.0.12]
* @return Illuminate\View\View
*/ */
public function printInventory() : View public function printInventory()
{ {
$show_user = auth()->user(); $show_user = Auth::user();
return view('users/print') return view('users/print')
->with('assets', auth()->user()->assets) ->with('assets', Auth::user()->assets)
->with('licenses', $show_user->licenses()->get()) ->with('licenses', $show_user->licenses()->get())
->with('accessories', $show_user->accessories()->get()) ->with('accessories', $show_user->accessories()->get())
->with('consumables', $show_user->consumables()->get()) ->with('consumables', $show_user->consumables()->get())
@@ -210,11 +215,12 @@ class ProfileController extends Controller
* *
* @author A. Gianotto * @author A. Gianotto
* @since [v6.0.12] * @since [v6.0.12]
* @return \Illuminate\Http\RedirectResponse
*/ */
public function emailAssetList() : RedirectResponse public function emailAssetList()
{ {
if (!$user = User::find(auth()->id())) { if (!$user = User::find(Auth::user()->id)) {
return redirect()->back() return redirect()->back()
->with('error', trans('admin/users/message.user_not_found', ['id' => $id])); ->with('error', trans('admin/users/message.user_not_found', ['id' => $id]));
} }
+61 -45
View File
@@ -6,8 +6,6 @@ use App\Helpers\Helper;
use App\Models\Accessory; use App\Models\Accessory;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\AssetMaintenance; use App\Models\AssetMaintenance;
use App\Models\CheckoutAcceptance; use App\Models\CheckoutAcceptance;
use App\Models\CustomField; use App\Models\CustomField;
@@ -16,16 +14,17 @@ use App\Models\License;
use App\Models\Setting; use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification; use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
use \Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\View;
use League\Csv\Reader; use League\Csv\Reader;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
use League\Csv\EscapeFormula; use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest; use App\Http\Requests\CustomAssetReportRequest;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Http\RedirectResponse;
/** /**
* This controller handles all actions related to Reports for * This controller handles all actions related to Reports for
@@ -48,9 +47,9 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Contracts\View\View * @return View
*/ */
public function getAccessoryReport() : View public function getAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -66,7 +65,7 @@ class ReportsController extends Controller
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function exportAccessoryReport() : Response public function exportAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$accessories = Accessory::orderBy('created_at', 'DESC')->get(); $accessories = Accessory::orderBy('created_at', 'DESC')->get();
@@ -93,7 +92,7 @@ class ReportsController extends Controller
} }
$csv = implode("\n", $rows); $csv = implode("\n", $rows);
$response = response()->make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -105,8 +104,9 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
*/ */
public function getDeprecationReport() : View public function getDeprecationReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$depreciations = Depreciation::get(); $depreciations = Depreciation::get();
@@ -119,8 +119,9 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2. * @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response
*/ */
public function exportDeprecationReport() : Response public function exportDeprecationReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
// Grab all the assets // Grab all the assets
@@ -196,10 +197,12 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0] * @since [v4.0]
* @return View
*/ */
public function audit() : View public function audit()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
return view('reports/audit'); return view('reports/audit');
} }
@@ -209,8 +212,9 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
*/ */
public function getActivityReport() : View public function getActivityReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -222,8 +226,9 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.0.7] * @since [v5.0.7]
* @return \Illuminate\Http\Response
*/ */
public function postActivityReport(Request $request) : StreamedResponse public function postActivityReport(Request $request)
{ {
ini_set('max_execution_time', 12000); ini_set('max_execution_time', 12000);
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -323,8 +328,9 @@ class ReportsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View
*/ */
public function getLicenseReport() : View public function getLicenseReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$licenses = License::with('depreciation')->orderBy('created_at', 'DESC') $licenses = License::with('depreciation')->orderBy('created_at', 'DESC')
@@ -340,8 +346,9 @@ class ReportsController extends Controller
* @deprecated Server-side exports have been replaced by datatables export since v2. * @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response
*/ */
public function exportLicenseReport() : Response public function exportLicenseReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$licenses = License::orderBy('created_at', 'DESC')->get(); $licenses = License::orderBy('created_at', 'DESC')->get();
@@ -378,7 +385,7 @@ class ReportsController extends Controller
$csv = implode("\n", $rows); $csv = implode("\n", $rows);
$response = response()->make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -391,8 +398,9 @@ class ReportsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see ReportsController::postCustomReport() method that generates the CSV * @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response
*/ */
public function getCustomReport() : View public function getCustomReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$customfields = CustomField::get(); $customfields = CustomField::get();
@@ -406,8 +414,9 @@ class ReportsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see ReportsController::getCustomReport() method that generates form view * @see ReportsController::getCustomReport() method that generates form view
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response
*/ */
public function postCustom(CustomAssetReportRequest $request) : StreamedResponse public function postCustom(CustomAssetReportRequest $request)
{ {
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -672,15 +681,15 @@ class ReportsController extends Controller
} }
if (($request->filled('created_start')) && ($request->filled('created_end'))) { if (($request->filled('created_start')) && ($request->filled('created_end'))) {
$created_start = Carbon::parse($request->input('created_start'))->startOfDay(); $created_start = \Carbon::parse($request->input('created_start'))->startOfDay();
$created_end = Carbon::parse($request->input('created_end'))->endOfDay(); $created_end = \Carbon::parse($request->input('created_end'))->endOfDay();
$assets->whereBetween('assets.created_at', [$created_start, $created_end]); $assets->whereBetween('assets.created_at', [$created_start, $created_end]);
} }
if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) { if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) {
$checkout_start = Carbon::parse($request->input('checkout_date_start'))->startOfDay(); $checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay();
$checkout_end = Carbon::parse($request->input('checkout_date_end',now()))->endOfDay(); $checkout_end = \Carbon::parse($request->input('checkout_date_end',now()))->endOfDay();
$actionlogassets = Actionlog::where('action_type','=', 'checkout') $actionlogassets = Actionlog::where('action_type','=', 'checkout')
->where('item_type', 'LIKE', '%Asset%',) ->where('item_type', 'LIKE', '%Asset%',)
@@ -691,9 +700,9 @@ class ReportsController extends Controller
} }
if (($request->filled('checkin_date_start'))) { if (($request->filled('checkin_date_start'))) {
$checkin_start = Carbon::parse($request->input('checkin_date_start'))->startOfDay(); $checkin_start = \Carbon::parse($request->input('checkin_date_start'))->startOfDay();
// use today's date is `checkin_date_end` is not provided // use today's date is `checkin_date_end` is not provided
$checkin_end = Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(); $checkin_end = \Carbon::parse($request->input('checkin_date_end', now()))->endOfDay();
$assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]); $assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]);
} }
@@ -703,13 +712,9 @@ class ReportsController extends Controller
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
} }
if (($request->filled('asset_eol_date_start')) && ($request->filled('asset_eol_date_end'))) {
$assets->whereBetween('assets.asset_eol_date', [$request->input('asset_eol_date_start'), $request->input('asset_eol_date_end')]);
}
if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) { if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) {
$last_audit_start = Carbon::parse($request->input('last_audit_start'))->startOfDay(); $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay();
$last_audit_end = Carbon::parse($request->input('last_audit_end'))->endOfDay(); $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay();
$assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]); $assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]);
} }
@@ -782,7 +787,7 @@ class ReportsController extends Controller
} }
if ($request->filled('eol')) { if ($request->filled('eol')) {
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : ''; $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : '';
} }
if ($request->filled('order')) { if ($request->filled('order')) {
@@ -1010,10 +1015,11 @@ class ReportsController extends Controller
/** /**
* getImprovementsReport * getImprovementsReport
* *
* @return View
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function getAssetMaintenancesReport() : View public function getAssetMaintenancesReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -1023,10 +1029,11 @@ class ReportsController extends Controller
/** /**
* exportImprovementsReport * exportImprovementsReport
* *
* @return \Illuminate\Http\Response
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function exportAssetMaintenancesReport() : Response public function exportAssetMaintenancesReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
// Grab all the improvements // Grab all the improvements
@@ -1073,7 +1080,7 @@ class ReportsController extends Controller
// spit out a csv // spit out a csv
$csv = implode("\n", $rows); $csv = implode("\n", $rows);
$response = response()->make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -1083,10 +1090,13 @@ class ReportsController extends Controller
/** /**
* getAssetAcceptanceReport * getAssetAcceptanceReport
* *
* @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function getAssetAcceptanceReport($deleted = false) : View public function getAssetAcceptanceReport($deleted = false)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$showDeleted = $deleted == 'deleted'; $showDeleted = $deleted == 'deleted';
@@ -1122,9 +1132,11 @@ class ReportsController extends Controller
* sentAssetAcceptanceReminder * sentAssetAcceptanceReminder
* *
* @param integer|null $acceptanceId * @param integer|null $acceptanceId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0 * @version v1.0
*/ */
public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse public function sentAssetAcceptanceReminder(Request $request)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -1178,9 +1190,11 @@ class ReportsController extends Controller
* sentAssetAcceptanceReminder * sentAssetAcceptanceReminder
* *
* @param integer|null $acceptanceId * @param integer|null $acceptanceId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0 * @version v1.0
*/ */
public function deleteAssetAcceptance($acceptanceId = null) : RedirectResponse public function deleteAssetAcceptance($acceptanceId = null)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -1199,10 +1213,11 @@ class ReportsController extends Controller
/** /**
* Exports the AssetAcceptance report to CSV * Exports the AssetAcceptance report to CSV
* *
* @return \Illuminate\Http\Response
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function postAssetAcceptanceReport($deleted = false) : Response public function postAssetAcceptanceReport($deleted = false)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$showDeleted = $deleted == 'deleted'; $showDeleted = $deleted == 'deleted';
@@ -1253,7 +1268,7 @@ class ReportsController extends Controller
// spit out a csv // spit out a csv
$csv = implode("\n", $rows); $csv = implode("\n", $rows);
$response = response()->make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@@ -1269,7 +1284,7 @@ class ReportsController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance) : View protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$assets = Asset::deployed() $assets = Asset::deployed()
@@ -1289,11 +1304,11 @@ class ReportsController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance) : array protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
return array_pluck(AssetModel::inCategory($assetCategoriesRequiringAcceptance) return array_pluck(Model::inCategory($assetCategoriesRequiringAcceptance)
->select('id') ->select('id')
->get() ->get()
->toArray(), 'id'); ->toArray(), 'id');
@@ -1306,7 +1321,7 @@ class ReportsController extends Controller
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
protected function getCategoriesThatRequireAcceptance() : array protected function getCategoriesThatRequireAcceptance()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@@ -1319,10 +1334,11 @@ class ReportsController extends Controller
/** /**
* getAssetsCheckedOutRequiringAcceptance * getAssetsCheckedOutRequiringAcceptance
* *
* @return array
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
protected function getAssetsCheckedOutRequiringAcceptance() : array protected function getAssetsCheckedOutRequiringAcceptance()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
+179 -112
View File
@@ -14,13 +14,12 @@ use App\Models\Asset;
use App\Models\User; use App\Models\User;
use App\Notifications\FirstAdminNotification; use App\Notifications\FirstAdminNotification;
use App\Notifications\MailTest; use App\Notifications\MailTest;
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Http\RedirectResponse; use Redirect;
use Illuminate\Http\JsonResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -31,7 +30,6 @@ use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/** /**
* This controller handles all actions related to Settings for * This controller handles all actions related to Settings for
@@ -49,9 +47,9 @@ class SettingsController extends Controller
* *
* @since [v3.0] * @since [v3.0]
* *
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\Response * @return View
*/ */
public function getSetupIndex() : View public function getSetupIndex()
{ {
$start_settings['php_version_min'] = false; $start_settings['php_version_min'] = false;
@@ -125,14 +123,14 @@ class SettingsController extends Controller
* @return bool This method will return true when exceptions (such as curl exception) is thrown. * @return bool This method will return true when exceptions (such as curl exception) is thrown.
* Check the log files to see more details about the exception. * Check the log files to see more details about the exception.
*/ */
protected function dotEnvFileIsExposed() : bool protected function dotEnvFileIsExposed()
{ {
try { try {
return Http::withoutVerifying()->timeout(10) return Http::timeout(10)
->accept('*/*') ->accept('*/*')
->get(URL::to('.env')) ->get(URL::to('.env'))
->successful(); ->successful();
} catch (\Exception $e) { } catch (HttpClientException $e) {
Log::debug($e->getMessage()); Log::debug($e->getMessage());
return true; return true;
} }
@@ -155,12 +153,13 @@ class SettingsController extends Controller
* Save the first admin user from Setup. * Save the first admin user from Setup.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0] * @since [v3.0]
* *
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postSaveFirstAdmin(SetupUserRequest $request) : RedirectResponse public function postSaveFirstAdmin(SetupUserRequest $request)
{ {
$user = new User(); $user = new User();
$user->first_name = $data['first_name'] = $request->input('first_name'); $user->first_name = $data['first_name'] = $request->input('first_name');
$user->last_name = $request->input('last_name'); $user->last_name = $request->input('last_name');
@@ -181,7 +180,7 @@ class SettingsController extends Controller
$settings->brand = 1; $settings->brand = 1;
$settings->locale = $request->input('locale', 'en-US'); $settings->locale = $request->input('locale', 'en-US');
$settings->default_currency = $request->input('default_currency', 'USD'); $settings->default_currency = $request->input('default_currency', 'USD');
$settings->created_by = 1; $settings->user_id = 1;
$settings->email_domain = $request->input('email_domain'); $settings->email_domain = $request->input('email_domain');
$settings->email_format = $request->input('email_format'); $settings->email_format = $request->input('email_format');
$settings->next_auto_tag_base = 1; $settings->next_auto_tag_base = 1;
@@ -215,8 +214,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v3.0] * @since [v3.0]
*
* @return View
*/ */
public function getSetupUser() : View public function getSetupUser()
{ {
return view('setup/user') return view('setup/user')
->with('step', 3) ->with('step', 3)
@@ -229,8 +230,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v3.0] * @since [v3.0]
*
* @return View
*/ */
public function getSetupDone() : View public function getSetupDone()
{ {
return view('setup/done') return view('setup/done')
->with('step', 4) ->with('step', 4)
@@ -244,8 +247,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v3.0] * @since [v3.0]
*
* @return View
*/ */
public function getSetupMigrate() : View public function getSetupMigrate()
{ {
Artisan::call('migrate', ['--force' => true]); Artisan::call('migrate', ['--force' => true]);
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) { if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
@@ -265,8 +270,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function index() : View public function index()
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
@@ -279,9 +286,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getEdit() : View public function getEdit()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -294,8 +302,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getSettings() : View public function getSettings()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -308,9 +318,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postSettings(Request $request) : RedirectResponse public function postSettings(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -324,7 +335,6 @@ class SettingsController extends Controller
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0'); $setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->unique_serial = $request->input('unique_serial', '0'); $setting->unique_serial = $request->input('unique_serial', '0');
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '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->show_archived_in_list = $request->input('show_archived_in_list', '0');
$setting->dashboard_message = $request->input('dashboard_message'); $setting->dashboard_message = $request->input('dashboard_message');
@@ -365,8 +375,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getBranding() : View public function getBranding()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -379,8 +391,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse
*/ */
public function postBranding(ImageUploadRequest $request) : RedirectResponse public function postBranding(ImageUploadRequest $request)
{ {
// Something has gone horribly wrong - no settings record exists! // Something has gone horribly wrong - no settings record exists!
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
@@ -414,7 +428,10 @@ class SettingsController extends Controller
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo'); $setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
if ($request->input('clear_logo') == '1') { if ($request->input('clear_logo') == '1') {
$setting = $request->deleteExistingImage($setting, '', 'logo');
if (($setting->logo) && (Storage::exists($setting->logo))) {
Storage::disk('public')->delete($setting->logo);
}
$setting->logo = null; $setting->logo = null;
$setting->brand = 1; $setting->brand = 1;
} }
@@ -422,38 +439,43 @@ class SettingsController extends Controller
// Email logo upload // Email logo upload
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo'); $setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
if ($request->input('clear_email_logo') == '1') { if ($request->input('clear_email_logo') == '1') {
$setting = $request->deleteExistingImage($setting, '', 'email_logo');
if (($setting->email_logo) && (Storage::exists($setting->email_logo))) {
Storage::disk('public')->delete($setting->email_logo);
}
$setting->email_logo = null; $setting->email_logo = null;
// If they are uploading an image, validate it and upload it
} }
// Label logo upload // Label logo upload
$setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo'); $setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo');
if ($request->input('clear_label_logo') == '1') { if ($request->input('clear_label_logo') == '1') {
$setting = $request->deleteExistingImage($setting, '', 'label_logo');
if (($setting->label_logo) && (Storage::exists($setting->label_logo))) {
Storage::disk('public')->delete($setting->label_logo);
}
$setting->label_logo = null; $setting->label_logo = null;
} }
// Favicon upload // Favicon upload
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon'); $setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
if ('1' == $request->input('clear_favicon')) { if ('1' == $request->input('clear_favicon')) {
$setting = $request->deleteExistingImage($setting, '', 'favicon');
if (($setting->favicon) && (Storage::exists($setting->favicon))) {
Storage::disk('public')->delete($setting->favicon);
}
$setting->favicon = null; $setting->favicon = null;
} }
// Default avatar upload // Default avatar upload
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar'); $setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
if ($request->input('clear_default_avatar') == '1') { if ($request->input('clear_default_avatar') == '1') {
// Don't delete the file, just update the field if this is the default
if ($setting->default_avatar!='default.png') { if (($setting->default_avatar) && (Storage::exists('avatars/'.$setting->default_avatar))) {
$setting = $request->deleteExistingImage($setting, 'avatars', 'default_avatar'); Storage::disk('public')->delete('avatars/'.$setting->default_avatar);
} }
$setting->default_avatar = null; $setting->default_avatar = null;
} }
if ($request->input('restore_default_avatar') == '1') {
$setting->default_avatar = 'default.png';
}
} }
if ($setting->save()) { if ($setting->save()) {
@@ -471,8 +493,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getSecurity() : View public function getSecurity()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -485,8 +509,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postSecurity(Request $request) : RedirectResponse public function postSecurity(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'pwd_secure_complexity' => 'array', 'pwd_secure_complexity' => 'array',
@@ -541,8 +567,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getLocalization() : View public function getLocalization()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -555,8 +583,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postLocalization(Request $request) : RedirectResponse public function postLocalization(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -585,8 +615,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getAlerts() : View public function getAlerts()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -597,9 +629,12 @@ class SettingsController extends Controller
* Return a form to allow a super admin to update settings. * Return a form to allow a super admin to update settings.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postAlerts(Request $request) : RedirectResponse public function postAlerts(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -637,7 +672,6 @@ class SettingsController extends Controller
$setting->alert_threshold = $request->input('alert_threshold'); $setting->alert_threshold = $request->input('alert_threshold');
$setting->audit_interval = $request->input('audit_interval'); $setting->audit_interval = $request->input('audit_interval');
$setting->audit_warning_days = $request->input('audit_warning_days'); $setting->audit_warning_days = $request->input('audit_warning_days');
$setting->due_checkin_days = $request->input('due_checkin_days');
$setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0'); $setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0');
if ($setting->save()) { if ($setting->save()) {
@@ -654,8 +688,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getSlack() : View public function getSlack()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -668,8 +704,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getAssetTags() : View public function getAssetTags()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -682,8 +720,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postAssetTags(Request $request) : RedirectResponse public function postAssetTags(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -708,8 +748,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function getBarcodes() : View public function getBarcodes()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
$is_gd_installed = extension_loaded('gd'); $is_gd_installed = extension_loaded('gd');
@@ -723,8 +765,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.0] * @since [v1.0]
*
* @return View
*/ */
public function postBarcodes(Request $request) : RedirectResponse public function postBarcodes(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -750,8 +794,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function getPhpInfo() : View | RedirectResponse public function getPhpInfo()
{ {
if (config('app.debug') === true) { if (config('app.debug') === true) {
return view('settings.phpinfo'); return view('settings.phpinfo');
@@ -765,9 +811,12 @@ class SettingsController extends Controller
* Return a form to allow a super admin to update settings. * Return a form to allow a super admin to update settings.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function getLabels() : View public function getLabels()
{ {
return view('settings.labels') return view('settings.labels')
->with('setting', Setting::getSettings()) ->with('setting', Setting::getSettings())
@@ -778,9 +827,12 @@ class SettingsController extends Controller
* Saves settings from form. * Saves settings from form.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function postLabels(Request $request) : RedirectResponse public function postLabels(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -854,8 +906,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function getLdapSettings() : View public function getLdapSettings()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
$groups = Group::pluck('name', 'id'); $groups = Group::pluck('name', 'id');
@@ -885,9 +939,12 @@ class SettingsController extends Controller
* Saves settings from form. * Saves settings from form.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function postLdapSettings(Request $request) : RedirectResponse public function postLdapSettings(Request $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -941,11 +998,15 @@ class SettingsController extends Controller
* Return a form to allow a super admin to update settings. * Return a form to allow a super admin to update settings.
* *
* @author Johnson Yi <jyi.dev@outlook.com> * @author Johnson Yi <jyi.dev@outlook.com>
*
* @since v5.0.0 * @since v5.0.0
*
* @return View
*/ */
public function getSamlSettings() : View public function getSamlSettings()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
return view('settings.saml', compact('setting')); return view('settings.saml', compact('setting'));
} }
@@ -953,9 +1014,12 @@ class SettingsController extends Controller
* Saves settings from form. * Saves settings from form.
* *
* @author Johnson Yi <jyi.dev@outlook.com> * @author Johnson Yi <jyi.dev@outlook.com>
*
* @since v5.0.0 * @since v5.0.0
*
* @return View
*/ */
public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse public function postSamlSettings(SettingsSamlRequest $request)
{ {
if (is_null($setting = Setting::getSettings())) { if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
@@ -984,13 +1048,10 @@ class SettingsController extends Controller
return redirect()->back()->withInput()->withErrors($setting->getErrors()); return redirect()->back()->withInput()->withErrors($setting->getErrors());
} }
public static function getPDFBranding()
/**
* Do we need this? Can we not just call getSettings() directly?
*/
public static function getPDFBranding() : Setting
{ {
$pdf_branding = Setting::getSettings(); $pdf_branding= Setting::getSettings();
return $pdf_branding; return $pdf_branding;
} }
@@ -1000,8 +1061,9 @@ class SettingsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1] * @since [v6.1.1]
* @return View
*/ */
public function getGoogleLoginSettings() : View public function getGoogleLoginSettings()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
return view('settings.google', compact('setting')); return view('settings.google', compact('setting'));
@@ -1012,8 +1074,9 @@ class SettingsController extends Controller
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.1.1] * @since [v6.1.1]
* @return View
*/ */
public function postGoogleLoginSettings(Request $request) : RedirectResponse public function postGoogleLoginSettings(Request $request)
{ {
if (!config('app.lock_passwords')) { if (!config('app.lock_passwords')) {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
@@ -1040,8 +1103,10 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v1.8] * @since [v1.8]
*
* @return View
*/ */
public function getBackups() : View public function getBackups()
{ {
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$path = 'app/backups'; $path = 'app/backups';
@@ -1077,9 +1142,12 @@ class SettingsController extends Controller
* Process the backup. * Process the backup.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.8] * @since [v1.8]
*
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postBackups() : RedirectResponse public function postBackups()
{ {
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H-i-s')]); Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H-i-s')]);
@@ -1108,9 +1176,12 @@ class SettingsController extends Controller
* Download the backup file. * Download the backup file.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.8] * @since [v1.8]
*
* @return Storage
*/ */
public function downloadFile($filename = null) : RedirectResponse | BinaryFileResponse public function downloadFile($filename = null)
{ {
$path = 'app/backups'; $path = 'app/backups';
@@ -1131,9 +1202,12 @@ class SettingsController extends Controller
* Delete the backup file. * Delete the backup file.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v1.8] * @since [v1.8]
*
* @return View
*/ */
public function deleteFile($filename = null) : RedirectResponse public function deleteFile($filename = null)
{ {
if (config('app.allow_backup_delete')=='true') { if (config('app.allow_backup_delete')=='true') {
@@ -1157,7 +1231,7 @@ class SettingsController extends Controller
} }
// Hell to the no // Hell to the no
Log::warning('User ID '.auth()->id().' is attempting to delete backup file '.$filename.' and is not authorized to.'); Log::warning('User ID '.Auth::user()->id.' is attempting to delete backup file '.$filename.' and is not authorized to.');
return redirect()->route('settings.backups.index')->with('error', trans('general.backup_delete_not_allowed')); return redirect()->route('settings.backups.index')->with('error', trans('general.backup_delete_not_allowed'));
} }
@@ -1166,11 +1240,13 @@ class SettingsController extends Controller
* Uploads a backup file * Uploads a backup file
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v6.0] * @since [v6.0]
*
* @return \Illuminate\Http\RedirectResponse
*/ */
public function postUploadBackup(Request $request) : RedirectResponse public function postUploadBackup(Request $request) {
{
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
if (!$request->hasFile('file')) { if (!$request->hasFile('file')) {
@@ -1202,9 +1278,12 @@ class SettingsController extends Controller
* Restore the backup file. * Restore the backup file.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v6.0] * @since [v6.0]
*
* @return View
*/ */
public function postRestore(Request $request, $filename = null): RedirectResponse public function postRestore($filename = null)
{ {
if (! config('app.lock_passwords')) { if (! config('app.lock_passwords')) {
@@ -1213,7 +1292,7 @@ class SettingsController extends Controller
if (Storage::exists($path.'/'.$filename)) { if (Storage::exists($path.'/'.$filename)) {
// grab the user's info so we can make sure they exist in the system // grab the user's info so we can make sure they exist in the system
$user = User::find(auth()->id()); $user = User::find(Auth::user()->id);
// TODO: run a backup // TODO: run a backup
@@ -1224,29 +1303,13 @@ class SettingsController extends Controller
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename); Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
$restore_params = [
'--force' => true,
'--no-progress' => true,
'filename' => storage_path($path) . '/' . $filename
];
if ($request->input('clean')) {
Log::debug("Attempting 'clean' - first, guessing prefix...");
Artisan::call('snipeit:restore', [
'--sanitize-guess-prefix' => true,
'filename' => storage_path($path) . '/' . $filename
]);
$guess_prefix_output = Artisan::output();
Log::debug("Sanitize output is: $guess_prefix_output");
list($prefix, $_output) = explode("\n", $guess_prefix_output);
Log::debug("prefix is: '$prefix'");
$restore_params['--sanitize-with-prefix'] = $prefix;
}
// run the restore command // run the restore command
Artisan::call('snipeit:restore', Artisan::call('snipeit:restore',
$restore_params [
); '--force' => true,
'--no-progress' => true,
'filename' => storage_path($path).'/'.$filename
]);
// If it's greater than 300, it probably worked // If it's greater than 300, it probably worked
$output = Artisan::output(); $output = Artisan::output();
@@ -1273,7 +1336,7 @@ class SettingsController extends Controller
DB::table('users')->update(['remember_token' => null]); DB::table('users')->update(['remember_token' => null]);
Auth::logout(); Auth::logout();
return redirect()->route('login')->with('success', trans('admin/settings/message.restore.success')); return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.');
} else { } else {
return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found'));
} }
@@ -1288,11 +1351,13 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* *
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function getPurge() : View | RedirectResponse public function getPurge()
{ {
Log::warning('User '.auth()->user()->username.' (ID: '.auth()->id().') is attempting a PURGE'); Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE');
if (config('app.allow_purge')=='true') { if (config('app.allow_purge')=='true') {
return view('settings.purge-form'); return view('settings.purge-form');
@@ -1305,11 +1370,14 @@ class SettingsController extends Controller
* Purges soft-deletes. * Purges soft-deletes.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0] * @since [v3.0]
*
* @return View
*/ */
public function postPurge(Request $request) : RedirectResponse public function postPurge(Request $request)
{ {
Log::warning('User '.auth()->user()->username.' (ID'.auth()->id().') is attempting a PURGE'); Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE');
if (config('app.allow_purge')=='true') { if (config('app.allow_purge')=='true') {
Log::debug('Purging is not allowed via the .env'); Log::debug('Purging is not allowed via the .env');
@@ -1318,7 +1386,7 @@ class SettingsController extends Controller
if ($request->input('confirm_purge')=='DELETE') { if ($request->input('confirm_purge')=='DELETE') {
Log::warning('User ID ' . auth()->id() . ' initiated a PURGE!'); Log::warning('User ID ' . Auth::user()->id . ' initiated a PURGE!');
// Run a backup immediately before processing // Run a backup immediately before processing
Artisan::call('backup:run'); Artisan::call('backup:run');
Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]); Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]);
@@ -1336,7 +1404,7 @@ class SettingsController extends Controller
} }
} }
Log::error('User '.auth()->user()->username.' (ID'.auth()->id().') is attempting to purge deleted data and is not authorized to.'); Log::error('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting to purge deleted data and is not authorized to.');
// Nope. // Nope.
@@ -1351,9 +1419,12 @@ class SettingsController extends Controller
* in the routes file if you want to be able to cache the routes. * in the routes file if you want to be able to cache the routes.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0] * @since [v4.0]
*
* @return View
*/ */
public function api() : View public function api()
{ {
return view('settings.api'); return view('settings.api');
} }
@@ -1362,9 +1433,12 @@ class SettingsController extends Controller
* Test the email configuration. * Test the email configuration.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0] * @since [v3.0]
*
* @return \Illuminate\Http\RedirectResponse
*/ */
public function ajaxTestEmail() : JsonResponse public function ajaxTestEmail()
{ {
try { try {
(new User())->forceFill([ (new User())->forceFill([
@@ -1372,20 +1446,13 @@ class SettingsController extends Controller
'email' => config('mail.from.address'), 'email' => config('mail.from.address'),
])->notify(new MailTest()); ])->notify(new MailTest());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent'))); return response()->json(Helper::formatStandardApiResponse('success', null, 'Maiol sent!'));
} catch (\Exception $e) { } catch (\Exception $e) {
return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage())); return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage()));
} }
} }
public function getLoginAttempts()
/**
* Get login attempts view
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function getLoginAttempts() : View
{ {
return view('settings.logins'); return view('settings.logins');
} }

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